Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sending interactions to a decentralized sequencer #442

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"test:integration:basic": "jest ./src/__tests__/integration/basic",
"test:integration:basic:load": "jest --silent=false --detectOpenHandles ./src/__tests__/integration/basic/contract-loading.test.ts ",
"test:integration:basic:arweave": "jest ./src/__tests__/integration/basic/arweave-transactions-loading",
"test:integration:decentralized-sequencer": "jest ./src/__tests__/integration/decentralized-sequencer --detectOpenHandles",
"test:integration:internal-writes": "jest ./src/__tests__/integration/internal-writes",
"test:integration:wasm": "jest ./src/__tests__/integration/wasm",
"test:regression": "node ./node_modules/.bin/jest ./src/__tests__/regression",
Expand Down Expand Up @@ -100,9 +101,11 @@
"@types/node": "^18.0.6",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"arbundles": "^0.9.6",
"arlocal": "1.1.42",
"cheerio": "^1.0.0-rc.10",
"colors": "^1.4.0",
"command-line-args": "^5.2.1",
"elliptic": "^6.5.4",
"esbuild": "0.17.5",
"eslint": "^7.32.0",
Expand Down
89 changes: 89 additions & 0 deletions src/__tests__/integration/decentralized-sequencer/interactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import fs from 'fs';
import path from 'path';
import { DeployPlugin, ArweaveSigner } from 'warp-contracts-plugin-deploy';
import { Contract } from '../../../contract/Contract';
import { Warp } from '../../../core/Warp';
import { WarpFactory, defaultCacheOptions, defaultWarpGwOptions } from '../../../core/WarpFactory';
import { SourceType } from '../../../core/modules/impl/WarpGatewayInteractionsLoader';

interface ExampleContractState {
counter: number;
}

// FIXME: change to the address of the sequencer on dev
const SEQUENCER_URL = 'http://localhost:1317';

describe('Testing a decentralized sequencer', () => {
let contractSrc: string;
let initialState: string;

let wallet: JWKInterface;

let arlocal: ArLocal;
let warp: Warp;
let contract: Contract<ExampleContractState>;

beforeAll(async () => {
const port = 1813;
arlocal = new ArLocal(port, false);
await arlocal.start();

const arweave = Arweave.init({
host: 'localhost',
port: port,
protocol: 'http'
});

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({
useDecentralizedSequencer: true,
sequencerUrl: SEQUENCER_URL
});
contract.connect(wallet);

});

afterAll(async () => {
await arlocal.stop();
});

it('should add new interactions waiting for confirmation from the sequencer', async () => {
await contract.writeInteraction({ function: 'add' }, { waitForConfirmation: true });
const result = await contract.writeInteraction({ function: 'add' }, { waitForConfirmation: true });
expect(result).toHaveProperty('originalTxId')
});

it('should throw an error after adding an interaction without waiting for confirmation of the previous one', async () => {
await contract.writeInteraction({ function: 'add' });
try {
await contract.writeInteraction({ function: 'add' })
} catch(e) {
expect(e.message).toContain('account sequence mismatch, expected 3, got 2: incorrect account sequence');
}
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import Arweave from 'arweave';
import { createData, DataItem, Signer } from 'warp-arbundles';
import { ArweaveSigner } from 'warp-contracts-plugin-deploy';
import { DecentralizedSequencer } from '../../../contract/DecentralizedSequencer';
import { WARP_TAGS } from '../../../core/KnownTags';
import { Tag } from '../../../utils/types/arweave-types';

// FIXME: change to the address of the sequencer on dev
const SEQUENCER_URL = 'http://localhost:1317';

describe('Testing a decentralized sequencer', () => {
let decentralizedSequencer: DecentralizedSequencer;

beforeAll(async () => {
decentralizedSequencer = new DecentralizedSequencer(SEQUENCER_URL);
});

const createSigner = async (): Promise<Signer> => {
const wallet = await Arweave.crypto.generateJWK();
return new ArweaveSigner(wallet);
}

const createDataItem = async (signer: Signer, nonce: number): Promise<DataItem> => {
const tag = new Tag(WARP_TAGS.SEQUENCER_NONCE, String(nonce));
const dataItem = createData('some data', signer, { tags: [tag] });
await dataItem.sign(signer);
return dataItem;
}

it('should always return a zero nonce for a new signer', async () => {
const signer = await createSigner();

let nonce = await decentralizedSequencer.fetchNonce(signer);
expect(nonce).toEqual(0);

nonce = await decentralizedSequencer.fetchNonce(signer);
expect(nonce).toEqual(0);
});

it('should reject a data item with an invalid nonce', async () => {
const signer = await createSigner();
const dataItem = await createDataItem(signer, 13);

expect(decentralizedSequencer.sendDataItem(dataItem, false))
.rejects
.toThrowError('account sequence mismatch, expected 0, got 13: incorrect account sequence');
});

it('should increment the nonce after sending the data item', async () => {
const signer = await createSigner();

let nonce = await decentralizedSequencer.fetchNonce(signer);
expect(nonce).toEqual(0);

const dataItem = await createDataItem(signer, nonce);
await decentralizedSequencer.sendDataItem(dataItem, true);
nonce = await decentralizedSequencer.fetchNonce(signer);
expect(nonce).toEqual(1);
});

it('should reject an unsigned data item', async () => {
const signer = await createSigner();
const dataItem = createData('some data', signer);

expect(decentralizedSequencer.sendDataItem(dataItem, true))
.rejects
.toThrowError('broadcast transaction error');
});

it('should return an unconfirmed result', async () => {
const signer = await createSigner();
const nonce = await decentralizedSequencer.fetchNonce(signer);
const dataItem = await createDataItem(signer, nonce);
const result = await decentralizedSequencer.sendDataItem(dataItem, true, 0);

expect(result.confirmed).toEqual(false);
});

it('should return the result without confirmation', async () => {
const signer = await createSigner();
const nonce = await decentralizedSequencer.fetchNonce(signer);
const dataItem = await createDataItem(signer, nonce);
const result = await decentralizedSequencer.sendDataItem(dataItem, false);

expect(result.confirmed).toEqual(false);
});

});
12 changes: 6 additions & 6 deletions src/__tests__/unit/evaluation-options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: SourceType.BOTH,
stackTrace: {
saveState: false
Expand All @@ -28,7 +29,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'throw',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand Down Expand Up @@ -57,6 +57,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: SourceType.BOTH,
stackTrace: {
saveState: false
Expand All @@ -65,7 +66,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'throw',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand All @@ -89,6 +89,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: 'both',
stackTrace: {
saveState: false
Expand All @@ -97,7 +98,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'allow',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand All @@ -118,6 +118,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: 'both',
stackTrace: {
saveState: false
Expand All @@ -126,7 +127,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'allow',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand All @@ -147,6 +147,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: 'both',
stackTrace: {
saveState: false
Expand All @@ -155,7 +156,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'throw',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand All @@ -176,6 +176,7 @@ describe('Evaluation options evaluator', () => {
remoteStateSyncEnabled: false,
remoteStateSyncSource: 'https://dre-1.warp.cc/contract',
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
useDecentralizedSequencer: false,
sourceType: 'both',
stackTrace: {
saveState: false
Expand All @@ -184,7 +185,6 @@ describe('Evaluation options evaluator', () => {
unsafeClient: 'skip',
updateCacheForEachInteraction: false,
useKVStorage: false,
waitForConfirmation: false,
useConstructor: false,
walletBalanceUrl: 'http://nyc-1.dev.arweave.net:1984/'
});
Expand Down
7 changes: 4 additions & 3 deletions src/__tests__/unit/gateway-interactions.loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import { GQLNodeInterface } from '../../legacy/gqlResult';
import { LoggerFactory } from '../../logging/LoggerFactory';
import { WarpFactory } from '../../core/WarpFactory';
import { NetworkCommunicationError } from '../../utils/utils';

const responseData = {
paging: {
Expand Down Expand Up @@ -140,7 +141,7 @@ describe('WarpGatewayInteractionsLoader -> load', () => {
try {
await loader.load(contractId, fromBlockHeight, toBlockHeight);
} catch (e) {
expect(e).toEqual(new Error('Error while communicating with gateway: {"status":504,"ok":false}'));
expect(e).toEqual(new NetworkCommunicationError('Error during network communication: {"status":504,"ok":false}'));
}
});
it('should throw an error when request fails', async () => {
Expand All @@ -152,8 +153,8 @@ describe('WarpGatewayInteractionsLoader -> load', () => {
await loader.load(contractId, fromBlockHeight, toBlockHeight);
} catch (e) {
expect(e).toEqual(
new Error(
'Error while communicating with gateway: {"status":500,"ok":false,"body":{"message":"request fails"}}'
new NetworkCommunicationError(
'Error during network communication: {"status":500,"ok":false,"body":{"message":"request fails"}}'
)
);
}
Expand Down
6 changes: 5 additions & 1 deletion src/contract/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Signer } from 'warp-arbundles';

export type BenchmarkStats = { gatewayCommunication: number; stateEvaluation: number; total: number };

interface BundlrResponse {
export interface BundlrResponse {
id: string;
public: string;
signature: string;
Expand Down Expand Up @@ -48,6 +48,10 @@ export type ArweaveOptions = {
export type CommonOptions = {
tags?: Tags;
strict?: boolean;
// Allows waiting for confirmation of the interaction.
// In the case of the 'disableBundling' option, the confirmation comes from the Arweave network,
// otherwise from the decentralized sequencer.
waitForConfirmation?: boolean;
};

export type WriteInteractionOptions = WarpOptions & ArweaveOptions & CommonOptions;
Expand Down
Loading