feat: make cache puts configurable

This commit is contained in:
ppedziwiatr
2021-09-22 11:20:19 +02:00
committed by Piotr Pędziwiatr
parent 02b58217f7
commit d858c08b72
15 changed files with 172 additions and 58 deletions

View File

@@ -13,6 +13,7 @@ The base motivation behind rewriting the original SDK (and roadmap proposal) has
To further improve contract state evaluation time, one can additionally use AWS CloudFront based Arweave cache described [here](https://github.com/redstone-finance/redstone-smartweave-contracts/blob/main/docs/CACHE.md). To further improve contract state evaluation time, one can additionally use AWS CloudFront based Arweave cache described [here](https://github.com/redstone-finance/redstone-smartweave-contracts/blob/main/docs/CACHE.md).
- [Architecture](#architecture) - [Architecture](#architecture)
- [State evaluation diagram](#state-evaluation-diagram)
- [Development](#development) - [Development](#development)
- [Installation and import](#installation-and-import) - [Installation and import](#installation-and-import)
- [Examples](#examples) - [Examples](#examples)
@@ -46,6 +47,17 @@ This modular architecture has several advantages:
2. The SmartWeave client can be customized depending on user needs (e.g. different type of caches for web and node environment) 2. The SmartWeave client can be customized depending on user needs (e.g. different type of caches for web and node environment)
3. It makes it easier to add new features on top of the core protocol - without the risk of breaking the functionality of the core layer. 3. It makes it easier to add new features on top of the core protocol - without the risk of breaking the functionality of the core layer.
## State evaluation diagram
![readState](docs/img/readstate.png)
In order to perform contract state evaluation (at given block height), SDK performs certain operations.
The diagram above and description assume the most basic “mem-cached” SDK client.
1. Users who are interacting with the contract, call the “readState” method.
2. Interactions Loader and Contract Definition Loader modules are then called in parallel - to load all the data required for state evaluation. Both Interactions Loader and Contract Definition Loader first check its corresponding cache whether data is already loaded - and load from Arweave only the missing part.
3. With interactions and contract definition loaded - Executor Factory creates a handle to the SmartWeave contract main function (or loads it from its own cache)
4. With all the interactions and a contract handle - the State Evaluator evaluates the state from the lastly cached value - and returns the result to User.
## Development ## Development
PRs are welcome! :-) Also, feel free to submit [issues](https://github.com/redstone-finance/redstone-smartcontracts/issues) - with both bugs and feature proposals. PRs are welcome! :-) Also, feel free to submit [issues](https://github.com/redstone-finance/redstone-smartcontracts/issues) - with both bugs and feature proposals.
In case of creating a PR - please use [semantic commit messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716). In case of creating a PR - please use [semantic commit messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716).

View File

@@ -101,6 +101,8 @@ In functional programming terms it acts as a `fold` function - the state of the
2. A contract function 2. A contract function
3. An ordered list of actions 3. An ordered list of actions
![protocol](img/protocol.png)
In order to evaluate contract state, SmartWeave Protocol client: In order to evaluate contract state, SmartWeave Protocol client:
1. Loads all the contract's interaction transactions up to the requested block height. 1. Loads all the contract's interaction transactions up to the requested block height.
2. Sorts the interaction transactions. The order of the interactions is determined firstly by interaction 2. Sorts the interaction transactions. The order of the interactions is determined firstly by interaction

BIN
docs/img/protocol.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

BIN
docs/img/readstate.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -65,7 +65,7 @@
"@types/node": "^16.7.1", "@types/node": "^16.7.1",
"@typescript-eslint/eslint-plugin": "^4.29.2", "@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2", "@typescript-eslint/parser": "^4.29.2",
"arlocal": "^1.0.43", "arlocal": "^1.0.44",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^3.4.1", "eslint-plugin-prettier": "^3.4.1",
@@ -78,6 +78,7 @@
"ts-node": "^10.2.1", "ts-node": "^10.2.1",
"tsc-alias": "^1.3.9", "tsc-alias": "^1.3.9",
"tsconfig-paths": "^3.10.1", "tsconfig-paths": "^3.10.1",
"typescript": "^4.3.5" "typescript": "^4.3.5",
"cors": "^2.8.5"
} }
} }

View File

@@ -21,7 +21,7 @@ const arweave = Arweave.init({
}); });
LoggerFactory.INST.logLevel('fatal'); LoggerFactory.INST.logLevel('fatal');
const smartWeave = SmartWeaveNodeFactory.memCached(arweave); const smartWeave = SmartWeaveNodeFactory.memCached(arweave, 10);
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.json'), 'utf-8'));
@@ -37,6 +37,7 @@ describe.each(chunked)('.suite %#', (contracts: string[]) => {
const result = await readContract(arweave, contractTxId); const result = await readContract(arweave, contractTxId);
const resultString = JSON.stringify(result).trim(); const resultString = JSON.stringify(result).trim();
console.log('readState', contractTxId);
const result2 = await smartWeave const result2 = await smartWeave
.contract(contractTxId) .contract(contractTxId)
.setEvaluationOptions({ .setEvaluationOptions({

View File

@@ -4,6 +4,9 @@ import axios, { AxiosInstance } from 'axios';
/** /**
* A {@link BlockHeightSwCache} implementation that delegates all its methods * A {@link BlockHeightSwCache} implementation that delegates all its methods
* to remote endpoints. * to remote endpoints.
*
* TODO: this could be further optimised - i.e. with the help of "level 1" memory cache
* that would store max X elements - and would be backed up by the "level 2" remote cache.
*/ */
export class RemoteBlockHeightCache<V = any> implements BlockHeightSwCache<V> { export class RemoteBlockHeightCache<V = any> implements BlockHeightSwCache<V> {
private axios: AxiosInstance; private axios: AxiosInstance;
@@ -39,9 +42,13 @@ export class RemoteBlockHeightCache<V = any> implements BlockHeightSwCache<V> {
} }
/** /**
* TODO: data should "flushed" in batches...
* PUT '/:type/:key/:blockHeight' {data: value} * PUT '/:type/:key/:blockHeight' {data: value}
*/ */
async put({ cacheKey, blockHeight }: BlockHeightKey, value: V): Promise<void> { async put({ cacheKey, blockHeight }: BlockHeightKey, value: V): Promise<void> {
if (!value) {
return;
}
await this.axios.put(`/${this.type}/${cacheKey}/${blockHeight}`, value); await this.axios.put(`/${this.type}/${cacheKey}/${blockHeight}`, value);
} }

View File

@@ -32,7 +32,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
/** /**
* wallet connected to this contract * wallet connected to this contract
* @protected
*/ */
protected wallet?: ArWallet; protected wallet?: ArWallet;
private evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions(); private evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions();
@@ -87,7 +86,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
const executionContext = await this.createExecutionContext(this.contractTxId, blockHeight); const executionContext = await this.createExecutionContext(this.contractTxId, blockHeight);
this.logger.info('Execution Context', { this.logger.info('Execution Context', {
blockHeight: executionContext.blockHeight, blockHeight: executionContext.blockHeight,
srcTxId: executionContext.contractDefinition.srcTxId srcTxId: executionContext.contractDefinition.srcTxId,
missingInteractions: executionContext.sortedInteractions.length
}); });
this.logger.debug('context', benchmark.elapsed()); this.logger.debug('context', benchmark.elapsed());
benchmark.reset(); benchmark.reset();
@@ -149,7 +149,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
// call one of the contract's view method // call one of the contract's view method
const handleResult = await executionContext.handler.handle<Input, View>( const handleResult = await executionContext.handler.handle<Input, View>(
executionContext, executionContext,
evalStateResult.state, evalStateResult,
interaction, interaction,
{ {
id: null, id: null,
@@ -192,7 +192,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
return await executionContext.handler.handle<Input, View>( return await executionContext.handler.handle<Input, View>(
executionContext, executionContext,
evalStateResult.state, evalStateResult,
interaction, interaction,
transaction, transaction,
[] []

View File

@@ -1,4 +1,4 @@
import { ExecutionContext, GQLEdgeInterface, GQLNodeInterface } from '@smartweave'; import { ExecutionContext, GQLNodeInterface, InteractionTx } from '@smartweave';
/** /**
* Implementors of this class are responsible for evaluating contract's state * Implementors of this class are responsible for evaluating contract's state
@@ -10,11 +10,32 @@ export interface StateEvaluator {
currentTx: { interactionTxId: string; contractTxId: string }[] currentTx: { interactionTxId: string; contractTxId: string }[]
): Promise<EvalStateResult<State>>; ): Promise<EvalStateResult<State>>;
/**
* a hook that is called on each state update (i.e. after evaluating state for each interaction)
*/
onStateUpdate<State>( onStateUpdate<State>(
currentInteraction: GQLNodeInterface, currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void>; ): Promise<void>;
/**
* a hook that is called after state has been fully evaluated
*/
onStateEvaluated<State>(
lastInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void>;
/**
* a hook that is called before communicating with other contract
*/
onContractCall<State>(
currentInteraction: InteractionTx,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void>;
} }
export class EvalStateResult<State> { export class EvalStateResult<State> {
@@ -32,6 +53,8 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
waitForConfirmation = false; waitForConfirmation = false;
fcpOptimization = false; fcpOptimization = false;
updateCacheForEachInteraction = true;
} }
// an interface for the contract EvaluationOptions - can be used to change the behaviour of some of the features. // an interface for the contract EvaluationOptions - can be used to change the behaviour of some of the features.
@@ -45,4 +68,9 @@ export interface EvaluationOptions {
// experimental optimization for contracts that utilize the Foreign Call Protocol // experimental optimization for contracts that utilize the Foreign Call Protocol
fcpOptimization: boolean; fcpOptimization: boolean;
// whether cache should be updated after evaluating each interaction transaction.
// this can be switched off to speed up cache writes (ie. for some contracts (with flat structure)
// and caches it maybe more suitable to cache only after state has been fully evaluated)
updateCacheForEachInteraction: boolean;
} }

View File

@@ -10,6 +10,7 @@ import {
GQLTagInterface, GQLTagInterface,
HandlerApi, HandlerApi,
InteractionResult, InteractionResult,
InteractionTx,
LoggerFactory, LoggerFactory,
MemCache, MemCache,
StateEvaluator, StateEvaluator,
@@ -50,30 +51,25 @@ export class DefaultStateEvaluator implements StateEvaluator {
): Promise<EvalStateResult<State>> { ): Promise<EvalStateResult<State>> {
const stateEvaluationBenchmark = Benchmark.measure(); const stateEvaluationBenchmark = Benchmark.measure();
const { ignoreExceptions } = executionContext.evaluationOptions; const { ignoreExceptions } = executionContext.evaluationOptions;
const { contractDefinition, sortedInteractions } = executionContext;
let currentState = baseState.state; let currentState = baseState.state;
let validity = deepCopy(baseState.validity); let validity = deepCopy(baseState.validity);
this.logger.info( this.logger.info(
`Evaluating state for ${executionContext.contractDefinition.txId} [${missingInteractions.length} non-cached of ${executionContext.sortedInteractions.length} all]` `Evaluating state for ${executionContext.contractDefinition.txId}
[${missingInteractions.length} non-cached of ${executionContext.sortedInteractions.length} all]`
); );
this.logger.trace( let lastEvaluatedInteraction = null;
'missingInteractions',
missingInteractions.map((int) => {
return int.node.id;
})
);
this.logger.trace('Init state', JSON.stringify(baseState.state));
for (const missingInteraction of missingInteractions) { for (const missingInteraction of missingInteractions) {
const currentInteraction: GQLNodeInterface = missingInteraction.node; const currentInteraction: GQLNodeInterface = missingInteraction.node;
this.logger.debug( this.logger.debug(
`[${executionContext.contractDefinition.txId}][${missingInteraction.node.id}]: ${ `[${contractDefinition.txId}][${missingInteraction.node.id}][${missingInteraction.node.block.height}]: ${
missingInteractions.indexOf(missingInteraction) + 1 missingInteractions.indexOf(missingInteraction) + 1
}/${missingInteractions.length} [of all:${executionContext.sortedInteractions.length}]` }/${missingInteractions.length} [of all:${sortedInteractions.length}]`
); );
const state = await this.onNextIteration(currentInteraction, executionContext); const state = await this.onNextIteration(currentInteraction, executionContext);
@@ -84,7 +80,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
} else { } else {
const singleInteractionBenchmark = Benchmark.measure(); const singleInteractionBenchmark = Benchmark.measure();
const inputTag = this.tagsParser.getInputTag(missingInteraction, executionContext.contractDefinition.txId); const inputTag = this.tagsParser.getInputTag(missingInteraction, contractDefinition.txId);
if (!inputTag) { if (!inputTag) {
this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`); this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
continue; continue;
@@ -103,7 +99,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
const result = await executionContext.handler.handle( const result = await executionContext.handler.handle(
executionContext, executionContext,
currentState, new EvalStateResult(currentState, validity),
interaction, interaction,
currentInteraction, currentInteraction,
currentTx currentTx
@@ -115,10 +111,6 @@ export class DefaultStateEvaluator implements StateEvaluator {
throw new Error(`Exception while processing ${JSON.stringify(interaction)}:\n${result.result}`); throw new Error(`Exception while processing ${JSON.stringify(interaction)}:\n${result.result}`);
} }
if (result.type === 'exception') {
this.logger.error('Credit:', (currentState as any).credit);
}
validity[currentInteraction.id] = result.type === 'ok'; validity[currentInteraction.id] = result.type === 'ok';
// strangely - state is for some reason modified for some contracts (eg. YLVpmhSq5JmLltfg6R-5fL04rIRPrlSU22f6RQ6VyYE) // strangely - state is for some reason modified for some contracts (eg. YLVpmhSq5JmLltfg6R-5fL04rIRPrlSU22f6RQ6VyYE)
// when calling any async (even simple timeout) function here... // when calling any async (even simple timeout) function here...
@@ -126,6 +118,10 @@ export class DefaultStateEvaluator implements StateEvaluator {
// see https://github.com/ArweaveTeam/SmartWeave/pull/92 for more details // see https://github.com/ArweaveTeam/SmartWeave/pull/92 for more details
currentState = deepCopy(result.state); currentState = deepCopy(result.state);
// cannot simply take last element of the missingInteractions
// as there is no certainty that it has been evaluated (e.g. issues with input tag).
lastEvaluatedInteraction = currentInteraction;
this.logger.debug('Interaction evaluation', singleInteractionBenchmark.elapsed()); this.logger.debug('Interaction evaluation', singleInteractionBenchmark.elapsed());
} }
@@ -142,7 +138,15 @@ export class DefaultStateEvaluator implements StateEvaluator {
} }
} }
this.logger.debug('State evaluation total:', stateEvaluationBenchmark.elapsed()); this.logger.debug('State evaluation total:', stateEvaluationBenchmark.elapsed());
return new EvalStateResult<State>(currentState, validity); const result = new EvalStateResult<State>(currentState, validity);
// state could have been full retrieved from cache
// or there were no interactions below requested block height
if (lastEvaluatedInteraction !== null) {
await this.onStateEvaluated(lastEvaluatedInteraction, executionContext, result);
}
return result;
} }
private logResult<State>( private logResult<State>(
@@ -199,4 +203,20 @@ export class DefaultStateEvaluator implements StateEvaluator {
return deepCopy(cachedState as EvalStateResult<State>); return deepCopy(cachedState as EvalStateResult<State>);
} }
} }
onContractCall<State>(
currentInteraction: InteractionTx,
executionContext: ExecutionContext<State, unknown>,
state: EvalStateResult<State>
): Promise<void> {
return Promise.resolve(undefined);
}
onStateEvaluated<State>(
lastInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void> {
return Promise.resolve(undefined);
}
} }

View File

@@ -4,6 +4,7 @@ import * as clarity from '@weavery/clarity';
import { import {
ContractDefinition, ContractDefinition,
deepCopy, deepCopy,
EvalStateResult,
ExecutionContext, ExecutionContext,
ExecutorFactory, ExecutorFactory,
InteractionTx, InteractionTx,
@@ -17,7 +18,7 @@ import {
export interface HandlerApi<State> { export interface HandlerApi<State> {
handle<Input, Result>( handle<Input, Result>(
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: State, currentResult: EvalStateResult<State>,
interaction: ContractInteraction<Input>, interaction: ContractInteraction<Input>,
interactionTx: InteractionTx, interactionTx: InteractionTx,
currentTx: { interactionTxId: string; contractTxId: string }[] currentTx: { interactionTxId: string; contractTxId: string }[]
@@ -54,7 +55,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
return { return {
async handle<Input, Result>( async handle<Input, Result>(
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: State, currentResult: EvalStateResult<State>,
interaction: ContractInteraction<Input>, interaction: ContractInteraction<Input>,
interactionTx: InteractionTx, interactionTx: InteractionTx,
currentTx: { interactionTxId: string; contractTxId: string }[] currentTx: { interactionTxId: string; contractTxId: string }[]
@@ -65,11 +66,19 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
Input, Input,
Result Result
>; >;
const stateCopy = JSON.parse(JSON.stringify(state)); const stateCopy = JSON.parse(JSON.stringify(currentResult.state));
swGlobal._activeTx = interactionTx; swGlobal._activeTx = interactionTx;
self.logger.trace(`SmartWeave.contract.id:`, swGlobal.contract.id); self.logger.trace(`SmartWeave.contract.id:`, swGlobal.contract.id);
self.assignReadContractState<Input, State>(swGlobal, contractDefinition, executionContext, currentTx); // TODO: refactor - too many arguments
self.assignReadContractState<Input, State>(
swGlobal,
contractDefinition,
executionContext,
currentTx,
currentResult,
interactionTx
);
self.assignViewContractState<Input, State>(swGlobal, contractDefinition, executionContext); self.assignViewContractState<Input, State>(swGlobal, contractDefinition, executionContext);
const handlerResult = await handler(stateCopy, interaction); const handlerResult = await handler(stateCopy, interaction);
@@ -78,7 +87,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
return { return {
type: 'ok', type: 'ok',
result: handlerResult.result, result: handlerResult.result,
state: handlerResult.state || state state: handlerResult.state || currentResult.state
}; };
} }
@@ -90,7 +99,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
return { return {
type: 'error', type: 'error',
errorMessage: err.message, errorMessage: err.message,
state, state: currentResult.state,
// note: previous version was writing error message to a "result" field, // note: previous version was writing error message to a "result" field,
// which fucks-up the HandlerResult type definition - // which fucks-up the HandlerResult type definition -
// HandlerResult.result had to be declared as 'Result | string' - and that led to a poor dev exp. // HandlerResult.result had to be declared as 'Result | string' - and that led to a poor dev exp.
@@ -101,7 +110,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
return { return {
type: 'exception', type: 'exception',
errorMessage: `${(err && err.stack) || (err && err.message)}`, errorMessage: `${(err && err.stack) || (err && err.message)}`,
state, state: currentResult.state,
result: null result: null
}; };
} }
@@ -133,7 +142,9 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
swGlobal: SmartWeaveGlobal, swGlobal: SmartWeaveGlobal,
contractDefinition: ContractDefinition<State>, contractDefinition: ContractDefinition<State>,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
currentTx: { interactionTxId: string; contractTxId: string }[] currentTx: { interactionTxId: string; contractTxId: string }[],
currentResult: EvalStateResult<State>,
interactionTx: InteractionTx
) { ) {
swGlobal.contracts.readContractState = async (contractTxId: string, height?: number, returnValidity?: boolean) => { swGlobal.contracts.readContractState = async (contractTxId: string, height?: number, returnValidity?: boolean) => {
const requestedHeight = height || swGlobal.block.height; const requestedHeight = height || swGlobal.block.height;
@@ -143,10 +154,14 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
height: requestedHeight, height: requestedHeight,
transaction: swGlobal.transaction.id transaction: swGlobal.transaction.id
}); });
const { stateEvaluator } = executionContext.smartweave;
const childContract = executionContext.smartweave const childContract = executionContext.smartweave
.contract(contractTxId, executionContext.contract) .contract(contractTxId, executionContext.contract)
.setEvaluationOptions(executionContext.evaluationOptions); .setEvaluationOptions(executionContext.evaluationOptions);
await stateEvaluator.onContractCall(interactionTx, executionContext, currentResult);
const stateWithValidity = await childContract.readState(requestedHeight, [ const stateWithValidity = await childContract.readState(requestedHeight, [
...(currentTx || []), ...(currentTx || []),
{ {

View File

@@ -64,7 +64,7 @@ export class SmartWeaveWebFactory {
/** /**
* Returns a fully configured {@link SmartWeave} that is using mem cache for all layers. * Returns a fully configured {@link SmartWeave} that is using mem cache for all layers.
*/ */
static memCached(arweave: Arweave): SmartWeave { static memCached(arweave: Arweave, maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER): SmartWeave {
return this.memCachedBased(arweave).build(); return this.memCachedBased(arweave).build();
} }
@@ -72,19 +72,21 @@ export class SmartWeaveWebFactory {
* Returns a preconfigured, memCached {@link SmartWeaveBuilder}, that allows for customization of the SmartWeave instance. * Returns a preconfigured, memCached {@link SmartWeaveBuilder}, that allows for customization of the SmartWeave instance.
* Use {@link SmartWeaveBuilder.build()} to finish the configuration. * Use {@link SmartWeaveBuilder.build()} to finish the configuration.
*/ */
static memCachedBased(arweave: Arweave): SmartWeaveBuilder { static memCachedBased(arweave: Arweave, maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER): SmartWeaveBuilder {
const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache()); const definitionLoader = new ContractDefinitionLoader(arweave, new MemCache());
const interactionsLoader = new CacheableContractInteractionsLoader( const interactionsLoader = new CacheableContractInteractionsLoader(
new ContractInteractionsLoader(arweave), new ContractInteractionsLoader(arweave),
new MemBlockHeightSwCache() new MemBlockHeightSwCache(maxStoredBlockHeights)
); );
const executorFactory = new CacheableExecutorFactory(arweave, new HandlerExecutorFactory(arweave), new MemCache()); const executorFactory = new CacheableExecutorFactory(arweave, new HandlerExecutorFactory(arweave), new MemCache());
const stateEvaluator = new CacheableStateEvaluator(arweave, new MemBlockHeightSwCache<EvalStateResult<unknown>>(), [ const stateEvaluator = new CacheableStateEvaluator(
new Evolve(definitionLoader, executorFactory) arweave,
]); new MemBlockHeightSwCache<EvalStateResult<unknown>>(maxStoredBlockHeights),
[new Evolve(definitionLoader, executorFactory)]
);
const interactionsSorter = new LexicographicalInteractionsSorter(arweave); const interactionsSorter = new LexicographicalInteractionsSorter(arweave);

View File

@@ -1,4 +1,4 @@
import { BlockHeightCacheResult, BlockHeightKey, BlockHeightSwCache, MemCache } from '@smartweave/cache'; import { BlockHeightCacheResult, BlockHeightKey, BlockHeightSwCache } from '@smartweave/cache';
import { import {
DefaultStateEvaluator, DefaultStateEvaluator,
EvalStateResult, EvalStateResult,
@@ -7,9 +7,8 @@ import {
HandlerApi HandlerApi
} from '@smartweave/core'; } from '@smartweave/core';
import Arweave from 'arweave'; import Arweave from 'arweave';
import { GQLEdgeInterface, GQLNodeInterface } from '@smartweave/legacy'; import { GQLNodeInterface, InteractionTx } from '@smartweave/legacy';
import { Benchmark, LoggerFactory } from '@smartweave/logging'; import { Benchmark, LoggerFactory } from '@smartweave/logging';
import { deepCopy } from '@smartweave/utils';
/** /**
* An implementation of DefaultStateEvaluator that adds caching capabilities * An implementation of DefaultStateEvaluator that adds caching capabilities
@@ -34,12 +33,16 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
let cachedState: BlockHeightCacheResult<EvalStateResult<State>> | null = null; let cachedState: BlockHeightCacheResult<EvalStateResult<State>> | null = null;
this.cLogger.debug('executionContext.sortedInteractions', executionContext.sortedInteractions.length);
const sortedInteractionsUpToBlock = executionContext.sortedInteractions.filter((tx) => { const sortedInteractionsUpToBlock = executionContext.sortedInteractions.filter((tx) => {
return tx.node.block.height <= executionContext.blockHeight; return tx.node.block.height <= executionContext.blockHeight;
}); });
let missingInteractions = sortedInteractionsUpToBlock.slice(); let missingInteractions = sortedInteractionsUpToBlock.slice();
this.cLogger.debug('missingInteractions', missingInteractions.length);
// if there was anything to cache... // if there was anything to cache...
if (sortedInteractionsUpToBlock.length > 0) { if (sortedInteractionsUpToBlock.length > 0) {
// get latest available cache for the requested block height // get latest available cache for the requested block height
@@ -52,7 +55,7 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
if (cachedState != null) { if (cachedState != null) {
this.cLogger.debug(`Cached state for ${executionContext.contractDefinition.txId}`, { this.cLogger.debug(`Cached state for ${executionContext.contractDefinition.txId}`, {
block: cachedState.cachedHeight, cachedHeight: cachedState.cachedHeight,
requestedBlockHeight requestedBlockHeight
}); });
@@ -107,16 +110,30 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
); );
} }
async onStateUpdate<State>( async onStateEvaluated<State>(
currentInteraction: GQLNodeInterface, lastInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
) { ): Promise<void> {
await super.onStateUpdate(currentInteraction, executionContext, state); this.cLogger.debug(
`onStateEvaluated: cache update for contract ${executionContext.contractDefinition.txId} [${lastInteraction.block.height}]`
);
await this.cache.put( await this.cache.put(
new BlockHeightKey(executionContext.contractDefinition.txId, currentInteraction.block.height), new BlockHeightKey(executionContext.contractDefinition.txId, lastInteraction.block.height),
state state
); );
} }
async onStateUpdate<State>(
currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State, unknown>,
state: EvalStateResult<State>
): Promise<void> {
if (executionContext.evaluationOptions.updateCacheForEachInteraction) {
await this.cache.put(
new BlockHeightKey(executionContext.contractDefinition.txId, currentInteraction.block.height),
state
);
}
}
} }

View File

@@ -1,11 +1,12 @@
const express = require('express'); const express = require('express');
const cors = require('cors');
const { MemBlockHeightSwCache } = require('../lib/cjs/cache/impl/MemBlockHeightCache'); const { MemBlockHeightSwCache } = require('../lib/cjs/cache/impl/MemBlockHeightCache');
const app = express(); const app = express();
const port = 3000; const port = 3000;
console.log(MemBlockHeightSwCache); app.use(cors());
app.use(express.json({ limit: "50mb", extended: true }));
app.use(express.json());
const caches = { const caches = {
STATE: new MemBlockHeightSwCache(1), STATE: new MemBlockHeightSwCache(1),

View File

@@ -1475,10 +1475,10 @@ argparse@^1.0.7:
dependencies: dependencies:
sprintf-js "~1.0.2" sprintf-js "~1.0.2"
arlocal@^1.0.43: arlocal@^1.0.44:
version "1.0.43" version "1.0.44"
resolved "https://registry.yarnpkg.com/arlocal/-/arlocal-1.0.43.tgz#ce45ed2882a75293990aadd57dea1c085b7f345f" resolved "https://registry.yarnpkg.com/arlocal/-/arlocal-1.0.44.tgz#21f0d206b6d539aff04a24c2002f4c42ad467419"
integrity sha512-9G5QAtuDrD/8DvkrLk7O8uCIIVwo2tW4+1XCx90OrDfOh3QH0/7peKLcpE2dWYkl3684lvyJ/ku/kRJcQhQvNA== integrity sha512-NviY1QzOb4v66rh0zTwNV8eoYu4my1hJbLE+RvuPgqrg32nBS3Snv53CyZ36czs4ffpDkpHv01p4dpFIrox1wA==
dependencies: dependencies:
"@koa/cors" "^3.1.0" "@koa/cors" "^3.1.0"
apollo-server-koa "^2.25.1" apollo-server-koa "^2.25.1"
@@ -2163,6 +2163,14 @@ core-util-is@^1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
create-require@^1.1.0: create-require@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
@@ -4794,7 +4802,7 @@ oauth-sign@~0.9.0:
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==
object-assign@^4.1.0: object-assign@^4, object-assign@^4.1.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@@ -6230,7 +6238,7 @@ v8-to-istanbul@^8.0.0:
convert-source-map "^1.6.0" convert-source-map "^1.6.0"
source-map "^0.7.3" source-map "^0.7.3"
vary@^1.1.2, vary@~1.1.2: vary@^1, vary@^1.1.2, vary@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=