diff --git a/src/__tests__/integration/internal-writes/internal-write-back.test.ts b/src/__tests__/integration/internal-writes/internal-write-back.test.ts index 4b67a4a..40cc008 100644 --- a/src/__tests__/integration/internal-writes/internal-write-back.test.ts +++ b/src/__tests__/integration/internal-writes/internal-write-back.test.ts @@ -96,8 +96,18 @@ describe('Testing internal writes', () => { src: contractBSrc }); - contractA = smartweave.contract(contractATxId).connect(wallet); - contractB = smartweave.contract(contractBTxId).connect(wallet); + contractA = smartweave + .contract(contractATxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + contractB = smartweave + .contract(contractBTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); await mine(); } diff --git a/src/__tests__/integration/internal-writes/internal-write-callee.test.ts b/src/__tests__/integration/internal-writes/internal-write-callee.test.ts index 8142187..788d7ab 100644 --- a/src/__tests__/integration/internal-writes/internal-write-callee.test.ts +++ b/src/__tests__/integration/internal-writes/internal-write-callee.test.ts @@ -11,7 +11,6 @@ interface ExampleContractState { counter: number; } - /** * The most basic example of writes between contracts. * In this suite "User" is calling CallingContract.writeContract @@ -97,8 +96,18 @@ describe('Testing internal writes', () => { src: callingContractSrc }); - calleeContract = smartweave.contract(calleeTxId).connect(wallet); - callingContract = smartweave.contract(callingTxId).connect(wallet); + calleeContract = smartweave + .contract(calleeTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + callingContract = smartweave + .contract(callingTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); await mine(); } diff --git a/src/__tests__/integration/internal-writes/internal-write-caller.test.ts b/src/__tests__/integration/internal-writes/internal-write-caller.test.ts index dfc854c..ec777dd 100644 --- a/src/__tests__/integration/internal-writes/internal-write-caller.test.ts +++ b/src/__tests__/integration/internal-writes/internal-write-caller.test.ts @@ -7,7 +7,6 @@ import { Contract, LoggerFactory, SmartWeave, SmartWeaveNodeFactory } from '@sma import path from 'path'; import { TsLogFactory } from '../../../logging/node/TsLogFactory'; - /** * This verifies whether combination of read and write state works properly. * 1. User calls ContractA.writeContractCheck(amount) @@ -109,8 +108,18 @@ describe('Testing internal writes', () => { src: callingContractSrc }); - calleeContract = smartweave.contract(calleeTxId).connect(wallet); - callingContract = smartweave.contract(callingTxId).connect(wallet); + calleeContract = smartweave + .contract(calleeTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + callingContract = smartweave + .contract(callingTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); await mine(); } diff --git a/src/__tests__/integration/internal-writes/internal-write-depth.test.ts b/src/__tests__/integration/internal-writes/internal-write-depth.test.ts index 903131c..42c6603 100644 --- a/src/__tests__/integration/internal-writes/internal-write-depth.test.ts +++ b/src/__tests__/integration/internal-writes/internal-write-depth.test.ts @@ -116,9 +116,24 @@ describe('Testing internal writes', () => { src: contractBSrc }); - contractA = smartweave.contract(contractATxId).connect(wallet); - contractB = smartweave.contract(contractBTxId).connect(wallet); - contractC = smartweave.contract(contractCTxId).connect(wallet); + contractA = smartweave + .contract(contractATxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + contractB = smartweave + .contract(contractBTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + contractC = smartweave + .contract(contractCTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); await mine(); } diff --git a/src/__tests__/integration/internal-writes/multi-internal-calls.test.ts b/src/__tests__/integration/internal-writes/multi-internal-calls.test.ts index 1affcf5..cf4a1ed 100644 --- a/src/__tests__/integration/internal-writes/multi-internal-calls.test.ts +++ b/src/__tests__/integration/internal-writes/multi-internal-calls.test.ts @@ -61,7 +61,6 @@ describe('Testing internal writes', () => { let contractBTxId; let contractCTxId; - beforeAll(async () => { // 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!) @@ -111,9 +110,9 @@ describe('Testing internal writes', () => { src: contractBSrc }); - contractA = smartweave.contract(contractATxId).connect(wallet); - contractB = smartweave.contract(contractBTxId).connect(wallet); - contractC = smartweave.contract(contractCTxId).connect(wallet); + contractA = smartweave.contract(contractATxId).setEvaluationOptions({ internalWrites: true }).connect(wallet); + contractB = smartweave.contract(contractBTxId).setEvaluationOptions({ internalWrites: true }).connect(wallet); + contractC = smartweave.contract(contractCTxId).setEvaluationOptions({ internalWrites: true }).connect(wallet); await mine(); } diff --git a/src/__tests__/integration/internal-writes/staking.test.ts b/src/__tests__/integration/internal-writes/staking.test.ts index 547dd34..060c92d 100644 --- a/src/__tests__/integration/internal-writes/staking.test.ts +++ b/src/__tests__/integration/internal-writes/staking.test.ts @@ -79,8 +79,14 @@ describe('Testing internal writes', () => { src: stakingContractSrc }); - tokenContract = smartweave.contract(tokenContractTxId).connect(wallet); - stakingContract = smartweave.contract(stakingContractTxId).connect(wallet); + tokenContract = smartweave + .contract(tokenContractTxId) + .setEvaluationOptions({ internalWrites: true }) + .connect(wallet); + stakingContract = smartweave + .contract(stakingContractTxId) + .setEvaluationOptions({ internalWrites: true }) + .connect(wallet); await mine(); } @@ -129,12 +135,11 @@ describe('Testing internal writes', () => { }); await mine(); - expect((await stakingContract.readState()).state.stakes).toEqual({ - }); + expect((await stakingContract.readState()).state.stakes).toEqual({}); const tokenState = (await tokenContract.readState()).state; expect(tokenState.balances).toEqual({ - [walletAddress]: 10000, + [walletAddress]: 10000 }); }); @@ -178,8 +183,6 @@ describe('Testing internal writes', () => { [stakingContractTxId]: 1000 }); }); - - }); describe('with read states at the end', () => { @@ -240,7 +243,6 @@ describe('Testing internal writes', () => { } }); }); - }); async function mine() { diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index 96e41bc..2dc3711 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -146,20 +146,22 @@ export class HandlerBasedContract implements Contract { } const { arweave } = this.smartweave; - await this.callContract(input, undefined, tags, transfer); - const callStack: ContractCallStack = this.getCallStack(); - const innerWrites = this.innerWritesEvaluator.eval(callStack); - this.logger.debug('Input', input); - this.logger.debug('Callstack', callStack.print()); + if (this.evaluationOptions.internalWrites) { + await this.callContract(input, undefined, tags, transfer); + const callStack: ContractCallStack = this.getCallStack(); + const innerWrites = this.innerWritesEvaluator.eval(callStack); + this.logger.debug('Input', input); + this.logger.debug('Callstack', callStack.print()); - innerWrites.forEach((contractTxId) => { - tags.push({ - name: SmartWeaveTags.INTERACT_WRITE, - value: contractTxId + innerWrites.forEach((contractTxId) => { + tags.push({ + name: SmartWeaveTags.INTERACT_WRITE, + value: contractTxId + }); }); - }); - this.logger.debug('Tags with inner calls', tags); + this.logger.debug('Tags with inner calls', tags); + } const interactionTx = await createTx( this.smartweave.arweave, @@ -287,7 +289,12 @@ export class HandlerBasedContract implements Contract { // (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(contractTxId, cachedBlockHeight + 1, this.rootBlockHeight || this.networkInfo.height) + interactionsLoader.load( + contractTxId, + cachedBlockHeight + 1, + this.rootBlockHeight || this.networkInfo.height, + this.evaluationOptions + ) ]); this.logger.debug('contract and interactions load', benchmark.elapsed()); sortedInteractions = await interactionsSorter.sort(interactions); @@ -338,7 +345,7 @@ export class HandlerBasedContract implements Contract { if (cachedBlockHeight != blockHeight) { [contractDefinition, interactions] = await Promise.all([ definitionLoader.load(contractTxId), - await interactionsLoader.load(contractTxId, 0, blockHeight) + await interactionsLoader.load(contractTxId, 0, blockHeight, this.evaluationOptions) ]); sortedInteractions = await interactionsSorter.sort(interactions); } else { @@ -466,7 +473,7 @@ export class HandlerBasedContract implements Contract { const interaction: ContractInteraction = { input, - caller: this.callingContract.txId()//executionContext.caller + caller: this.callingContract.txId() //executionContext.caller }; const interactionData: InteractionData = { diff --git a/src/core/modules/InteractionsLoader.ts b/src/core/modules/InteractionsLoader.ts index a591eef..05feef6 100644 --- a/src/core/modules/InteractionsLoader.ts +++ b/src/core/modules/InteractionsLoader.ts @@ -1,4 +1,4 @@ -import { GQLEdgeInterface } from '@smartweave'; +import { EvaluationOptions, GQLEdgeInterface } from '@smartweave'; /** * Implementors of this interface add functionality of loading contract's interaction transactions. @@ -7,5 +7,10 @@ import { GQLEdgeInterface } from '@smartweave'; * Note: InteractionsLoaders are not responsible for sorting interaction transactions! */ export interface InteractionsLoader { - load(contractId: string, fromBlockHeight: number, toBlockHeight: number): Promise; + load( + contractId: string, + fromBlockHeight: number, + toBlockHeight: number, + evaluationOptions: EvaluationOptions + ): Promise; } diff --git a/src/core/modules/StateEvaluator.ts b/src/core/modules/StateEvaluator.ts index abdf233..dc51cd8 100644 --- a/src/core/modules/StateEvaluator.ts +++ b/src/core/modules/StateEvaluator.ts @@ -72,11 +72,11 @@ export class DefaultEvaluationOptions implements EvaluationOptions { updateCacheForEachInteraction = true; - enhancedValidity = false; - stackTrace = { saveState: false }; + + internalWrites: false; } // an interface for the contract EvaluationOptions - can be used to change the behaviour of some of the features. @@ -96,12 +96,13 @@ export interface EvaluationOptions { // and caches it maybe more suitable to cache only after state has been fully evaluated) updateCacheForEachInteraction: boolean; - // enhanced validity report with error/exception messages included - enhancedValidity: boolean; - // a set of options that control the behaviour of the stack trace generator stackTrace: { // whether output state should be saved for each interaction in the stack trace (may result in huuuuge json files!) saveState: boolean; }; + + // a new, experimental enhancement of the protocol that allows for interactWrites from + // smart contract's source code. + internalWrites: boolean; } diff --git a/src/core/modules/impl/ContractHandlerApi.ts b/src/core/modules/impl/ContractHandlerApi.ts index 61525cf..e166cdd 100644 --- a/src/core/modules/impl/ContractHandlerApi.ts +++ b/src/core/modules/impl/ContractHandlerApi.ts @@ -96,6 +96,10 @@ export class ContractHandlerApi implements HandlerApi { private assignWrite(executionContext: ExecutionContext, currentTx: CurrentTx[]) { this.swGlobal.contracts.write = async (contractTxId: string, input: Input) => { + if (!executionContext.evaluationOptions.internalWrites) { + throw new Error("Internal writes feature switched off. Change EvaluationOptions.internalWrites flag to 'true'"); + } + this.logger.debug('swGlobal.write call:', { from: this.contractDefinition.txId, to: contractTxId, diff --git a/src/core/modules/impl/ContractInteractionsLoader.ts b/src/core/modules/impl/ContractInteractionsLoader.ts index c17b74e..1f0c012 100644 --- a/src/core/modules/impl/ContractInteractionsLoader.ts +++ b/src/core/modules/impl/ContractInteractionsLoader.ts @@ -1,5 +1,5 @@ import { - Benchmark, + Benchmark, EvaluationOptions, GQLEdgeInterface, GQLResultInterface, GQLTransactionsResultInterface, @@ -64,8 +64,13 @@ export class ContractInteractionsLoader implements InteractionsLoader { constructor(private readonly arweave: Arweave) {} - async load(contractTxId: string, fromBlockHeight: number, toBlockHeight: number): Promise { - this.logger.debug('Loading interactions for', contractTxId); + async load( + contractId: string, + fromBlockHeight: number, + toBlockHeight: number, + evaluationOptions: EvaluationOptions + ): Promise { + this.logger.debug('Loading interactions for', contractId); const mainTransactionsVariables: ReqVariables = { tags: [ { @@ -74,7 +79,7 @@ export class ContractInteractionsLoader implements InteractionsLoader { }, { name: SmartWeaveTags.CONTRACT_TX_ID, - values: [contractTxId] + values: [contractId] } ], blockFilter: { @@ -83,35 +88,35 @@ export class ContractInteractionsLoader implements InteractionsLoader { }, first: MAX_REQUEST }; - const mainInteractions = await this.loadPages(mainTransactionsVariables); - this.logger.debug('Main Interactions:', mainInteractions); - this.logger.debug('Main interactions length:', mainInteractions.length); - let innerWritesVariables: ReqVariables = { - tags: [ - { - name: SmartWeaveTags.INTERACT_WRITE, - values: [contractTxId] - } - ], - blockFilter: { - min: fromBlockHeight, - max: toBlockHeight - }, - first: MAX_REQUEST - }; - const innerWritesInteractions = await this.loadPages(innerWritesVariables); - this.logger.debug('Inner Writes Interactions', innerWritesInteractions); - this.logger.debug('Inner writes interactions length:', innerWritesInteractions.length); - const allInteractions = mainInteractions.concat(innerWritesInteractions); + let interactions = await this.loadPages(mainTransactionsVariables); + + if (evaluationOptions.internalWrites) { + let innerWritesVariables: ReqVariables = { + tags: [ + { + name: SmartWeaveTags.INTERACT_WRITE, + values: [contractId] + } + ], + blockFilter: { + min: fromBlockHeight, + max: toBlockHeight + }, + first: MAX_REQUEST + }; + const innerWritesInteractions = await this.loadPages(innerWritesVariables); + this.logger.debug('Inner writes interactions length:', innerWritesInteractions.length); + interactions = interactions.concat(innerWritesInteractions); + } this.logger.debug('All loaded interactions:', { from: fromBlockHeight, to: toBlockHeight, - loaded: allInteractions.length + loaded: interactions.length }); - return allInteractions; + return interactions; } private async loadPages(variables: ReqVariables) { diff --git a/src/plugins/CacheableContractInteractionsLoader.ts b/src/plugins/CacheableContractInteractionsLoader.ts index 025fa31..5c2e04e 100644 --- a/src/plugins/CacheableContractInteractionsLoader.ts +++ b/src/plugins/CacheableContractInteractionsLoader.ts @@ -1,4 +1,11 @@ -import { BlockHeightKey, BlockHeightSwCache, GQLEdgeInterface, InteractionsLoader, LoggerFactory } from '@smartweave'; +import { + BlockHeightKey, + BlockHeightSwCache, + EvaluationOptions, + GQLEdgeInterface, + InteractionsLoader, + LoggerFactory +} from '@smartweave'; /** * This implementation of the {@link InteractionsLoader} tries to limit the amount of interactions @@ -13,7 +20,12 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader { private readonly cache: BlockHeightSwCache ) {} - async load(contractId: string, fromBlockHeight: number, toBlockHeight: number): Promise { + async load( + contractId: string, + fromBlockHeight: number, + toBlockHeight: number, + evaluationOptions: EvaluationOptions + ): Promise { this.logger.debug('Loading interactions', { contractId, fromBlockHeight, @@ -38,7 +50,12 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader { cachedValue }); - const missingInteractions = await this.baseImplementation.load(contractId, cachedHeight + 1, toBlockHeight); + const missingInteractions = await this.baseImplementation.load( + contractId, + cachedHeight + 1, + toBlockHeight, + evaluationOptions + ); const result = cachedValue .filter((interaction: GQLEdgeInterface) => interaction.node.block.height >= fromBlockHeight) diff --git a/tools/FromFileInteractionsLoader.ts b/tools/FromFileInteractionsLoader.ts index 268bd21..4927a09 100644 --- a/tools/FromFileInteractionsLoader.ts +++ b/tools/FromFileInteractionsLoader.ts @@ -47,7 +47,12 @@ export class FromFileInteractionsLoader implements InteractionsLoader { }); } - async load(contractId: string, fromBlockHeight: number, toBlockHeight: number): Promise { + async load( + contractId: string, + fromBlockHeight: number, + toBlockHeight: number, + evaluationOptions: EvaluationOptions + ): Promise { return this.transactions; } } diff --git a/tools/swc-stats.ts b/tools/swc-stats.ts index 0914f51..05d116e 100644 --- a/tools/swc-stats.ts +++ b/tools/swc-stats.ts @@ -110,7 +110,7 @@ async function main() { console.log( `\n[${contractTxs.indexOf(contractTx) + 1} / ${contractTxs.length}] loading interactions of the ${contractTxId}` ); - const interactions = await transactionsLoader.load(contractTxId, 0, 779826); + const interactions = await transactionsLoader.load(contractTxId, 0, 779826, evaluationOptions); console.log(`${contractTxId}: ${interactions.length}`); result[contractTxId] = interactions.length; diff --git a/tools/transactions-loader.ts b/tools/transactions-loader.ts index 3b31da3..1a6de53 100644 --- a/tools/transactions-loader.ts +++ b/tools/transactions-loader.ts @@ -21,7 +21,12 @@ async function main() { const transactionsLoader = new ContractInteractionsLoader(arweave); - const result = await transactionsLoader.load('Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY', 0, 779820); + const result = await transactionsLoader.load( + 'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY', + 0, + 779820, + evaluationOptions + ); console.log(result.length);