v1.5.0-rc.0
Some checks failed
CI / build (push) Has been cancelled

This commit is contained in:
Michał Szynwelski
2023-12-11 13:47:54 +01:00
parent a4342a6121
commit 5da88ff8c8
4 changed files with 172 additions and 167 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "warp-contracts", "name": "warp-contracts",
"version": "1.4.25", "version": "1.5.0-rc.0",
"description": "An implementation of the SmartWeave smart contract protocol.", "description": "An implementation of the SmartWeave smart contract protocol.",
"types": "./lib/types/index.d.ts", "types": "./lib/types/index.d.ts",
"main": "./lib/cjs/index.js", "main": "./lib/cjs/index.js",

View File

@@ -14,171 +14,177 @@ import { WARP_TAGS } from '../../../core/KnownTags';
import { LoggerFactory } from '../../../logging/LoggerFactory'; import { LoggerFactory } from '../../../logging/LoggerFactory';
interface ExampleContractState { interface ExampleContractState {
counter: number; counter: number;
} }
const DECENTRALIZED_SEQUENCER_URLS = ['http://sequencer-0.testnet.warp.cc:1317', const DECENTRALIZED_SEQUENCER_URLS = [
'http://sequencer-1.testnet.warp.cc:1317', 'http://sequencer-0.testnet.warp.cc:1317',
'http://sequencer-2.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/'; const GW_URL = 'http://35.242.203.146:5666/';
describe('Testing sending of interactions to a decentralized sequencer', () => { describe('Testing sending of interactions to a decentralized sequencer', () => {
let contractSrc: string; let contractSrc: string;
let initialState: string; let initialState: string;
let wallet: JWKInterface; let wallet: JWKInterface;
let arlocal: ArLocal; let arlocal: ArLocal;
let warp: Warp; let warp: Warp;
let contract: Contract<ExampleContractState>; let contract: Contract<ExampleContractState>;
let mockGwServer: Server; let mockGwServer: Server;
let mockGwUrl: string; let mockGwUrl: string;
let centralizedSequencerType: boolean; let centralizedSequencerType: boolean;
let confirmAnyTx: 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<void>(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'
});
/**
* 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; centralizedSequencerType = false;
confirmAnyTx = false; res.writeHead(301, { Location: DECENTRALIZED_SEQUENCER_URLS[0] });
await mockGw(); res.end();
return;
} else if (req.url?.startsWith('/gateway/interactions/')) {
res.writeHead(confirmAnyTx ? 200 : 204);
res.end();
return;
}
const cacheOptions = { var options = {
...defaultCacheOptions, hostname: new URL(GW_URL).hostname,
inMemory: true 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 req.pipe(proxy, {
.custom(arweave, cacheOptions, 'custom') end: true
.useWarpGateway(gatewayOptions, cacheOptions) });
.build() });
.useGwUrl(mockGwUrl) await new Promise<void>((resolve) => {
.use(new DeployPlugin()); mockGwServer.listen(() => {
const address = mockGwServer.address() as AddressInfo;
mockGwUrl = `http://localhost:${address.port}`;
resolve();
});
});
};
({ jwk: wallet } = await warp.generateWallet()); beforeAll(async () => {
LoggerFactory.INST.logLevel('debug');
contractSrc = fs.readFileSync(path.join(__dirname, '../data/example-contract.js'), 'utf8'); const port = 1813;
initialState = fs.readFileSync(path.join(__dirname, '../data/example-contract-state.json'), 'utf8'); arlocal = new ArLocal(port, false);
await arlocal.start();
const { contractTxId } = await warp.deploy({
wallet: new ArweaveSigner(wallet),
initState: initialState,
src: contractSrc
});
contract = warp.contract<ExampleContractState>(contractTxId).setEvaluationOptions({
sequencerUrl: mockGwUrl
});
contract.connect(wallet);
const arweave = Arweave.init({
host: 'localhost',
port: port,
protocol: 'http'
}); });
afterAll(async () => { centralizedSequencerType = false;
await arlocal.stop(); confirmAnyTx = false;
await new Promise(resolve => { await mockGw();
mockGwServer.close(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()
.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
}); });
const getNonceFromResult = (result: WriteInteractionResponse | null): number => { contract = warp.contract<ExampleContractState>(contractTxId).setEvaluationOptions({
if (result) { sequencerUrl: mockGwUrl
for (let tag of result.interactionTx.tags) { });
if (tag.name === WARP_TAGS.SEQUENCER_NONCE) { contract.connect(wallet);
return Number(tag.value) });
}
} 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);
} }
return -1 }
} }
return -1;
};
it('should follow the redirection returned by the centralized sequencer.', async () => { it('should follow the redirection returned by the centralized sequencer.', async () => {
confirmAnyTx = true; confirmAnyTx = true;
centralizedSequencerType = true; centralizedSequencerType = true;
contract.setEvaluationOptions({ contract.setEvaluationOptions({
waitForConfirmation: true waitForConfirmation: true
});
await contract.writeInteraction({ function: 'add' });
const result = await contract.writeInteraction({ function: 'add' });
expect(getNonceFromResult(result)).toEqual(1)
}); });
it('should add new interactions waiting for confirmation from the gateway', async () => { await contract.writeInteraction({ function: 'add' });
contract.setEvaluationOptions({ waitForConfirmation: true }) const result = await contract.writeInteraction({ function: 'add' });
setTimeout(() => confirmAnyTx = true, 2000); expect(getNonceFromResult(result)).toEqual(1);
});
await contract.writeInteraction({ function: 'add' }); it('should add new interactions waiting for confirmation from the gateway', async () => {
const result = await contract.writeInteraction({ function: 'add' }); contract.setEvaluationOptions({ waitForConfirmation: true });
expect(getNonceFromResult(result)).toEqual(3) setTimeout(() => (confirmAnyTx = true), 2000);
});
it('should add new interactions without waiting for confirmation from the gateway', async () => { await contract.writeInteraction({ function: 'add' });
contract.setEvaluationOptions({ waitForConfirmation: false }) const result = await contract.writeInteraction({ function: 'add' });
expect(getNonceFromResult(result)).toEqual(3);
});
await contract.writeInteraction({ function: 'add' }); it('should add new interactions without waiting for confirmation from the gateway', async () => {
const result = await contract.writeInteraction({ function: 'add' }); contract.setEvaluationOptions({ waitForConfirmation: false });
expect(getNonceFromResult(result)).toEqual(5)
}); await contract.writeInteraction({ function: 'add' });
const result = await contract.writeInteraction({ function: 'add' });
expect(getNonceFromResult(result)).toEqual(5);
});
}); });

View File

@@ -13,33 +13,38 @@ const SEQUENCER_URL = 'http://sequencer-0.testnet.warp.cc:1317';
const GW_URL = 'https://gw-testnet.warp.cc'; const GW_URL = 'https://gw-testnet.warp.cc';
describe('Testing a decentralized sequencer client', () => { describe('Testing a decentralized sequencer client', () => {
const createClient = (): SequencerClient => { const createClient = (): SequencerClient => {
const warpFetchWrapper = new WarpFetchWrapper(WarpFactory.forLocal()) const warpFetchWrapper = new WarpFetchWrapper(WarpFactory.forLocal());
return new DecentralizedSequencerClient(SEQUENCER_URL, GW_URL, warpFetchWrapper); return new DecentralizedSequencerClient(SEQUENCER_URL, GW_URL, warpFetchWrapper);
} };
const createSignature = async (): Promise<Signature> => { const createSignature = async (): Promise<Signature> => {
const wallet = await Arweave.crypto.generateJWK(); const wallet = await Arweave.crypto.generateJWK();
const signer = new ArweaveSigner(wallet); const signer = new ArweaveSigner(wallet);
return new Signature(WarpFactory.forLocal(), signer) return new Signature(WarpFactory.forLocal(), signer);
} };
const createDataItem = async (signature: Signature, nonce: number, addNonceTag = true, addContractTag = true, signDataItem = true): Promise<DataItem> => { const createDataItem = async (
signature: Signature,
nonce: number,
addNonceTag = true,
addContractTag = true,
signDataItem = true
): Promise<DataItem> => {
const signer = signature.bundlerSigner; const signer = signature.bundlerSigner;
const tags: Tag[] = []; const tags: Tag[] = [];
if (addNonceTag) { if (addNonceTag) {
tags.push(new Tag(WARP_TAGS.SEQUENCER_NONCE, String(nonce))); tags.push(new Tag(WARP_TAGS.SEQUENCER_NONCE, String(nonce)));
} }
if (addContractTag) { 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 }); const dataItem = createData('some data', signer, { tags });
if (signDataItem) { if (signDataItem) {
await dataItem.sign(signer); await dataItem.sign(signer);
} }
return dataItem; return dataItem;
} };
it('should return consecutive nonces for a given signature', async () => { it('should return consecutive nonces for a given signature', async () => {
const client = createClient(); const client = createClient();
@@ -56,9 +61,9 @@ describe('Testing a decentralized sequencer client', () => {
const signature = await createSignature(); const signature = await createSignature();
const dataItem = await createDataItem(signature, 13); const dataItem = await createDataItem(signature, 13);
expect(client.sendDataItem(dataItem, false)) expect(client.sendDataItem(dataItem, false)).rejects.toThrowError(
.rejects 'account sequence mismatch, expected 0, got 13: incorrect account sequence'
.toThrowError('account sequence mismatch, expected 0, got 13: incorrect account sequence'); );
}); });
it('should reject a data item without nonce', async () => { it('should reject a data item without nonce', async () => {
@@ -66,9 +71,7 @@ describe('Testing a decentralized sequencer client', () => {
const signature = await createSignature(); const signature = await createSignature();
const dataItem = await createDataItem(signature, 0, false); const dataItem = await createDataItem(signature, 0, false);
expect(client.sendDataItem(dataItem, true)) expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('no sequencer nonce tag');
.rejects
.toThrowError('no sequencer nonce tag');
}); });
it('should reject a data item without contract', async () => { it('should reject a data item without contract', async () => {
@@ -76,9 +79,7 @@ describe('Testing a decentralized sequencer client', () => {
const signature = await createSignature(); const signature = await createSignature();
const dataItem = await createDataItem(signature, 0, true, false); const dataItem = await createDataItem(signature, 0, true, false);
expect(client.sendDataItem(dataItem, true)) expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('no contract tag');
.rejects
.toThrowError('no contract tag');
}); });
it('should reject an unsigned data item', async () => { it('should reject an unsigned data item', async () => {
@@ -86,9 +87,7 @@ describe('Testing a decentralized sequencer client', () => {
const signature = await createSignature(); const signature = await createSignature();
const dataItem = await createDataItem(signature, 0, true, true, false); const dataItem = await createDataItem(signature, 0, true, true, false);
expect(client.sendDataItem(dataItem, true)) expect(client.sendDataItem(dataItem, true)).rejects.toThrowError('data item verification error');
.rejects
.toThrowError('data item verification error');
}); });
it('should return an unconfirmed result', async () => { it('should return an unconfirmed result', async () => {

View File

@@ -166,9 +166,9 @@ export class SmartWeaveGlobal {
throw new Error(`Integer max value must be in the range [1, ${Number.MAX_SAFE_INTEGER}]`); 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 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 bigInt = Buffer.from(array).readBigUInt64BE();
const result = (bigInt % BigInt(maxValue)) + BigInt(1) const result = (bigInt % BigInt(maxValue)) + BigInt(1);
return Number(result); return Number(result);
} }
} }