feat: generate 'stacktrace' from all the contract interactions #21
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
fb0b108b29
commit
dc0191edd0
@@ -7,6 +7,8 @@ import {
|
||||
InteractionTx,
|
||||
Tags
|
||||
} from '@smartweave';
|
||||
import { NetworkInfoInterface } from 'arweave/node/network';
|
||||
import { ContractCallStack } from '../core/ContractCallStack';
|
||||
|
||||
/**
|
||||
* A base interface to be implemented by SmartWeave Contracts clients
|
||||
@@ -100,4 +102,8 @@ export interface Contract<State = unknown> {
|
||||
* @param transfer - additional {@link ArTransfer} than can be attached to the interaction transaction
|
||||
*/
|
||||
writeInteraction<Input = unknown>(input: Input, tags?: Tags, transfer?: ArTransfer): Promise<string | null>;
|
||||
|
||||
getCallStack(): ContractCallStack;
|
||||
|
||||
getNetworkInfo(): NetworkInfoInterface;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
EvaluationOptions,
|
||||
ExecutionContext,
|
||||
HandlerApi,
|
||||
InteractionData,
|
||||
InteractionResult,
|
||||
InteractionTx,
|
||||
LoggerFactory,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
} from '@smartweave';
|
||||
import { TransactionStatusResponse } from 'arweave/node/transactions';
|
||||
import { NetworkInfoInterface } from 'arweave/node/network';
|
||||
import { ContractCallStack, InteractionCall } from '../core/ContractCallStack';
|
||||
|
||||
/**
|
||||
* An implementation of {@link Contract} that is backwards compatible with current style
|
||||
@@ -30,10 +32,7 @@ import { NetworkInfoInterface } from 'arweave/node/network';
|
||||
export class HandlerBasedContract<State> implements Contract<State> {
|
||||
private readonly logger = LoggerFactory.INST.create('HandlerBasedContract');
|
||||
|
||||
/**
|
||||
* wallet connected to this contract
|
||||
*/
|
||||
protected wallet?: ArWallet;
|
||||
private callStack: ContractCallStack;
|
||||
private evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions();
|
||||
|
||||
/**
|
||||
@@ -41,45 +40,44 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
* Only the 'root' contract call should read this data from Arweave - all the inner calls ("child" contracts)
|
||||
* should reuse this data from the parent ("calling) contract.
|
||||
*/
|
||||
public networkInfo?: NetworkInfoInterface = null;
|
||||
private networkInfo?: NetworkInfoInterface = null;
|
||||
|
||||
/**
|
||||
* wallet connected to this contract
|
||||
*/
|
||||
protected wallet?: ArWallet;
|
||||
|
||||
constructor(
|
||||
readonly contractTxId: string,
|
||||
protected readonly smartweave: SmartWeave,
|
||||
// note: this will be probably used for creating contract's
|
||||
// call hierarchy and generating some sort of "stack trace"
|
||||
private readonly callingContract: Contract = null
|
||||
private readonly callingContract: Contract = null,
|
||||
private readonly callingInteraction: InteractionTx = null
|
||||
) {
|
||||
this.waitForConfirmation = this.waitForConfirmation.bind(this);
|
||||
if (callingContract != null) {
|
||||
this.networkInfo = (callingContract as HandlerBasedContract<State>).networkInfo;
|
||||
|
||||
this.networkInfo = callingContract.getNetworkInfo();
|
||||
//callingContract.getCallStack().
|
||||
// sanity-check...
|
||||
if (this.networkInfo == null) {
|
||||
throw Error('Calling contract should have the network info already set!');
|
||||
}
|
||||
const interaction: InteractionCall = callingContract.getCallStack().getInteraction(callingInteraction.id);
|
||||
const callStack = new ContractCallStack(contractTxId);
|
||||
interaction.interactionInput.foreignContractCalls.set(contractTxId, callStack);
|
||||
this.callStack = callStack;
|
||||
} else {
|
||||
this.callStack = new ContractCallStack(contractTxId);
|
||||
}
|
||||
}
|
||||
|
||||
connect(wallet: ArWallet): Contract<State> {
|
||||
this.wallet = wallet;
|
||||
return this;
|
||||
}
|
||||
|
||||
setEvaluationOptions(options: Partial<EvaluationOptions>): Contract<State> {
|
||||
this.evaluationOptions = {
|
||||
...this.evaluationOptions,
|
||||
...options
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
async readState(
|
||||
blockHeight?: number,
|
||||
currentTx?: { interactionTxId: string; contractTxId: string }[]
|
||||
): Promise<EvalStateResult<State>> {
|
||||
this.logger.info('Read state for', this.contractTxId);
|
||||
this.maybeClearNetworkInfo();
|
||||
this.maybeClear();
|
||||
|
||||
const { stateEvaluator } = this.smartweave;
|
||||
const benchmark = Benchmark.measure();
|
||||
@@ -103,7 +101,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
transfer: ArTransfer = emptyTransfer
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info('View state for', this.contractTxId);
|
||||
this.maybeClearNetworkInfo();
|
||||
this.maybeClear();
|
||||
if (!this.wallet) {
|
||||
this.logger.warn('Wallet not set.');
|
||||
}
|
||||
@@ -147,11 +145,9 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
// creating a real transaction, with multiple calls to Arweave, seems like a huge waste.
|
||||
|
||||
// call one of the contract's view method
|
||||
const handleResult = await executionContext.handler.handle<Input, View>(
|
||||
executionContext,
|
||||
evalStateResult,
|
||||
const handleResult = await executionContext.handler.handle<Input, View>(executionContext, evalStateResult, {
|
||||
interaction,
|
||||
{
|
||||
interactionTx: {
|
||||
id: null,
|
||||
recipient: transfer.target,
|
||||
owner: {
|
||||
@@ -164,8 +160,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
},
|
||||
block: executionContext.currentBlockData
|
||||
},
|
||||
[]
|
||||
);
|
||||
currentTx: []
|
||||
});
|
||||
|
||||
if (handleResult.type !== 'ok') {
|
||||
this.logger.fatal('Error while interacting with contract', {
|
||||
@@ -177,12 +173,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
return handleResult;
|
||||
}
|
||||
|
||||
async viewStateForTx<Input, View>(input: Input, transaction: InteractionTx): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info(`Vies state for ${this.contractTxId}`, transaction);
|
||||
this.maybeClearNetworkInfo();
|
||||
async viewStateForTx<Input, View>(
|
||||
input: Input,
|
||||
interactionTx: InteractionTx
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info(`Vies state for ${this.contractTxId}`, interactionTx);
|
||||
this.maybeClear();
|
||||
const { stateEvaluator } = this.smartweave;
|
||||
|
||||
const executionContext = await this.createExecutionContextFromTx(this.contractTxId, transaction);
|
||||
const executionContext = await this.createExecutionContextFromTx(this.contractTxId, interactionTx);
|
||||
const evalStateResult = await stateEvaluator.eval<State>(executionContext, []);
|
||||
|
||||
const interaction: ContractInteraction<Input> = {
|
||||
@@ -190,13 +189,11 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
caller: executionContext.caller
|
||||
};
|
||||
|
||||
return await executionContext.handler.handle<Input, View>(
|
||||
executionContext,
|
||||
evalStateResult,
|
||||
return await executionContext.handler.handle<Input, View>(executionContext, evalStateResult, {
|
||||
interaction,
|
||||
transaction,
|
||||
[]
|
||||
);
|
||||
interactionTx,
|
||||
currentTx: []
|
||||
});
|
||||
}
|
||||
|
||||
async writeInteraction<Input>(
|
||||
@@ -207,7 +204,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
if (!this.wallet) {
|
||||
throw new Error("Wallet not connected. Use 'connect' method first.");
|
||||
}
|
||||
this.maybeClearNetworkInfo();
|
||||
this.maybeClear();
|
||||
const { arweave } = this.smartweave;
|
||||
|
||||
const interactionTx = await createTx(
|
||||
@@ -346,14 +343,36 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
};
|
||||
}
|
||||
|
||||
private maybeClearNetworkInfo() {
|
||||
private maybeClear() {
|
||||
if (this.callingContract == null) {
|
||||
this.logger.debug('Clearing network info for the root contract');
|
||||
this.logger.debug('Clearing network info and call stack for the root contract');
|
||||
this.networkInfo = null;
|
||||
this.callStack = new ContractCallStack(this.txId());
|
||||
}
|
||||
}
|
||||
|
||||
txId(): string {
|
||||
return this.contractTxId;
|
||||
}
|
||||
|
||||
getCallStack(): ContractCallStack {
|
||||
return this.callStack;
|
||||
}
|
||||
|
||||
getNetworkInfo(): NetworkInfoInterface {
|
||||
return this.networkInfo;
|
||||
}
|
||||
|
||||
connect(wallet: ArWallet): Contract<State> {
|
||||
this.wallet = wallet;
|
||||
return this;
|
||||
}
|
||||
|
||||
setEvaluationOptions(options: Partial<EvaluationOptions>): Contract<State> {
|
||||
this.evaluationOptions = {
|
||||
...this.evaluationOptions,
|
||||
...options
|
||||
};
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
67
src/core/ContractCallStack.ts
Normal file
67
src/core/ContractCallStack.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { InteractionData } from '@smartweave';
|
||||
|
||||
export class ContractCallStack {
|
||||
readonly interactions: Map<string, InteractionCall> = new Map();
|
||||
|
||||
constructor(public readonly contractTxId: string, public readonly label: string = '') {}
|
||||
|
||||
addInteractionData(interactionData: InteractionData<any>): InteractionCall {
|
||||
const { interaction, interactionTx } = interactionData;
|
||||
|
||||
const interactionCall = InteractionCall.create(
|
||||
new InteractionInput(
|
||||
interactionTx.id,
|
||||
interactionTx.block.height,
|
||||
interactionTx.block.timestamp,
|
||||
interaction.caller,
|
||||
interaction.input.function,
|
||||
interaction.input,
|
||||
new Map()
|
||||
)
|
||||
);
|
||||
|
||||
this.interactions.set(interactionTx.id, interactionCall);
|
||||
|
||||
return interactionCall;
|
||||
}
|
||||
|
||||
getInteraction(txId: string) {
|
||||
return this.interactions.get(txId);
|
||||
}
|
||||
}
|
||||
|
||||
export class InteractionCall {
|
||||
interactionOutput: InteractionOutput;
|
||||
private constructor(readonly interactionInput: InteractionInput) {}
|
||||
|
||||
static create(interactionInput: InteractionInput): InteractionCall {
|
||||
return new InteractionCall(interactionInput);
|
||||
}
|
||||
|
||||
update(interactionOutput: InteractionOutput) {
|
||||
this.interactionOutput = interactionOutput;
|
||||
}
|
||||
}
|
||||
|
||||
export class InteractionInput {
|
||||
constructor(
|
||||
public readonly txId: string,
|
||||
public readonly blockHeight: number,
|
||||
public readonly blockTimestamp: number,
|
||||
public readonly caller: string,
|
||||
public readonly functionName: string,
|
||||
public readonly functionArguments: [],
|
||||
public readonly foreignContractCalls: Map<string, ContractCallStack> = new Map()
|
||||
) {}
|
||||
}
|
||||
|
||||
export class InteractionOutput {
|
||||
constructor(
|
||||
public readonly cacheHit: boolean,
|
||||
public readonly intermediaryCacheHit: boolean,
|
||||
public readonly outputState: any,
|
||||
public readonly executionTime: number,
|
||||
public readonly valid: boolean,
|
||||
public readonly errorMessage: string = ''
|
||||
) {}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@smartweave/core';
|
||||
import Arweave from 'arweave';
|
||||
import { Contract, HandlerBasedContract, PstContract, PstContractImpl } from '@smartweave/contract';
|
||||
import { InteractionTx } from '@smartweave/legacy';
|
||||
|
||||
/**
|
||||
* The SmartWeave "motherboard" ;-).
|
||||
@@ -43,8 +44,8 @@ export class SmartWeave {
|
||||
* @param contractTxId
|
||||
* @param callingContract
|
||||
*/
|
||||
contract<State>(contractTxId: string, callingContract?: Contract): Contract<State> {
|
||||
return new HandlerBasedContract<State>(contractTxId, this, callingContract);
|
||||
contract<State>(contractTxId: string, callingContract?: Contract, callingInteraction?: InteractionTx): Contract<State> {
|
||||
return new HandlerBasedContract<State>(contractTxId, this, callingContract, callingInteraction);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,6 +30,14 @@ export interface StateEvaluator {
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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<State>(
|
||||
currentInteraction: InteractionTx,
|
||||
@@ -55,6 +63,12 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
|
||||
fcpOptimization = false;
|
||||
|
||||
updateCacheForEachInteraction = true;
|
||||
|
||||
enhancedValidity = false;
|
||||
|
||||
stackTrace = {
|
||||
saveState: false
|
||||
}
|
||||
}
|
||||
|
||||
// an interface for the contract EvaluationOptions - can be used to change the behaviour of some of the features.
|
||||
@@ -73,4 +87,13 @@ export interface EvaluationOptions {
|
||||
// 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;
|
||||
|
||||
// enhanced validity report with error/exception messages included
|
||||
enhancedValidity: boolean;
|
||||
|
||||
// a set of options that control the behaviour of the stack trace generator
|
||||
stackTrace: {
|
||||
// whether output state should be saved for each interaction in the stack trace (may result in huuuuge json files!)
|
||||
saveState: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
TagsParser
|
||||
} from '@smartweave';
|
||||
import Arweave from 'arweave';
|
||||
import { InteractionCall } from '../../ContractCallStack';
|
||||
|
||||
// FIXME: currently this is tightly coupled with the HandlerApi
|
||||
export class DefaultStateEvaluator implements StateEvaluator {
|
||||
@@ -50,8 +51,8 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
||||
currentTx: { interactionTxId: string; contractTxId: string }[]
|
||||
): Promise<EvalStateResult<State>> {
|
||||
const stateEvaluationBenchmark = Benchmark.measure();
|
||||
const { ignoreExceptions } = executionContext.evaluationOptions;
|
||||
const { contractDefinition, sortedInteractions } = executionContext;
|
||||
const { ignoreExceptions, stackTrace } = executionContext.evaluationOptions;
|
||||
const { contract, contractDefinition, sortedInteractions } = executionContext;
|
||||
|
||||
let currentState = baseState.state;
|
||||
let validity = deepCopy(baseState.validity);
|
||||
@@ -61,9 +62,12 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
||||
);
|
||||
|
||||
let lastEvaluatedInteraction = null;
|
||||
let errorMessage = null;
|
||||
|
||||
for (const missingInteraction of missingInteractions) {
|
||||
const currentInteraction: GQLNodeInterface = missingInteraction.node;
|
||||
const singleInteractionBenchmark = Benchmark.measure();
|
||||
|
||||
const interactionTx: GQLNodeInterface = missingInteraction.node;
|
||||
|
||||
this.logger.debug(
|
||||
`[${contractDefinition.txId}][${missingInteraction.node.id}][${missingInteraction.node.block.height}]: ${
|
||||
@@ -71,46 +75,53 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
||||
}/${missingInteractions.length} [of all:${sortedInteractions.length}]`
|
||||
);
|
||||
|
||||
const state = await this.onNextIteration(currentInteraction, executionContext);
|
||||
if (state !== null) {
|
||||
this.logger.debug('Found in cache');
|
||||
currentState = state.state;
|
||||
validity = state.validity;
|
||||
} else {
|
||||
const singleInteractionBenchmark = Benchmark.measure();
|
||||
|
||||
const inputTag = this.tagsParser.getInputTag(missingInteraction, contractDefinition.txId);
|
||||
const state = await this.onNextIteration(interactionTx, executionContext);
|
||||
const inputTag = this.tagsParser.getInputTag(missingInteraction, executionContext.contractDefinition.txId);
|
||||
if (!inputTag) {
|
||||
this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
|
||||
this.logger.error(`Skipping tx - Input tag not found for ${interactionTx.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const input = this.parseInput(inputTag);
|
||||
if (!input) {
|
||||
this.logger.error(`Skipping tx with missing or invalid Input tag - ${currentInteraction.id}`);
|
||||
this.logger.error(`Skipping tx - invalid Input tag - ${interactionTx.id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const interaction: ContractInteraction<unknown> = {
|
||||
input,
|
||||
caller: currentInteraction.owner.address
|
||||
caller: interactionTx.owner.address
|
||||
};
|
||||
|
||||
let intermediaryCacheHit = false;
|
||||
|
||||
const interactionData = {
|
||||
interaction,
|
||||
interactionTx,
|
||||
currentTx
|
||||
};
|
||||
|
||||
const interactionCall: InteractionCall = contract.getCallStack().addInteractionData(interactionData);
|
||||
|
||||
if (state !== null) {
|
||||
this.logger.debug('Found in intermediary cache');
|
||||
intermediaryCacheHit = true;
|
||||
currentState = state.state;
|
||||
validity = state.validity;
|
||||
} else {
|
||||
const result = await executionContext.handler.handle(
|
||||
executionContext,
|
||||
new EvalStateResult(currentState, validity),
|
||||
interaction,
|
||||
currentInteraction,
|
||||
currentTx
|
||||
interactionData
|
||||
);
|
||||
errorMessage = result.errorMessage;
|
||||
|
||||
this.logResult<State>(result, currentInteraction, executionContext);
|
||||
this.logResult<State>(result, interactionTx, executionContext);
|
||||
|
||||
if (result.type === 'exception' && ignoreExceptions !== true) {
|
||||
throw new Error(`Exception while processing ${JSON.stringify(interaction)}:\n${result.result}`);
|
||||
}
|
||||
|
||||
validity[currentInteraction.id] = result.type === 'ok';
|
||||
validity[interactionTx.id] = result.type === 'ok';
|
||||
// strangely - state is for some reason modified for some contracts (eg. YLVpmhSq5JmLltfg6R-5fL04rIRPrlSU22f6RQ6VyYE)
|
||||
// when calling any async (even simple timeout) function here...
|
||||
// that's (ie. deepCopy) a dumb workaround for this issue
|
||||
@@ -119,16 +130,21 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
||||
|
||||
// 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;
|
||||
lastEvaluatedInteraction = interactionTx;
|
||||
|
||||
this.logger.debug('Interaction evaluation', singleInteractionBenchmark.elapsed());
|
||||
}
|
||||
|
||||
await this.onStateUpdate<State>(
|
||||
currentInteraction,
|
||||
executionContext,
|
||||
new EvalStateResult(currentState, validity)
|
||||
);
|
||||
interactionCall.update({
|
||||
cacheHit: false,
|
||||
intermediaryCacheHit,
|
||||
outputState: stackTrace.saveState ? currentState : undefined,
|
||||
executionTime: singleInteractionBenchmark.elapsed(true) as number,
|
||||
valid: validity[interactionTx.id],
|
||||
errorMessage: errorMessage
|
||||
});
|
||||
|
||||
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
|
||||
// implement the "evolve" feature
|
||||
@@ -137,15 +153,15 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
||||
}
|
||||
}
|
||||
this.logger.debug('State evaluation total:', stateEvaluationBenchmark.elapsed());
|
||||
const result = new EvalStateResult<State>(currentState, validity);
|
||||
const evalStateResult = 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);
|
||||
await this.onStateEvaluated(lastEvaluatedInteraction, executionContext, evalStateResult);
|
||||
}
|
||||
|
||||
return result;
|
||||
return evalStateResult;
|
||||
}
|
||||
|
||||
private logResult<State>(
|
||||
|
||||
@@ -11,6 +11,13 @@ import {
|
||||
LoggerFactory,
|
||||
SmartWeaveGlobal
|
||||
} from '@smartweave';
|
||||
import { InteractionCall } from '../../ContractCallStack';
|
||||
|
||||
export interface InteractionData<Input> {
|
||||
interaction: ContractInteraction<Input>,
|
||||
interactionTx: InteractionTx,
|
||||
currentTx: { interactionTxId: string; contractTxId: string }[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A handle that effectively runs contract's code.
|
||||
@@ -19,9 +26,7 @@ export interface HandlerApi<State> {
|
||||
handle<Input, Result>(
|
||||
executionContext: ExecutionContext<State>,
|
||||
currentResult: EvalStateResult<State>,
|
||||
interaction: ContractInteraction<Input>,
|
||||
interactionTx: InteractionTx,
|
||||
currentTx: { interactionTxId: string; contractTxId: string }[]
|
||||
interactionData: InteractionData<Input>,
|
||||
): Promise<InteractionResult<State, Result>>;
|
||||
}
|
||||
|
||||
@@ -56,11 +61,11 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
|
||||
async handle<Input, Result>(
|
||||
executionContext: ExecutionContext<State>,
|
||||
currentResult: EvalStateResult<State>,
|
||||
interaction: ContractInteraction<Input>,
|
||||
interactionTx: InteractionTx,
|
||||
currentTx: { interactionTxId: string; contractTxId: string }[]
|
||||
interactionData: InteractionData<Input>
|
||||
): Promise<InteractionResult<State, Result>> {
|
||||
try {
|
||||
const { interaction, interactionTx, currentTx } = interactionData;
|
||||
|
||||
const handler = contractFunction(swGlobal, BigNumber, clarity, contractLogger) as HandlerFunction<
|
||||
State,
|
||||
Input,
|
||||
@@ -77,7 +82,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
|
||||
executionContext,
|
||||
currentTx,
|
||||
currentResult,
|
||||
interactionTx
|
||||
interactionTx,
|
||||
);
|
||||
self.assignViewContractState<Input, State>(swGlobal, contractDefinition, executionContext);
|
||||
|
||||
@@ -157,7 +162,7 @@ export class HandlerExecutorFactory implements ExecutorFactory<HandlerApi<unknow
|
||||
|
||||
const { stateEvaluator } = executionContext.smartweave;
|
||||
const childContract = executionContext.smartweave
|
||||
.contract(contractTxId, executionContext.contract)
|
||||
.contract(contractTxId, executionContext.contract, interactionTx)
|
||||
.setEvaluationOptions(executionContext.evaluationOptions);
|
||||
|
||||
await stateEvaluator.onContractCall(interactionTx, executionContext, currentResult);
|
||||
@@ -220,7 +225,7 @@ export type HandlerResult<State, Result> = {
|
||||
};
|
||||
|
||||
export type InteractionResult<State, Result> = HandlerResult<State, Result> & {
|
||||
type: 'ok' | 'error' | 'exception';
|
||||
type: InteractionResultType;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
@@ -228,3 +233,6 @@ export type ContractInteraction<Input> = {
|
||||
input: Input;
|
||||
caller: string;
|
||||
};
|
||||
|
||||
export type InteractionResultType = 'ok' | 'error' | 'exception';
|
||||
|
||||
|
||||
@@ -8,14 +8,19 @@ export class Benchmark {
|
||||
}
|
||||
|
||||
private start = Date.now();
|
||||
private end = null;
|
||||
|
||||
public reset() {
|
||||
this.start = Date.now();
|
||||
this.end = null;
|
||||
}
|
||||
|
||||
public elapsed(rawValue = false): string | number {
|
||||
const end = Date.now();
|
||||
const result = end - this.start;
|
||||
return rawValue ? result : `${(end - this.start).toFixed(0)}ms`;
|
||||
if (this.end === null) {
|
||||
this.end = Date.now();
|
||||
}
|
||||
|
||||
const result = this.end - this.start;
|
||||
return rawValue ? result : `${(this.end - this.start).toFixed(0)}ms`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,3 +5,14 @@ export const sleep = (ms: number) => {
|
||||
export const deepCopy = (input: unknown) => {
|
||||
return JSON.parse(JSON.stringify(input));
|
||||
};
|
||||
|
||||
export const mapReplacer = (key: unknown, value: unknown) => {
|
||||
if (value instanceof Map) {
|
||||
return {
|
||||
dataType: 'Map',
|
||||
value: Array.from(value.entries())
|
||||
};
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
39
tools/call-stack.ts
Normal file
39
tools/call-stack.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import Arweave from 'arweave';
|
||||
import { LoggerFactory, mapReplacer } from '../src';
|
||||
import { TsLogFactory } from '../src/logging/node/TsLogFactory';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { SmartWeaveWebFactory } from '../src/core/web/SmartWeaveWebFactory';
|
||||
|
||||
async function main() {
|
||||
LoggerFactory.use(new TsLogFactory());
|
||||
LoggerFactory.INST.logLevel('debug');
|
||||
|
||||
const arweave = Arweave.init({
|
||||
host: 'arweave.net', // Hostname or IP address for a Arweave host
|
||||
port: 443, // Port
|
||||
protocol: 'https', // Network protocol http or https
|
||||
timeout: 60000, // Network request timeouts in milliseconds
|
||||
logging: false // Enable network request logging
|
||||
});
|
||||
|
||||
const contractTxId = 'LppT1p3wri4FCKzW5buohsjWxpJHC58_rgIO-rYTMB8';
|
||||
|
||||
const smartweave = SmartWeaveWebFactory.memCached(arweave);
|
||||
|
||||
const contract = smartweave.contract(contractTxId)
|
||||
.setEvaluationOptions({
|
||||
ignoreExceptions: false,
|
||||
stackTrace: {
|
||||
saveState: false
|
||||
}
|
||||
});
|
||||
|
||||
const { state, validity } = await contract.readState();
|
||||
|
||||
const callStack = contract.getCallStack();
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, 'data', 'call_stack.json'), JSON.stringify(callStack, mapReplacer));
|
||||
}
|
||||
|
||||
main().catch((e) => console.error(e));
|
||||
22020
tools/data/call_stack.json
Normal file
22020
tools/data/call_stack.json
Normal file
File diff suppressed because it is too large
Load Diff
1005
tools/data/state_broken_tx.json
Normal file
1005
tools/data/state_broken_tx.json
Normal file
File diff suppressed because it is too large
Load Diff
1
tools/data/state_new.json
Normal file
1
tools/data/state_new.json
Normal file
File diff suppressed because one or more lines are too long
1005
tools/data/viewblock-resync.json
Normal file
1005
tools/data/viewblock-resync.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user