Skip to content

Commit

Permalink
feat: introduce InteractionType for explicit marking of contract inte…
Browse files Browse the repository at this point in the history
…ractions as 'write' or 'view'

This allows for some optimizations in WASM code: 'view' interactions do not require cloning of contract state

gh-309
  • Loading branch information
rpiszczatowski committed Mar 10, 2023
1 parent af229b0 commit 1c55d96
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 25 deletions.
25 changes: 15 additions & 10 deletions src/contract/HandlerBasedContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
ContractInteraction,
HandlerApi,
InteractionData,
InteractionResult
InteractionResult,
InteractionType
} from '../core/modules/impl/HandlerExecutorFactory';
import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter';
import { InteractionsSorter } from '../core/modules/InteractionsSorter';
Expand Down Expand Up @@ -212,15 +213,15 @@ export class HandlerBasedContract<State> implements Contract<State> {
transfer: ArTransfer = emptyTransfer
): Promise<InteractionResult<State, View>> {
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>(
input: Input,
interactionTx: GQLNodeInterface
): Promise<InteractionResult<State, View>> {
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>(
Expand All @@ -230,12 +231,12 @@ export class HandlerBasedContract<State> implements Contract<State> {
transfer?: ArTransfer
): Promise<InteractionResult<State, unknown>> {
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>> {
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>(
Expand Down Expand Up @@ -376,7 +377,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
// 3. Verify the callStack and search for any "internalWrites" transactions
// 4. For each found "internalWrite" transaction - generate additional tag:
// {name: 'InternalWrite', value: callingContractTxId}
const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf);
const handlerResult = await this.callContract(input, 'write', undefined, undefined, tags, transfer, strict, vrf);

if (strict && handlerResult.type !== 'ok') {
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
Expand Down Expand Up @@ -422,7 +423,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
this.signature.type == 'arweave'
? await arweave.wallets.ownerToAddress(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') {
throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`);
}
Expand Down Expand Up @@ -644,6 +645,7 @@ export class HandlerBasedContract<State> implements Contract<State> {

private async callContract<Input, View = unknown>(
input: Input,
interactionType: InteractionType,
caller?: string,
sortKey?: string,
tags: Tags = [],
Expand Down Expand Up @@ -694,7 +696,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
// create interaction transaction
const interaction: ContractInteraction<Input> = {
input,
caller: executionContext.caller
caller: executionContext.caller,
interactionType
};

this.logger.debug('interaction', interaction);
Expand Down Expand Up @@ -744,7 +747,8 @@ export class HandlerBasedContract<State> implements Contract<State> {

private async doApplyInputOnTx<Input, View = unknown>(
input: Input,
interactionTx: GQLNodeInterface
interactionTx: GQLNodeInterface,
interactionType: InteractionType
): Promise<InteractionResult<State, View>> {
this.maybeResetRootContract();

Expand All @@ -769,7 +773,8 @@ export class HandlerBasedContract<State> implements Contract<State> {

const interaction: ContractInteraction<Input> = {
input,
caller: this._parentContract.txId()
caller: this._parentContract.txId(),
interactionType
};

const interactionData: InteractionData<Input> = {
Expand Down
3 changes: 2 additions & 1 deletion src/core/modules/impl/DefaultStateEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {

const interaction: ContractInteraction<unknown> = {
input,
caller: missingInteraction.owner.address
caller: missingInteraction.owner.address,
interactionType: 'write'
};

const interactionData = {
Expand Down
5 changes: 4 additions & 1 deletion src/core/modules/impl/HandlerExecutorFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ async function getWasmModule(wasmResponse: Response, binary: Buffer): Promise<We
}

export interface InteractionData<Input> {
interaction?: ContractInteraction<Input>;
interaction: ContractInteraction<Input>;
interactionTx: GQLNodeInterface;
}

Expand Down Expand Up @@ -281,9 +281,12 @@ export type InteractionResult<State, Result> = HandlerResult<State, Result> & {
originalErrorMessages?: Record<string, string>;
};

export type InteractionType = 'view' | 'write';

export type ContractInteraction<Input> = {
input: Input;
caller: string;
interactionType: InteractionType;
};

export type InteractionResultType = 'ok' | 'error' | 'exception';
Expand Down
5 changes: 3 additions & 2 deletions src/core/modules/impl/handler/JsHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
executionContext: ExecutionContext<State>
): Promise<State> {
if (this.contractDefinition.manifest?.evaluationOptions.useConstructor) {
const interaction = {
const interaction: ContractInteraction<Input> = {
input: { function: INIT_FUNC_NAME, args: initialState } as Input,
caller: this.contractDefinition.owner
caller: this.contractDefinition.owner,
interactionType: 'write'
};
const interactionTx = { ...this.contractDefinition.contractTx, sortKey: genesisSortKey };
// this is hard corded sortKey to make KV possible
Expand Down
26 changes: 17 additions & 9 deletions src/core/modules/impl/handler/WasmHandlerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ContractDefinition } from '../../../../core/ContractDefinition';
import { ExecutionContext } from '../../../../core/ExecutionContext';
import { EvalStateResult } from '../../../../core/modules/StateEvaluator';
import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global';
import { InteractionData, InteractionResult } from '../HandlerExecutorFactory';
import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory';
import { AbstractContractHandler } from './AbstractContractHandler';

export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
Expand Down Expand Up @@ -89,17 +89,25 @@ 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) {
case 'rust': {
let handleResult = await this.wasmExports.handle(action.input);
if (!handleResult) {
return;
}
if (Object.prototype.hasOwnProperty.call(handleResult, 'Ok')) {
return handleResult.Ok;

let handleResult;
if (action.interactionType === 'write') {
handleResult = await this.wasmExports.warpContractWrite(action.input);
} else {
this.logger.debug('Error from rust', handleResult.Err);
handleResult = await this.wasmExports.warpContractView(action.input);
}

if (Object.prototype.hasOwnProperty.call(handleResult, 'WriteResponse')) {
return handleResult.WriteResponse;
}
if (Object.prototype.hasOwnProperty.call(handleResult, 'ViewResponse')) {
return handleResult.ViewResponse;
}
{
this.logger.error('Error from rust', handleResult);
let errorKey;
let errorArgs = '';
if (typeof handleResult.Err === 'string' || handleResult.Err instanceof String) {
Expand Down
13 changes: 11 additions & 2 deletions src/core/modules/impl/wasm/rust-wasm-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -585,11 +585,20 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal
* @param {any} interaction
* @returns {Promise<any>}
*/
module.handle = function (interaction) {
var ret = wasmInstance.exports.handle(addHeapObject(interaction));
module.warpContractWrite = function (interaction) {
var ret = wasmInstance.exports.warpContractWrite(addHeapObject(interaction));
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;

function addBorrowedObject(obj) {
Expand Down

0 comments on commit 1c55d96

Please sign in to comment.