From ddeabf4b925f9db9d88b17e59a2042e0bdb5d4f8 Mon Sep 17 00:00:00 2001 From: Eyal Chojnowski Date: Thu, 22 Dec 2022 20:22:59 +0100 Subject: [PATCH] feat: allow choosing rust wasm contracts serialization format --- package.json | 2 +- src/contract/deploy/CreateContract.ts | 16 +++--- .../deploy/impl/DefaultCreateContract.ts | 21 +++++--- src/core/SmartWeaveTags.ts | 1 + src/core/Warp.ts | 14 ++++-- src/core/modules/StateEvaluator.ts | 49 +++++++++++++++++++ .../modules/impl/CacheableStateEvaluator.ts | 9 ++-- .../modules/impl/ContractDefinitionLoader.ts | 44 +++++++++++++---- .../modules/impl/DefaultStateEvaluator.ts | 15 ++++-- .../modules/impl/HandlerExecutorFactory.ts | 21 ++++++-- .../impl/handler/AbstractContractHandler.ts | 4 +- .../modules/impl/handler/WasmHandlerApi.ts | 33 ++++++++----- ...m-imports.ts => rust-wasm-imports-json.ts} | 2 +- .../impl/wasm/rust-wasm-imports-msgpack.ts | 2 +- src/plugins/Evolve.ts | 2 +- src/utils/utils.ts | 4 ++ 16 files changed, 180 insertions(+), 59 deletions(-) rename src/core/modules/impl/wasm/{rust-wasm-imports.ts => rust-wasm-imports-json.ts} (99%) diff --git a/package.json b/package.json index 05265f03..d202c9d0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "mjs:burn:in:hell": "bash mjs-package.sh", "build:types": "tsc -b tsconfig.types.json", "bundle": "node bundle.js", - "build": "yarn build:cjs && yarn build:mjs && yarn mjs:burn:in:hell && yarn build:types && yarn bundle", + "build": "yarn run clean && yarn build:cjs && yarn build:mjs && yarn mjs:burn:in:hell && yarn build:types && yarn bundle", "format": "prettier --write 'src/**/*.ts'", "clean": "rimraf ./lib", "lint": "eslint . --ext .ts", diff --git a/src/contract/deploy/CreateContract.ts b/src/contract/deploy/CreateContract.ts index 839e7496..63404c39 100644 --- a/src/contract/deploy/CreateContract.ts +++ b/src/contract/deploy/CreateContract.ts @@ -1,4 +1,5 @@ import { JWKInterface } from 'arweave/node/lib/wallet'; +import { SerializationFormat } from 'core/modules/StateEvaluator'; import { SignatureType } from '../../contract/Signature'; import { Source } from './Source'; @@ -18,9 +19,10 @@ export const emptyTransfer: ArTransfer = { winstonQty: '0' }; -export interface CommonContractData { +export interface CommonContractData { wallet: ArWallet | SignatureType; - initState: string | Buffer; + stateFormat: T; + initState: T extends SerializationFormat.JSON ? string : Buffer; tags?: Tags; transfer?: ArTransfer; data?: { @@ -29,13 +31,13 @@ export interface CommonContractData { }; } -export interface ContractData extends CommonContractData { +export interface ContractData extends CommonContractData { src: string | Buffer; wasmSrcCodeDir?: string; wasmGlueCode?: string; } -export interface FromSrcTxContractData extends CommonContractData { +export interface FromSrcTxContractData extends CommonContractData { srcTxId: string; } @@ -44,10 +46,10 @@ export interface ContractDeploy { srcTxId?: string; } -export interface CreateContract extends Source { - deploy(contractData: ContractData, disableBundling?: boolean): Promise; +export interface CreateContract extends Source { + deploy(contractData: ContractData, disableBundling?: boolean): Promise; - deployFromSourceTx(contractData: FromSrcTxContractData, disableBundling?: boolean): Promise; + deployFromSourceTx(contractData: FromSrcTxContractData, disableBundling?: boolean): Promise; deployBundled(rawDataItem: Buffer): Promise; } diff --git a/src/contract/deploy/impl/DefaultCreateContract.ts b/src/contract/deploy/impl/DefaultCreateContract.ts index f6614bd2..aa71eeea 100644 --- a/src/contract/deploy/impl/DefaultCreateContract.ts +++ b/src/contract/deploy/impl/DefaultCreateContract.ts @@ -9,8 +9,10 @@ import { LoggerFactory } from '../../../logging/LoggerFactory'; import { CreateContract, ContractData, ContractDeploy, FromSrcTxContractData, ArWallet } from '../CreateContract'; import { SourceData, SourceImpl } from './SourceImpl'; import { Buffer } from 'redstone-isomorphic'; +import { SerializationFormat } from 'core/modules/StateEvaluator'; +import { exhaustive } from 'utils/utils'; -export class DefaultCreateContract implements CreateContract { +export class DefaultCreateContract implements CreateContract { private readonly logger = LoggerFactory.INST.create('DefaultCreateContract'); private readonly source: SourceImpl; @@ -21,8 +23,11 @@ export class DefaultCreateContract implements CreateContract { this.source = new SourceImpl(this.warp); } - async deploy(contractData: ContractData, disableBundling?: boolean): Promise { - const { wallet, initState, tags, transfer, data } = contractData; + async deploy( + contractData: ContractData, + disableBundling?: boolean + ): Promise { + const { wallet, stateFormat, initState, tags, transfer, data } = contractData; const effectiveUseBundler = disableBundling == undefined ? this.warp.definitionLoader.type() == 'warp' : !disableBundling; @@ -38,6 +43,7 @@ export class DefaultCreateContract implements CreateContract { { srcTxId: srcTx.id, wallet, + stateFormat, initState, tags, transfer, @@ -48,13 +54,13 @@ export class DefaultCreateContract implements CreateContract { ); } - async deployFromSourceTx( - contractData: FromSrcTxContractData, + async deployFromSourceTx( + contractData: FromSrcTxContractData, disableBundling?: boolean, srcTx: Transaction = null ): Promise { this.logger.debug('Creating new contract from src tx'); - const { wallet, srcTxId, initState, tags, transfer, data } = contractData; + const { wallet, srcTxId, stateFormat, initState, tags, transfer, data } = contractData; this.signature = new Signature(this.warp, wallet); const signer = this.signature.signer; @@ -85,12 +91,13 @@ export class DefaultCreateContract implements CreateContract { contractTX.addTag(SmartWeaveTags.SDK, 'RedStone'); if (data) { contractTX.addTag(SmartWeaveTags.CONTENT_TYPE, data['Content-Type']); + contractTX.addTag(SmartWeaveTags.INIT_STATE_FORMAT, stateFormat); contractTX.addTag( SmartWeaveTags.INIT_STATE, typeof initState === 'string' ? initState : new TextDecoder().decode(initState) ); } else { - contractTX.addTag(SmartWeaveTags.CONTENT_TYPE, 'application/json'); + contractTX.addTag(SmartWeaveTags.CONTENT_TYPE, stateFormat); } if (this.warp.environment === 'testnet') { diff --git a/src/core/SmartWeaveTags.ts b/src/core/SmartWeaveTags.ts index 044cd377..4f293335 100644 --- a/src/core/SmartWeaveTags.ts +++ b/src/core/SmartWeaveTags.ts @@ -11,6 +11,7 @@ export enum SmartWeaveTags { SDK = 'SDK', MIN_FEE = 'Min-Fee', INIT_STATE = 'Init-State', + INIT_STATE_FORMAT = 'Init-State-Format', INIT_STATE_TX = 'Init-State-TX', INTERACT_WRITE = 'Interact-Write', WASM_LANG = 'Wasm-Lang', diff --git a/src/core/Warp.ts b/src/core/Warp.ts index 19b722bf..98489241 100644 --- a/src/core/Warp.ts +++ b/src/core/Warp.ts @@ -16,7 +16,7 @@ import { DefinitionLoader } from './modules/DefinitionLoader'; import { ExecutorFactory } from './modules/ExecutorFactory'; import { HandlerApi } from './modules/impl/HandlerExecutorFactory'; import { InteractionsLoader } from './modules/InteractionsLoader'; -import { EvalStateResult, StateEvaluator } from './modules/StateEvaluator'; +import { EvalStateResult, SerializationFormat, StateEvaluator } from './modules/StateEvaluator'; import { WarpBuilder } from './WarpBuilder'; import { WarpPluginType, WarpPlugin, knownWarpPlugins } from './WarpPlugin'; import { SortKeyCache } from '../cache/SortKeyCache'; @@ -39,7 +39,7 @@ export class Warp { /** * @deprecated createContract will be a private field, please use its methods directly e.g. await warp.deploy(...) */ - readonly createContract: CreateContract; + readonly createContract: CreateContract; readonly testing: Testing; private readonly plugins: Map> = new Map(); @@ -73,11 +73,17 @@ export class Warp { return new HandlerBasedContract(contractTxId, this, callingContract, innerCallData); } - async deploy(contractData: ContractData, disableBundling?: boolean): Promise { + async deploy( + contractData: ContractData, + disableBundling?: boolean + ): Promise { return await this.createContract.deploy(contractData, disableBundling); } - async deployFromSourceTx(contractData: FromSrcTxContractData, disableBundling?: boolean): Promise { + async deployFromSourceTx( + contractData: FromSrcTxContractData, + disableBundling?: boolean + ): Promise { return await this.createContract.deployFromSourceTx(contractData, disableBundling); } diff --git a/src/core/modules/StateEvaluator.ts b/src/core/modules/StateEvaluator.ts index add07fe8..7862f1ec 100644 --- a/src/core/modules/StateEvaluator.ts +++ b/src/core/modules/StateEvaluator.ts @@ -1,3 +1,7 @@ +import { pack, unpack } from 'msgpackr'; +import stringify from 'safe-stable-stringify'; +import { exhaustive } from 'utils/utils'; + import { SortKeyCache, SortKeyCacheResult } from '../../cache/SortKeyCache'; import { CurrentTx } from '../../contract/Contract'; import { ExecutionContext } from '../../core/ExecutionContext'; @@ -138,8 +142,46 @@ export class DefaultEvaluationOptions implements EvaluationOptions { throwOnInternalWriteError = true; cacheEveryNInteractions = -1; + + wasmSerializationFormat = SerializationFormat.JSON; +} + +export enum SerializationFormat { + JSON = 'application/json', + // NOTE: There is still no officially registered Media Type for Messagepack and there are + // apparently multiple different versions used in the wild like: + // - application/msgpack + // - application/x-msgpack + // - application/x.msgpack + // - [...] + // See . I've decided to use the first one + // as it looks like the one that makes the most sense. + MSGPACK = 'application/msgpack' } +export const stringToSerializationFormat = (format: string): SerializationFormat => { + const formatTyped = format as SerializationFormat; + + switch (formatTyped) { + case SerializationFormat.JSON: + return SerializationFormat.JSON; + case SerializationFormat.MSGPACK: + return SerializationFormat.MSGPACK; + default: + return exhaustive(formatTyped, `Unsupported serialization format: "${formatTyped}"`); + } +}; + +export const Serializers = { + [SerializationFormat.JSON]: stringify, + [SerializationFormat.MSGPACK]: pack +} as const satisfies Record; + +export const Deserializers = { + [SerializationFormat.JSON]: JSON.parse, + [SerializationFormat.MSGPACK]: unpack +} as const satisfies Record; + // an interface for the contract EvaluationOptions - can be used to change the behaviour of some features. export interface EvaluationOptions { // whether exceptions from given transaction interaction should be ignored @@ -214,4 +256,11 @@ export interface EvaluationOptions { // force SDK to cache the state after evaluating each N interactions // defaults to -1, which effectively turns off this feature cacheEveryNInteractions: number; + + /** + * What serialization format should be used for the WASM<->JS bridge. Note that changing this + * currently only affects Rust smartcontracts. AssemblyScript and Go smartcontracts will always + * use JSON. Defaults to JSON. + */ + wasmSerializationFormat: SerializationFormat; } diff --git a/src/core/modules/impl/CacheableStateEvaluator.ts b/src/core/modules/impl/CacheableStateEvaluator.ts index 41ee407d..26098d72 100644 --- a/src/core/modules/impl/CacheableStateEvaluator.ts +++ b/src/core/modules/impl/CacheableStateEvaluator.ts @@ -6,7 +6,7 @@ import { ExecutionContextModifier } from '../../../core/ExecutionContextModifier import { GQLNodeInterface } from '../../../legacy/gqlResult'; import { LoggerFactory } from '../../../logging/LoggerFactory'; import { indent } from '../../../utils/utils'; -import { EvalStateResult } from '../StateEvaluator'; +import { EvalStateResult, SerializationFormat } from '../StateEvaluator'; import { DefaultStateEvaluator } from './DefaultStateEvaluator'; import { HandlerApi } from './HandlerExecutorFactory'; import { genesisSortKey } from './LexicographicalInteractionsSorter'; @@ -35,11 +35,12 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator { currentTx: CurrentTx[] ): Promise>> { const cachedState = executionContext.cachedState; + const { wasmSerializationFormat: serializationFormat } = executionContext.evaluationOptions; if (cachedState && cachedState.sortKey == executionContext.requestedSortKey) { this.cLogger.info( `Exact cache hit for sortKey ${executionContext?.contractDefinition?.txId}:${cachedState.sortKey}` ); - executionContext.handler?.initState(cachedState.cachedValue.state); + executionContext.handler?.initState(cachedState.cachedValue.state, serializationFormat); return cachedState; } @@ -71,10 +72,10 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator { if (missingInteractions.length == 0) { this.cLogger.info(`No missing interactions ${contractTxId}`); if (cachedState) { - executionContext.handler?.initState(cachedState.cachedValue.state); + executionContext.handler?.initState(cachedState.cachedValue.state, serializationFormat); return cachedState; } else { - executionContext.handler?.initState(executionContext.contractDefinition.initState); + executionContext.handler?.initState(executionContext.contractDefinition.initState, serializationFormat); this.cLogger.debug('Inserting initial state into cache'); const stateToCache = new EvalStateResult(executionContext.contractDefinition.initState, {}, {}); // no real sort-key - as we're returning the initial state diff --git a/src/core/modules/impl/ContractDefinitionLoader.ts b/src/core/modules/impl/ContractDefinitionLoader.ts index 31e3491b..90649103 100644 --- a/src/core/modules/impl/ContractDefinitionLoader.ts +++ b/src/core/modules/impl/ContractDefinitionLoader.ts @@ -12,7 +12,8 @@ import { TagsParser } from './TagsParser'; import { WasmSrc } from './wasm/WasmSrc'; import { WarpEnvironment } from '../../Warp'; import { SortKeyCache } from 'cache/SortKeyCache'; -import { unpack } from 'msgpackr'; +import { Deserializers, SerializationFormat, stringToSerializationFormat } from '../StateEvaluator'; +import { exhaustive } from 'utils/utils'; const supportedSrcContentTypes = ['application/javascript', 'application/wasm']; @@ -56,10 +57,7 @@ export class ContractDefinitionLoader implements DefinitionLoader { const minFee = this.tagsParser.getTag(contractTx, SmartWeaveTags.MIN_FEE); this.logger.debug('Tags decoding', benchmark.elapsed()); benchmark.reset(); - // TODO: It should be stored somewhere whether the initial state is stored as a JSON string or - // as a MsgPack binary. For the sake of this experiment, we assume it's always MsgPack. - // const initState = JSON.parse(await this.evalInitialState(contractTx)); - const initState = unpack(await this.arweaveWrapper.txData(contractTx.id)); + const initState = await this.evalInitialState(contractTx); this.logger.debug('Parsing src and init state', benchmark.elapsed()); const { src, srcBinary, srcWasmLang, contractType, metadata, srcTx } = await this.loadContractSource( @@ -123,14 +121,42 @@ export class ContractDefinitionLoader implements DefinitionLoader { }; } - private async evalInitialState(contractTx: Transaction): Promise { + private async evalInitialState(contractTx: Transaction): Promise { if (this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE)) { - return this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE); + const format = stringToSerializationFormat( + this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE_FORMAT) ?? 'application/json' + ); + const initState = this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE); + + switch (format) { + case SerializationFormat.JSON: + return Deserializers[format](initState); + case SerializationFormat.MSGPACK: + return Deserializers[format](new TextEncoder().encode(initState)); + default: + exhaustive(format); + } } else if (this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE_TX)) { const stateTX = this.tagsParser.getTag(contractTx, SmartWeaveTags.INIT_STATE_TX); - return this.arweaveWrapper.txDataString(stateTX); + + return this.getInitialStateFromTx(await this.arweave.transactions.get(stateTX)); } else { - return this.arweaveWrapper.txDataString(contractTx.id); + return this.getInitialStateFromTx(contractTx); + } + } + + private async getInitialStateFromTx(tx: Transaction): Promise { + const format = stringToSerializationFormat( + this.tagsParser.getTag(tx, SmartWeaveTags.CONTENT_TYPE) ?? 'application/json' + ); + + switch (format) { + case SerializationFormat.JSON: + return Deserializers[format](await this.arweaveWrapper.txDataString(tx.id)); + case SerializationFormat.MSGPACK: + return Deserializers[format](await this.arweaveWrapper.txData(tx.id)); + default: + exhaustive(format); } } diff --git a/src/core/modules/impl/DefaultStateEvaluator.ts b/src/core/modules/impl/DefaultStateEvaluator.ts index 30387434..74f22bdb 100644 --- a/src/core/modules/impl/DefaultStateEvaluator.ts +++ b/src/core/modules/impl/DefaultStateEvaluator.ts @@ -53,8 +53,13 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { executionContext: ExecutionContext>, currentTx: CurrentTx[] ): Promise>> { - const { ignoreExceptions, stackTrace, internalWrites, cacheEveryNInteractions } = - executionContext.evaluationOptions; + const { + ignoreExceptions, + stackTrace, + internalWrites, + cacheEveryNInteractions, + wasmSerializationFormat: serializationFormat + } = executionContext.evaluationOptions; const { contract, contractDefinition, sortedInteractions, warp } = executionContext; let currentState = baseState.state; @@ -62,7 +67,7 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { const validity = baseState.validity; const errorMessages = baseState.errorMessages; - executionContext?.handler.initState(currentState); + executionContext?.handler.initState(currentState, serializationFormat); const depth = executionContext.contract.callDepth(); @@ -76,7 +81,7 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { let lastConfirmedTxState: { tx: GQLNodeInterface; state: EvalStateResult } = null; const missingInteractionsLength = missingInteractions.length; - executionContext.handler.initState(currentState); + executionContext.handler.initState(currentState, serializationFormat); const evmSignatureVerificationPlugin = warp.hasPlugin('evm-signature-verification') ? warp.loadPlugin>('evm-signature-verification') @@ -166,7 +171,7 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { if (newState !== null) { currentState = newState.cachedValue.state; // we need to update the state in the wasm module - executionContext?.handler.initState(currentState); + executionContext?.handler.initState(currentState, serializationFormat); validity[missingInteraction.id] = newState.cachedValue.validity[missingInteraction.id]; if (newState.cachedValue.errorMessages?.[missingInteraction.id]) { diff --git a/src/core/modules/impl/HandlerExecutorFactory.ts b/src/core/modules/impl/HandlerExecutorFactory.ts index 0e23bd29..94ac7536 100644 --- a/src/core/modules/impl/HandlerExecutorFactory.ts +++ b/src/core/modules/impl/HandlerExecutorFactory.ts @@ -1,7 +1,8 @@ import Arweave from 'arweave'; import loader from '@assemblyscript/loader'; import { asWasmImports } from './wasm/as-wasm-imports'; -import { rustWasmImports } from './wasm/rust-wasm-imports-msgpack'; +import { rustWasmImportsJson } from './wasm/rust-wasm-imports-json'; +import { rustWasmImportsMsgpack } from './wasm/rust-wasm-imports-msgpack'; import { Go } from './wasm/go-wasm-imports'; import * as vm2 from 'vm2'; import { WarpCache } from '../../../cache/WarpCache'; @@ -12,14 +13,14 @@ import { SmartWeaveGlobal } from '../../../legacy/smartweave-global'; import { Benchmark } from '../../../logging/Benchmark'; import { LoggerFactory } from '../../../logging/LoggerFactory'; import { ExecutorFactory } from '../ExecutorFactory'; -import { EvalStateResult, EvaluationOptions } from '../StateEvaluator'; +import { EvalStateResult, EvaluationOptions, SerializationFormat } from '../StateEvaluator'; import { JsHandlerApi } from './handler/JsHandlerApi'; import { WasmHandlerApi } from './handler/WasmHandlerApi'; import { normalizeContractSource } from './normalize-source'; import { MemCache } from '../../../cache/impl/MemCache'; import BigNumber from '../../../legacy/bignumber'; import { Warp } from '../../Warp'; -import { isBrowser } from '../../../utils/utils'; +import { exhaustive, isBrowser } from '../../../utils/utils'; import { Buffer } from 'redstone-isomorphic'; class ContractError extends Error { @@ -105,6 +106,18 @@ export class HandlerExecutorFactory implements ExecutorFactory imp.name); + let rustWasmImports; + switch (evaluationOptions.wasmSerializationFormat) { + case SerializationFormat.JSON: + rustWasmImports = rustWasmImportsJson; + break; + case SerializationFormat.MSGPACK: + rustWasmImports = rustWasmImportsMsgpack; + break; + default: + return exhaustive(evaluationOptions.wasmSerializationFormat); + } + const { imports, exports } = rustWasmImports( swGlobal, wbindgenImports, @@ -245,7 +258,7 @@ export interface HandlerApi { interactionData: InteractionData ): Promise>; - initState(state: State): void; + initState(state: State, format: SerializationFormat): void; } export type HandlerFunction = ( diff --git a/src/core/modules/impl/handler/AbstractContractHandler.ts b/src/core/modules/impl/handler/AbstractContractHandler.ts index 0c4a82ff..d25ed9ba 100644 --- a/src/core/modules/impl/handler/AbstractContractHandler.ts +++ b/src/core/modules/impl/handler/AbstractContractHandler.ts @@ -1,7 +1,7 @@ import { ContractError, CurrentTx } from '../../../../contract/Contract'; import { ContractDefinition } from '../../../../core/ContractDefinition'; import { ExecutionContext } from '../../../../core/ExecutionContext'; -import { EvalStateResult } from '../../../../core/modules/StateEvaluator'; +import { EvalStateResult, SerializationFormat } from '../../../../core/modules/StateEvaluator'; import { GQLNodeInterface } from '../../../../legacy/gqlResult'; import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global'; import { LoggerFactory } from '../../../../logging/LoggerFactory'; @@ -27,7 +27,7 @@ export abstract class AbstractContractHandler implements HandlerApi ): Promise>; - abstract initState(state: State): void; + abstract initState(state: State, format: SerializationFormat): void; async dispose(): Promise { // noop by default; diff --git a/src/core/modules/impl/handler/WasmHandlerApi.ts b/src/core/modules/impl/handler/WasmHandlerApi.ts index c2b29ba8..640ce62c 100644 --- a/src/core/modules/impl/handler/WasmHandlerApi.ts +++ b/src/core/modules/impl/handler/WasmHandlerApi.ts @@ -1,13 +1,17 @@ /* eslint-disable */ -import { unpack, pack } from 'msgpackr'; - import { ContractDefinition } from '../../../../core/ContractDefinition'; import { ExecutionContext } from '../../../../core/ExecutionContext'; -import { EvalStateResult } from '../../../../core/modules/StateEvaluator'; +import { + Deserializers, + EvalStateResult, + SerializationFormat, + Serializers +} from '../../../../core/modules/StateEvaluator'; import { SmartWeaveGlobal } from '../../../../legacy/smartweave-global'; import stringify from 'safe-stable-stringify'; -import { InteractionData, InteractionResult } from '../HandlerExecutorFactory'; +import { ContractInteraction, InteractionData, InteractionResult } from '../HandlerExecutorFactory'; import { AbstractContractHandler } from './AbstractContractHandler'; +import { exhaustive } from 'utils/utils'; export class WasmHandlerApi extends AbstractContractHandler { constructor( @@ -26,21 +30,22 @@ export class WasmHandlerApi extends AbstractContractHandler { ): Promise> { try { const { interaction, interactionTx, currentTx } = interactionData; + const { gasLimit, wasmSerializationFormat } = executionContext.evaluationOptions; this.swGlobal._activeTx = interactionTx; this.swGlobal.caller = interaction.caller; // either contract tx id (for internal writes) or transaction.owner - this.swGlobal.gasLimit = executionContext.evaluationOptions.gasLimit; + this.swGlobal.gasLimit = gasLimit; this.swGlobal.gasUsed = 0; this.assignReadContractState(executionContext, currentTx, currentResult, interactionTx); this.assignWrite(executionContext, currentTx); - const handlerResult = await this.doHandle(interaction); + const handlerResult = await this.doHandle(interaction, wasmSerializationFormat); return { type: 'ok', result: handlerResult, - state: this.doGetCurrentState(), // TODO: return only at the end of evaluation and when caching is required + state: this.doGetCurrentState(wasmSerializationFormat), // TODO: return only at the end of evaluation and when caching is required gasUsed: this.swGlobal.gasUsed }; } catch (e) { @@ -75,7 +80,8 @@ export class WasmHandlerApi extends AbstractContractHandler { } } - initState(state: State): void { + // TODO:noom support SerializationFormat here too + initState(state: State, format: SerializationFormat): void { switch (this.contractDefinition.srcWasmLang) { case 'assemblyscript': { const statePtr = this.wasmExports.__newString(stringify(state)); @@ -83,7 +89,7 @@ export class WasmHandlerApi extends AbstractContractHandler { break; } case 'rust': { - this.wasmExports.initState(pack(state)); + this.wasmExports.initState(Serializers[format](state)); break; } case 'go': { @@ -96,7 +102,7 @@ export class WasmHandlerApi extends AbstractContractHandler { } } - private async doHandle(action: any): Promise { + private async doHandle(action: ContractInteraction, format: SerializationFormat): Promise { switch (this.contractDefinition.srcWasmLang) { case 'assemblyscript': { const actionPtr = this.wasmExports.__newString(stringify(action.input)); @@ -106,7 +112,8 @@ export class WasmHandlerApi extends AbstractContractHandler { return JSON.parse(result); } case 'rust': { - let handleResult = await this.wasmExports.handle(pack(action.input)); + const handleResult = await this.wasmExports.handle(Serializers[format](action.input)); + if (!handleResult) { return; } @@ -140,14 +147,14 @@ export class WasmHandlerApi extends AbstractContractHandler { } } - private doGetCurrentState(): State { + private doGetCurrentState(format: SerializationFormat): State { switch (this.contractDefinition.srcWasmLang) { case 'assemblyscript': { const currentStatePtr = this.wasmExports.currentState(); return JSON.parse(this.wasmExports.__getString(currentStatePtr)); } case 'rust': { - return unpack(this.wasmExports.currentState()); + return Deserializers[format](this.wasmExports.currentState()); } case 'go': { const result = this.wasmExports.currentState(); diff --git a/src/core/modules/impl/wasm/rust-wasm-imports.ts b/src/core/modules/impl/wasm/rust-wasm-imports-json.ts similarity index 99% rename from src/core/modules/impl/wasm/rust-wasm-imports.ts rename to src/core/modules/impl/wasm/rust-wasm-imports-json.ts index 86609b04..0adaae70 100644 --- a/src/core/modules/impl/wasm/rust-wasm-imports.ts +++ b/src/core/modules/impl/wasm/rust-wasm-imports-json.ts @@ -6,7 +6,7 @@ import { LoggerFactory } from '../../../../logging/LoggerFactory'; // note: this is (somewhat heavily) modified code // of the js that is normally generated by the wasm-bindgen -export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorValue): any => { +export const rustWasmImportsJson = (swGlobal, wbindgenImports, wasmInstance, dtorValue): any => { const wasmLogger = LoggerFactory.INST.create('WASM:Rust'); // the raw functions, that we want to make available from the diff --git a/src/core/modules/impl/wasm/rust-wasm-imports-msgpack.ts b/src/core/modules/impl/wasm/rust-wasm-imports-msgpack.ts index 07541512..00ffb77c 100644 --- a/src/core/modules/impl/wasm/rust-wasm-imports-msgpack.ts +++ b/src/core/modules/impl/wasm/rust-wasm-imports-msgpack.ts @@ -6,7 +6,7 @@ import { LoggerFactory } from '../../../../logging/LoggerFactory'; // note: this is (somewhat heavily) modified code // of the js that is normally generated by the wasm-bindgen -export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorValue): any => { +export const rustWasmImportsMsgpack = (swGlobal, wbindgenImports, wasmInstance, dtorValue): any => { const wasmLogger = LoggerFactory.INST.create('WASM:Rust'); // the raw functions, that we want to make available from the diff --git a/src/plugins/Evolve.ts b/src/plugins/Evolve.ts index d90fe443..e95e628c 100644 --- a/src/plugins/Evolve.ts +++ b/src/plugins/Evolve.ts @@ -52,7 +52,7 @@ export class Evolve implements ExecutionContextModifier { //FIXME: side-effect... executionContext.contractDefinition = newContractDefinition; executionContext.handler = newHandler; - executionContext.handler.initState(state); + executionContext.handler.initState(state, executionContext.evaluationOptions.wasmSerializationFormat); this.logger.debug('evolved to:', { evolve: evolvedSrcTxId, newSrcTxId: executionContext.contractDefinition.srcTxId, diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b4192c8a..b41affe6 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,6 +3,10 @@ import copy from 'fast-copy'; import { Buffer } from 'redstone-isomorphic'; import { randomUUID } from 'crypto'; +export const exhaustive = (_: never, errorMessage = 'Exhaustive check failed') => { + throw new Error(errorMessage); +}; + export const sleep = (ms: number): Promise => { return new Promise((resolve) => setTimeout(resolve, ms)); };