From 5da88ff8c80edec840c6703e5ee5a175909e6939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Szynwelski?= Date: Mon, 11 Dec 2023 13:47:54 +0100 Subject: [PATCH] v1.5.0-rc.0 --- package.json | 2 +- .../decentralized-sequencer/interactions.ts | 298 +++++++++--------- .../decentralized-sequencer/send-data-item.ts | 43 ++- src/legacy/smartweave-global.ts | 4 +- 4 files changed, 176 insertions(+), 171 deletions(-) diff --git a/package.json b/package.json index 17fdbae0..7c9ff926 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "warp-contracts", - "version": "1.4.25", + "version": "1.5.0-rc.0", "description": "An implementation of the SmartWeave smart contract protocol.", "types": "./lib/types/index.d.ts", "main": "./lib/cjs/index.js", diff --git a/src/__tests__/integration/decentralized-sequencer/interactions.ts b/src/__tests__/integration/decentralized-sequencer/interactions.ts index 996c46c1..40637661 100644 --- a/src/__tests__/integration/decentralized-sequencer/interactions.ts +++ b/src/__tests__/integration/decentralized-sequencer/interactions.ts @@ -14,171 +14,177 @@ import { WARP_TAGS } from '../../../core/KnownTags'; import { LoggerFactory } from '../../../logging/LoggerFactory'; interface ExampleContractState { - counter: number; + counter: number; } -const DECENTRALIZED_SEQUENCER_URLS = ['http://sequencer-0.testnet.warp.cc:1317', - 'http://sequencer-1.testnet.warp.cc:1317', - 'http://sequencer-2.testnet.warp.cc:1317']; +const DECENTRALIZED_SEQUENCER_URLS = [ + 'http://sequencer-0.testnet.warp.cc:1317', + 'http://sequencer-1.testnet.warp.cc:1317', + 'http://sequencer-2.testnet.warp.cc:1317' +]; const GW_URL = 'http://35.242.203.146:5666/'; 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; - let mockGwServer: Server; - let mockGwUrl: string; - let centralizedSequencerType: boolean; - let confirmAnyTx: boolean; - - /** - * For testing purposes, operations returning the sequencer's address and registering/confirming interactions are mocked. - * Other requests are forwarded to the real Gateway. - */ - const mockGw = async () => { - mockGwServer = createServer((req, res) => { - if (req.url === '/gateway/sequencer/address') { - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - urls: centralizedSequencerType ? [mockGwUrl] : DECENTRALIZED_SEQUENCER_URLS, - type: centralizedSequencerType ? 'centralized' : 'decentralized' - })); - return; - } else if (req.url === '/gateway/v2/sequencer/register') { - centralizedSequencerType = false; - res.writeHead(301, { Location: DECENTRALIZED_SEQUENCER_URLS[0] }); - res.end(); - return; - } else if (req.url?.startsWith('/gateway/interactions/')) { - res.writeHead(confirmAnyTx ? 200 : 204); - res.end(); - return; - } - - var options = { - hostname: new URL(GW_URL).hostname, - port: new URL(GW_URL).port, - path: req.url, - method: req.method, - headers: req.headers - }; - - var proxy = request(options, (gwRes) => { - if (gwRes.statusCode) { - res.writeHead(gwRes.statusCode, gwRes.headers) - gwRes.pipe(res, { - end: true - }); - } - }); - - req.pipe(proxy, { - end: true - }); - }); - await new Promise(resolve => { - mockGwServer.listen(() => { - const address = mockGwServer.address() as AddressInfo - mockGwUrl = `http://localhost:${address.port}` - resolve() - }) - }); - } - - beforeAll(async () => { - LoggerFactory.INST.logLevel('debug'); - const port = 1813; - arlocal = new ArLocal(port, false); - await arlocal.start(); - - const arweave = Arweave.init({ - host: 'localhost', - port: port, - protocol: 'http' - }); - + let contractSrc: string; + let initialState: string; + let wallet: JWKInterface; + let arlocal: ArLocal; + let warp: Warp; + let contract: Contract; + let mockGwServer: Server; + let mockGwUrl: string; + let centralizedSequencerType: boolean; + let confirmAnyTx: boolean; + + /** + * For testing purposes, operations returning the sequencer's address and registering/confirming interactions are mocked. + * Other requests are forwarded to the real Gateway. + */ + const mockGw = async () => { + mockGwServer = createServer((req, res) => { + if (req.url === '/gateway/sequencer/address') { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ + urls: centralizedSequencerType ? [mockGwUrl] : DECENTRALIZED_SEQUENCER_URLS, + type: centralizedSequencerType ? 'centralized' : 'decentralized' + }) + ); + return; + } else if (req.url === '/gateway/v2/sequencer/register') { centralizedSequencerType = false; - confirmAnyTx = false; - await mockGw(); - - const cacheOptions = { - ...defaultCacheOptions, - inMemory: true + res.writeHead(301, { Location: DECENTRALIZED_SEQUENCER_URLS[0] }); + res.end(); + return; + } else if (req.url?.startsWith('/gateway/interactions/')) { + res.writeHead(confirmAnyTx ? 200 : 204); + res.end(); + return; + } + + var options = { + hostname: new URL(GW_URL).hostname, + port: new URL(GW_URL).port, + path: req.url, + method: req.method, + headers: req.headers + }; + + var proxy = request(options, (gwRes) => { + if (gwRes.statusCode) { + res.writeHead(gwRes.statusCode, gwRes.headers); + gwRes.pipe(res, { + end: true + }); } - const gatewayOptions = { ...defaultWarpGwOptions, source: SourceType.WARP_SEQUENCER, confirmationStatus: { notCorrupted: true } } + }); - warp = WarpFactory - .custom(arweave, cacheOptions, 'custom') - .useWarpGateway(gatewayOptions, cacheOptions) - .build() - .useGwUrl(mockGwUrl) - .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 - }); + req.pipe(proxy, { + end: true + }); + }); + await new Promise((resolve) => { + mockGwServer.listen(() => { + const address = mockGwServer.address() as AddressInfo; + mockGwUrl = `http://localhost:${address.port}`; + resolve(); + }); + }); + }; + + beforeAll(async () => { + LoggerFactory.INST.logLevel('debug'); + const port = 1813; + arlocal = new ArLocal(port, false); + await arlocal.start(); + + const arweave = Arweave.init({ + host: 'localhost', + port: port, + protocol: 'http' + }); - contract = warp.contract(contractTxId).setEvaluationOptions({ - sequencerUrl: mockGwUrl - }); - contract.connect(wallet); + centralizedSequencerType = false; + confirmAnyTx = false; + await mockGw(); + + 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() + .useGwUrl(mockGwUrl) + .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(contractTxId).setEvaluationOptions({ + sequencerUrl: mockGwUrl }); + contract.connect(wallet); + }); - afterAll(async () => { - await arlocal.stop(); - await new Promise(resolve => { - mockGwServer.close(resolve) - }) + afterAll(async () => { + await arlocal.stop(); + await new Promise((resolve) => { + mockGwServer.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) - } - } + 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 follow the redirection returned by the centralized sequencer.', async () => { - confirmAnyTx = true; - centralizedSequencerType = true; - contract.setEvaluationOptions({ - waitForConfirmation: true - }); - - await contract.writeInteraction({ function: 'add' }); - const result = await contract.writeInteraction({ function: 'add' }); - expect(getNonceFromResult(result)).toEqual(1) + return -1; + }; + + it('should follow the redirection returned by the centralized sequencer.', async () => { + confirmAnyTx = true; + centralizedSequencerType = true; + contract.setEvaluationOptions({ + waitForConfirmation: true }); - it('should add new interactions waiting for confirmation from the gateway', async () => { - contract.setEvaluationOptions({ waitForConfirmation: true }) - setTimeout(() => confirmAnyTx = true, 2000); + await contract.writeInteraction({ function: 'add' }); + const result = await contract.writeInteraction({ function: 'add' }); + expect(getNonceFromResult(result)).toEqual(1); + }); - await contract.writeInteraction({ function: 'add' }); - const result = await contract.writeInteraction({ function: 'add' }); - expect(getNonceFromResult(result)).toEqual(3) - }); + it('should add new interactions waiting for confirmation from the gateway', async () => { + contract.setEvaluationOptions({ waitForConfirmation: true }); + setTimeout(() => (confirmAnyTx = true), 2000); - it('should add new interactions without waiting for confirmation from the gateway', async () => { - contract.setEvaluationOptions({ waitForConfirmation: false }) + await contract.writeInteraction({ function: 'add' }); + const result = await contract.writeInteraction({ function: 'add' }); + expect(getNonceFromResult(result)).toEqual(3); + }); - await contract.writeInteraction({ function: 'add' }); - const result = await contract.writeInteraction({ function: 'add' }); - expect(getNonceFromResult(result)).toEqual(5) - }); + it('should add new interactions without waiting for confirmation from the gateway', async () => { + contract.setEvaluationOptions({ waitForConfirmation: false }); + + await contract.writeInteraction({ function: 'add' }); + const result = await contract.writeInteraction({ function: 'add' }); + expect(getNonceFromResult(result)).toEqual(5); + }); }); diff --git a/src/__tests__/integration/decentralized-sequencer/send-data-item.ts b/src/__tests__/integration/decentralized-sequencer/send-data-item.ts index d25deb5b..693d22fa 100644 --- a/src/__tests__/integration/decentralized-sequencer/send-data-item.ts +++ b/src/__tests__/integration/decentralized-sequencer/send-data-item.ts @@ -13,33 +13,38 @@ const SEQUENCER_URL = 'http://sequencer-0.testnet.warp.cc:1317'; const GW_URL = 'https://gw-testnet.warp.cc'; describe('Testing a decentralized sequencer client', () => { - const createClient = (): SequencerClient => { - const warpFetchWrapper = new WarpFetchWrapper(WarpFactory.forLocal()) + const warpFetchWrapper = new WarpFetchWrapper(WarpFactory.forLocal()); return new DecentralizedSequencerClient(SEQUENCER_URL, GW_URL, warpFetchWrapper); - } + }; const createSignature = async (): Promise => { 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 => { + return new Signature(WarpFactory.forLocal(), signer); + }; + + const createDataItem = async ( + signature: Signature, + nonce: number, + addNonceTag = true, + addContractTag = true, + signDataItem = true + ): Promise => { 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")); + 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 client = createClient(); @@ -56,9 +61,9 @@ describe('Testing a decentralized sequencer client', () => { 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'); + 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 () => { @@ -66,9 +71,7 @@ describe('Testing a decentralized sequencer client', () => { const signature = await createSignature(); const dataItem = await createDataItem(signature, 0, false); - expect(client.sendDataItem(dataItem, true)) - .rejects - .toThrowError('no sequencer nonce tag'); + expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('no sequencer nonce tag'); }); it('should reject a data item without contract', async () => { @@ -76,9 +79,7 @@ describe('Testing a decentralized sequencer client', () => { const signature = await createSignature(); const dataItem = await createDataItem(signature, 0, true, false); - expect(client.sendDataItem(dataItem, true)) - .rejects - .toThrowError('no contract tag'); + expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('no contract tag'); }); it('should reject an unsigned data item', async () => { @@ -86,9 +87,7 @@ describe('Testing a decentralized sequencer client', () => { const signature = await createSignature(); const dataItem = await createDataItem(signature, 0, true, true, false); - expect(client.sendDataItem(dataItem, true)) - .rejects - .toThrowError('data item verification error'); + expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('data item verification error'); }); it('should return an unconfirmed result', async () => { @@ -97,7 +96,7 @@ describe('Testing a decentralized sequencer client', () => { const nonce = await client.getNonce(signature); const dataItem = await createDataItem(signature, nonce); const result = await client.sendDataItem(dataItem, false); - + expect(result.sequencerMoved).toEqual(false); }); }); diff --git a/src/legacy/smartweave-global.ts b/src/legacy/smartweave-global.ts index bf25c6f8..9b38bdc1 100644 --- a/src/legacy/smartweave-global.ts +++ b/src/legacy/smartweave-global.ts @@ -166,9 +166,9 @@ export class SmartWeaveGlobal { throw new Error(`Integer max value must be in the range [1, ${Number.MAX_SAFE_INTEGER}]`); } const base64 = this._activeTx.random.replace(/-/g, '+').replace(/_/g, '/'); - const array = Uint8Array.from(atob(base64), c => c.charCodeAt(0)); + const array = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); const bigInt = Buffer.from(array).readBigUInt64BE(); - const result = (bigInt % BigInt(maxValue)) + BigInt(1) + const result = (bigInt % BigInt(maxValue)) + BigInt(1); return Number(result); } }