feat: maxCallDepth implemented
This commit is contained in:
committed by
Piotr Pędziwiatr
parent
aeb8dc0be6
commit
d63ab29129
@@ -119,7 +119,7 @@ describe('Testing internal writes', () => {
|
|||||||
contractA = smartweave
|
contractA = smartweave
|
||||||
.contract(contractATxId)
|
.contract(contractATxId)
|
||||||
.setEvaluationOptions({
|
.setEvaluationOptions({
|
||||||
internalWrites: true
|
internalWrites: true,
|
||||||
})
|
})
|
||||||
.connect(wallet);
|
.connect(wallet);
|
||||||
contractB = smartweave
|
contractB = smartweave
|
||||||
@@ -271,6 +271,56 @@ describe('Testing internal writes', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('with different maxDepths', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await deployContracts();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly evaluate contractC state for maxDepth = 3', async () => {
|
||||||
|
contractC.setEvaluationOptions({
|
||||||
|
maxCallDepth: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
await contractB.writeInteraction({ function: 'add' });
|
||||||
|
await contractB.writeInteraction({ function: 'add' });
|
||||||
|
await contractC.writeInteraction({ function: 'add' });
|
||||||
|
await mine();
|
||||||
|
|
||||||
|
await contractA.writeInteraction({
|
||||||
|
function: 'writeInDepth',
|
||||||
|
contractId1: contractBTxId,
|
||||||
|
contractId2: contractCTxId,
|
||||||
|
amount: 10
|
||||||
|
});
|
||||||
|
await mine();
|
||||||
|
|
||||||
|
expect((await contractC.readState()).state.counter).toEqual(231);
|
||||||
|
expect((await contractC.readState()).state.counter).toEqual(231);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when evaluating ContractC state for maxDepth = 2', async () => {
|
||||||
|
contractC.setEvaluationOptions({
|
||||||
|
maxCallDepth: 2,
|
||||||
|
ignoreExceptions: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await contractB.writeInteraction({ function: 'add' });
|
||||||
|
await contractB.writeInteraction({ function: 'add' });
|
||||||
|
await contractC.writeInteraction({ function: 'add' });
|
||||||
|
await mine();
|
||||||
|
|
||||||
|
await contractA.writeInteraction({
|
||||||
|
function: 'writeInDepth',
|
||||||
|
contractId1: contractBTxId,
|
||||||
|
contractId2: contractCTxId,
|
||||||
|
amount: 10
|
||||||
|
});
|
||||||
|
await mine();
|
||||||
|
|
||||||
|
await expect(contractC.readState()).rejects.toThrow(/(.)*Error: Max call depth(.*)/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
async function mine() {
|
async function mine() {
|
||||||
await arweave.api.get('mine');
|
await arweave.api.get('mine');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,4 +115,10 @@ export interface Contract<State = unknown> {
|
|||||||
getNetworkInfo(): NetworkInfoInterface;
|
getNetworkInfo(): NetworkInfoInterface;
|
||||||
|
|
||||||
getRootBlockHeight(): number | null;
|
getRootBlockHeight(): number | null;
|
||||||
|
|
||||||
|
parent(): Contract | null;
|
||||||
|
|
||||||
|
callDepth(): number;
|
||||||
|
|
||||||
|
evaluationOptions(): EvaluationOptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,19 +37,21 @@ import { NetworkInfoInterface } from 'arweave/node/network';
|
|||||||
export class HandlerBasedContract<State> implements Contract<State> {
|
export class HandlerBasedContract<State> implements Contract<State> {
|
||||||
private readonly logger = LoggerFactory.INST.create('HandlerBasedContract');
|
private readonly logger = LoggerFactory.INST.create('HandlerBasedContract');
|
||||||
|
|
||||||
private callStack: ContractCallStack;
|
private _callStack: ContractCallStack;
|
||||||
private evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions();
|
private _evaluationOptions: EvaluationOptions = new DefaultEvaluationOptions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* current Arweave networkInfo that will be used for all operations of the SmartWeave protocol.
|
* current Arweave networkInfo that will be used for all operations of the SmartWeave protocol.
|
||||||
* Only the 'root' contract call should read this data from Arweave - all the inner calls ("child" contracts)
|
* 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.
|
* should reuse this data from the parent ("calling") contract.
|
||||||
*/
|
*/
|
||||||
private networkInfo?: NetworkInfoInterface = null;
|
private _networkInfo?: NetworkInfoInterface = null;
|
||||||
|
|
||||||
private rootBlockHeight: number = null;
|
private _rootBlockHeight: number = null;
|
||||||
|
|
||||||
private readonly innerWritesEvaluator = new InnerWritesEvaluator();
|
private readonly _innerWritesEvaluator = new InnerWritesEvaluator();
|
||||||
|
|
||||||
|
private readonly _callDepth: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* wallet connected to this contract
|
* wallet connected to this contract
|
||||||
@@ -57,39 +59,56 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
protected wallet?: ArWallet;
|
protected wallet?: ArWallet;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly contractTxId: string,
|
private readonly _contractTxId: string,
|
||||||
protected readonly smartweave: SmartWeave,
|
protected readonly smartweave: SmartWeave,
|
||||||
private readonly callingContract: Contract = null,
|
private readonly _parentContract: Contract = null,
|
||||||
private readonly callingInteraction: GQLNodeInterface = null
|
private readonly _callingInteraction: GQLNodeInterface = null
|
||||||
) {
|
) {
|
||||||
this.waitForConfirmation = this.waitForConfirmation.bind(this);
|
this.waitForConfirmation = this.waitForConfirmation.bind(this);
|
||||||
if (callingContract != null) {
|
if (_parentContract != null) {
|
||||||
this.networkInfo = callingContract.getNetworkInfo();
|
this._networkInfo = _parentContract.getNetworkInfo();
|
||||||
this.rootBlockHeight = callingContract.getRootBlockHeight();
|
this._rootBlockHeight = _parentContract.getRootBlockHeight();
|
||||||
|
this._evaluationOptions = _parentContract.evaluationOptions();
|
||||||
|
this._callDepth = _parentContract.callDepth() + 1;
|
||||||
|
const interaction: InteractionCall = _parentContract.getCallStack().getInteraction(_callingInteraction.id);
|
||||||
|
|
||||||
|
console.log('Call depth', {
|
||||||
|
callDepth: this._callDepth,
|
||||||
|
max: this._evaluationOptions.maxCallDepth,
|
||||||
|
options: this._evaluationOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._callDepth > this._evaluationOptions.maxCallDepth) {
|
||||||
|
throw Error(
|
||||||
|
`Max call depth of ${this._evaluationOptions.maxCallDepth} has been exceeded for interaction ${JSON.stringify(
|
||||||
|
interaction.interactionInput
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
// sanity-check...
|
// sanity-check...
|
||||||
if (this.networkInfo == null) {
|
if (this._networkInfo == null) {
|
||||||
throw Error('Calling contract should have the network info already set!');
|
throw Error('Calling contract should have the network info already set!');
|
||||||
}
|
}
|
||||||
this.logger.debug('Calling interaction id', callingInteraction.id);
|
this.logger.debug('Calling interaction id', _callingInteraction.id);
|
||||||
const interaction: InteractionCall = callingContract.getCallStack().getInteraction(callingInteraction.id);
|
const callStack = new ContractCallStack(_contractTxId, this._callDepth);
|
||||||
const callStack = new ContractCallStack(contractTxId);
|
interaction.interactionInput.foreignContractCalls.set(_contractTxId, callStack);
|
||||||
interaction.interactionInput.foreignContractCalls.set(contractTxId, callStack);
|
this._callStack = callStack;
|
||||||
this.callStack = callStack;
|
|
||||||
} else {
|
} else {
|
||||||
this.callStack = new ContractCallStack(contractTxId);
|
this._callDepth = 0;
|
||||||
|
this._callStack = new ContractCallStack(_contractTxId, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readState(blockHeight?: number, currentTx?: CurrentTx[]): Promise<EvalStateResult<State>> {
|
async readState(blockHeight?: number, currentTx?: CurrentTx[]): Promise<EvalStateResult<State>> {
|
||||||
this.logger.info('Read state for', {
|
this.logger.info('Read state for', {
|
||||||
contractTxId: this.contractTxId,
|
contractTxId: this._contractTxId,
|
||||||
currentTx
|
currentTx
|
||||||
});
|
});
|
||||||
this.maybeResetRootContract(blockHeight);
|
this.maybeResetRootContract(blockHeight);
|
||||||
|
|
||||||
const { stateEvaluator } = this.smartweave;
|
const { stateEvaluator } = this.smartweave;
|
||||||
const benchmark = Benchmark.measure();
|
const benchmark = Benchmark.measure();
|
||||||
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,
|
||||||
@@ -109,7 +128,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
tags: Tags = [],
|
tags: Tags = [],
|
||||||
transfer: ArTransfer = emptyTransfer
|
transfer: ArTransfer = emptyTransfer
|
||||||
): Promise<InteractionResult<State, View>> {
|
): Promise<InteractionResult<State, View>> {
|
||||||
this.logger.info('View state for', this.contractTxId);
|
this.logger.info('View state for', this._contractTxId);
|
||||||
return await this.callContract<Input, View>(input, blockHeight, tags, transfer);
|
return await this.callContract<Input, View>(input, blockHeight, tags, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,12 +136,12 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
input: Input,
|
input: Input,
|
||||||
interactionTx: GQLNodeInterface
|
interactionTx: GQLNodeInterface
|
||||||
): Promise<InteractionResult<State, View>> {
|
): Promise<InteractionResult<State, View>> {
|
||||||
this.logger.info(`View state for ${this.contractTxId}`, interactionTx);
|
this.logger.info(`View state for ${this._contractTxId}`, interactionTx);
|
||||||
return await this.callContractForTx<Input, View>(input, interactionTx);
|
return await this.callContractForTx<Input, View>(input, interactionTx);
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryWrite<Input>(input: Input, tags?: Tags, transfer?: ArTransfer): Promise<InteractionResult<State, unknown>> {
|
async dryWrite<Input>(input: Input, tags?: Tags, transfer?: ArTransfer): Promise<InteractionResult<State, unknown>> {
|
||||||
this.logger.info('Dry-write for', this.contractTxId);
|
this.logger.info('Dry-write for', this._contractTxId);
|
||||||
return await this.callContract<Input>(input, undefined, tags, transfer);
|
return await this.callContract<Input>(input, undefined, tags, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,8 +150,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
transaction: GQLNodeInterface,
|
transaction: GQLNodeInterface,
|
||||||
currentTx?: CurrentTx[]
|
currentTx?: CurrentTx[]
|
||||||
): Promise<InteractionResult<State, unknown>> {
|
): Promise<InteractionResult<State, unknown>> {
|
||||||
this.logger.info(`Dry-write from transaction ${transaction.id} for ${this.contractTxId}`);
|
this.logger.info(`Dry-write from transaction ${transaction.id} for ${this._contractTxId}`);
|
||||||
return await this.callContractForTx<Input>(input, transaction, true, currentTx || []);
|
return await this.callContractForTx<Input>(input, transaction, currentTx || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeInteraction<Input>(
|
async writeInteraction<Input>(
|
||||||
@@ -146,10 +165,10 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
}
|
}
|
||||||
const { arweave } = this.smartweave;
|
const { arweave } = this.smartweave;
|
||||||
|
|
||||||
if (this.evaluationOptions.internalWrites) {
|
if (this._evaluationOptions.internalWrites) {
|
||||||
await this.callContract(input, undefined, tags, transfer);
|
await this.callContract(input, undefined, tags, transfer);
|
||||||
const callStack: ContractCallStack = this.getCallStack();
|
const callStack: ContractCallStack = this.getCallStack();
|
||||||
const innerWrites = this.innerWritesEvaluator.eval(callStack);
|
const innerWrites = this._innerWritesEvaluator.eval(callStack);
|
||||||
this.logger.debug('Input', input);
|
this.logger.debug('Input', input);
|
||||||
this.logger.debug('Callstack', callStack.print());
|
this.logger.debug('Callstack', callStack.print());
|
||||||
|
|
||||||
@@ -166,7 +185,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
const interactionTx = await createTx(
|
const interactionTx = await createTx(
|
||||||
this.smartweave.arweave,
|
this.smartweave.arweave,
|
||||||
this.wallet,
|
this.wallet,
|
||||||
this.contractTxId,
|
this._contractTxId,
|
||||||
input,
|
input,
|
||||||
tags,
|
tags,
|
||||||
transfer.target,
|
transfer.target,
|
||||||
@@ -180,7 +199,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.evaluationOptions.waitForConfirmation) {
|
if (this._evaluationOptions.waitForConfirmation) {
|
||||||
this.logger.info('Waiting for confirmation of', interactionTx.id);
|
this.logger.info('Waiting for confirmation of', interactionTx.id);
|
||||||
const benchmark = Benchmark.measure();
|
const benchmark = Benchmark.measure();
|
||||||
await this.waitForConfirmation(interactionTx.id);
|
await this.waitForConfirmation(interactionTx.id);
|
||||||
@@ -190,15 +209,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
txId(): string {
|
txId(): string {
|
||||||
return this.contractTxId;
|
return this._contractTxId;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCallStack(): ContractCallStack {
|
getCallStack(): ContractCallStack {
|
||||||
return this.callStack;
|
return this._callStack;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNetworkInfo(): NetworkInfoInterface {
|
getNetworkInfo(): NetworkInfoInterface {
|
||||||
return this.networkInfo;
|
return this._networkInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(wallet: ArWallet): Contract<State> {
|
connect(wallet: ArWallet): Contract<State> {
|
||||||
@@ -207,15 +226,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setEvaluationOptions(options: Partial<EvaluationOptions>): Contract<State> {
|
setEvaluationOptions(options: Partial<EvaluationOptions>): Contract<State> {
|
||||||
this.evaluationOptions = {
|
this._evaluationOptions = {
|
||||||
...this.evaluationOptions,
|
...this._evaluationOptions,
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRootBlockHeight(): number {
|
getRootBlockHeight(): number {
|
||||||
return this.rootBlockHeight;
|
return this._rootBlockHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async waitForConfirmation(transactionId: string): Promise<TransactionStatusResponse> {
|
private async waitForConfirmation(transactionId: string): Promise<TransactionStatusResponse> {
|
||||||
@@ -245,10 +264,10 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
const benchmark = Benchmark.measure();
|
const benchmark = Benchmark.measure();
|
||||||
// if this is a "root" call (ie. original call from SmartWeave's client)
|
// if this is a "root" call (ie. original call from SmartWeave's client)
|
||||||
if (this.callingContract == null) {
|
if (this._parentContract == null) {
|
||||||
this.logger.debug('Reading network info for root call');
|
this.logger.debug('Reading network info for root call');
|
||||||
currentNetworkInfo = await arweave.network.getInfo();
|
currentNetworkInfo = await arweave.network.getInfo();
|
||||||
this.networkInfo = currentNetworkInfo;
|
this._networkInfo = currentNetworkInfo;
|
||||||
} else {
|
} else {
|
||||||
// if that's a call from within contract's source code
|
// if that's a call from within contract's source code
|
||||||
this.logger.debug('Reusing network info from the calling contract');
|
this.logger.debug('Reusing network info from the calling contract');
|
||||||
@@ -258,7 +277,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
// call to contract (from contract's source code) was loading network info independently
|
// call to contract (from contract's source code) was loading network info independently
|
||||||
// if the contract was evaluating for many minutes/hours, this could effectively lead to reading
|
// if the contract was evaluating for many minutes/hours, this could effectively lead to reading
|
||||||
// state on different block heights...
|
// state on different block heights...
|
||||||
currentNetworkInfo = (this.callingContract as HandlerBasedContract<State>).networkInfo;
|
currentNetworkInfo = (this._parentContract as HandlerBasedContract<State>)._networkInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockHeight == null) {
|
if (blockHeight == null) {
|
||||||
@@ -292,8 +311,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
interactionsLoader.load(
|
interactionsLoader.load(
|
||||||
contractTxId,
|
contractTxId,
|
||||||
cachedBlockHeight + 1,
|
cachedBlockHeight + 1,
|
||||||
this.rootBlockHeight || this.networkInfo.height,
|
this._rootBlockHeight || this._networkInfo.height,
|
||||||
this.evaluationOptions
|
this._evaluationOptions
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
this.logger.debug('contract and interactions load', benchmark.elapsed());
|
this.logger.debug('contract and interactions load', benchmark.elapsed());
|
||||||
@@ -315,7 +334,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
handler,
|
handler,
|
||||||
smartweave: this.smartweave,
|
smartweave: this.smartweave,
|
||||||
contract: this,
|
contract: this,
|
||||||
evaluationOptions: this.evaluationOptions,
|
evaluationOptions: this._evaluationOptions,
|
||||||
currentNetworkInfo,
|
currentNetworkInfo,
|
||||||
cachedState
|
cachedState
|
||||||
};
|
};
|
||||||
@@ -345,7 +364,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
if (cachedBlockHeight != blockHeight) {
|
if (cachedBlockHeight != blockHeight) {
|
||||||
[contractDefinition, interactions] = await Promise.all([
|
[contractDefinition, interactions] = await Promise.all([
|
||||||
definitionLoader.load<State>(contractTxId),
|
definitionLoader.load<State>(contractTxId),
|
||||||
await interactionsLoader.load(contractTxId, 0, blockHeight, this.evaluationOptions)
|
await interactionsLoader.load(contractTxId, 0, blockHeight, this._evaluationOptions)
|
||||||
]);
|
]);
|
||||||
sortedInteractions = await interactionsSorter.sort(interactions);
|
sortedInteractions = await interactionsSorter.sort(interactions);
|
||||||
} else {
|
} else {
|
||||||
@@ -364,18 +383,18 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
handler,
|
handler,
|
||||||
smartweave: this.smartweave,
|
smartweave: this.smartweave,
|
||||||
contract: this,
|
contract: this,
|
||||||
evaluationOptions: this.evaluationOptions,
|
evaluationOptions: this._evaluationOptions,
|
||||||
caller,
|
caller,
|
||||||
cachedState
|
cachedState
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private maybeResetRootContract(blockHeight?: number) {
|
private maybeResetRootContract(blockHeight?: number) {
|
||||||
if (this.callingContract == null) {
|
if (this._parentContract == null) {
|
||||||
this.logger.debug('Clearing network info and call stack for the root contract');
|
this.logger.debug('Clearing network info and call stack for the root contract');
|
||||||
this.networkInfo = null;
|
this._networkInfo = null;
|
||||||
this.callStack = new ContractCallStack(this.txId());
|
this._callStack = new ContractCallStack(this.txId(), 0);
|
||||||
this.rootBlockHeight = blockHeight;
|
this._rootBlockHeight = blockHeight;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,7 +411,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
}
|
}
|
||||||
const { arweave, stateEvaluator } = this.smartweave;
|
const { arweave, stateEvaluator } = this.smartweave;
|
||||||
// create execution context
|
// create execution context
|
||||||
let executionContext = await this.createExecutionContext(this.contractTxId, blockHeight, true);
|
let executionContext = await this.createExecutionContext(this._contractTxId, blockHeight, true);
|
||||||
|
|
||||||
// add block data to execution context
|
// add block data to execution context
|
||||||
if (!executionContext.currentBlockData) {
|
if (!executionContext.currentBlockData) {
|
||||||
@@ -427,7 +446,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
const tx = await createTx(
|
const tx = await createTx(
|
||||||
arweave,
|
arweave,
|
||||||
this.wallet,
|
this.wallet,
|
||||||
this.contractTxId,
|
this._contractTxId,
|
||||||
input,
|
input,
|
||||||
tags,
|
tags,
|
||||||
transfer.target,
|
transfer.target,
|
||||||
@@ -458,22 +477,21 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
private async callContractForTx<Input, View = unknown>(
|
private async callContractForTx<Input, View = unknown>(
|
||||||
input: Input,
|
input: Input,
|
||||||
interactionTx: GQLNodeInterface,
|
interactionTx: GQLNodeInterface,
|
||||||
dryWrite = false,
|
|
||||||
currentTx?: CurrentTx[]
|
currentTx?: CurrentTx[]
|
||||||
): Promise<InteractionResult<State, View>> {
|
): Promise<InteractionResult<State, View>> {
|
||||||
this.maybeResetRootContract();
|
this.maybeResetRootContract();
|
||||||
|
|
||||||
const executionContext = await this.createExecutionContextFromTx(this.contractTxId, interactionTx);
|
const executionContext = await this.createExecutionContextFromTx(this._contractTxId, interactionTx);
|
||||||
const evalStateResult = await this.smartweave.stateEvaluator.eval<State>(executionContext, currentTx);
|
const evalStateResult = await this.smartweave.stateEvaluator.eval<State>(executionContext, currentTx);
|
||||||
|
|
||||||
this.logger.debug('callContractForTx - evalStateResult', {
|
this.logger.debug('callContractForTx - evalStateResult', {
|
||||||
result: evalStateResult.state,
|
result: evalStateResult.state,
|
||||||
txId: this.contractTxId
|
txId: this._contractTxId
|
||||||
});
|
});
|
||||||
|
|
||||||
const interaction: ContractInteraction<Input> = {
|
const interaction: ContractInteraction<Input> = {
|
||||||
input,
|
input,
|
||||||
caller: this.callingContract.txId() //executionContext.caller
|
caller: this._parentContract.txId() //executionContext.caller
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionData: InteractionData<Input> = {
|
const interactionData: InteractionData<Input> = {
|
||||||
@@ -482,16 +500,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
currentTx
|
currentTx
|
||||||
};
|
};
|
||||||
|
|
||||||
return await this.evalInteraction(interactionData, executionContext, evalStateResult, dryWrite);
|
return await this.evalInteraction(interactionData, executionContext, evalStateResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async evalInteraction<Input, View = unknown>(
|
private async evalInteraction<Input, View = unknown>(
|
||||||
interactionData: InteractionData<Input>,
|
interactionData: InteractionData<Input>,
|
||||||
executionContext: ExecutionContext<State, HandlerApi<State>>,
|
executionContext: ExecutionContext<State, HandlerApi<State>>,
|
||||||
evalStateResult: EvalStateResult<State>,
|
evalStateResult: EvalStateResult<State>
|
||||||
dryWrite = false
|
|
||||||
) {
|
) {
|
||||||
const interactionCall: InteractionCall = this.getCallStack().addInteractionData(interactionData, dryWrite);
|
const interactionCall: InteractionCall = this.getCallStack().addInteractionData(interactionData);
|
||||||
|
|
||||||
const benchmark = Benchmark.measure();
|
const benchmark = Benchmark.measure();
|
||||||
const result = await executionContext.handler.handle<Input, View>(
|
const result = await executionContext.handler.handle<Input, View>(
|
||||||
@@ -503,7 +520,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
interactionCall.update({
|
interactionCall.update({
|
||||||
cacheHit: false,
|
cacheHit: false,
|
||||||
intermediaryCacheHit: false,
|
intermediaryCacheHit: false,
|
||||||
outputState: this.evaluationOptions.stackTrace.saveState ? result.state : undefined,
|
outputState: this._evaluationOptions.stackTrace.saveState ? result.state : undefined,
|
||||||
executionTime: benchmark.elapsed(true) as number,
|
executionTime: benchmark.elapsed(true) as number,
|
||||||
valid: result.type === 'ok',
|
valid: result.type === 'ok',
|
||||||
errorMessage: result.errorMessage
|
errorMessage: result.errorMessage
|
||||||
@@ -511,4 +528,16 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent(): Contract | null {
|
||||||
|
return this._parentContract;
|
||||||
|
}
|
||||||
|
|
||||||
|
callDepth(): number {
|
||||||
|
return this._callDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluationOptions(): EvaluationOptions {
|
||||||
|
return this._evaluationOptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { InteractionData, mapReplacer } from '@smartweave';
|
|||||||
export class ContractCallStack {
|
export class ContractCallStack {
|
||||||
readonly interactions: Map<string, InteractionCall> = new Map();
|
readonly interactions: Map<string, InteractionCall> = new Map();
|
||||||
|
|
||||||
constructor(public readonly contractTxId: string, public readonly label: string = '') {}
|
constructor(public readonly contractTxId: string, public readonly depth: number, public readonly label: string = '') {}
|
||||||
|
|
||||||
addInteractionData(interactionData: InteractionData<any>, dryWrite = false): InteractionCall {
|
addInteractionData(interactionData: InteractionData<any>): InteractionCall {
|
||||||
const { interaction, interactionTx } = interactionData;
|
const { interaction, interactionTx } = interactionData;
|
||||||
|
|
||||||
const interactionCall = InteractionCall.create(
|
const interactionCall = InteractionCall.create(
|
||||||
|
|||||||
@@ -72,11 +72,14 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
|
|||||||
|
|
||||||
updateCacheForEachInteraction = true;
|
updateCacheForEachInteraction = true;
|
||||||
|
|
||||||
|
internalWrites = false;
|
||||||
|
|
||||||
|
maxCallDepth = 7; // your lucky number...
|
||||||
|
|
||||||
stackTrace = {
|
stackTrace = {
|
||||||
saveState: false
|
saveState: false
|
||||||
};
|
};
|
||||||
|
|
||||||
internalWrites: false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -96,13 +99,22 @@ export interface EvaluationOptions {
|
|||||||
// and caches it maybe more suitable to cache only after state has been fully evaluated)
|
// and caches it maybe more suitable to cache only after state has been fully evaluated)
|
||||||
updateCacheForEachInteraction: boolean;
|
updateCacheForEachInteraction: boolean;
|
||||||
|
|
||||||
|
// a new, experimental enhancement of the protocol that allows for interactWrites from
|
||||||
|
// smart contract's source code.
|
||||||
|
internalWrites: boolean;
|
||||||
|
|
||||||
|
// maximum call depth between contracts
|
||||||
|
// eg. ContractA calls ContractB,
|
||||||
|
// then ContractB calls ContractC,
|
||||||
|
// then ContractC calls ContractD
|
||||||
|
// - call depth = 3
|
||||||
|
// this is added as a protection from "stackoverflow" errors
|
||||||
|
maxCallDepth: number;
|
||||||
|
|
||||||
// a set of options that control the behaviour of the stack trace generator
|
// a set of options that control the behaviour of the stack trace generator
|
||||||
stackTrace: {
|
stackTrace: {
|
||||||
// whether output state should be saved for each interaction in the stack trace (may result in huuuuge json files!)
|
// whether output state should be saved for each interaction in the stack trace (may result in huuuuge json files!)
|
||||||
saveState: boolean;
|
saveState: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a new, experimental enhancement of the protocol that allows for interactWrites from
|
|
||||||
// smart contract's source code.
|
|
||||||
internalWrites: boolean;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,9 +106,11 @@ export class ContractHandlerApi<State> implements HandlerApi<State> {
|
|||||||
input
|
input
|
||||||
});
|
});
|
||||||
|
|
||||||
const calleeContract = executionContext.smartweave
|
const calleeContract = executionContext.smartweave.contract(
|
||||||
.contract(contractTxId, executionContext.contract, this.swGlobal._activeTx)
|
contractTxId,
|
||||||
.setEvaluationOptions(executionContext.evaluationOptions);
|
executionContext.contract,
|
||||||
|
this.swGlobal._activeTx
|
||||||
|
);
|
||||||
|
|
||||||
const result = await calleeContract.dryWriteFromTx<Input>(input, this.swGlobal._activeTx, [
|
const result = await calleeContract.dryWriteFromTx<Input>(input, this.swGlobal._activeTx, [
|
||||||
...(currentTx || []),
|
...(currentTx || []),
|
||||||
@@ -139,9 +141,11 @@ export class ContractHandlerApi<State> implements HandlerApi<State> {
|
|||||||
to: contractTxId,
|
to: contractTxId,
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
const childContract = executionContext.smartweave
|
const childContract = executionContext.smartweave.contract(
|
||||||
.contract(contractTxId, executionContext.contract, this.swGlobal._activeTx)
|
contractTxId,
|
||||||
.setEvaluationOptions(executionContext.evaluationOptions);
|
executionContext.contract,
|
||||||
|
this.swGlobal._activeTx
|
||||||
|
);
|
||||||
|
|
||||||
return await childContract.viewStateForTx(input, this.swGlobal._activeTx);
|
return await childContract.viewStateForTx(input, this.swGlobal._activeTx);
|
||||||
};
|
};
|
||||||
@@ -167,9 +171,11 @@ export class ContractHandlerApi<State> implements HandlerApi<State> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { stateEvaluator } = executionContext.smartweave;
|
const { stateEvaluator } = executionContext.smartweave;
|
||||||
const childContract = executionContext.smartweave
|
const childContract = executionContext.smartweave.contract(
|
||||||
.contract(contractTxId, executionContext.contract, interactionTx)
|
contractTxId,
|
||||||
.setEvaluationOptions(executionContext.evaluationOptions);
|
executionContext.contract,
|
||||||
|
interactionTx
|
||||||
|
);
|
||||||
|
|
||||||
await stateEvaluator.onContractCall(interactionTx, executionContext, currentResult);
|
await stateEvaluator.onContractCall(interactionTx, executionContext, currentResult);
|
||||||
|
|
||||||
|
|||||||
@@ -91,9 +91,11 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
.getCallStack()
|
.getCallStack()
|
||||||
.addInteractionData({ interaction: null, interactionTx, currentTx });
|
.addInteractionData({ interaction: null, interactionTx, currentTx });
|
||||||
|
|
||||||
const writingContract = executionContext.smartweave
|
const writingContract = executionContext.smartweave.contract(
|
||||||
.contract(writingContractTxId, executionContext.contract, interactionTx)
|
writingContractTxId,
|
||||||
.setEvaluationOptions(executionContext.evaluationOptions);
|
executionContext.contract,
|
||||||
|
interactionTx
|
||||||
|
);
|
||||||
|
|
||||||
this.logger.debug('Reading state of the calling contract', interactionTx.block.height);
|
this.logger.debug('Reading state of the calling contract', interactionTx.block.height);
|
||||||
await writingContract.readState(interactionTx.block.height, [
|
await writingContract.readState(interactionTx.block.height, [
|
||||||
@@ -162,7 +164,7 @@ export class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
this.logResult<State>(result, interactionTx, executionContext);
|
this.logResult<State>(result, interactionTx, executionContext);
|
||||||
|
|
||||||
if (result.type === 'exception' && ignoreExceptions !== true) {
|
if (result.type === 'exception' && ignoreExceptions !== true) {
|
||||||
throw new Error(`Exception while processing ${JSON.stringify(interaction)}:\n${result.result}`);
|
throw new Error(`Exception while processing ${JSON.stringify(interaction)}:\n${result.errorMessage}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
validity[interactionTx.id] = result.type === 'ok';
|
validity[interactionTx.id] = result.type === 'ok';
|
||||||
|
|||||||
Reference in New Issue
Block a user