From fd7a63db6dd10976f17f24092d666936786b6abd Mon Sep 17 00:00:00 2001 From: ppedziwiatr Date: Thu, 4 Nov 2021 11:30:15 +0100 Subject: [PATCH] refactor: state cache #51 --- src/cache/BlockHeightSwCache.ts | 2 +- src/cache/impl/MemBlockHeightCache.ts | 6 +- src/contract/HandlerBasedContract.ts | 1 + src/core/index.ts | 1 + src/core/modules/StateEvaluator.ts | 36 +++-- .../modules/impl}/CacheableStateEvaluator.ts | 137 ++++++++++++++---- .../impl/ContractInteractionsLoader.ts | 2 +- .../modules/impl/DefaultStateEvaluator.ts | 115 +++++++-------- .../impl/LexicographicalInteractionsSorter.ts | 14 +- src/core/modules/impl/StateCache.ts | 3 + src/core/web/SmartWeaveWebFactory.ts | 36 +---- src/legacy/gqlResult.ts | 1 + src/plugins/index.ts | 2 +- src/utils/utils.ts | 11 +- tools/inner-write.ts | 11 +- tools/transactions-loader.ts | 3 +- 16 files changed, 225 insertions(+), 156 deletions(-) rename src/{plugins => core/modules/impl}/CacheableStateEvaluator.ts (56%) create mode 100644 src/core/modules/impl/StateCache.ts diff --git a/src/cache/BlockHeightSwCache.ts b/src/cache/BlockHeightSwCache.ts index 7702e54..279f61b 100644 --- a/src/cache/BlockHeightSwCache.ts +++ b/src/cache/BlockHeightSwCache.ts @@ -4,7 +4,7 @@ * * @typeParam V - type of value stored in cache, defaults to `any`. */ -export interface BlockHeightSwCache { +export interface BlockHeightSwCache { /** * returns cached value for the highest available in cache block that is not higher than `blockHeight`. */ diff --git a/src/cache/impl/MemBlockHeightCache.ts b/src/cache/impl/MemBlockHeightCache.ts index 2880be8..7082b30 100644 --- a/src/cache/impl/MemBlockHeightCache.ts +++ b/src/cache/impl/MemBlockHeightCache.ts @@ -8,10 +8,14 @@ import { LoggerFactory } from '@smartweave/logging'; export class MemBlockHeightSwCache implements BlockHeightSwCache { private readonly logger = LoggerFactory.INST.create('MemBlockHeightSwCache'); - private storage: { [key: string]: Map } = {}; + protected storage: { [key: string]: Map } = {}; constructor(private maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER) {} + flush(): Promise { + throw new Error('Method not implemented.'); + } + async getLast(key: string): Promise | null> { if (!(await this.contains(key))) { return null; diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index a1f7ee1..0a6f939 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -328,6 +328,7 @@ export class HandlerBasedContract implements Contract { ]); this.logger.debug('contract and interactions load', benchmark.elapsed()); sortedInteractions = await interactionsSorter.sort(interactions); + this.logger.debug('Sorted interactions', sortedInteractions); handler = (await executorFactory.create(contractDefinition)) as HandlerApi; } else { this.logger.debug('State fully cached, not loading interactions.'); diff --git a/src/core/index.ts b/src/core/index.ts index 53616c2..ae50e4f 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -14,6 +14,7 @@ export * from './modules/impl/LexicographicalInteractionsSorter'; export * from './modules/impl/DefaultCreateContract'; export * from './modules/impl/TagsParser'; export * from './modules/impl/normalize-source'; +export * from './modules/impl/StateCache'; export * from './ExecutionContextModifier'; export * from './SmartWeaveTags'; diff --git a/src/core/modules/StateEvaluator.ts b/src/core/modules/StateEvaluator.ts index d826d00..16b4cfe 100644 --- a/src/core/modules/StateEvaluator.ts +++ b/src/core/modules/StateEvaluator.ts @@ -8,10 +8,10 @@ export interface StateEvaluator { eval(executionContext: ExecutionContext, currentTx: CurrentTx[]): Promise>; /** - * a hook that is called on each state update (i.e. after evaluating state for each interaction) + * a hook that is called on each state update (i.e. after evaluating state for each interaction transaction) */ onStateUpdate( - currentInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, executionContext: ExecutionContext, state: EvalStateResult ): Promise; @@ -20,42 +20,56 @@ export interface StateEvaluator { * a hook that is called after state has been fully evaluated */ onStateEvaluated( - lastInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, executionContext: ExecutionContext, state: EvalStateResult ): Promise; + /** + * a hook that is called after performing internal write between contracts + */ onInternalWriteStateUpdate( - currentInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, contractTxId: string, state: EvalStateResult ): Promise; /** - * a hook that is called before communicating with other contract + * a hook that is called before communicating with other contract. * note to myself: putting values into cache only "onContractCall" may degrade performance. - * For example" - * block 722317 - contract A calls B - * block 722727 - contract A calls B - * block 722695 - contract B calls A + * For example: + * 1. block 722317 - contract A calls B + * 2. block 722727 - contract A calls B + * 3. block 722695 - contract B calls A * If we update cache only on contract call - for the last above call (B->A) * we would retrieve state cached for 722317. If there are any transactions * between 722317 and 722695 - the performance will be degraded. */ onContractCall( - currentInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, executionContext: ExecutionContext, state: EvalStateResult ): Promise; + /** + * loads latest available state for given contract for given blockHeight. + * - implementors should be aware that there might multiple interactions + * for single block - and sort them according to protocol specification. + */ latestAvailableState( contractTxId: string, blockHeight: number ): Promise> | null>; + + transactionState(transaction: GQLNodeInterface, contractTxId: string): Promise | undefined>; } export class EvalStateResult { - constructor(readonly state: State, readonly validity: Record) {} + constructor( + readonly state: State, + readonly validity: Record, + readonly transactionId?: string, + readonly blockId?: string) {} } export class DefaultEvaluationOptions implements EvaluationOptions { diff --git a/src/plugins/CacheableStateEvaluator.ts b/src/core/modules/impl/CacheableStateEvaluator.ts similarity index 56% rename from src/plugins/CacheableStateEvaluator.ts rename to src/core/modules/impl/CacheableStateEvaluator.ts index 11da3f1..16941a5 100644 --- a/src/plugins/CacheableStateEvaluator.ts +++ b/src/core/modules/impl/CacheableStateEvaluator.ts @@ -4,22 +4,30 @@ import { EvalStateResult, ExecutionContext, ExecutionContextModifier, - HandlerApi + HandlerApi, + LexicographicalInteractionsSorter, + StateCache } from '@smartweave/core'; import Arweave from 'arweave'; import { GQLNodeInterface } from '@smartweave/legacy'; import { LoggerFactory } from '@smartweave/logging'; import { CurrentTx } from '@smartweave/contract'; +import { mapReplacer } from '@smartweave/utils'; /** - * An implementation of DefaultStateEvaluator that adds caching capabilities + * An implementation of DefaultStateEvaluator that adds caching capabilities. + * + * The main responsibility of this class is to compute whether there are + * any interaction transactions, for which the state hasn't been evaluated yet - + * if so - it generates a list of such transactions and evaluates the state + * for them - taking as an input state the last cached state. */ export class CacheableStateEvaluator extends DefaultStateEvaluator { private readonly cLogger = LoggerFactory.INST.create('CacheableStateEvaluator'); constructor( arweave: Arweave, - private readonly cache: BlockHeightSwCache>, + private readonly cache: BlockHeightSwCache>, executionContextModifiers: ExecutionContextModifier[] = [] ) { super(arweave, executionContextModifiers); @@ -105,61 +113,132 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator { } async onStateEvaluated( - lastInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, executionContext: ExecutionContext, state: EvalStateResult ): Promise { - if (lastInteraction.dry) { + if (transaction.dry) { return; } - this.cLogger.debug( - `onStateEvaluated: cache update for contract ${executionContext.contractDefinition.txId} [${lastInteraction.block.height}]` - ); - await this.cache.put( - new BlockHeightKey(executionContext.contractDefinition.txId, lastInteraction.block.height), - state - ); + const contractTxId = executionContext.contractDefinition.txId; + + this.cLogger.debug(`onStateEvaluated: cache update for contract ${contractTxId} [${transaction.block.height}]`); + await this.putInCache(contractTxId, transaction, state); } async onStateUpdate( - currentInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, executionContext: ExecutionContext, state: EvalStateResult ): Promise { - if (currentInteraction.dry) { - return; - } if (executionContext.evaluationOptions.updateCacheForEachInteraction) { - await this.cache.put( - new BlockHeightKey(executionContext.contractDefinition.txId, currentInteraction.block.height), - state - ); + await this.putInCache(executionContext.contractDefinition.txId, transaction, state); } - await super.onStateUpdate(currentInteraction, executionContext, state); } async latestAvailableState( contractTxId: string, blockHeight: number ): Promise> | null> { - return (await this.cache.getLessOrEqual(contractTxId, blockHeight)) as BlockHeightCacheResult< - EvalStateResult + const stateCache = (await this.cache.getLessOrEqual(contractTxId, blockHeight)) as BlockHeightCacheResult< + StateCache >; + + if (stateCache == null) { + return null; + } + + /*if (stateCache.cachedValue.length == 1) { + this.cLogger.debug('CacheValue size 1', stateCache.cachedValue.values().next().value); + return new BlockHeightCacheResult>( + stateCache.cachedHeight, + stateCache.cachedValue.values().next().value + ); + } + + const sorter = new LexicographicalInteractionsSorter(this.arweave); + + this.cLogger.debug('State cache', JSON.stringify(stateCache.cachedValue, mapReplacer)); + const toSort = await Promise.all( + [...stateCache.cachedValue.values()].map(async (k) => { + return { + transactionId: k.transactionId, + sortKey: await sorter.createSortKey(k.blockId, k.transactionId, blockHeight) + }; + }) + ); + const sorted = toSort.sort((a, b) => a.sortKey.localeCompare(b.sortKey)); + this.cLogger.debug('sorted:', sorted); + + const lastKey = sorted.pop(); + + this.cLogger.debug('Last key: ', lastKey);*/ + + return new BlockHeightCacheResult>( + stateCache.cachedHeight, + [...stateCache.cachedValue].pop() + ); } async onInternalWriteStateUpdate( - currentInteraction: GQLNodeInterface, + transaction: GQLNodeInterface, contractTxId: string, state: EvalStateResult ): Promise { - if (currentInteraction.dry) { - return; - } this.cLogger.debug('Internal write state update:', { - height: currentInteraction.block.height, + height: transaction.block.height, contractTxId, state }); - await this.cache.put(new BlockHeightKey(contractTxId, currentInteraction.block.height), state); + await this.putInCache(contractTxId, transaction, state); + } + + async onContractCall( + transaction: GQLNodeInterface, + executionContext: ExecutionContext, + state: EvalStateResult + ): Promise { + await this.putInCache(executionContext.contractDefinition.txId, transaction, state); + } + + async transactionState( + transaction: GQLNodeInterface, + contractTxId: string + ): Promise | undefined> { + const stateCache = (await this.cache.get(contractTxId, transaction.block.height)) as BlockHeightCacheResult< + StateCache + >; + + if (stateCache == null) { + return undefined; + } + + return stateCache.cachedValue.find((sc) => { + return sc.transactionId === transaction.id; + }); + } + + protected async putInCache( + contractTxId: string, + transaction: GQLNodeInterface, + state: EvalStateResult + ): Promise { + if (transaction.dry) { + return; + } + const transactionId = transaction.id; + const blockHeight = transaction.block.height; + this.cLogger.debug('putInCache:', { + state: state.state, + transactionId + }); + const stateToCache = new EvalStateResult(state.state, state.validity, transactionId, transaction.block.id); + const stateCache = await this.cache.get(contractTxId, blockHeight); + if (stateCache != null) { + stateCache.cachedValue.push(stateToCache); + await this.cache.put(new BlockHeightKey(contractTxId, blockHeight), stateCache.cachedValue); + } else { + await this.cache.put(new BlockHeightKey(contractTxId, blockHeight), [stateToCache]); + } } } diff --git a/src/core/modules/impl/ContractInteractionsLoader.ts b/src/core/modules/impl/ContractInteractionsLoader.ts index 4d95353..196fbe0 100644 --- a/src/core/modules/impl/ContractInteractionsLoader.ts +++ b/src/core/modules/impl/ContractInteractionsLoader.ts @@ -149,7 +149,7 @@ export class ContractInteractionsLoader implements InteractionsLoader { this.logger.debug('GQL page load:', benchmark.elapsed()); while (response.status === 403) { - this.logger.debug(`GQL rate limiting, waiting ${ContractInteractionsLoader._30seconds}ms before next try.`); + this.logger.warn(`GQL rate limiting, waiting ${ContractInteractionsLoader._30seconds}ms before next try.`); await sleep(ContractInteractionsLoader._30seconds); diff --git a/src/core/modules/impl/DefaultStateEvaluator.ts b/src/core/modules/impl/DefaultStateEvaluator.ts index 45c2d49..2fecfd7 100644 --- a/src/core/modules/impl/DefaultStateEvaluator.ts +++ b/src/core/modules/impl/DefaultStateEvaluator.ts @@ -14,22 +14,25 @@ import { InteractionCall, InteractionResult, LoggerFactory, - MemCache, StateEvaluator, TagsParser } from '@smartweave'; import Arweave from 'arweave'; -// FIXME: currently this is tightly coupled with the HandlerApi -export class DefaultStateEvaluator implements StateEvaluator { +/** + * This class contains the base functionality of evaluating the contracts state - according + * to the SmartWeave protocol. + * Marked as abstract - as without help of any cache - the evaluation in real-life applications + * would be really slow - so using this class without any caching ({@link CacheableStateEvaluator}) + * mechanism built on top makes no sense. + */ +export abstract class DefaultStateEvaluator implements StateEvaluator { private readonly logger = LoggerFactory.INST.create('DefaultStateEvaluator'); - private readonly transactionStateCache: MemCache> = new MemCache(); - private readonly tagsParser = new TagsParser(); - constructor( - private readonly arweave: Arweave, + protected constructor( + protected readonly arweave: Arweave, private readonly executionContextModifiers: ExecutionContextModifier[] = [] ) {} @@ -59,7 +62,7 @@ export class DefaultStateEvaluator implements StateEvaluator { let validity = deepCopy(baseState.validity); this.logger.info( - `Evaluating state for ${executionContext.contractDefinition.txId} [${missingInteractions.length} non-cached of ${executionContext.sortedInteractions.length} all]` + `Evaluating state for ${contractDefinition.txId} [${missingInteractions.length} non-cached of ${sortedInteractions.length} all]` ); this.logger.debug('Base state:', baseState.state); @@ -78,7 +81,8 @@ export class DefaultStateEvaluator implements StateEvaluator { }/${missingInteractions.length} [of all:${sortedInteractions.length}]` ); - const state = await this.onNextIteration(interactionTx, executionContext); + // verifying whether state isn't already available for this exact interaction. + const state = await this.transactionState(interactionTx, contractDefinition.txId); const isInteractWrite = this.tagsParser.isInteractWrite(missingInteraction, contractDefinition.txId); this.logger.debug('interactWrite?:', isInteractWrite); @@ -116,7 +120,7 @@ export class DefaultStateEvaluator implements StateEvaluator { ]); // loading latest state of THIS contract from cache - const newState = await this.latestAvailableState(contractDefinition.txId, interactionTx.block.height); + const newState = await this.latestAvailableState(contractDefinition.txId, interactionTx.block.height); this.logger.debug('New state:', { height: interactionTx.block.height, newState, @@ -126,10 +130,11 @@ export class DefaultStateEvaluator implements StateEvaluator { if (newState !== null) { currentState = deepCopy(newState.cachedValue.state); validity[interactionTx.id] = newState.cachedValue.validity[interactionTx.id]; + await this.onStateUpdate(interactionTx, executionContext, new EvalStateResult(currentState, validity)); + lastEvaluatedInteraction = interactionTx; } else { validity[interactionTx.id] = false; } - lastEvaluatedInteraction = interactionTx; interactionCall.update({ cacheHit: false, @@ -171,8 +176,8 @@ export class DefaultStateEvaluator implements StateEvaluator { const interactionCall: InteractionCall = contract.getCallStack().addInteractionData(interactionData); - if (state !== null) { - this.logger.debug('Found in intermediary cache'); + if (state) { + this.logger.debug('Found in cache'); intermediaryCacheHit = true; currentState = state.state; validity = state.validity; @@ -208,9 +213,11 @@ export class DefaultStateEvaluator implements StateEvaluator { valid: validity[interactionTx.id], errorMessage: errorMessage }); + + await this.onStateUpdate(interactionTx, executionContext, new EvalStateResult(currentState, validity)); + } - await this.onStateUpdate(interactionTx, executionContext, new EvalStateResult(currentState, validity)); // I'm really NOT a fan of this "modify" feature, but I don't have idea how to better // implement the "evolve" feature for (const { modify } of this.executionContextModifiers) { @@ -257,61 +264,37 @@ export class DefaultStateEvaluator implements StateEvaluator { } } - async onStateUpdate( - currentInteraction: GQLNodeInterface, - executionContext: ExecutionContext, - state: EvalStateResult - ): Promise { - if (executionContext.evaluationOptions.fcpOptimization && !currentInteraction.dry) { - this.transactionStateCache.put( - `${executionContext.contractDefinition.txId}|${currentInteraction.id}`, - deepCopy(state) - ); - } - } - - async onNextIteration( - currentInteraction: GQLNodeInterface, - executionContext: ExecutionContext - ): Promise> { - const cacheKey = `${executionContext.contractDefinition.txId}|${currentInteraction.id}`; - const cachedState = this.transactionStateCache.get(cacheKey); - - if (cachedState == null) { - return null; - } else { - return deepCopy(cachedState as EvalStateResult); - } - } - - onContractCall( - currentInteraction: GQLNodeInterface, - executionContext: ExecutionContext, - state: EvalStateResult - ): Promise { - return Promise.resolve(undefined); - } - - onStateEvaluated( - lastInteraction: GQLNodeInterface, - executionContext: ExecutionContext, - state: EvalStateResult - ): Promise { - return Promise.resolve(undefined); - } - - async latestAvailableState( + abstract latestAvailableState( contractTxId: string, blockHeight: number - ): Promise> | null> { - return null; - } + ): Promise> | null>; - onInternalWriteStateUpdate( - currentInteraction: GQLNodeInterface, + abstract onContractCall( + transaction: GQLNodeInterface, + executionContext: ExecutionContext, + state: EvalStateResult + ): Promise; + + abstract onInternalWriteStateUpdate( + transaction: GQLNodeInterface, contractTxId: string, state: EvalStateResult - ): Promise { - return Promise.resolve(undefined); - } + ): Promise; + + abstract onStateEvaluated( + transaction: GQLNodeInterface, + executionContext: ExecutionContext, + state: EvalStateResult + ): Promise; + + abstract onStateUpdate( + transaction: GQLNodeInterface, + executionContext: ExecutionContext, + state: EvalStateResult + ): Promise; + + abstract transactionState( + transaction: GQLNodeInterface, + contractTxId: string + ): Promise | undefined>; } diff --git a/src/core/modules/impl/LexicographicalInteractionsSorter.ts b/src/core/modules/impl/LexicographicalInteractionsSorter.ts index 9f9c06e..d817095 100644 --- a/src/core/modules/impl/LexicographicalInteractionsSorter.ts +++ b/src/core/modules/impl/LexicographicalInteractionsSorter.ts @@ -15,15 +15,19 @@ export class LexicographicalInteractionsSorter implements InteractionsSorter { return copy.sort((a, b) => a.sortKey.localeCompare(b.sortKey)); } - private async addSortKey(txInfo: GQLEdgeInterface) { + private async addSortKey(txInfo: GQLEdgeInterface) { const { node } = txInfo; - const blockHashBytes = this.arweave.utils.b64UrlToBuffer(node.block.id); - const txIdBytes = this.arweave.utils.b64UrlToBuffer(node.id); + txInfo.sortKey = await this.createSortKey(node.block.id, node.id, node.block.height); + } + + public async createSortKey(blockId: string, transactionId: string, blockHeight: number) { + const blockHashBytes = this.arweave.utils.b64UrlToBuffer(blockId); + const txIdBytes = this.arweave.utils.b64UrlToBuffer(transactionId); const concatenated = this.arweave.utils.concatBuffers([blockHashBytes, txIdBytes]); const hashed = arrayToHex(await this.arweave.crypto.hash(concatenated)); - const blockHeight = `000000${node.block.height}`.slice(-12); + const blockHeightString = `000000${blockHeight}`.slice(-12); - txInfo.sortKey = `${blockHeight},${hashed}`; + return `${blockHeightString},${hashed}`; } } diff --git a/src/core/modules/impl/StateCache.ts b/src/core/modules/impl/StateCache.ts new file mode 100644 index 0000000..c8539a4 --- /dev/null +++ b/src/core/modules/impl/StateCache.ts @@ -0,0 +1,3 @@ +import { EvalStateResult } from '@smartweave'; + +export type StateCache = Array>; diff --git a/src/core/web/SmartWeaveWebFactory.ts b/src/core/web/SmartWeaveWebFactory.ts index 9fc95f1..32ff078 100644 --- a/src/core/web/SmartWeaveWebFactory.ts +++ b/src/core/web/SmartWeaveWebFactory.ts @@ -8,12 +8,11 @@ import { import { ContractDefinitionLoader, ContractInteractionsLoader, - DefaultStateEvaluator, - EvalStateResult, HandlerExecutorFactory, LexicographicalInteractionsSorter, SmartWeave, - SmartWeaveBuilder + SmartWeaveBuilder, + StateCache } from '@smartweave/core'; import { MemBlockHeightSwCache, MemCache, RemoteBlockHeightCache } from '@smartweave/cache'; @@ -47,7 +46,7 @@ export class SmartWeaveWebFactory { const stateEvaluator = new CacheableStateEvaluator( arweave, - new RemoteBlockHeightCache>('STATE', cacheBaseURL), + new RemoteBlockHeightCache>('STATE', cacheBaseURL), [new Evolve(definitionLoader, executorFactory)] ); @@ -84,7 +83,7 @@ export class SmartWeaveWebFactory { const stateEvaluator = new CacheableStateEvaluator( arweave, - new MemBlockHeightSwCache>(maxStoredBlockHeights), + new MemBlockHeightSwCache>(maxStoredBlockHeights), [new Evolve(definitionLoader, executorFactory)] ); @@ -97,31 +96,4 @@ export class SmartWeaveWebFactory { .setExecutorFactory(executorFactory) .setStateEvaluator(stateEvaluator); } - - /** - * Returns a fully configured, nonCached {@link SmartWeave}. - */ - static nonCached(arweave: Arweave): SmartWeave { - return this.nonCachedBased(arweave).build(); - } - - /** - * Returns a preconfigured {@link SmartWeave} that (yup, you've guessed it!) does not use any caches. - * This one is gonna be slooow! - * Use {@link SmartWeaveBuilder.build()} to finish the configuration. - */ - static nonCachedBased(arweave: Arweave): SmartWeaveBuilder { - const definitionLoader = new ContractDefinitionLoader(arweave); - const interactionsLoader = new ContractInteractionsLoader(arweave); - const executorFactory = new HandlerExecutorFactory(arweave); - const stateEvaluator = new DefaultStateEvaluator(arweave, [new Evolve(definitionLoader, executorFactory)]); - const interactionsSorter = new LexicographicalInteractionsSorter(arweave); - - return SmartWeave.builder(arweave) - .setDefinitionLoader(definitionLoader) - .setInteractionsLoader(interactionsLoader) - .setInteractionsSorter(interactionsSorter) - .setExecutorFactory(executorFactory) - .setStateEvaluator(stateEvaluator); - } } diff --git a/src/legacy/gqlResult.ts b/src/legacy/gqlResult.ts index 547f1a7..745acea 100644 --- a/src/legacy/gqlResult.ts +++ b/src/legacy/gqlResult.ts @@ -44,6 +44,7 @@ export interface GQLNodeInterface { id: string; }; dry?: boolean; + sortKey?: string; //added dynamically by the LexicographicalInteractionsSorter } export interface GQLEdgeInterface { diff --git a/src/plugins/index.ts b/src/plugins/index.ts index b0e1f52..b303615 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,5 +1,5 @@ export * from './CacheableContractInteractionsLoader'; export * from './CacheableExecutorFactory'; -export * from './CacheableStateEvaluator'; +export * from '../core/modules/impl/CacheableStateEvaluator'; export * from './DebuggableExecutorFactor'; export * from './Evolve'; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index f2e6ed7..debc956 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -4,7 +4,7 @@ export const sleep = (ms: number): Promise => { }; export const deepCopy = (input: unknown): any => { - return JSON.parse(JSON.stringify(input)); + return JSON.parse(JSON.stringify(input, mapReplacer), mapReviver); }; export const mapReplacer = (key: unknown, value: unknown) => { @@ -18,6 +18,15 @@ export const mapReplacer = (key: unknown, value: unknown) => { } }; +export const mapReviver = (key: unknown, value: any) => { + if (typeof value === 'object' && value !== null) { + if (value.dataType === 'Map') { + return new Map(value.value); + } + } + return value; +}; + export const asc = (a: number, b: number): number => a - b; export const desc = (a: number, b: number): number => b - a; diff --git a/tools/inner-write.ts b/tools/inner-write.ts index 7fd7d09..e76610c 100644 --- a/tools/inner-write.ts +++ b/tools/inner-write.ts @@ -66,17 +66,15 @@ async function main() { }); calleeContract = smartweave.contract(calleeTxId).connect(wallet).setEvaluationOptions({ - ignoreExceptions: false + ignoreExceptions: false, + internalWrites: true, }); callingContract = smartweave.contract(callingTxId).connect(wallet).setEvaluationOptions({ - ignoreExceptions: false + ignoreExceptions: false, + internalWrites: true }); await mine(); - await calleeContract.writeInteraction({ function: 'add' }); - await calleeContract.writeInteraction({ function: 'add' }); - await mine(); // 102 - await calleeContract.writeInteraction({ function: 'add' }); await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 }); await mine(); // 113 @@ -85,7 +83,6 @@ async function main() { logger.info('Read state 1', result1.state);*/ await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 }); - await mine(); // 123 await calleeContract.writeInteraction({ function: 'add' }); await mine(); //124 diff --git a/tools/transactions-loader.ts b/tools/transactions-loader.ts index 1a6de53..6d602a3 100644 --- a/tools/transactions-loader.ts +++ b/tools/transactions-loader.ts @@ -5,6 +5,7 @@ import { TsLogFactory } from '../src/logging/node/TsLogFactory'; import fs from 'fs'; import path from 'path'; import { ContractInteractionsLoader } from '../src/core/modules/impl/ContractInteractionsLoader'; +import { DefaultEvaluationOptions } from '../../smartweave-loot/.yalc/redstone-smartweave'; async function main() { LoggerFactory.use(new TsLogFactory()); @@ -25,7 +26,7 @@ async function main() { 'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY', 0, 779820, - evaluationOptions + new DefaultEvaluationOptions() ); console.log(result.length);