feat: introduce InteractionType for explicit marking of contract interactions as 'write' or 'view'
This allows for some optimizations in WASM code: 'view' interactions do not require cloning of contract state gh-309
This commit is contained in:
@@ -6,7 +6,8 @@ import {
|
|||||||
ContractInteraction,
|
ContractInteraction,
|
||||||
HandlerApi,
|
HandlerApi,
|
||||||
InteractionData,
|
InteractionData,
|
||||||
InteractionResult
|
InteractionResult,
|
||||||
|
InteractionType
|
||||||
} from '../core/modules/impl/HandlerExecutorFactory';
|
} from '../core/modules/impl/HandlerExecutorFactory';
|
||||||
import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter';
|
import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter';
|
||||||
import { InteractionsSorter } from '../core/modules/InteractionsSorter';
|
import { InteractionsSorter } from '../core/modules/InteractionsSorter';
|
||||||
@@ -212,7 +213,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
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, undefined, undefined, tags, transfer);
|
return await this.callContract<Input, View>(input, 'view', undefined, undefined, tags, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async viewStateForTx<Input, View>(
|
async viewStateForTx<Input, View>(
|
||||||
@@ -220,7 +221,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
interactionTx: GQLNodeInterface
|
interactionTx: GQLNodeInterface
|
||||||
): 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.doApplyInputOnTx<Input, View>(input, interactionTx);
|
return await this.doApplyInputOnTx<Input, View>(input, interactionTx, 'view');
|
||||||
}
|
}
|
||||||
|
|
||||||
async dryWrite<Input>(
|
async dryWrite<Input>(
|
||||||
@@ -230,12 +231,12 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
transfer?: ArTransfer
|
transfer?: ArTransfer
|
||||||
): Promise<InteractionResult<State, unknown>> {
|
): 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, caller, undefined, tags, transfer);
|
return await this.callContract<Input>(input, 'write', caller, undefined, tags, transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async applyInput<Input>(input: Input, transaction: GQLNodeInterface): Promise<InteractionResult<State, unknown>> {
|
async applyInput<Input>(input: Input, transaction: GQLNodeInterface): Promise<InteractionResult<State, unknown>> {
|
||||||
this.logger.info(`Apply-input from transaction ${transaction.id} for ${this._contractTxId}`);
|
this.logger.info(`Apply-input from transaction ${transaction.id} for ${this._contractTxId}`);
|
||||||
return await this.doApplyInputOnTx<Input>(input, transaction);
|
return await this.doApplyInputOnTx<Input>(input, transaction, 'write');
|
||||||
}
|
}
|
||||||
|
|
||||||
async writeInteraction<Input>(
|
async writeInteraction<Input>(
|
||||||
@@ -370,7 +371,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
reward?: string
|
reward?: string
|
||||||
) {
|
) {
|
||||||
if (this._evaluationOptions.internalWrites) {
|
if (this._evaluationOptions.internalWrites) {
|
||||||
// it modifies tags
|
// it modifies tags
|
||||||
await this.discoverInternalWrites<Input>(input, tags, transfer, strict, vrf);
|
await this.discoverInternalWrites<Input>(input, tags, transfer, strict, vrf);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,7 +401,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
this.signature.type == 'arweave'
|
this.signature.type == 'arweave'
|
||||||
? await arweave.wallets.ownerToAddress(interactionTx.owner)
|
? await arweave.wallets.ownerToAddress(interactionTx.owner)
|
||||||
: interactionTx.owner;
|
: interactionTx.owner;
|
||||||
const handlerResult = await this.callContract(input, caller, undefined, tags, transfer, strict, vrf);
|
const handlerResult = await this.callContract(input, 'write', caller, undefined, tags, transfer, strict, vrf);
|
||||||
if (handlerResult.type !== 'ok') {
|
if (handlerResult.type !== 'ok') {
|
||||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||||
}
|
}
|
||||||
@@ -630,6 +631,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
private async callContract<Input, View = unknown>(
|
private async callContract<Input, View = unknown>(
|
||||||
input: Input,
|
input: Input,
|
||||||
|
interactionType: InteractionType,
|
||||||
caller?: string,
|
caller?: string,
|
||||||
sortKey?: string,
|
sortKey?: string,
|
||||||
tags: Tags = [],
|
tags: Tags = [],
|
||||||
@@ -673,7 +675,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
// create interaction transaction
|
// create interaction transaction
|
||||||
const interaction: ContractInteraction<Input> = {
|
const interaction: ContractInteraction<Input> = {
|
||||||
input,
|
input,
|
||||||
caller: executionContext.caller
|
caller: executionContext.caller,
|
||||||
|
interactionType
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.debug('interaction', interaction);
|
this.logger.debug('interaction', interaction);
|
||||||
@@ -723,7 +726,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
private async doApplyInputOnTx<Input, View = unknown>(
|
private async doApplyInputOnTx<Input, View = unknown>(
|
||||||
input: Input,
|
input: Input,
|
||||||
interactionTx: GQLNodeInterface
|
interactionTx: GQLNodeInterface,
|
||||||
|
interactionType: InteractionType
|
||||||
): Promise<InteractionResult<State, View>> {
|
): Promise<InteractionResult<State, View>> {
|
||||||
this.maybeResetRootContract();
|
this.maybeResetRootContract();
|
||||||
|
|
||||||
@@ -748,7 +752,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
|
|
||||||
const interaction: ContractInteraction<Input> = {
|
const interaction: ContractInteraction<Input> = {
|
||||||
input,
|
input,
|
||||||
caller: this._parentContract.txId()
|
caller: this._parentContract.txId(),
|
||||||
|
interactionType
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionData: InteractionData<Input> = {
|
const interactionData: InteractionData<Input> = {
|
||||||
@@ -990,7 +995,17 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
strict: boolean,
|
strict: boolean,
|
||||||
vrf: boolean
|
vrf: boolean
|
||||||
) {
|
) {
|
||||||
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf, false);
|
const handlerResult = await this.callContract(
|
||||||
|
input,
|
||||||
|
'write',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
tags,
|
||||||
|
transfer,
|
||||||
|
strict,
|
||||||
|
vrf,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
if (strict && handlerResult.type !== 'ok') {
|
if (strict && handlerResult.type !== 'ok') {
|
||||||
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
|
|
||||||
const interaction: ContractInteraction<unknown> = {
|
const interaction: ContractInteraction<unknown> = {
|
||||||
input,
|
input,
|
||||||
caller: missingInteraction.owner.address
|
caller: missingInteraction.owner.address,
|
||||||
|
interactionType: 'write'
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionData = {
|
const interactionData = {
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ async function getWasmModule(wasmResponse: Response, binary: Buffer): Promise<We
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface InteractionData<Input> {
|
export interface InteractionData<Input> {
|
||||||
interaction?: ContractInteraction<Input>;
|
interaction: ContractInteraction<Input>;
|
||||||
interactionTx: GQLNodeInterface;
|
interactionTx: GQLNodeInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,9 +281,12 @@ export type InteractionResult<State, Result> = HandlerResult<State, Result> & {
|
|||||||
originalErrorMessages?: Record<string, string>;
|
originalErrorMessages?: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type InteractionType = 'view' | 'write';
|
||||||
|
|
||||||
export type ContractInteraction<Input> = {
|
export type ContractInteraction<Input> = {
|
||||||
input: Input;
|
input: Input;
|
||||||
caller: string;
|
caller: string;
|
||||||
|
interactionType: InteractionType;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type InteractionResultType = 'ok' | 'error' | 'exception';
|
export type InteractionResultType = 'ok' | 'error' | 'exception';
|
||||||
|
|||||||
@@ -52,9 +52,10 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
|
|||||||
executionContext: ExecutionContext<State>
|
executionContext: ExecutionContext<State>
|
||||||
): Promise<State> {
|
): Promise<State> {
|
||||||
if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) {
|
if (this.contractDefinition.manifest?.evaluationOptions?.useConstructor) {
|
||||||
const interaction = {
|
const interaction: ContractInteraction<Input> = {
|
||||||
input: { function: INIT_FUNC_NAME, args: initialState } as Input,
|
input: { function: INIT_FUNC_NAME, args: initialState } as Input,
|
||||||
caller: this.contractDefinition.owner
|
caller: this.contractDefinition.owner,
|
||||||
|
interactionType: 'write'
|
||||||
};
|
};
|
||||||
|
|
||||||
const interactionTx = {
|
const interactionTx = {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ContractDefinition } from '../../../../core/ContractDefinition';
|
|||||||
import { ExecutionContext } from '../../../../core/ExecutionContext';
|
import { ExecutionContext } from '../../../../core/ExecutionContext';
|
||||||
import { EvalStateResult } from '../../../../core/modules/StateEvaluator';
|
import { EvalStateResult } from '../../../../core/modules/StateEvaluator';
|
||||||
import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global';
|
import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global';
|
||||||
import { InteractionData, InteractionResult } from '../HandlerExecutorFactory';
|
import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
|
||||||
import { AbstractContractHandler } from './AbstractContractHandler';
|
import { AbstractContractHandler } from './AbstractContractHandler';
|
||||||
|
|
||||||
export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
|
export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
|
||||||
@@ -89,17 +89,20 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async doHandle(action: any): Promise<any> {
|
private async doHandle(action: ContractInteraction<unknown>): Promise<any> {
|
||||||
switch (this.contractDefinition.srcWasmLang) {
|
switch (this.contractDefinition.srcWasmLang) {
|
||||||
case 'rust': {
|
case 'rust': {
|
||||||
let handleResult = await this.wasmExports.handle(action.input);
|
|
||||||
if (!handleResult) {
|
const handleResult = action.interactionType === 'write' ? await this.wasmExports.warpContractWrite(action.input) : await this.wasmExports.warpContractView(action.input);
|
||||||
return;
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(handleResult, 'WriteResponse')) {
|
||||||
|
return handleResult.WriteResponse;
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(handleResult, 'Ok')) {
|
if (Object.prototype.hasOwnProperty.call(handleResult, 'ViewResponse')) {
|
||||||
return handleResult.Ok;
|
return handleResult.ViewResponse;
|
||||||
} else {
|
}
|
||||||
this.logger.debug('Error from rust', handleResult.Err);
|
{
|
||||||
|
this.logger.error('Error from rust', handleResult);
|
||||||
let errorKey;
|
let errorKey;
|
||||||
let errorArgs = '';
|
let errorArgs = '';
|
||||||
if (typeof handleResult.Err === 'string' || handleResult.Err instanceof String) {
|
if (typeof handleResult.Err === 'string' || handleResult.Err instanceof String) {
|
||||||
|
|||||||
@@ -585,11 +585,20 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal
|
|||||||
* @param {any} interaction
|
* @param {any} interaction
|
||||||
* @returns {Promise<any>}
|
* @returns {Promise<any>}
|
||||||
*/
|
*/
|
||||||
module.handle = function (interaction) {
|
module.warpContractWrite = function (interaction) {
|
||||||
var ret = wasmInstance.exports.handle(addHeapObject(interaction));
|
var ret = wasmInstance.exports.warpContractWrite(addHeapObject(interaction));
|
||||||
return takeObject(ret);
|
return takeObject(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {any} interaction
|
||||||
|
* @returns {Promise<any>}
|
||||||
|
*/
|
||||||
|
module.warpContractView = function (interaction) {
|
||||||
|
var ret = wasmInstance.exports.warpContractView(addHeapObject(interaction));
|
||||||
|
return takeObject(ret);
|
||||||
|
};
|
||||||
|
|
||||||
let stack_pointer = 32;
|
let stack_pointer = 32;
|
||||||
|
|
||||||
function addBorrowedObject(obj) {
|
function addBorrowedObject(obj) {
|
||||||
|
|||||||
Reference in New Issue
Block a user