feat: InteractionsLoader implementation for redstone-sw-gateway #59
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
3dd1eb67be
commit
15bd729db3
@@ -56,6 +56,7 @@
|
||||
"arweave-multihost": "^0.1.0",
|
||||
"axios": "^0.21.4",
|
||||
"bignumber.js": "^9.0.1",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
"json-beautify": "^1.1.1",
|
||||
"knex": "^0.95.14",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -69,6 +70,8 @@
|
||||
"@typescript-eslint/parser": "^4.29.2",
|
||||
"arlocal": "1.1.13",
|
||||
"cheerio": "^1.0.0-rc.10",
|
||||
"cli-table": "^0.3.11",
|
||||
"colors": "^1.4.0",
|
||||
"cors": "^2.8.5",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
|
||||
104
src/__tests__/regression/gateway-interactions.test.ts
Normal file
104
src/__tests__/regression/gateway-interactions.test.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import Arweave from 'arweave';
|
||||
import {
|
||||
LoggerFactory,
|
||||
RedstoneGatewayInteractionsLoader,
|
||||
ArweaveGatewayInteractionsLoader,
|
||||
DefaultEvaluationOptions
|
||||
} from '@smartweave';
|
||||
import { GQLEdgeInterface } from '../../legacy/gqlResult';
|
||||
|
||||
/*
|
||||
TODO: two test cases have been removed from the list - gateway-interaction test is failing due to the different
|
||||
amount of interactions returned from Redstone gateway and Arweave GQL gateway
|
||||
should be fixed in https://github.com/redstone-finance/redstone-sw-gateway/issues/17, following cases should be
|
||||
then added to ../test-cases/gateway-interactions.json
|
||||
"eWB7FHyPyCYnkcbK1aINbAQ9YYTDhKGkS7lDiNPZ5Mg",
|
||||
"cpXtKvM0e6cqAgjv-BCfanWQmYGupECt1MxRk1N9Mjk"
|
||||
*/
|
||||
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net',
|
||||
port: 443,
|
||||
protocol: 'https',
|
||||
timeout: 600000,
|
||||
logging: false
|
||||
});
|
||||
|
||||
const gatewayUrl = 'https://gateway.redstone.finance';
|
||||
|
||||
LoggerFactory.INST.logLevel('fatal');
|
||||
|
||||
const testCases: string[] = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, 'test-cases/gateway-interactions.json'), 'utf-8')
|
||||
);
|
||||
|
||||
/**
|
||||
* These regression tests should verify whether ArweaveGatewayInteractionsLoader and RedstoneGatewayInteractionsLoader
|
||||
* return same results for given variables
|
||||
*/
|
||||
|
||||
describe.each(testCases)('testing for contract %#', (contractTxId) => {
|
||||
it('returns same amount of interactions for RedstoneGatewayInteractionsLoader and ArweaveGatewayInteractionsLoader', async () => {
|
||||
const redstoneInteractionsLoader = new RedstoneGatewayInteractionsLoader(gatewayUrl);
|
||||
const arweaveInteractionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
const responseRedstoneInteractionsLoader = await redstoneInteractionsLoader.load(contractTxId, 0, 8301901);
|
||||
const responseArweaveInteractionsLoader = await arweaveInteractionsLoader.load(
|
||||
contractTxId,
|
||||
0,
|
||||
8301901,
|
||||
new DefaultEvaluationOptions()
|
||||
);
|
||||
|
||||
expect(responseRedstoneInteractionsLoader.length).toEqual(responseArweaveInteractionsLoader.length);
|
||||
}, 600000);
|
||||
});
|
||||
|
||||
describe.each([750000, 775000, 800000, 825000, 850000])('testing for block height %#', (toBlockHeight) => {
|
||||
it('returns same amount of interactions for the same block height', async () => {
|
||||
const redstoneInteractionsLoader = new RedstoneGatewayInteractionsLoader(gatewayUrl);
|
||||
const arweaveInteractionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
const responseRedstoneInteractionsLoader: GQLEdgeInterface[] = await redstoneInteractionsLoader.load(
|
||||
'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY',
|
||||
0,
|
||||
toBlockHeight
|
||||
);
|
||||
const responseArweaveInteractionsLoader: GQLEdgeInterface[] = await arweaveInteractionsLoader.load(
|
||||
'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY',
|
||||
0,
|
||||
toBlockHeight,
|
||||
new DefaultEvaluationOptions()
|
||||
);
|
||||
|
||||
expect(responseRedstoneInteractionsLoader.length).toEqual(responseArweaveInteractionsLoader.length);
|
||||
}, 600000);
|
||||
});
|
||||
|
||||
describe.each(testCases)('testing contractId %#', (contractTxId) => {
|
||||
it('returns same interactions ids for RedstoneGatewayLoader and ArweaveGatewayInteractionsLoader', async () => {
|
||||
const redstoneInteractionsLoader = new RedstoneGatewayInteractionsLoader(gatewayUrl);
|
||||
const arweaveInteractionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
const responseRedstoneInteractionsLoader: GQLEdgeInterface[] = await redstoneInteractionsLoader.load(
|
||||
contractTxId,
|
||||
0,
|
||||
8301901
|
||||
);
|
||||
const responseArweaveInteractionsLoader: GQLEdgeInterface[] = await arweaveInteractionsLoader.load(
|
||||
contractTxId,
|
||||
0,
|
||||
8301901,
|
||||
new DefaultEvaluationOptions()
|
||||
);
|
||||
|
||||
let arr = [];
|
||||
responseRedstoneInteractionsLoader.forEach((resRedstone) => {
|
||||
arr.push(
|
||||
responseArweaveInteractionsLoader.find((resArweave) => resArweave.node.id === resRedstone.node.id) !== undefined
|
||||
);
|
||||
});
|
||||
const result = arr.every((a) => a === true);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
}, 600000);
|
||||
});
|
||||
@@ -4,7 +4,6 @@ import path from 'path';
|
||||
import { interactRead, readContract } from 'smartweave';
|
||||
import Arweave from 'arweave';
|
||||
import { LoggerFactory, SmartWeaveNodeFactory } from '@smartweave';
|
||||
import { TsLogFactory } from '../../logging/node/TsLogFactory';
|
||||
|
||||
function* chunks(arr, n) {
|
||||
for (let i = 0; i < arr.length; i += n) {
|
||||
@@ -23,7 +22,7 @@ const arweave = Arweave.init({
|
||||
|
||||
LoggerFactory.INST.logLevel('fatal');
|
||||
|
||||
const testCases: string[] = JSON.parse(fs.readFileSync(path.join(__dirname, 'test-cases.json'), 'utf-8'));
|
||||
const testCases: string[] = JSON.parse(fs.readFileSync(path.join(__dirname, 'test-cases/read-state.json'), 'utf-8'));
|
||||
|
||||
const chunked: string[][][] = [...chunks(testCases, 10)];
|
||||
|
||||
@@ -39,7 +38,6 @@ describe.each(chunked)('.suite %#', (contracts: string[]) => {
|
||||
console.log('readState', contractTxId);
|
||||
const result2 = await SmartWeaveNodeFactory.memCached(arweave, 5).contract(contractTxId).readState();
|
||||
const result2String = JSON.stringify(result2.state).trim();
|
||||
|
||||
expect(result2String).toEqual(resultString);
|
||||
},
|
||||
600000
|
||||
@@ -0,0 +1,15 @@
|
||||
[
|
||||
"Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY",
|
||||
"-8A6RexFkpfWwuyVO98wzSFZh0d6VJuI-buTJvlwOJQ",
|
||||
"AVTqjPQGCCXim7Nl_gn3HMjE4k0Zi_eTFRJCNEVXZxw",
|
||||
"6eTVr8IKPNYbMHVcpHFXr-XNaL5hT6zRJXimcP-owmo",
|
||||
"mzvUgNc8YFk0w5K5H7c8pyT-FC5Y_ba0r7_8766Kx74",
|
||||
"SJ3l7474UHh3Dw6dWVT1bzsJ-8JvOewtGoDdOecWIZo",
|
||||
"ydjfv0hRQdD2c-MASv4L5Qahjap_LXJyD9tNyKWvf50",
|
||||
"c25-RdheC6khcACLv23-XXg1W7YuA-VSZ_1_qnNFbhw",
|
||||
"ZT-70ovBlkF6cRIqvyHy5lC2LcjudsmCz9z19M4_QC4",
|
||||
"o-qJmQ4B0d6TnyA_awjhiBdiq0O4Vt_dNWU3pTnhTu8",
|
||||
"BYRf00nIE4pGkJ8GyUY2PIJ1rBtxFPoWIu-0dd8iukg",
|
||||
"5NgGX4OToJ4M5ohWP4yxaTz_2oPsnk7vmR0v3mqXi_A",
|
||||
"4o-2xMPa45BXjGuII_LbOMQWfhE1F0qugdEUZvRlXRY"
|
||||
]
|
||||
149
src/__tests__/unit/gateway-interactions.loader.test.ts
Normal file
149
src/__tests__/unit/gateway-interactions.loader.test.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { RedstoneGatewayInteractionsLoader, LoggerFactory } from '@smartweave';
|
||||
import { GQLEdgeInterface } from '../../legacy/gqlResult';
|
||||
|
||||
const responseData = {
|
||||
paging: {
|
||||
total: '1',
|
||||
limit: 500,
|
||||
items: 1,
|
||||
page: 1,
|
||||
pages: 1
|
||||
},
|
||||
interactions: [
|
||||
{
|
||||
status: 'confirmed',
|
||||
confirming_peers: '94.130.135.178,159.203.49.13,95.217.114.57',
|
||||
confirmations: '172044,172044,172044',
|
||||
interaction: {
|
||||
id: 'XyJm1OERe__Q-YcwTQrCeYsI14_ylASey6eYdPg-HYg',
|
||||
fee: {
|
||||
winston: '48173811033'
|
||||
},
|
||||
tags: [],
|
||||
block: {
|
||||
id: 'w8y2bxCQd3-26lvvy2NOt6Qz0kVooN9h4rwy6UIeC5mEfVnbftqcnWEavZfT14vY',
|
||||
height: 655393,
|
||||
timestamp: 1617060107
|
||||
},
|
||||
owner: {
|
||||
address: 'oZjQWwcTYbEvnwr6zkxFqpEoDTPvWkaL3zO3-SFq2g0'
|
||||
},
|
||||
parent: null,
|
||||
quantity: {
|
||||
winston: '0'
|
||||
},
|
||||
recipient: ''
|
||||
}
|
||||
},
|
||||
{
|
||||
status: 'confirmed',
|
||||
confirming_peers: '94.130.135.178,159.203.49.13,95.217.114.57',
|
||||
confirmations: '172044,172044,172044',
|
||||
interaction: {
|
||||
id: 'XyJm1OERe__Q-YcwTQrCeYsI14_ylASey6eYdPg-HYg',
|
||||
fee: {
|
||||
winston: '48173811033'
|
||||
},
|
||||
tags: [],
|
||||
block: {
|
||||
id: 'w8y2bxCQd3-26lvvy2NOt6Qz0kVooN9h4rwy6UIeC5mEfVnbftqcnWEavZfT14vY',
|
||||
height: 655393,
|
||||
timestamp: 1617060107
|
||||
},
|
||||
owner: {
|
||||
address: 'oZjQWwcTYbEvnwr6zkxFqpEoDTPvWkaL3zO3-SFq2g0'
|
||||
},
|
||||
parent: null,
|
||||
quantity: {
|
||||
winston: '0'
|
||||
},
|
||||
recipient: ''
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const responseDataPaging = {
|
||||
paging: {
|
||||
total: '5',
|
||||
limit: 500,
|
||||
items: 1,
|
||||
page: 1,
|
||||
pages: 5
|
||||
},
|
||||
interactions: []
|
||||
};
|
||||
|
||||
LoggerFactory.INST.logLevel('error');
|
||||
|
||||
const contractId = 'SJ3l7474UHh3Dw6dWVT1bzsJ-8JvOewtGoDdOecWIZo';
|
||||
const fromBlockHeight = 600000;
|
||||
const toBlockHeight = 655393;
|
||||
const baseUrl = `http://baseUrl/gateway/interactions?contractId=SJ3l7474UHh3Dw6dWVT1bzsJ-8JvOewtGoDdOecWIZo&from=600000&to=655393`;
|
||||
const fetchMock = jest
|
||||
.spyOn(global, 'fetch')
|
||||
.mockImplementation(
|
||||
() => Promise.resolve({ json: () => Promise.resolve(responseData), ok: true, status: 200 }) as Promise<Response>
|
||||
);
|
||||
|
||||
describe('RedstoneGatewayInteractionsLoader -> load', () => {
|
||||
it('should return correct number of interactions', async () => {
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl');
|
||||
const response: GQLEdgeInterface[] = await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
expect(fetchMock).toHaveBeenCalled();
|
||||
expect(response.length).toEqual(2);
|
||||
});
|
||||
it('should be called with correct params', async () => {
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl');
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=1`);
|
||||
});
|
||||
it('should be called accordingly to the amount of pages', async () => {
|
||||
const fetchMock = jest.spyOn(global, 'fetch').mockImplementation(
|
||||
() =>
|
||||
Promise.resolve({
|
||||
json: () => Promise.resolve(responseDataPaging),
|
||||
ok: true,
|
||||
status: 200
|
||||
}) as Promise<Response>
|
||||
);
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl');
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=1`);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=2`);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=3`);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=4`);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=4`);
|
||||
expect(fetchMock).toHaveBeenCalledTimes(5);
|
||||
});
|
||||
it('should be called with confirmationStatus set to "confirmed"', async () => {
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl', { confirmed: true });
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=1&confirmationStatus=confirmed`);
|
||||
});
|
||||
it('should be called with confirmationStatus set to "not_corrupted"', async () => {
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl', { notCorrupted: true });
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
expect(fetchMock).toBeCalledWith(`${baseUrl}&page=1&confirmationStatus=not_corrupted`);
|
||||
});
|
||||
it('should throw an error in case of timeout', async () => {
|
||||
jest.spyOn(global, 'fetch').mockImplementation(() => Promise.reject({ status: 504, ok: false }));
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl');
|
||||
try {
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error('Unable to retrieve transactions. Redstone gateway responded with status 504.'));
|
||||
}
|
||||
});
|
||||
it('should throw an error when request fails', async () => {
|
||||
jest
|
||||
.spyOn(global, 'fetch')
|
||||
.mockImplementation(() => Promise.reject({ status: 500, ok: false, body: { message: 'request fails' } }));
|
||||
const loader = new RedstoneGatewayInteractionsLoader('http://baseUrl');
|
||||
try {
|
||||
await loader.load(contractId, fromBlockHeight, toBlockHeight);
|
||||
} catch (e) {
|
||||
expect(e).toEqual(new Error('Unable to retrieve transactions. Redstone gateway responded with status 500.'));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -273,8 +273,14 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
blockHeight?: number,
|
||||
forceDefinitionLoad = false
|
||||
): Promise<ExecutionContext<State, HandlerApi<State>>> {
|
||||
const { arweave, definitionLoader, interactionsLoader, interactionsSorter, executorFactory, stateEvaluator } =
|
||||
this.smartweave;
|
||||
const {
|
||||
arweave,
|
||||
definitionLoader,
|
||||
cacheableContractInteractionsLoader,
|
||||
interactionsSorter,
|
||||
executorFactory,
|
||||
stateEvaluator
|
||||
} = this.smartweave;
|
||||
|
||||
let currentNetworkInfo;
|
||||
|
||||
@@ -324,7 +330,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
// (eg. if contract is calling different contracts on different block heights).
|
||||
// This basically limits the amount of interactions with Arweave GraphQL endpoint -
|
||||
// each such interaction takes at least ~500ms.
|
||||
interactionsLoader.load(
|
||||
cacheableContractInteractionsLoader.load(
|
||||
contractTxId,
|
||||
cachedBlockHeight + 1,
|
||||
this._rootBlockHeight || this._networkInfo.height,
|
||||
@@ -362,8 +368,13 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
transaction: GQLNodeInterface
|
||||
): Promise<ExecutionContext<State, HandlerApi<State>>> {
|
||||
const benchmark = Benchmark.measure();
|
||||
const { definitionLoader, interactionsLoader, interactionsSorter, executorFactory, stateEvaluator } =
|
||||
this.smartweave;
|
||||
const {
|
||||
definitionLoader,
|
||||
cacheableContractInteractionsLoader,
|
||||
interactionsSorter,
|
||||
executorFactory,
|
||||
stateEvaluator
|
||||
} = this.smartweave;
|
||||
const blockHeight = transaction.block.height;
|
||||
const caller = transaction.owner.address;
|
||||
|
||||
@@ -380,7 +391,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
if (cachedBlockHeight != blockHeight) {
|
||||
[contractDefinition, interactions] = await Promise.all([
|
||||
definitionLoader.load<State>(contractTxId),
|
||||
await interactionsLoader.load(contractTxId, 0, blockHeight, this._evaluationOptions)
|
||||
await cacheableContractInteractionsLoader.load(contractTxId, 0, blockHeight, this._evaluationOptions)
|
||||
]);
|
||||
sortedInteractions = await interactionsSorter.sort(interactions);
|
||||
} else {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import Arweave from 'arweave';
|
||||
import { Contract, HandlerBasedContract, PstContract, PstContractImpl } from '@smartweave/contract';
|
||||
import { GQLNodeInterface } from '@smartweave/legacy';
|
||||
import { CacheableContractInteractionsLoader } from '@smartweave/plugins';
|
||||
|
||||
/**
|
||||
* The SmartWeave "motherboard" ;-).
|
||||
@@ -28,6 +29,7 @@ export class SmartWeave {
|
||||
readonly arweave: Arweave,
|
||||
readonly definitionLoader: DefinitionLoader,
|
||||
readonly interactionsLoader: InteractionsLoader,
|
||||
readonly cacheableContractInteractionsLoader: CacheableContractInteractionsLoader,
|
||||
readonly interactionsSorter: InteractionsSorter,
|
||||
readonly executorFactory: ExecutorFactory<HandlerApi<unknown>>,
|
||||
readonly stateEvaluator: StateEvaluator
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Arweave from 'arweave';
|
||||
import {
|
||||
BlockHeightSwCache,
|
||||
CacheableContractInteractionsLoader,
|
||||
DebuggableExecutorFactory,
|
||||
DefinitionLoader,
|
||||
ExecutorFactory,
|
||||
HandlerApi,
|
||||
InteractionsLoader,
|
||||
InteractionsSorter,
|
||||
MemBlockHeightSwCache,
|
||||
SmartWeave,
|
||||
StateEvaluator
|
||||
} from '@smartweave';
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
export class SmartWeaveBuilder {
|
||||
private _definitionLoader?: DefinitionLoader;
|
||||
private _interactionsLoader?: InteractionsLoader;
|
||||
private _cacheableContractInteractionsLoader?: CacheableContractInteractionsLoader;
|
||||
private _interactionsSorter?: InteractionsSorter;
|
||||
private _executorFactory?: ExecutorFactory<HandlerApi<unknown>>;
|
||||
private _stateEvaluator?: StateEvaluator;
|
||||
@@ -30,6 +32,17 @@ export class SmartWeaveBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public setCacheableInteractionsLoader(
|
||||
value: InteractionsLoader,
|
||||
maxStoredInMemoryBlockHeights: number = Number.MAX_SAFE_INTEGER
|
||||
): SmartWeaveBuilder {
|
||||
this._cacheableContractInteractionsLoader = new CacheableContractInteractionsLoader(
|
||||
value,
|
||||
new MemBlockHeightSwCache(maxStoredInMemoryBlockHeights)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public setInteractionsSorter(value: InteractionsSorter): SmartWeaveBuilder {
|
||||
this._interactionsSorter = value;
|
||||
return this;
|
||||
@@ -58,6 +71,7 @@ export class SmartWeaveBuilder {
|
||||
this._arweave,
|
||||
this._definitionLoader,
|
||||
this._interactionsLoader,
|
||||
this._cacheableContractInteractionsLoader,
|
||||
this._interactionsSorter,
|
||||
this._executorFactory,
|
||||
this._stateEvaluator
|
||||
|
||||
@@ -7,7 +7,8 @@ export * from './modules/CreateContract';
|
||||
|
||||
export * from './modules/impl/BlockHeightInteractionsSorter';
|
||||
export * from './modules/impl/ContractDefinitionLoader';
|
||||
export * from './modules/impl/ContractInteractionsLoader';
|
||||
export * from './modules/impl/ArweaveGatewayInteractionsLoader';
|
||||
export * from './modules/impl/RedstoneGatewayInteractionsLoader';
|
||||
export * from './modules/impl/DefaultStateEvaluator';
|
||||
export * from './modules/impl/CacheableStateEvaluator';
|
||||
export * from './modules/impl/HandlerExecutorFactory';
|
||||
|
||||
@@ -11,6 +11,6 @@ export interface InteractionsLoader {
|
||||
contractId: string,
|
||||
fromBlockHeight: number,
|
||||
toBlockHeight: number,
|
||||
evaluationOptions: EvaluationOptions
|
||||
evaluationOptions?: EvaluationOptions
|
||||
): Promise<GQLEdgeInterface[]>;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ interface ReqVariables {
|
||||
after?: string;
|
||||
}
|
||||
|
||||
export class ContractInteractionsLoader implements InteractionsLoader {
|
||||
private readonly logger = LoggerFactory.INST.create('ContractInteractionsLoader');
|
||||
export class ArweaveGatewayInteractionsLoader implements InteractionsLoader {
|
||||
private readonly logger = LoggerFactory.INST.create('ArweaveGatewayInteractionsLoader');
|
||||
|
||||
private static readonly query = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) {
|
||||
transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) {
|
||||
@@ -147,18 +147,18 @@ export class ContractInteractionsLoader implements InteractionsLoader {
|
||||
private async getNextPage(variables: ReqVariables): Promise<GQLTransactionsResultInterface> {
|
||||
const benchmark = Benchmark.measure();
|
||||
let response = await this.arweave.api.post('graphql', {
|
||||
query: ContractInteractionsLoader.query,
|
||||
query: ArweaveGatewayInteractionsLoader.query,
|
||||
variables
|
||||
});
|
||||
this.logger.debug('GQL page load:', benchmark.elapsed());
|
||||
|
||||
while (response.status === 403) {
|
||||
this.logger.warn(`GQL rate limiting, waiting ${ContractInteractionsLoader._30seconds}ms before next try.`);
|
||||
this.logger.warn(`GQL rate limiting, waiting ${ArweaveGatewayInteractionsLoader._30seconds}ms before next try.`);
|
||||
|
||||
await sleep(ContractInteractionsLoader._30seconds);
|
||||
await sleep(ArweaveGatewayInteractionsLoader._30seconds);
|
||||
|
||||
response = await this.arweave.api.post('graphql', {
|
||||
query: ContractInteractionsLoader.query,
|
||||
query: ArweaveGatewayInteractionsLoader.query,
|
||||
variables
|
||||
});
|
||||
}
|
||||
112
src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts
Normal file
112
src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { Benchmark, InteractionsLoader, LoggerFactory } from '@smartweave';
|
||||
import { GQLEdgeInterface, GQLNodeInterface } from 'legacy/gqlResult';
|
||||
import 'isomorphic-fetch';
|
||||
interface Paging {
|
||||
total: string;
|
||||
limit: number;
|
||||
items: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
interface Interaction {
|
||||
status: string;
|
||||
confirming_peers: string;
|
||||
confirmations: string;
|
||||
interaction: GQLNodeInterface;
|
||||
}
|
||||
|
||||
export interface RedstoneGatewayInteractions {
|
||||
paging: Paging;
|
||||
interactions: Interaction[];
|
||||
message?: string;
|
||||
}
|
||||
|
||||
type ConfirmationStatus =
|
||||
| {
|
||||
notCorrupted?: boolean;
|
||||
confirmed?: null;
|
||||
}
|
||||
| {
|
||||
notCorrupted?: null;
|
||||
confirmed?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* The aim of this implementation of the {@link InteractionsLoader} is to make use of Redstone Gateway endpoint
|
||||
* and retrieve contracts' interactions. Optionally - it is possible to pass skipOrphans flag in the constructor
|
||||
* and therefore receive only these transactions which are confirmed. To learn more about Redstone Gateway please visit
|
||||
* {@link https://github.com/redstone-finance/redstone-sw-gateway}.
|
||||
* Please note that currently caching is switched off for RedstoneGatewayInteractionsLoader due to the issue mentioned in the
|
||||
* following comment {@link https://github.com/redstone-finance/redstone-smartcontracts/pull/62#issuecomment-995249264}
|
||||
*/
|
||||
export class RedstoneGatewayInteractionsLoader implements InteractionsLoader {
|
||||
constructor(private readonly baseUrl: string, private readonly confirmationStatus: ConfirmationStatus = {}) {
|
||||
Object.assign(this, confirmationStatus);
|
||||
}
|
||||
|
||||
private readonly logger = LoggerFactory.INST.create('RedstoneGatewayInteractionsLoader');
|
||||
|
||||
async load(contractId: string, fromBlockHeight: number, toBlockHeight: number): Promise<GQLEdgeInterface[]> {
|
||||
this.logger.debug('Loading interactions: for ', { contractId, fromBlockHeight, toBlockHeight });
|
||||
|
||||
const interactions: GQLEdgeInterface[] = [];
|
||||
let page = 0;
|
||||
let totalPages = 0;
|
||||
|
||||
const benchmarkTotalTime = Benchmark.measure();
|
||||
do {
|
||||
const benchmarkRequestTime = Benchmark.measure();
|
||||
const response = await fetch(
|
||||
`${this.baseUrl}/gateway/interactions?${new URLSearchParams({
|
||||
contractId: contractId,
|
||||
from: fromBlockHeight.toString(),
|
||||
to: toBlockHeight.toString(),
|
||||
page: (++page).toString(),
|
||||
...(this.confirmationStatus.confirmed ? { confirmationStatus: 'confirmed' } : ''),
|
||||
...(this.confirmationStatus.notCorrupted ? { confirmationStatus: 'not_corrupted' } : '')
|
||||
})}`
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.ok) {
|
||||
return res.json();
|
||||
} else {
|
||||
return Promise.reject(res);
|
||||
}
|
||||
})
|
||||
.then((data) => {
|
||||
return data;
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error.body?.message) {
|
||||
this.logger.error(error.body.message);
|
||||
}
|
||||
throw new Error(`Unable to retrieve transactions. Redstone gateway responded with status ${error.status}.`);
|
||||
});
|
||||
this.logger.debug(`Loading interactions: page ${page}, time: `, benchmarkRequestTime.elapsed());
|
||||
|
||||
totalPages = response.paging.pages;
|
||||
|
||||
this.logger.debug(`Loading interactions: page ${page} of ${totalPages} loaded`);
|
||||
|
||||
response.interactions.forEach((interaction) =>
|
||||
interactions.push({
|
||||
cursor: '',
|
||||
node: interaction.interaction
|
||||
})
|
||||
);
|
||||
|
||||
this.logger.debug(`Loaded interactions length: ${interactions.length}`);
|
||||
} while (page < totalPages);
|
||||
|
||||
this.logger.debug(`Loading interactions for ${contractId}:`, benchmarkTotalTime.elapsed());
|
||||
|
||||
this.logger.debug('All loaded interactions:', {
|
||||
from: fromBlockHeight,
|
||||
to: toBlockHeight,
|
||||
loaded: interactions.length
|
||||
});
|
||||
|
||||
return interactions;
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,15 @@ import Arweave from 'arweave';
|
||||
import {
|
||||
CacheableStateEvaluator,
|
||||
ContractDefinitionLoader,
|
||||
ContractInteractionsLoader,
|
||||
ArweaveGatewayInteractionsLoader,
|
||||
HandlerExecutorFactory,
|
||||
LexicographicalInteractionsSorter,
|
||||
SmartWeave,
|
||||
SmartWeaveBuilder,
|
||||
SmartWeaveWebFactory
|
||||
} from '@smartweave/core';
|
||||
import { CacheableContractInteractionsLoader, CacheableExecutorFactory, Evolve } from '@smartweave/plugins';
|
||||
import { FileBlockHeightSwCache, MemBlockHeightSwCache, MemCache } from '@smartweave/cache';
|
||||
import { CacheableExecutorFactory, Evolve } from '@smartweave/plugins';
|
||||
import { FileBlockHeightSwCache, MemCache } from '@smartweave/cache';
|
||||
import { Knex } from 'knex';
|
||||
import { KnexStateCache } from '../../cache/impl/KnexStateCache';
|
||||
|
||||
@@ -45,10 +45,7 @@ export class SmartWeaveNodeFactory extends SmartWeaveWebFactory {
|
||||
): SmartWeaveBuilder {
|
||||
const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache());
|
||||
|
||||
const interactionsLoader = new CacheableContractInteractionsLoader(
|
||||
new ContractInteractionsLoader(arweave),
|
||||
new MemBlockHeightSwCache()
|
||||
);
|
||||
const gatewayInteractionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
|
||||
const executorFactory = new CacheableExecutorFactory(arweave, new HandlerExecutorFactory(arweave), new MemCache());
|
||||
|
||||
@@ -62,7 +59,7 @@ export class SmartWeaveNodeFactory extends SmartWeaveWebFactory {
|
||||
|
||||
return SmartWeave.builder(arweave)
|
||||
.setDefinitionLoader(definitionLoader)
|
||||
.setInteractionsLoader(interactionsLoader)
|
||||
.setCacheableInteractionsLoader(gatewayInteractionsLoader)
|
||||
.setInteractionsSorter(interactionsSorter)
|
||||
.setExecutorFactory(executorFactory)
|
||||
.setStateEvaluator(stateEvaluator);
|
||||
@@ -85,10 +82,7 @@ export class SmartWeaveNodeFactory extends SmartWeaveWebFactory {
|
||||
): Promise<SmartWeaveBuilder> {
|
||||
const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache());
|
||||
|
||||
const interactionsLoader = new CacheableContractInteractionsLoader(
|
||||
new ContractInteractionsLoader(arweave),
|
||||
new MemBlockHeightSwCache()
|
||||
);
|
||||
const gatewayInteractionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
|
||||
const executorFactory = new CacheableExecutorFactory(arweave, new HandlerExecutorFactory(arweave), new MemCache());
|
||||
|
||||
@@ -102,7 +96,7 @@ export class SmartWeaveNodeFactory extends SmartWeaveWebFactory {
|
||||
|
||||
return SmartWeave.builder(arweave)
|
||||
.setDefinitionLoader(definitionLoader)
|
||||
.setInteractionsLoader(interactionsLoader)
|
||||
.setCacheableInteractionsLoader(gatewayInteractionsLoader)
|
||||
.setInteractionsSorter(interactionsSorter)
|
||||
.setExecutorFactory(executorFactory)
|
||||
.setStateEvaluator(stateEvaluator);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { CacheableContractInteractionsLoader, CacheableExecutorFactory, Evolve }
|
||||
import {
|
||||
CacheableStateEvaluator,
|
||||
ContractDefinitionLoader,
|
||||
ContractInteractionsLoader,
|
||||
ArweaveGatewayInteractionsLoader,
|
||||
HandlerExecutorFactory,
|
||||
LexicographicalInteractionsSorter,
|
||||
SmartWeave,
|
||||
@@ -34,7 +34,7 @@ export class SmartWeaveWebFactory {
|
||||
const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache());
|
||||
|
||||
const interactionsLoader = new CacheableContractInteractionsLoader(
|
||||
new ContractInteractionsLoader(arweave),
|
||||
new ArweaveGatewayInteractionsLoader(arweave),
|
||||
new RemoteBlockHeightCache('INTERACTIONS', cacheBaseURL)
|
||||
);
|
||||
|
||||
@@ -74,10 +74,7 @@ export class SmartWeaveWebFactory {
|
||||
): SmartWeaveBuilder {
|
||||
const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache());
|
||||
|
||||
const interactionsLoader = new CacheableContractInteractionsLoader(
|
||||
new ContractInteractionsLoader(arweave),
|
||||
new MemBlockHeightSwCache(maxStoredBlockHeights)
|
||||
);
|
||||
const interactionsLoader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
|
||||
const executorFactory = new CacheableExecutorFactory(arweave, new HandlerExecutorFactory(arweave), new MemCache());
|
||||
|
||||
@@ -91,7 +88,7 @@ export class SmartWeaveWebFactory {
|
||||
|
||||
return SmartWeave.builder(arweave)
|
||||
.setDefinitionLoader(definitionLoader)
|
||||
.setInteractionsLoader(interactionsLoader)
|
||||
.setCacheableInteractionsLoader(interactionsLoader, maxStoredBlockHeights)
|
||||
.setInteractionsSorter(interactionsSorter)
|
||||
.setExecutorFactory(executorFactory)
|
||||
.setStateEvaluator(stateEvaluator);
|
||||
|
||||
@@ -24,7 +24,7 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader {
|
||||
contractId: string,
|
||||
fromBlockHeight: number,
|
||||
toBlockHeight: number,
|
||||
evaluationOptions: EvaluationOptions
|
||||
evaluationOptions?: EvaluationOptions
|
||||
): Promise<GQLEdgeInterface[]> {
|
||||
this.logger.debug('Loading interactions', {
|
||||
contractId,
|
||||
|
||||
59
tools/gateway-benchmark.ts
Normal file
59
tools/gateway-benchmark.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import Arweave from 'arweave';
|
||||
import {
|
||||
ArweaveGatewayInteractionsLoader,
|
||||
DefaultEvaluationOptions,
|
||||
LoggerFactory,
|
||||
RedstoneGatewayInteractionsLoader,
|
||||
Benchmark
|
||||
} from '@smartweave';
|
||||
import { TsLogFactory } from '../src/logging/node/TsLogFactory';
|
||||
import Table from 'cli-table';
|
||||
import colors from 'colors/safe';
|
||||
|
||||
/*
|
||||
Script allows to benchmark loading interactions response time based on the given gateway
|
||||
To run this script properly, one need to pass [gateway name][contract id][from][to] variables as script's arguments
|
||||
e.g yarn ts-node -r tsconfig-paths/register tools/gateways-comparison-benchmark.ts arweave Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY 0 70000
|
||||
*/
|
||||
|
||||
async function gatewayBenchmark() {
|
||||
let table = new Table({
|
||||
head: ['gateway', 'contractId', 'fromBlockHeight', 'toBlockHeight', 'timeSpent'],
|
||||
colWidths: [10, 50, 20, 20, 20]
|
||||
});
|
||||
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net',
|
||||
port: 443,
|
||||
protocol: 'https',
|
||||
logging: false
|
||||
});
|
||||
|
||||
LoggerFactory.use(new TsLogFactory());
|
||||
LoggerFactory.INST.logLevel('debug');
|
||||
|
||||
const gateway = process.argv[2];
|
||||
const contractId = process.argv[3];
|
||||
const fromBlockHeight = process.argv[4];
|
||||
const toBlockHeight = process.argv[5];
|
||||
|
||||
const loader =
|
||||
gateway == 'arweave'
|
||||
? new ArweaveGatewayInteractionsLoader(arweave)
|
||||
: new RedstoneGatewayInteractionsLoader('https://d1o5nlqr4okus2.cloudfront.net');
|
||||
|
||||
const options = gateway == 'arweave' ? new DefaultEvaluationOptions() : null;
|
||||
|
||||
const benchmark = Benchmark.measure();
|
||||
await loader.load(contractId, parseInt(fromBlockHeight), parseInt(toBlockHeight), options);
|
||||
|
||||
const timeSpent = benchmark.elapsed();
|
||||
|
||||
table.push([gateway, contractId, fromBlockHeight, toBlockHeight, timeSpent.toString()].map((el) => colors.blue(el)));
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
|
||||
gatewayBenchmark().catch((e) => console.error(e));
|
||||
75
tools/gateways-comparison-benchmark.ts
Normal file
75
tools/gateways-comparison-benchmark.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import Arweave from 'arweave';
|
||||
import {
|
||||
ArweaveGatewayInteractionsLoader,
|
||||
DefaultEvaluationOptions,
|
||||
LoggerFactory,
|
||||
RedstoneGatewayInteractionsLoader,
|
||||
Benchmark
|
||||
} from '@smartweave';
|
||||
import { TsLogFactory } from '../src/logging/node/TsLogFactory';
|
||||
import Table from 'cli-table';
|
||||
import colors from 'colors/safe';
|
||||
|
||||
/*
|
||||
Script allows to benchmark loading interactions response time for given contract for both Arweave and Redstone gateways
|
||||
To run this script properly, one need to pass [contract id][from][to] variables as script's arugments
|
||||
e.g yarn ts-node -r tsconfig-paths/register tools/gateways-comparison-benchmark.ts Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY 0 70000
|
||||
*/
|
||||
|
||||
async function gatewayComparisonBenchmark() {
|
||||
let table = new Table({
|
||||
head: ['contractId', 'fromBlockHeight', 'toBlockHeight', 'arweave', 'redstone'],
|
||||
colWidths: [50, 20, 20, 15, 15]
|
||||
});
|
||||
|
||||
LoggerFactory.use(new TsLogFactory());
|
||||
LoggerFactory.INST.logLevel('debug');
|
||||
|
||||
const contractId = process.argv[2];
|
||||
const fromBlockHeight = process.argv[3];
|
||||
const toBlockHeight = process.argv[4];
|
||||
|
||||
const timeSpentArweave = await loadFromAweaveGateway(contractId, fromBlockHeight, toBlockHeight);
|
||||
|
||||
const timeSpentRedstone = await loadFromRedstoneGateway(contractId, fromBlockHeight, toBlockHeight);
|
||||
|
||||
table.push(
|
||||
[contractId, fromBlockHeight, toBlockHeight, timeSpentArweave.toString(), `${timeSpentRedstone.toString()}ms`].map(
|
||||
(el) => colors.blue(el)
|
||||
)
|
||||
);
|
||||
|
||||
console.log(table.toString());
|
||||
}
|
||||
|
||||
async function loadFromRedstoneGateway(contractId: string, fromBlockHeight?: string, toBlockHeight?: string) {
|
||||
const loader = new RedstoneGatewayInteractionsLoader('https://d1o5nlqr4okus2.cloudfront.net');
|
||||
|
||||
const benchmark = Benchmark.measure();
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await loader.load(contractId, parseInt(fromBlockHeight), parseInt(toBlockHeight));
|
||||
}
|
||||
return Math.round(parseInt(benchmark.elapsed().toString().replace('ms', ''))) / 3;
|
||||
}
|
||||
|
||||
async function loadFromAweaveGateway(contractId: string, fromBlockHeight?: string, toBlockHeight?: string) {
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net',
|
||||
port: 443,
|
||||
protocol: 'https',
|
||||
logging: false
|
||||
});
|
||||
|
||||
const loader = new ArweaveGatewayInteractionsLoader(arweave);
|
||||
const benchmark = Benchmark.measure();
|
||||
await loader.load(
|
||||
contractId,
|
||||
fromBlockHeight ? parseInt(fromBlockHeight) : 0,
|
||||
toBlockHeight ? parseInt(toBlockHeight) : 831900,
|
||||
new DefaultEvaluationOptions()
|
||||
);
|
||||
return benchmark.elapsed();
|
||||
}
|
||||
gatewayComparisonBenchmark().catch((e) => console.error(e));
|
||||
Reference in New Issue
Block a user