diff --git a/package.json b/package.json index f1d8fc01..7328b7f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "warp-contracts", - "version": "1.4.33", + "version": "1.4.34-beta.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__/unit/evaluation-options.test.ts b/src/__tests__/unit/evaluation-options.test.ts index 38877c16..fa9850cc 100644 --- a/src/__tests__/unit/evaluation-options.test.ts +++ b/src/__tests__/unit/evaluation-options.test.ts @@ -26,6 +26,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'throw', @@ -65,6 +66,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'throw', @@ -99,6 +101,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'allow', @@ -130,6 +133,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'allow', @@ -161,6 +165,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'throw', @@ -192,6 +197,7 @@ describe('Evaluation options evaluator', () => { stackTrace: { saveState: false }, + strictSortKey: false, throwOnInternalWriteError: true, transactionsPagesPerBatch: null, unsafeClient: 'skip', diff --git a/src/contract/Contract.ts b/src/contract/Contract.ts index e33d6105..4d6da3c5 100644 --- a/src/contract/Contract.ts +++ b/src/contract/Contract.ts @@ -155,7 +155,8 @@ export interface Contract { tags?: Tags, transfer?: ArTransfer, caller?: string, - signal?: AbortSignal + signal?: AbortSignal, + sortKey?: string ): Promise>; /** @@ -186,13 +187,15 @@ export interface Contract { * @param caller - an option to override the caller - if available, this value will overwrite the caller evaluated * from the wallet connected to this contract. * @param vrf - whether a mock VRF data should be generated + * @param sortKey - sortKey at which the state will be loaded prior to applying input */ dryWrite( input: Input, caller?: string, tags?: Tags, transfer?: ArTransfer, - vrf?: boolean + vrf?: boolean, + sortKey?: string ): Promise>; applyInput( diff --git a/src/contract/EvaluationOptionsEvaluator.ts b/src/contract/EvaluationOptionsEvaluator.ts index 47d69184..4118fb11 100644 --- a/src/contract/EvaluationOptionsEvaluator.ts +++ b/src/contract/EvaluationOptionsEvaluator.ts @@ -108,14 +108,16 @@ export class EvaluationOptionsEvaluator { useKVStorage: (foreignOptions) => foreignOptions['useKVStorage'], useConstructor: (foreignOptions) => foreignOptions['useConstructor'], whitelistSources: () => this.rootOptions['whitelistSources'], - transactionsPagesPerBatch: () => this.rootOptions['transactionsPagesPerBatch'] + transactionsPagesPerBatch: () => this.rootOptions['transactionsPagesPerBatch'], + strictSortKey: () => this.rootOptions['strictSortKey'] }; private readonly notConflictingEvaluationOptions: (keyof EvaluationOptions)[] = [ 'useKVStorage', 'sourceType', 'useConstructor', - 'transactionsPagesPerBatch' + 'transactionsPagesPerBatch', + 'strictSortKey' ]; /** diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index 556360b6..4de00042 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -281,14 +281,15 @@ export class HandlerBasedContract implements Contract { tags: Tags = [], transfer: ArTransfer = emptyTransfer, caller?: string, - signal?: AbortSignal + signal?: AbortSignal, + sortKey?: string ): Promise> { this.logger.info('View state for', this._contractTxId); return await this.callContract( input, 'view', caller, - undefined, + sortKey, tags, transfer, false, @@ -312,10 +313,11 @@ export class HandlerBasedContract implements Contract { caller?: string, tags?: Tags, transfer?: ArTransfer, - vrf?: boolean + vrf?: boolean, + sortKey?: string ): Promise> { this.logger.info('Dry-write for', this._contractTxId); - return await this.callContract(input, 'write', caller, undefined, tags, transfer, undefined, vrf); + return await this.callContract(input, 'write', caller, sortKey, tags, transfer, undefined, vrf); } async applyInput( @@ -645,6 +647,9 @@ export class HandlerBasedContract implements Contract { >; } cachedState = cachedState || (await stateEvaluator.latestAvailableState(contractTxId, upToSortKey)); + if (upToSortKey && this.evaluationOptions().strictSortKey && cachedState?.sortKey != upToSortKey) { + throw new Error(`State not cached at the exact required ${upToSortKey} sortKey`); + } this.logger.debug('cache lookup', benchmark.elapsed()); benchmark.reset(); diff --git a/src/core/modules/StateEvaluator.ts b/src/core/modules/StateEvaluator.ts index 98fe638d..47502bbe 100644 --- a/src/core/modules/StateEvaluator.ts +++ b/src/core/modules/StateEvaluator.ts @@ -154,6 +154,8 @@ export class DefaultEvaluationOptions implements EvaluationOptions { whitelistSources = []; transactionsPagesPerBatch = null; + + strictSortKey = false; } // an interface for the contract EvaluationOptions - can be used to change the behaviour of some features. @@ -243,9 +245,16 @@ export interface EvaluationOptions { // remote source for fetching most recent contract state, only applicable if remoteStateSyncEnabled is set to true remoteStateSyncSource: string; + // an array of source tx ids that are allowed to be evaluated by the SDK whitelistSources: string[]; + // how many interactions pages are evaluated in a single evaluation batch transactionsPagesPerBatch: number; + + // whether passing sortKey to some functions like viewState or readStateFor is strict + // - if it is, then we're requiring the SDK to have the state cached at this exact sortKey + // - so that SDK won't load and evaluated missing interactions + strictSortKey: boolean; } // https://github.com/nodejs/node/issues/40678 duh...