refactor: client api refactor

This commit is contained in:
ppedziwiatr
2021-08-25 12:06:49 +02:00
parent d8fd584a0b
commit 671a97b7ca
31 changed files with 516 additions and 527 deletions

View File

@@ -8,19 +8,19 @@ const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of ExecutorFactory that adds caching capabilities
*/
export class CacheableExecutorFactory<State, Api> implements ExecutorFactory<State, Api> {
export class CacheableExecutorFactory<Api> implements ExecutorFactory<Api> {
constructor(
arweave: Arweave,
private readonly baseImplementation: ExecutorFactory<State, Api>,
private readonly baseImplementation: ExecutorFactory<Api>,
private readonly cache: SwCache<string, Api>
) {}
async create(contractDefinition: ContractDefinition<State>): Promise<Api> {
async create<State>(contractDefinition: ContractDefinition<State>): Promise<Api> {
// warn: do not cache on the contractDefinition.srcTxId. This might look like a good optimisation
// (as many contracts share the same source code), but unfortunately this is causing issues
// with the same SwGlobal object being cached for all contracts with the same source code
// (eg. SwGlobal.contract.id field - which of course should have different value for contracts
// with the same source).
// (eg. SwGlobal.contract.id field - which of course should have different value for different contracts
// that share the same source).
const cacheKey = contractDefinition.txId;
if (!this.cache.contains(cacheKey)) {
logger.debug('Updating executor factory cache');

View File

@@ -15,17 +15,17 @@ const logger = LoggerFactory.INST.create(__filename);
/**
* An implementation of DefaultStateEvaluator that adds caching capabilities
*/
export class CacheableStateEvaluator<State> extends DefaultStateEvaluator<State> {
export class CacheableStateEvaluator extends DefaultStateEvaluator {
constructor(
arweave: Arweave,
private readonly cache: BlockHeightSwCache<EvalStateResult<State>>,
executionContextModifiers: ExecutionContextModifier<State>[] = []
private readonly cache: BlockHeightSwCache<EvalStateResult<unknown>>,
executionContextModifiers: ExecutionContextModifier[] = []
) {
super(arweave, executionContextModifiers);
}
async eval(
executionContext: ExecutionContext<State, any>,
async eval<State, Api>(
executionContext: ExecutionContext<State>,
currentTx: { interactionTxId: string; contractTxId: string }[]
): Promise<EvalStateResult<State>> {
const requestedBlockHeight = executionContext.blockHeight;
@@ -42,7 +42,10 @@ export class CacheableStateEvaluator<State> extends DefaultStateEvaluator<State>
// if there was anything to cache...
if (sortedInteractionsUpToBlock.length > 0) {
// get latest available cache for the requested block height
cachedState = this.cache.getLessOrEqual(executionContext.contractDefinition.txId, requestedBlockHeight);
cachedState = this.cache.getLessOrEqual(
executionContext.contractDefinition.txId,
requestedBlockHeight
) as BlockHeightCacheResult<EvalStateResult<State>>;
if (cachedState != null) {
logger.debug(`Cached state for ${executionContext.contractDefinition.txId}`, {
@@ -101,9 +104,9 @@ export class CacheableStateEvaluator<State> extends DefaultStateEvaluator<State>
);
}
onStateUpdate(
onStateUpdate<State>(
currentInteraction: GQLNodeInterface,
executionContext: ExecutionContext<State, HandlerApi<State>>,
executionContext: ExecutionContext<State>,
state: EvalStateResult<State>
) {
this.cache.put(

View File

@@ -7,15 +7,15 @@ import { ContractDefinition, ExecutorFactory } from '@smartweave/core';
*
* Not meant to be used in production env! ;-)
*/
export class DebuggableExecutorFactory<State, Api> implements ExecutorFactory<State, Api> {
export class DebuggableExecutorFactory<Api> implements ExecutorFactory<Api> {
constructor(
private readonly baseImplementation: ExecutorFactory<State, Api>,
private readonly baseImplementation: ExecutorFactory<Api>,
private readonly sourceCode: { [key: string]: string }
) {
// contract source code before default "normalization"
}
async create(contractDefinition: ContractDefinition<State>): Promise<Api> {
async create<State>(contractDefinition: ContractDefinition<State>): Promise<Api> {
if (Object.prototype.hasOwnProperty.call(this.sourceCode, contractDefinition.txId)) {
contractDefinition = {
...contractDefinition,

View File

@@ -3,13 +3,14 @@ import {
ExecutionContext,
ExecutionContextModifier,
ExecutorFactory,
HandlerApi,
LoggerFactory,
SmartWeaveError,
SmartWeaveErrorType
} from '@smartweave';
export interface EvolveCompatibleState {
settings: never[]; // some..erm..settings?
settings: any[]; // some..erm..settings?
canEvolve: boolean; // whether contract is allowed to evolve. seems to default to true..
evolve: string; // the transaction id of the Arweave transaction with the updated source code. odd naming convention..
}
@@ -34,21 +35,32 @@ without the need of hard-coding contract's txId in the client's source code.
This also makes it easier to audit given contract - as you keep all its versions in one place.
*/
export class Evolve<State extends EvolveCompatibleState, Api> implements ExecutionContextModifier<State> {
function isEvolveCompatible(state: any): state is EvolveCompatibleState {
const settings =
state.settings && isIterable(state.settings) ? new Map<string, any>(state.settings) : new Map<string, any>();
return state.evolve !== undefined || settings.has('evolve');
}
export class Evolve implements ExecutionContextModifier {
constructor(
private readonly definitionLoader: DefinitionLoader<State>,
private readonly executorFactory: ExecutorFactory<State, Api>
private readonly definitionLoader: DefinitionLoader,
private readonly executorFactory: ExecutorFactory<HandlerApi<unknown>>
) {
this.modify = this.modify.bind(this);
}
async modify(state: State, executionContext: ExecutionContext<State, Api>): Promise<ExecutionContext<State, Api>> {
async modify<State>(state: State, executionContext: ExecutionContext<State>): Promise<ExecutionContext<State>> {
const contractTxId = executionContext.contractDefinition.txId;
logger.debug(`trying to evolve for: ${contractTxId}`);
if (!isEvolveCompatible(state)) {
logger.verbose('State is not evolve compatible');
return executionContext;
}
const currentSrcTxId = executionContext.contractDefinition.srcTxId;
const settings =
state.settings && isIterable(state.settings) ? new Map<string, never>(state.settings) : new Map<string, never>();
state.settings && isIterable(state.settings) ? new Map<string, any>(state.settings) : new Map<string, any>();
// note: from my understanding - this variable holds the id of the transaction with updated source code.
const evolve: string = state.evolve || settings.get('evolve');
@@ -68,9 +80,9 @@ export class Evolve<State extends EvolveCompatibleState, Api> implements Executi
if (currentSrcTxId !== evolve) {
try {
// note: that's really nasty IMO - loading original contract definition, but forcing different sourceTxId...
logger.info(`Evolving to: ${evolve}`);
const newContractDefinition = await this.definitionLoader.load(contractTxId, evolve);
const newHandler = await this.executorFactory.create(newContractDefinition);
logger.info('Evolving to: ', evolve);
const newContractDefinition = await this.definitionLoader.load<State>(contractTxId, evolve);
const newHandler = await this.executorFactory.create<State>(newContractDefinition);
const modifiedContext = {
...executionContext,