-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Client for sending interactions to the sequencer
- Loading branch information
1 parent
49688e8
commit a19ba38
Showing
12 changed files
with
670 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
src/__tests__/integration/decentralized-sequencer/interactions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import ArLocal from 'arlocal'; | ||
import Arweave from 'arweave'; | ||
import { JWKInterface } from 'arweave/node/lib/wallet'; | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import { createServer, Server } from 'http'; | ||
import { DeployPlugin, ArweaveSigner } from 'warp-contracts-plugin-deploy'; | ||
import { Contract, WriteInteractionResponse } from '../../../contract/Contract'; | ||
import { Warp } from '../../../core/Warp'; | ||
import { WarpFactory, defaultCacheOptions, defaultWarpGwOptions } from '../../../core/WarpFactory'; | ||
import { SourceType } from '../../../core/modules/impl/WarpGatewayInteractionsLoader'; | ||
import { AddressInfo } from 'net'; | ||
import { WARP_TAGS } from '../../../core/KnownTags'; | ||
|
||
interface ExampleContractState { | ||
counter: number; | ||
} | ||
|
||
// FIXME: change to the address of the sequencer on dev | ||
const DECENTRALIZED_SEQUENCER_URL = 'http://localhost:1317'; | ||
|
||
describe('Testing sending of interactions to a decentralized sequencer', () => { | ||
let contractSrc: string; | ||
let initialState: string; | ||
let wallet: JWKInterface; | ||
let arlocal: ArLocal; | ||
let warp: Warp; | ||
let contract: Contract<ExampleContractState>; | ||
let sequencerServer: Server; | ||
let centralizedSeqeuencerUrl: string; | ||
let centralizedSequencerType: boolean; | ||
|
||
beforeAll(async () => { | ||
const port = 1813; | ||
arlocal = new ArLocal(port, false); | ||
await arlocal.start(); | ||
|
||
const arweave = Arweave.init({ | ||
host: 'localhost', | ||
port: port, | ||
protocol: 'http' | ||
}); | ||
|
||
// a mock server simulating a centralized sequencer | ||
centralizedSequencerType = false; | ||
sequencerServer = createServer((req, res) => { | ||
if (req.url === '/gateway/sequencer/address') { | ||
res.writeHead(200, { 'Content-Type': 'application/json' }); | ||
res.end(JSON.stringify({ | ||
url: centralizedSequencerType ? centralizedSeqeuencerUrl : DECENTRALIZED_SEQUENCER_URL, | ||
type: centralizedSequencerType ? 'centralized' : 'decentralized' | ||
})); | ||
return; | ||
} else if (req.url === '/gateway/v2/sequencer/register') { | ||
centralizedSequencerType = false; | ||
res.writeHead(301, { Location: DECENTRALIZED_SEQUENCER_URL }); | ||
res.end(); | ||
return; | ||
} | ||
throw new Error("Unexpected sequencer path: " + req.url); | ||
}) | ||
await new Promise<void>(resolve => { | ||
sequencerServer.listen(() => { | ||
const address = sequencerServer.address() as AddressInfo | ||
centralizedSeqeuencerUrl = `http://localhost:${address.port}` | ||
resolve() | ||
}) | ||
}) | ||
|
||
const cacheOptions = { | ||
...defaultCacheOptions, | ||
inMemory: true | ||
} | ||
const gatewayOptions = { ...defaultWarpGwOptions, source: SourceType.WARP_SEQUENCER, confirmationStatus: { notCorrupted: true } } | ||
|
||
warp = WarpFactory | ||
.custom(arweave, cacheOptions, 'custom') | ||
.useWarpGateway(gatewayOptions, cacheOptions) | ||
.build() | ||
.use(new DeployPlugin()); | ||
|
||
({ jwk: wallet } = await warp.generateWallet()); | ||
|
||
contractSrc = fs.readFileSync(path.join(__dirname, '../data/example-contract.js'), 'utf8'); | ||
initialState = fs.readFileSync(path.join(__dirname, '../data/example-contract-state.json'), 'utf8'); | ||
|
||
const { contractTxId } = await warp.deploy({ | ||
wallet: new ArweaveSigner(wallet), | ||
initState: initialState, | ||
src: contractSrc | ||
}); | ||
|
||
contract = warp.contract<ExampleContractState>(contractTxId).setEvaluationOptions({ | ||
sequencerUrl: centralizedSeqeuencerUrl | ||
}); | ||
contract.connect(wallet); | ||
|
||
}); | ||
|
||
afterAll(async () => { | ||
await arlocal.stop(); | ||
await new Promise(resolve => { | ||
sequencerServer.close(resolve) | ||
}) | ||
}); | ||
|
||
const getNonceFromResult = (result: WriteInteractionResponse | null): number => { | ||
if (result) { | ||
for (let tag of result.interactionTx.tags) { | ||
if (tag.name === WARP_TAGS.SEQUENCER_NONCE) { | ||
return Number(tag.value) | ||
} | ||
} | ||
} | ||
return -1 | ||
} | ||
|
||
it('should add new interactions waiting for confirmation from the sequencer', async () => { | ||
contract.setEvaluationOptions({ waitForConfirmation: true }) | ||
|
||
await contract.writeInteraction({ function: 'add' }); | ||
const result = await contract.writeInteraction({ function: 'add' }); | ||
expect(getNonceFromResult(result)).toEqual(1) | ||
expect(result?.bundlrResponse).toBeUndefined(); | ||
expect(result?.sequencerTxHash).toBeDefined(); | ||
}); | ||
|
||
it('should add new interactions without waiting for confirmation from the sequencer', async () => { | ||
contract.setEvaluationOptions({ waitForConfirmation: false }) | ||
|
||
await contract.writeInteraction({ function: 'add' }); | ||
const result = await contract.writeInteraction({ function: 'add' }); | ||
expect(getNonceFromResult(result)).toEqual(3) | ||
expect(result?.bundlrResponse).toBeUndefined(); | ||
expect(result?.sequencerTxHash).toBeUndefined(); | ||
}); | ||
|
||
it('should follow the redirection returned by the centralized sequencer.', async () => { | ||
centralizedSequencerType = true; | ||
contract.setEvaluationOptions({ | ||
sequencerUrl: centralizedSeqeuencerUrl, | ||
waitForConfirmation: true | ||
}); | ||
|
||
const result = await contract.writeInteraction({ function: 'add' }); | ||
expect(getNonceFromResult(result)).toEqual(4) | ||
expect(result?.bundlrResponse).toBeUndefined(); | ||
expect(result?.sequencerTxHash).toBeDefined(); | ||
}); | ||
}); |
110 changes: 110 additions & 0 deletions
110
src/__tests__/integration/decentralized-sequencer/send-data-item.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import Arweave from 'arweave'; | ||
import { createData, DataItem, Signer } from 'warp-arbundles'; | ||
import { ArweaveSigner } from 'warp-contracts-plugin-deploy'; | ||
import { DecentralizedSequencerClient } from '../../../contract/sequencer/DecentralizedSequencerClient'; | ||
import { SMART_WEAVE_TAGS, WARP_TAGS } from '../../../core/KnownTags'; | ||
import { Tag } from '../../../utils/types/arweave-types'; | ||
import { WarpFactory } from '../../../core/WarpFactory'; | ||
import { WarpFetchWrapper } from '../../../core/WarpFetchWrapper'; | ||
import { Signature } from '../../../contract/Signature'; | ||
|
||
// FIXME: change to the address of the sequencer on dev | ||
const SEQUENCER_URL = 'http://localhost:1317'; | ||
|
||
describe('Testing a decentralized sequencer client', () => { | ||
let client: DecentralizedSequencerClient; | ||
|
||
beforeAll(async () => { | ||
const warpFetchWrapper = new WarpFetchWrapper(WarpFactory.forLocal()) | ||
client = new DecentralizedSequencerClient(SEQUENCER_URL, warpFetchWrapper); | ||
}); | ||
|
||
const createSignature = async (): Promise<Signature> => { | ||
const wallet = await Arweave.crypto.generateJWK(); | ||
const signer = new ArweaveSigner(wallet); | ||
return new Signature(WarpFactory.forLocal(), signer) | ||
} | ||
|
||
const createDataItem = async (signature: Signature, nonce: number, addNonceTag = true, addContractTag = true, signDataItem = true): Promise<DataItem> => { | ||
const signer = signature.bundlerSigner; | ||
const tags: Tag[] = []; | ||
if (addNonceTag) { | ||
tags.push(new Tag(WARP_TAGS.SEQUENCER_NONCE, String(nonce))); | ||
} | ||
if (addContractTag) { | ||
tags.push(new Tag(SMART_WEAVE_TAGS.CONTRACT_TX_ID, "unit test contract")); | ||
} | ||
const dataItem = createData('some data', signer, { tags }); | ||
if (signDataItem) { | ||
await dataItem.sign(signer); | ||
} | ||
return dataItem; | ||
} | ||
|
||
it('should return consecutive nonces for a given signature', async () => { | ||
const signature = await createSignature() | ||
let nonce = await client.getNonce(signature); | ||
expect(nonce).toEqual(0); | ||
|
||
nonce = await client.getNonce(signature); | ||
expect(nonce).toEqual(1); | ||
}); | ||
|
||
it('should reject a data item with an invalid nonce', async () => { | ||
const signature = await createSignature() | ||
const dataItem = await createDataItem(signature, 13); | ||
|
||
expect(client.sendDataItem(dataItem, false)) | ||
.rejects | ||
.toThrowError('account sequence mismatch, expected 0, got 13: incorrect account sequence'); | ||
}); | ||
|
||
it('should reject a data item without nonce', async () => { | ||
const signature = await createSignature() | ||
const dataItem = await createDataItem(signature, 0, false); | ||
|
||
expect(client.sendDataItem(dataItem, true)) | ||
.rejects | ||
.toThrowError('no sequencer nonce tag'); | ||
}); | ||
|
||
it('should reject a data item without contract', async () => { | ||
const signature = await createSignature() | ||
const dataItem = await createDataItem(signature, 0, true, false); | ||
|
||
expect(client.sendDataItem(dataItem, true)) | ||
.rejects | ||
.toThrowError('no contract tag'); | ||
}); | ||
|
||
it('should reject an unsigned data item', async () => { | ||
const signature = await createSignature() | ||
const dataItem = await createDataItem(signature, 0, true, true, false); | ||
|
||
expect(client.sendDataItem(dataItem, true)) | ||
.rejects | ||
.toThrowError('data item verification error'); | ||
}); | ||
|
||
it('should return a confirmed result', async () => { | ||
const signature = await createSignature(); | ||
const nonce = await client.getNonce(signature); | ||
const dataItem = await createDataItem(signature, nonce); | ||
const result = await client.sendDataItem(dataItem, true); | ||
|
||
expect(result.sequencerMoved).toEqual(false); | ||
expect(result.bundlrResponse).toBeUndefined(); | ||
expect(result.sequencerTxHash).toBeDefined(); | ||
}); | ||
|
||
it('should return an unconfirmed result', async () => { | ||
const signature = await createSignature(); | ||
const nonce = await client.getNonce(signature); | ||
const dataItem = await createDataItem(signature, nonce); | ||
const result = await client.sendDataItem(dataItem, false); | ||
|
||
expect(result.sequencerMoved).toEqual(false); | ||
expect(result.bundlrResponse).toBeUndefined(); | ||
expect(result.sequencerTxHash).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.