feat: AutoSync connected contracts when syncState #333

This commit is contained in:
Tadeuchi
2023-02-21 12:43:46 +01:00
parent 9eb87f19d7
commit 446532f4a3
9 changed files with 561 additions and 21 deletions

View File

@@ -0,0 +1,197 @@
import fs from 'fs';
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import path from 'path';
import { mineBlock } from '../_helpers';
import { PstState, PstContract } from '../../../contract/PstContract';
import { Warp } from '../../../core/Warp';
import { WarpFactory } from '../../../core/WarpFactory';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { DeployPlugin } from "warp-contracts-plugin-deploy";
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
const AR_PORT = 1825;
describe('Testing the Profit Sharing Token', () => {
let contractSrc: string;
let wallet: JWKInterface;
let walletAddress: string;
let initialState: PstState;
let arweave: Arweave;
let arlocal: ArLocal;
let warp: Warp;
let contractTxId: string;
let pst: PstContract;
const actualFetch = global.fetch
let responseData = {
sortKey: "",
state: {}
}
let firstSortKey = ''
const remoteCalls = {
total: 0,
measure: function() {
const self = this;
const start = self.total;
return {
diff: () => self.total - start
}
}
}
const localWarp = async function() {
if (!arlocal) {
arlocal = new ArLocal(AR_PORT, false);
await arlocal.start();
}
return WarpFactory.forLocal(AR_PORT).use(new DeployPlugin());
}
const autoSyncPst = async function() {
const autoSyncPst = (await localWarp()).pst(contractTxId);
autoSyncPst.setEvaluationOptions({
remoteStateSyncEnabled: true
})
return autoSyncPst;
}
beforeAll(async () => {
LoggerFactory.INST.logLevel('error');
warp = await localWarp();
({ arweave } = warp);
({ jwk: wallet, address: walletAddress } = await warp.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../data/token-pst.js'), 'utf8');
const stateFromFile: PstState = JSON.parse(fs.readFileSync(path.join(__dirname, '../data/token-pst.json'), 'utf8'));
initialState = {
...stateFromFile,
...{
owner: walletAddress,
balances: {
...stateFromFile.balances,
[walletAddress]: 555669
}
}
};
// deploying contract using the new SDK.
({ contractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify(initialState),
src: contractSrc
}));
// connecting to the PST contract
pst = warp.pst(contractTxId);
// connecting wallet to the PST contract
pst.connect(wallet);
await mineBlock(warp);
jest
.spyOn(global, 'fetch')
.mockImplementation( async (input: string, init) => {
if (input.includes(pst.evaluationOptions().remoteStateSyncSource)) {
remoteCalls.total++;
return Promise.resolve({ json: () => Promise.resolve(responseData), ok: true, status: 200 }) as Promise<Response>;
}
return actualFetch(input, init);
})
});
afterAll(async () => {
await arlocal.stop();
jest.restoreAllMocks();
});
it('should read pst state and balance data', async () => {
const dreCalls = remoteCalls.measure();
const state = await pst.readState();
firstSortKey = state.sortKey;
responseData.sortKey = state.sortKey;
responseData.state = state.cachedValue.state;
expect(state.cachedValue.state).toEqual(initialState);
const syncPst = await autoSyncPst();
expect(await syncPst.currentState()).toEqual(initialState);
expect((await pst.currentBalance('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M')).balance).toEqual(10000000);
expect((await syncPst.currentBalance('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M')).balance).toEqual(10000000);
expect((await pst.currentBalance('33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA')).balance).toEqual(23111222);
expect((await syncPst.currentBalance('33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA')).balance).toEqual(23111222);
expect((await pst.currentBalance(walletAddress)).balance).toEqual(555669);
expect((await syncPst.currentBalance(walletAddress)).balance).toEqual(555669);
expect(dreCalls.diff()).toEqual(4);
});
it('should properly transfer tokens and ignore remote source', async () => {
const dreCalls = remoteCalls.measure();
await pst.transfer({
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 100
});
await mineBlock(warp);
const state = await pst.readState();
responseData.sortKey = state.sortKey;
responseData.state = state.cachedValue.state;
expect((await pst.currentState()).balances[walletAddress]).toEqual(555669 - 100);
expect((await pst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 100);
expect(dreCalls.diff()).toEqual(0);
});
it('should transfer tokens and read state from remote source', async () => {
const dreCalls = remoteCalls.measure();
await pst.transfer({
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 450
});
await mineBlock(warp);
expect((await pst.currentState()).balances[walletAddress]).toEqual(555669 - 550);
expect((await pst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 550);
const syncPst = await autoSyncPst();
expect((await syncPst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000 + 100);
expect((await syncPst.currentState()).balances[walletAddress]).toEqual(555669 - 100);
syncPst.setEvaluationOptions({
remoteStateSyncEnabled: false
})
expect((await syncPst.currentState()).balances[walletAddress]).toEqual(555669 - 550);
expect((await syncPst.currentState()).balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(
10000000 + 550);
expect(dreCalls.diff()).toEqual(2);
});
it('should read pst state for previous sortKey', async () => {
const dreCalls = remoteCalls.measure();
const state = await pst.readState(firstSortKey);
expect(state.cachedValue.state.balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000);
expect(state.cachedValue.state.balances['33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA']).toEqual(23111222);
expect(state.cachedValue.state).toEqual(initialState);
const syncPst = await autoSyncPst();
const syncState = (await syncPst.readState(firstSortKey));
expect(await syncState.cachedValue.state).toEqual(initialState);
expect(syncState.cachedValue.state.balances['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M']).toEqual(10000000);
expect(syncState.cachedValue.state.balances['33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA']).toEqual(23111222);
expect(dreCalls.diff()).toEqual(1);
});
});

View File

@@ -0,0 +1,219 @@
import fs from 'fs';
import ArLocal from 'arlocal';
import Arweave from 'arweave';
import { JWKInterface } from 'arweave/node/lib/wallet';
import path from 'path';
import { mineBlock } from '../_helpers';
import { PstState, PstContract } from '../../../contract/PstContract';
import { Warp } from '../../../core/Warp';
import { DEFAULT_LEVEL_DB_LOCATION, defaultCacheOptions, WarpFactory } from "../../../core/WarpFactory";
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { DeployPlugin } from "warp-contracts-plugin-deploy";
// note: each tests suit (i.e. file with tests that Jest is running concurrently
// with another files has to have ArLocal set to a different port!)
const AR_PORT = 1826;
describe('Testing the Profit Sharing Token', () => {
let contractSrc: string;
let wallet: JWKInterface;
let walletAddress: string;
let initialState: PstState;
let arweave: Arweave;
let arlocal: ArLocal;
let warp: Warp;
let contractTxId: string;
let pst: PstContract;
const actualFetch = global.fetch
let responseData = {
sortKey: "",
state: {}
}
const remoteCalls = {
total: 0,
measure: function() {
const self = this;
const start = self.total;
return {
diff: () => self.total - start
}
}
}
const localWarp = async function() {
if (!arlocal) {
arlocal = new ArLocal(AR_PORT, false);
await arlocal.start();
arweave = Arweave.init({
host: 'localhost',
port: AR_PORT,
protocol: 'http'
})
}
return WarpFactory.forLocal(AR_PORT, arweave, { ...defaultCacheOptions, inMemory: true }).use(new DeployPlugin());
}
const autoSyncPst = async function() {
// const autoSyncPst = warp.pst(contractTxId);
const autoSyncPst = (await localWarp()).pst(contractTxId);
autoSyncPst.setEvaluationOptions({
remoteStateSyncEnabled: true
})
return autoSyncPst;
}
beforeAll(async () => {
LoggerFactory.INST.logLevel('error');
warp = await localWarp();
({ jwk: wallet, address: walletAddress } = await warp.generateWallet());
contractSrc = fs.readFileSync(path.join(__dirname, '../data/kv-storage.js'), 'utf8');
const stateFromFile: PstState = JSON.parse(fs.readFileSync(path.join(__dirname, '../data/token-pst.json'), 'utf8'));
initialState = {
...stateFromFile,
...{
owner: walletAddress
}
};
// deploying contract using the new SDK.
({ contractTxId } = await warp.deploy({
wallet,
initState: JSON.stringify(initialState),
src: contractSrc,
evaluationManifest: {
evaluationOptions: {
useKVStorage: true,
}
}
}));
pst = warp.pst(contractTxId);
pst.connect(wallet);
await mineBlock(warp);
jest
.spyOn(global, 'fetch')
.mockImplementation( async (input: string, init) => {
if (input.includes(pst.evaluationOptions().remoteStateSyncSource)) {
remoteCalls.total++;
return Promise.resolve({ json: () => Promise.resolve(responseData), ok: true, status: 200 }) as Promise<Response>;
}
return actualFetch(input, init);
})
});
afterAll(async () => {
await arlocal.stop();
jest.restoreAllMocks();
fs.rmSync(`${DEFAULT_LEVEL_DB_LOCATION}/kv/ldb/${contractTxId}`, { recursive: true });
});
it('should initialize', async () => {
// this is done to "initialize" the state
await pst.writeInteraction({
function: 'mint',
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 10000000
});
await mineBlock(warp);
await pst.writeInteraction({
function: 'mint',
target: '33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA',
qty: 23111222
});
await mineBlock(warp);
await pst.writeInteraction({
function: 'mint',
target: walletAddress,
qty: 555669
});
await mineBlock(warp);
});
it('should properly transfer tokens and ignore remote source', async () => {
const dreCalls = remoteCalls.measure();
await pst.transfer({
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 100
});
await mineBlock(warp);
const state = await pst.readState();
responseData.sortKey = state.sortKey;
responseData.state = state.cachedValue.state;
expect((await pst.currentBalance(walletAddress)).balance).toEqual(555669 - 100);
expect((await pst.currentBalance('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M')).balance).toEqual(10000000 + 100);
expect(dreCalls.diff()).toEqual(0);
});
it('should properly transfer tokens and read state from remote source', async () => {
const dreCalls = remoteCalls.measure();
await pst.transfer({
target: 'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M',
qty: 400
});
await mineBlock(warp);
expect((await pst.currentBalance(walletAddress)).balance).toEqual(555669 - 500);
expect((await pst.currentBalance('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M')).balance).toEqual(10000000 + 500);
expect(dreCalls.diff()).toEqual(0);
});
it('should properly read storage value', async () => {
expect((await pst.getStorageValues([walletAddress])).cachedValue.get(walletAddress)).toEqual(555669 - 500);
expect(
(await pst.getStorageValues(['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'])).cachedValue.get(
'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'
)
).toEqual(10000000 + 500);
expect(
(await pst.getStorageValues(['33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'])).cachedValue.get(
'33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'
)
).toEqual(23111222);
expect((await pst.getStorageValues(['foo'])).cachedValue.get('foo')).toBeNull();
fs.rmSync(`${DEFAULT_LEVEL_DB_LOCATION}/kv/ldb/${contractTxId}`, { recursive: true });
});
it('should properly calculate kv storage with auto sync', async () => {
const dreCalls = remoteCalls.measure();
const syncPst = await autoSyncPst();
expect(await syncPst.currentState()).toEqual(initialState);
expect((await syncPst.currentBalance(walletAddress)).balance).toEqual(555669 - 500);
expect((await syncPst.currentBalance('uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M')).balance).toEqual(10000000 + 500);
expect((await syncPst.getStorageValues([walletAddress])).cachedValue.get(walletAddress)).toEqual(555669 - 500);
expect(
(await syncPst.getStorageValues(['uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'])).cachedValue.get(
'uhE-QeYS8i4pmUtnxQyHD7dzXFNaJ9oMK-IM-QPNY6M'
)
).toEqual(10000000 + 500);
expect(
(await syncPst.getStorageValues(['33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'])).cachedValue.get(
'33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'
)
).toEqual(23111222);
expect((await syncPst.getStorageValues(['foo'])).cachedValue.get('foo')).toBeNull();
expect(dreCalls.diff()).toEqual(0);
});
});

View File

@@ -17,6 +17,8 @@ describe('Evaluation options evaluator', () => {
maxCallDepth: 7,
maxInteractionEvaluationTimeSeconds: 60,
mineArLocalBlocks: true,
remoteStateSyncEnabled: false,
remoteStateSyncSource: "https://dre-1.warp.cc/contract",
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
sourceType: SourceType.BOTH,
stackTrace: {
@@ -54,6 +56,8 @@ describe('Evaluation options evaluator', () => {
maxCallDepth: 7,
maxInteractionEvaluationTimeSeconds: 60,
mineArLocalBlocks: true,
remoteStateSyncEnabled: false,
remoteStateSyncSource: "https://dre-1.warp.cc/contract",
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
sourceType: SourceType.BOTH,
stackTrace: {
@@ -85,7 +89,8 @@ describe('Evaluation options evaluator', () => {
maxCallDepth: 5,
maxInteractionEvaluationTimeSeconds: 60,
mineArLocalBlocks: true,
sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
remoteStateSyncEnabled: false,
remoteStateSyncSource: "https://dre-1.warp.cc/contract", sequencerUrl: 'https://d1o5nlqr4okus2.cloudfront.net/',
sourceType: 'both',
stackTrace: {
saveState: false

View File

@@ -21,6 +21,18 @@ export interface WriteInteractionResponse {
originalTxId: string;
}
export interface DREContractStatusResponse<State> {
status: string;
contractTxId: string;
state: State;
validity: Record<string, boolean>;
errorMessages: Record<string, string>;
sortKey: string;
timestamp: string;
signature: string;
stateHash: string;
}
export type WarpOptions = {
vrf?: boolean;
disableBundling?: boolean;

View File

@@ -104,6 +104,8 @@ export class EvaluationOptionsEvaluator {
walletBalanceUrl: () => this.rootOptions['walletBalanceUrl'],
mineArLocalBlocks: () => this.rootOptions['mineArLocalBlocks'],
cacheEveryNInteractions: () => this.rootOptions['cacheEveryNInteractions'],
remoteStateSyncEnabled: () => this.rootOptions['remoteStateSyncEnabled'],
remoteStateSyncSource: () => this.rootOptions['remoteStateSyncSource'],
useKVStorage: (foreignOptions) => foreignOptions['useKVStorage']
};

View File

@@ -20,7 +20,14 @@ import { LoggerFactory } from '../logging/LoggerFactory';
import { Evolve } from '../plugins/Evolve';
import { ArweaveWrapper } from '../utils/ArweaveWrapper';
import { sleep } from '../utils/utils';
import { BenchmarkStats, Contract, InnerCallData, WriteInteractionOptions, WriteInteractionResponse } from './Contract';
import {
BenchmarkStats,
Contract,
DREContractStatusResponse,
InnerCallData,
WriteInteractionOptions,
WriteInteractionResponse
} from './Contract';
import { ArTransfer, ArWallet, emptyTransfer, Tags } from './deploy/CreateContract';
import { InnerWritesEvaluator } from './InnerWritesEvaluator';
import { generateMockVrf } from '../utils/vrf';
@@ -58,6 +65,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
private _children: HandlerBasedContract<unknown>[] = [];
private _uncommittedStates = new Map<string, EvalStateResult<unknown>>();
private _dreStates = new Map<string, SortKeyCacheResult<EvalStateResult<State>>>();
private readonly mutex = new Mutex();
@@ -471,13 +479,14 @@ export class HandlerBasedContract<State> implements Contract<State> {
const { definitionLoader, interactionsLoader, stateEvaluator } = this.warp;
const benchmark = Benchmark.measure();
const cachedState = await stateEvaluator.latestAvailableState<State>(contractTxId, upToSortKey);
let cachedState = await stateEvaluator.latestAvailableState<State>(contractTxId, upToSortKey);
this.logger.debug('cache lookup', benchmark.elapsed());
benchmark.reset();
const evolvedSrcTxId = Evolve.evolvedSrcTxId(cachedState?.cachedValue?.state);
let handler, contractDefinition, sortedInteractions, contractEvaluationOptions;
let handler, contractDefinition, contractEvaluationOptions, remoteState;
let sortedInteractions = interactions || [];
this.logger.debug('Cached state', cachedState, upToSortKey);
@@ -502,22 +511,29 @@ export class HandlerBasedContract<State> implements Contract<State> {
contractDefinition = await definitionLoader.load<State>(contractTxId, evolvedSrcTxId);
contractEvaluationOptions = this.resolveEvaluationOptions(contractDefinition.manifest?.evaluationOptions);
sortedInteractions = interactions
? interactions
: await interactionsLoader.load(
if (contractEvaluationOptions.remoteStateSyncEnabled && !contractEvaluationOptions.useKVStorage) {
remoteState = await this.getRemoteContractState(contractTxId);
cachedState = await this.maybeSyncStateWithRemoteSource(remoteState, upToSortKey, cachedState);
}
if (!remoteState && sortedInteractions.length == 0) {
sortedInteractions = await interactionsLoader.load(
contractTxId,
cachedState?.sortKey,
this.getToSortKey(upToSortKey),
contractEvaluationOptions
);
}
// (2) ...but we still need to return only interactions up to original "upToSortKey"
// we still need to return only interactions up to original "upToSortKey"
if (cachedState?.sortKey) {
sortedInteractions = sortedInteractions.filter((i) => i.sortKey.localeCompare(cachedState?.sortKey) > 0);
}
if (upToSortKey) {
sortedInteractions = sortedInteractions.filter((i) => i.sortKey.localeCompare(upToSortKey) <= 0);
}
this.logger.debug('contract and interactions load', benchmark.elapsed());
if (this.isRoot() && sortedInteractions.length) {
// note: if the root contract has zero interactions, it still should be safe
@@ -563,6 +579,29 @@ export class HandlerBasedContract<State> implements Contract<State> {
return this.getRootEoEvaluator().forForeignContract(rootManifestEvalOptions);
}
private async getRemoteContractState(contractId: string): Promise<SortKeyCacheResult<EvalStateResult<State>>> {
if (this.hasDreState(contractId)) {
return this.getDreState(contractId);
} else {
const dreResponse = await this.fetchRemoteContractState(contractId);
if (dreResponse != null) {
return this.setDREState(contractId, dreResponse);
}
return null;
}
}
private async fetchRemoteContractState(contractId: string): Promise<DREContractStatusResponse<State> | null> {
return this.warpFetchWrapper
.fetch(`${this._evaluationOptions.remoteStateSyncSource}?id=${contractId}&events=false`)
.then((res) => {
return res.ok ? res.json() : Promise.reject(res);
})
.catch((error) => {
throw new Error(`Unable to read contract state from DRE. ${error.status}. ${error.body?.message}`);
});
}
private getToSortKey(upToSortKey?: string) {
if (this._parentContract?.rootSortKey) {
if (!upToSortKey) {
@@ -599,6 +638,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
this.warp.interactionsLoader.clearCache();
this._children = [];
this._uncommittedStates = new Map();
this._dreStates = new Map();
}
}
@@ -820,7 +860,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
throw new Error(`Unable to retrieve state. ${error.status}: ${error.body?.message}`);
});
await stateEvaluator.syncState(this._contractTxId, response.sortKey, response.state, response.validity);
await stateEvaluator.syncState<State>(this._contractTxId, response.sortKey, response.state, response.validity);
return this;
}
@@ -900,6 +940,55 @@ export class HandlerBasedContract<State> implements Contract<State> {
}
}
private async maybeSyncStateWithRemoteSource(
remoteState: SortKeyCacheResult<EvalStateResult<State>>,
upToSortKey: string,
cachedState: SortKeyCacheResult<EvalStateResult<State>>
): Promise<SortKeyCacheResult<EvalStateResult<State>>> {
const { stateEvaluator } = this.warp;
if (this.isStateHigherThanAndUpTo(remoteState, cachedState?.sortKey, upToSortKey)) {
return await stateEvaluator.syncState<State>(
this._contractTxId,
remoteState.sortKey,
remoteState.cachedValue.state,
remoteState.cachedValue.validity
);
}
return cachedState;
}
private isStateHigherThanAndUpTo(
remoteState: SortKeyCacheResult<EvalStateResult<State>>,
fromSortKey: string,
upToSortKey: string
) {
return (
remoteState &&
(!upToSortKey || upToSortKey >= remoteState.sortKey) &&
(!fromSortKey || remoteState.sortKey > fromSortKey)
);
}
setDREState(
contractTxId: string,
result: DREContractStatusResponse<State>
): SortKeyCacheResult<EvalStateResult<State>> {
const dreCachedState = new SortKeyCacheResult(
result.sortKey,
new EvalStateResult(result.state, {}, result.errorMessages)
);
this.getRoot()._dreStates.set(contractTxId, dreCachedState);
return dreCachedState;
}
getDreState(contractTxId: string): SortKeyCacheResult<EvalStateResult<State>> {
return this.getRoot()._dreStates.get(contractTxId) as SortKeyCacheResult<EvalStateResult<State>>;
}
hasDreState(contractTxId: string): boolean {
return this.getRoot()._dreStates.has(contractTxId);
}
private getRoot(): HandlerBasedContract<unknown> {
let result: Contract = this;
while (!result.isRoot()) {

View File

@@ -68,7 +68,12 @@ export interface StateEvaluator {
/**
* allows to syncState with an external state source (like Warp Distributed Execution Network)
*/
syncState(contractTxId: string, sortKey: string, state: unknown, validity: Record<string, boolean>): Promise<void>;
syncState<State>(
contractTxId: string,
sortKey: string,
state: State,
validity: Record<string, boolean>
): Promise<SortKeyCacheResult<EvalStateResult<State>>>;
internalWriteState<State>(
contractTxId: string,
@@ -142,6 +147,10 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
cacheEveryNInteractions = -1;
useKVStorage = false;
remoteStateSyncEnabled = false;
remoteStateSyncSource = 'https://dre-1.warp.cc/contract';
}
// an interface for the contract EvaluationOptions - can be used to change the behaviour of some features.
@@ -226,4 +235,10 @@ export interface EvaluationOptions {
// whether a separate key-value storage should be used for the contract
useKVStorage: boolean;
// whether contract state should be acquired from remote source, e.g. D.R.E.
remoteStateSyncEnabled: boolean;
// remote source for fetching most recent contract state, only applicable if remoteStateSyncEnabled is set to true
remoteStateSyncSource: string;
}

View File

@@ -187,14 +187,15 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
await this.cache.put(new CacheKey(contractTxId, transaction.sortKey), stateToCache);
}
async syncState(
async syncState<State>(
contractTxId: string,
sortKey: string,
state: unknown,
state: State,
validity: Record<string, boolean>
): Promise<void> {
): Promise<SortKeyCacheResult<EvalStateResult<State>>> {
const stateToCache = new EvalStateResult(state, validity, {});
await this.cache.put(new CacheKey(contractTxId, sortKey), stateToCache);
return new SortKeyCacheResult(sortKey, stateToCache);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -392,12 +392,12 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
state: EvalStateResult<State>
): Promise<void>;
abstract syncState(
abstract syncState<State>(
contractTxId: string,
sortKey: string,
state: unknown,
state: State,
validity: Record<string, boolean>
): Promise<void>;
): Promise<SortKeyCacheResult<EvalStateResult<State>>>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
abstract dumpCache(): Promise<any>;