refactor: state cache #51

This commit is contained in:
ppedziwiatr
2021-11-04 11:30:15 +01:00
committed by Piotr Pędziwiatr
parent 5f1834347b
commit fd7a63db6d
16 changed files with 225 additions and 156 deletions

View File

@@ -4,7 +4,7 @@
* *
* @typeParam V - type of value stored in cache, defaults to `any`. * @typeParam V - type of value stored in cache, defaults to `any`.
*/ */
export interface BlockHeightSwCache<V = any> { export interface BlockHeightSwCache<V> {
/** /**
* returns cached value for the highest available in cache block that is not higher than `blockHeight`. * returns cached value for the highest available in cache block that is not higher than `blockHeight`.
*/ */

View File

@@ -8,10 +8,14 @@ import { LoggerFactory } from '@smartweave/logging';
export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> { export class MemBlockHeightSwCache<V = any> implements BlockHeightSwCache<V> {
private readonly logger = LoggerFactory.INST.create('MemBlockHeightSwCache'); private readonly logger = LoggerFactory.INST.create('MemBlockHeightSwCache');
private storage: { [key: string]: Map<number, V> } = {}; protected storage: { [key: string]: Map<number, V> } = {};
constructor(private maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER) {} constructor(private maxStoredBlockHeights: number = Number.MAX_SAFE_INTEGER) {}
flush(): Promise<void> {
throw new Error('Method not implemented.');
}
async getLast(key: string): Promise<BlockHeightCacheResult<V> | null> { async getLast(key: string): Promise<BlockHeightCacheResult<V> | null> {
if (!(await this.contains(key))) { if (!(await this.contains(key))) {
return null; return null;

View File

@@ -328,6 +328,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
]); ]);
this.logger.debug('contract and interactions load', benchmark.elapsed()); this.logger.debug('contract and interactions load', benchmark.elapsed());
sortedInteractions = await interactionsSorter.sort(interactions); sortedInteractions = await interactionsSorter.sort(interactions);
this.logger.debug('Sorted interactions', sortedInteractions);
handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>; handler = (await executorFactory.create(contractDefinition)) as HandlerApi<State>;
} else { } else {
this.logger.debug('State fully cached, not loading interactions.'); this.logger.debug('State fully cached, not loading interactions.');

View File

@@ -14,6 +14,7 @@ export * from './modules/impl/LexicographicalInteractionsSorter';
export * from './modules/impl/DefaultCreateContract'; export * from './modules/impl/DefaultCreateContract';
export * from './modules/impl/TagsParser'; export * from './modules/impl/TagsParser';
export * from './modules/impl/normalize-source'; export * from './modules/impl/normalize-source';
export * from './modules/impl/StateCache';
export * from './ExecutionContextModifier'; export * from './ExecutionContextModifier';
export * from './SmartWeaveTags'; export * from './SmartWeaveTags';

View File

@@ -8,10 +8,10 @@ export interface StateEvaluator {
eval<State>(executionContext: ExecutionContext<State>, currentTx: CurrentTx[]): Promise<EvalStateResult<State>>; eval<State>(executionContext: ExecutionContext<State>, currentTx: CurrentTx[]): Promise<EvalStateResult<State>>;
/** /**
* 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<State>( onStateUpdate<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void>; ): Promise<void>;
@@ -20,42 +20,56 @@ export interface StateEvaluator {
* a hook that is called after state has been fully evaluated * a hook that is called after state has been fully evaluated
*/ */
onStateEvaluated<State>( onStateEvaluated<State>(
lastInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void>; ): Promise<void>;
/**
* a hook that is called after performing internal write between contracts
*/
onInternalWriteStateUpdate<State>( onInternalWriteStateUpdate<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
contractTxId: string, contractTxId: string,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void>; ): Promise<void>;
/** /**
* 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. * note to myself: putting values into cache only "onContractCall" may degrade performance.
* For example" * For example:
* block 722317 - contract A calls B * 1. block 722317 - contract A calls B
* block 722727 - contract A calls B * 2. block 722727 - contract A calls B
* block 722695 - contract B calls A * 3. block 722695 - contract B calls A
* If we update cache only on contract call - for the last above call (B->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 * we would retrieve state cached for 722317. If there are any transactions
* between 722317 and 722695 - the performance will be degraded. * between 722317 and 722695 - the performance will be degraded.
*/ */
onContractCall<State>( onContractCall<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void>; ): Promise<void>;
/**
* 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<State>( latestAvailableState<State>(
contractTxId: string, contractTxId: string,
blockHeight: number blockHeight: number
): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null>; ): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null>;
transactionState<State>(transaction: GQLNodeInterface, contractTxId: string): Promise<EvalStateResult<State> | undefined>;
} }
export class EvalStateResult<State> { export class EvalStateResult<State> {
constructor(readonly state: State, readonly validity: Record<string, boolean>) {} constructor(
readonly state: State,
readonly validity: Record<string, boolean>,
readonly transactionId?: string,
readonly blockId?: string) {}
} }
export class DefaultEvaluationOptions implements EvaluationOptions { export class DefaultEvaluationOptions implements EvaluationOptions {

View File

@@ -4,22 +4,30 @@ import {
EvalStateResult, EvalStateResult,
ExecutionContext, ExecutionContext,
ExecutionContextModifier, ExecutionContextModifier,
HandlerApi HandlerApi,
LexicographicalInteractionsSorter,
StateCache
} from '@smartweave/core'; } from '@smartweave/core';
import Arweave from 'arweave'; import Arweave from 'arweave';
import { GQLNodeInterface } from '@smartweave/legacy'; import { GQLNodeInterface } from '@smartweave/legacy';
import { LoggerFactory } from '@smartweave/logging'; import { LoggerFactory } from '@smartweave/logging';
import { CurrentTx } from '@smartweave/contract'; 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 { export class CacheableStateEvaluator extends DefaultStateEvaluator {
private readonly cLogger = LoggerFactory.INST.create('CacheableStateEvaluator'); private readonly cLogger = LoggerFactory.INST.create('CacheableStateEvaluator');
constructor( constructor(
arweave: Arweave, arweave: Arweave,
private readonly cache: BlockHeightSwCache<EvalStateResult<unknown>>, private readonly cache: BlockHeightSwCache<StateCache<unknown>>,
executionContextModifiers: ExecutionContextModifier[] = [] executionContextModifiers: ExecutionContextModifier[] = []
) { ) {
super(arweave, executionContextModifiers); super(arweave, executionContextModifiers);
@@ -105,61 +113,132 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator {
} }
async onStateEvaluated<State>( async onStateEvaluated<State>(
lastInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void> { ): Promise<void> {
if (lastInteraction.dry) { if (transaction.dry) {
return; return;
} }
this.cLogger.debug( const contractTxId = executionContext.contractDefinition.txId;
`onStateEvaluated: cache update for contract ${executionContext.contractDefinition.txId} [${lastInteraction.block.height}]`
); this.cLogger.debug(`onStateEvaluated: cache update for contract ${contractTxId} [${transaction.block.height}]`);
await this.cache.put( await this.putInCache(contractTxId, transaction, state);
new BlockHeightKey(executionContext.contractDefinition.txId, lastInteraction.block.height),
state
);
} }
async onStateUpdate<State>( async onStateUpdate<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>, executionContext: ExecutionContext<State>,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void> { ): Promise<void> {
if (currentInteraction.dry) {
return;
}
if (executionContext.evaluationOptions.updateCacheForEachInteraction) { if (executionContext.evaluationOptions.updateCacheForEachInteraction) {
await this.cache.put( await this.putInCache(executionContext.contractDefinition.txId, transaction, state);
new BlockHeightKey(executionContext.contractDefinition.txId, currentInteraction.block.height),
state
);
} }
await super.onStateUpdate(currentInteraction, executionContext, state);
} }
async latestAvailableState<State>( async latestAvailableState<State>(
contractTxId: string, contractTxId: string,
blockHeight: number blockHeight: number
): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null> { ): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null> {
return (await this.cache.getLessOrEqual(contractTxId, blockHeight)) as BlockHeightCacheResult< const stateCache = (await this.cache.getLessOrEqual(contractTxId, blockHeight)) as BlockHeightCacheResult<
EvalStateResult<State> StateCache<State>
>; >;
if (stateCache == null) {
return null;
}
/*if (stateCache.cachedValue.length == 1) {
this.cLogger.debug('CacheValue size 1', stateCache.cachedValue.values().next().value);
return new BlockHeightCacheResult<EvalStateResult<State>>(
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<EvalStateResult<State>>(
stateCache.cachedHeight,
[...stateCache.cachedValue].pop()
);
} }
async onInternalWriteStateUpdate<State>( async onInternalWriteStateUpdate<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
contractTxId: string, contractTxId: string,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void> { ): Promise<void> {
if (currentInteraction.dry) {
return;
}
this.cLogger.debug('Internal write state update:', { this.cLogger.debug('Internal write state update:', {
height: currentInteraction.block.height, height: transaction.block.height,
contractTxId, contractTxId,
state state
}); });
await this.cache.put(new BlockHeightKey(contractTxId, currentInteraction.block.height), state); await this.putInCache(contractTxId, transaction, state);
}
async onContractCall<State>(
transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void> {
await this.putInCache(executionContext.contractDefinition.txId, transaction, state);
}
async transactionState<State>(
transaction: GQLNodeInterface,
contractTxId: string
): Promise<EvalStateResult<State> | undefined> {
const stateCache = (await this.cache.get(contractTxId, transaction.block.height)) as BlockHeightCacheResult<
StateCache<State>
>;
if (stateCache == null) {
return undefined;
}
return stateCache.cachedValue.find((sc) => {
return sc.transactionId === transaction.id;
});
}
protected async putInCache<State>(
contractTxId: string,
transaction: GQLNodeInterface,
state: EvalStateResult<State>
): Promise<void> {
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]);
}
} }
} }

View File

@@ -149,7 +149,7 @@ export class ContractInteractionsLoader implements InteractionsLoader {
this.logger.debug('GQL page load:', benchmark.elapsed()); this.logger.debug('GQL page load:', benchmark.elapsed());
while (response.status === 403) { 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); await sleep(ContractInteractionsLoader._30seconds);

View File

@@ -14,22 +14,25 @@ import {
InteractionCall, InteractionCall,
InteractionResult, InteractionResult,
LoggerFactory, LoggerFactory,
MemCache,
StateEvaluator, StateEvaluator,
TagsParser TagsParser
} from '@smartweave'; } from '@smartweave';
import Arweave from 'arweave'; 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 logger = LoggerFactory.INST.create('DefaultStateEvaluator');
private readonly transactionStateCache: MemCache<EvalStateResult<unknown>> = new MemCache();
private readonly tagsParser = new TagsParser(); private readonly tagsParser = new TagsParser();
constructor( protected constructor(
private readonly arweave: Arweave, protected readonly arweave: Arweave,
private readonly executionContextModifiers: ExecutionContextModifier[] = [] private readonly executionContextModifiers: ExecutionContextModifier[] = []
) {} ) {}
@@ -59,7 +62,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
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 ${contractDefinition.txId} [${missingInteractions.length} non-cached of ${sortedInteractions.length} all]`
); );
this.logger.debug('Base state:', baseState.state); this.logger.debug('Base state:', baseState.state);
@@ -78,7 +81,8 @@ export class DefaultStateEvaluator implements StateEvaluator {
}/${missingInteractions.length} [of all:${sortedInteractions.length}]` }/${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<State>(interactionTx, contractDefinition.txId);
const isInteractWrite = this.tagsParser.isInteractWrite(missingInteraction, contractDefinition.txId); const isInteractWrite = this.tagsParser.isInteractWrite(missingInteraction, contractDefinition.txId);
this.logger.debug('interactWrite?:', isInteractWrite); this.logger.debug('interactWrite?:', isInteractWrite);
@@ -116,7 +120,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
]); ]);
// loading latest state of THIS contract from cache // loading latest state of THIS contract from cache
const newState = await this.latestAvailableState(contractDefinition.txId, interactionTx.block.height); const newState = await this.latestAvailableState<State>(contractDefinition.txId, interactionTx.block.height);
this.logger.debug('New state:', { this.logger.debug('New state:', {
height: interactionTx.block.height, height: interactionTx.block.height,
newState, newState,
@@ -126,10 +130,11 @@ export class DefaultStateEvaluator implements StateEvaluator {
if (newState !== null) { if (newState !== null) {
currentState = deepCopy(newState.cachedValue.state); currentState = deepCopy(newState.cachedValue.state);
validity[interactionTx.id] = newState.cachedValue.validity[interactionTx.id]; validity[interactionTx.id] = newState.cachedValue.validity[interactionTx.id];
await this.onStateUpdate<State>(interactionTx, executionContext, new EvalStateResult(currentState, validity));
lastEvaluatedInteraction = interactionTx;
} else { } else {
validity[interactionTx.id] = false; validity[interactionTx.id] = false;
} }
lastEvaluatedInteraction = interactionTx;
interactionCall.update({ interactionCall.update({
cacheHit: false, cacheHit: false,
@@ -171,8 +176,8 @@ export class DefaultStateEvaluator implements StateEvaluator {
const interactionCall: InteractionCall = contract.getCallStack().addInteractionData(interactionData); const interactionCall: InteractionCall = contract.getCallStack().addInteractionData(interactionData);
if (state !== null) { if (state) {
this.logger.debug('Found in intermediary cache'); this.logger.debug('Found in cache');
intermediaryCacheHit = true; intermediaryCacheHit = true;
currentState = state.state; currentState = state.state;
validity = state.validity; validity = state.validity;
@@ -208,9 +213,11 @@ export class DefaultStateEvaluator implements StateEvaluator {
valid: validity[interactionTx.id], valid: validity[interactionTx.id],
errorMessage: errorMessage errorMessage: errorMessage
}); });
await this.onStateUpdate<State>(interactionTx, executionContext, new EvalStateResult(currentState, validity));
} }
await this.onStateUpdate<State>(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 // I'm really NOT a fan of this "modify" feature, but I don't have idea how to better
// implement the "evolve" feature // implement the "evolve" feature
for (const { modify } of this.executionContextModifiers) { for (const { modify } of this.executionContextModifiers) {
@@ -257,61 +264,37 @@ export class DefaultStateEvaluator implements StateEvaluator {
} }
} }
async onStateUpdate<State>( abstract latestAvailableState<State>(
currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void> {
if (executionContext.evaluationOptions.fcpOptimization && !currentInteraction.dry) {
this.transactionStateCache.put(
`${executionContext.contractDefinition.txId}|${currentInteraction.id}`,
deepCopy(state)
);
}
}
async onNextIteration<State>(
currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>
): Promise<EvalStateResult<State>> {
const cacheKey = `${executionContext.contractDefinition.txId}|${currentInteraction.id}`;
const cachedState = this.transactionStateCache.get(cacheKey);
if (cachedState == null) {
return null;
} else {
return deepCopy(cachedState as EvalStateResult<State>);
}
}
onContractCall<State>(
currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
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);
}
async latestAvailableState<State>(
contractTxId: string, contractTxId: string,
blockHeight: number blockHeight: number
): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null> { ): Promise<BlockHeightCacheResult<EvalStateResult<State>> | null>;
return null;
}
onInternalWriteStateUpdate<State>( abstract onContractCall<State>(
currentInteraction: GQLNodeInterface, transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void>;
abstract onInternalWriteStateUpdate<State>(
transaction: GQLNodeInterface,
contractTxId: string, contractTxId: string,
state: EvalStateResult<State> state: EvalStateResult<State>
): Promise<void> { ): Promise<void>;
return Promise.resolve(undefined);
} abstract onStateEvaluated<State>(
transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void>;
abstract onStateUpdate<State>(
transaction: GQLNodeInterface,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
): Promise<void>;
abstract transactionState<State>(
transaction: GQLNodeInterface,
contractTxId: string
): Promise<EvalStateResult<State> | undefined>;
} }

View File

@@ -15,15 +15,19 @@ export class LexicographicalInteractionsSorter implements InteractionsSorter {
return copy.sort((a, b) => a.sortKey.localeCompare(b.sortKey)); return copy.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
} }
private async addSortKey(txInfo: GQLEdgeInterface) { private async addSortKey(txInfo: GQLEdgeInterface) {
const { node } = txInfo; const { node } = txInfo;
const blockHashBytes = this.arweave.utils.b64UrlToBuffer(node.block.id); txInfo.sortKey = await this.createSortKey(node.block.id, node.id, node.block.height);
const txIdBytes = this.arweave.utils.b64UrlToBuffer(node.id); }
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 concatenated = this.arweave.utils.concatBuffers([blockHashBytes, txIdBytes]);
const hashed = arrayToHex(await this.arweave.crypto.hash(concatenated)); 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}`;
} }
} }

View File

@@ -0,0 +1,3 @@
import { EvalStateResult } from '@smartweave';
export type StateCache<State> = Array<EvalStateResult<State>>;

View File

@@ -8,12 +8,11 @@ import {
import { import {
ContractDefinitionLoader, ContractDefinitionLoader,
ContractInteractionsLoader, ContractInteractionsLoader,
DefaultStateEvaluator,
EvalStateResult,
HandlerExecutorFactory, HandlerExecutorFactory,
LexicographicalInteractionsSorter, LexicographicalInteractionsSorter,
SmartWeave, SmartWeave,
SmartWeaveBuilder SmartWeaveBuilder,
StateCache
} from '@smartweave/core'; } from '@smartweave/core';
import { MemBlockHeightSwCache, MemCache, RemoteBlockHeightCache } from '@smartweave/cache'; import { MemBlockHeightSwCache, MemCache, RemoteBlockHeightCache } from '@smartweave/cache';
@@ -47,7 +46,7 @@ export class SmartWeaveWebFactory {
const stateEvaluator = new CacheableStateEvaluator( const stateEvaluator = new CacheableStateEvaluator(
arweave, arweave,
new RemoteBlockHeightCache<EvalStateResult<unknown>>('STATE', cacheBaseURL), new RemoteBlockHeightCache<StateCache<unknown>>('STATE', cacheBaseURL),
[new Evolve(definitionLoader, executorFactory)] [new Evolve(definitionLoader, executorFactory)]
); );
@@ -84,7 +83,7 @@ export class SmartWeaveWebFactory {
const stateEvaluator = new CacheableStateEvaluator( const stateEvaluator = new CacheableStateEvaluator(
arweave, arweave,
new MemBlockHeightSwCache<EvalStateResult<unknown>>(maxStoredBlockHeights), new MemBlockHeightSwCache<StateCache<unknown>>(maxStoredBlockHeights),
[new Evolve(definitionLoader, executorFactory)] [new Evolve(definitionLoader, executorFactory)]
); );
@@ -97,31 +96,4 @@ export class SmartWeaveWebFactory {
.setExecutorFactory(executorFactory) .setExecutorFactory(executorFactory)
.setStateEvaluator(stateEvaluator); .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);
}
} }

View File

@@ -44,6 +44,7 @@ export interface GQLNodeInterface {
id: string; id: string;
}; };
dry?: boolean; dry?: boolean;
sortKey?: string; //added dynamically by the LexicographicalInteractionsSorter
} }
export interface GQLEdgeInterface { export interface GQLEdgeInterface {

View File

@@ -1,5 +1,5 @@
export * from './CacheableContractInteractionsLoader'; export * from './CacheableContractInteractionsLoader';
export * from './CacheableExecutorFactory'; export * from './CacheableExecutorFactory';
export * from './CacheableStateEvaluator'; export * from '../core/modules/impl/CacheableStateEvaluator';
export * from './DebuggableExecutorFactor'; export * from './DebuggableExecutorFactor';
export * from './Evolve'; export * from './Evolve';

View File

@@ -4,7 +4,7 @@ export const sleep = (ms: number): Promise<void> => {
}; };
export const deepCopy = (input: unknown): any => { 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) => { 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 asc = (a: number, b: number): number => a - b;
export const desc = (a: number, b: number): number => b - a; export const desc = (a: number, b: number): number => b - a;

View File

@@ -66,17 +66,15 @@ async function main() {
}); });
calleeContract = smartweave.contract(calleeTxId).connect(wallet).setEvaluationOptions({ calleeContract = smartweave.contract(calleeTxId).connect(wallet).setEvaluationOptions({
ignoreExceptions: false ignoreExceptions: false,
internalWrites: true,
}); });
callingContract = smartweave.contract(callingTxId).connect(wallet).setEvaluationOptions({ callingContract = smartweave.contract(callingTxId).connect(wallet).setEvaluationOptions({
ignoreExceptions: false ignoreExceptions: false,
internalWrites: true
}); });
await mine(); await mine();
await calleeContract.writeInteraction({ function: 'add' });
await calleeContract.writeInteraction({ function: 'add' });
await mine(); // 102
await calleeContract.writeInteraction({ function: 'add' }); await calleeContract.writeInteraction({ function: 'add' });
await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 }); await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 });
await mine(); // 113 await mine(); // 113
@@ -85,7 +83,6 @@ async function main() {
logger.info('Read state 1', result1.state);*/ logger.info('Read state 1', result1.state);*/
await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 }); await callingContract.writeInteraction({ function: 'writeContract', contractId: calleeTxId, amount: 10 });
await mine(); // 123
await calleeContract.writeInteraction({ function: 'add' }); await calleeContract.writeInteraction({ function: 'add' });
await mine(); //124 await mine(); //124

View File

@@ -5,6 +5,7 @@ import { TsLogFactory } from '../src/logging/node/TsLogFactory';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { ContractInteractionsLoader } from '../src/core/modules/impl/ContractInteractionsLoader'; import { ContractInteractionsLoader } from '../src/core/modules/impl/ContractInteractionsLoader';
import { DefaultEvaluationOptions } from '../../smartweave-loot/.yalc/redstone-smartweave';
async function main() { async function main() {
LoggerFactory.use(new TsLogFactory()); LoggerFactory.use(new TsLogFactory());
@@ -25,7 +26,7 @@ async function main() {
'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY', 'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY',
0, 0,
779820, 779820,
evaluationOptions new DefaultEvaluationOptions()
); );
console.log(result.length); console.log(result.length);