From 49ed1a1058d07faa9b803766d1ad2501409b7dea Mon Sep 17 00:00:00 2001 From: Asia Date: Thu, 14 Dec 2023 11:03:24 +0100 Subject: [PATCH] feat: input in interaction data (#482) --- src/contract/Contract.ts | 1 + src/contract/HandlerBasedContract.ts | 79 +++++++++++++++++++++++++--- src/core/KnownTags.ts | 3 +- src/legacy/create-interaction-tx.ts | 1 + src/utils/utils.ts | 5 ++ 5 files changed, 80 insertions(+), 9 deletions(-) diff --git a/src/contract/Contract.ts b/src/contract/Contract.ts index dc4650a..abab596 100644 --- a/src/contract/Contract.ts +++ b/src/contract/Contract.ts @@ -40,6 +40,7 @@ export interface DREContractStatusResponse { export type WarpOptions = { vrf?: boolean; disableBundling?: boolean; + manifestData?: { [path: string]: string }; }; export type ArweaveOptions = { diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index e0f0a54..09f41a8 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -16,7 +16,7 @@ import { } from '../core/modules/impl/LexicographicalInteractionsSorter'; import { InteractionsSorter } from '../core/modules/InteractionsSorter'; import { DefaultEvaluationOptions, EvalStateResult, EvaluationOptions } from '../core/modules/StateEvaluator'; -import { WARP_TAGS } from '../core/KnownTags'; +import { SMART_WEAVE_TAGS, WARP_TAGS } from '../core/KnownTags'; import { Warp } from '../core/Warp'; import { createDummyTx, createInteractionTagsList, createInteractionTx } from '../legacy/create-interaction-tx'; import { GQLNodeInterface } from '../legacy/gqlResult'; @@ -24,7 +24,7 @@ import { Benchmark } from '../logging/Benchmark'; import { LoggerFactory } from '../logging/LoggerFactory'; import { Evolve } from '../plugins/Evolve'; import { ArweaveWrapper } from '../utils/ArweaveWrapper'; -import { getJsonResponse, isBrowser, sleep, stripTrailingSlash } from '../utils/utils'; +import { getJsonResponse, isBrowser, isTxIdValid, sleep, stripTrailingSlash } from '../utils/utils'; import { BenchmarkStats, Contract, @@ -46,6 +46,15 @@ import { Buffer, Crypto } from 'warp-isomorphic'; import { VrfPluginFunctions } from '../core/WarpPlugin'; import { createData, DataItem, Signer, tagsExceedLimit } from 'warp-arbundles'; +interface InteractionManifestData { + [path: string]: string; +} + +interface InteractionDataField { + input?: Input; + manifest?: InteractionManifestData; +} + /** * An implementation of {@link Contract} that is backwards compatible with current style * of writing SW contracts (ie. using the "handle" function). @@ -76,6 +85,7 @@ export class HandlerBasedContract implements Contract { private _children: HandlerBasedContract[] = []; private _interactionState; private _dreStates = new Map>>(); + private maxInteractionDataItemSizeBytes: number; constructor( private readonly _contractTxId: string, @@ -332,6 +342,7 @@ export class HandlerBasedContract implements Contract { const effectiveVrf = options?.vrf === true; const effectiveDisableBundling = options?.disableBundling === true; const effectiveReward = options?.reward; + const effectiveManifestData = options?.manifestData; const bundleInteraction = interactionsLoader.type() == 'warp' && !effectiveDisableBundling; @@ -358,7 +369,8 @@ export class HandlerBasedContract implements Contract { return await this.bundleInteraction(input, { tags: effectiveTags, strict: effectiveStrict, - vrf: effectiveVrf + vrf: effectiveVrf, + manifestData: effectiveManifestData }); } else { const interactionTx = await this.createInteraction( @@ -398,16 +410,25 @@ export class HandlerBasedContract implements Contract { tags: Tags; strict: boolean; vrf: boolean; + manifestData: InteractionManifestData; } ): Promise { this.logger.info('Bundle interaction input', input); + if (!this.maxInteractionDataItemSizeBytes) { + const response = fetch(`${stripTrailingSlash(this.warp.gwUrl())}`); + this.maxInteractionDataItemSizeBytes = ( + await getJsonResponse<{ maxInteractionDataItemSizeBytes: number }>(response) + ).maxInteractionDataItemSizeBytes; + } + const interactionDataItem = await this.createInteractionDataItem( input, options.tags, emptyTransfer, options.strict, - options.vrf + options.vrf, + options.manifestData ); const response = this._warpFetchWrapper.fetch( @@ -436,7 +457,8 @@ export class HandlerBasedContract implements Contract { tags: Tags, transfer: ArTransfer, strict: boolean, - vrf = false + vrf = false, + manifestData: InteractionManifestData ) { if (this._evaluationOptions.internalWrites) { // it modifies tags @@ -447,18 +469,33 @@ export class HandlerBasedContract implements Contract { tags.push(new Tag(WARP_TAGS.REQUEST_VRF, 'true')); } - const interactionTags = createInteractionTagsList( + let interactionTags = createInteractionTagsList( this._contractTxId, input, this.warp.environment === 'testnet', tags ); + let data: InteractionDataField | string; if (tagsExceedLimit(interactionTags)) { - throw new Error(`Interaction tags exceed limit of 4096 bytes.`); + interactionTags = [ + ...interactionTags.filter((t) => t.name != SMART_WEAVE_TAGS.INPUT && t.name != WARP_TAGS.INPUT_FORMAT), + new Tag(WARP_TAGS.INPUT_FORMAT, 'data') + ]; + data = { + input + }; } - const data = Math.random().toString().slice(-4); + if (manifestData) { + data = { + ...(data as InteractionData), + manifest: this.createManifest(manifestData) + }; + } + + data = data ? JSON.stringify(data) : Math.random().toString().slice(-4); + const bundlerSigner = this._signature.bundlerSigner; if (!bundlerSigner) { @@ -475,6 +512,14 @@ export class HandlerBasedContract implements Contract { await interactionDataItem.sign(bundlerSigner); } + if (interactionDataItem.getRaw().length > this.maxInteractionDataItemSizeBytes) { + throw new Error( + `Interaction data item size: ${interactionDataItem.getRaw().length} exceeds maximum interactions size limit: ${ + this.maxInteractionDataItemSizeBytes + }.` + ); + } + if (!this._evaluationOptions.internalWrites && strict) { await this.checkInteractionInStrictMode(interactionDataItem.owner, input, tags, transfer, strict, vrf); } @@ -1142,4 +1187,22 @@ export class HandlerBasedContract implements Contract { } this._children = []; } + + private createManifest(manifestData: InteractionManifestData) { + const paths = {}; + Object.keys(manifestData).forEach((m) => { + const id = manifestData[m]; + if (typeof m != 'string') { + throw new Error(`Incorrect manifest data. Manifest key should be of type 'string'`); + } else if (typeof id != 'string') { + throw new Error(`Incorrect manifest data. Manifest value should be of type 'string'`); + } else if (!isTxIdValid(id)) { + throw new Error(`Incorrect manifest data. Transaction id: ${id} is not valid.`); + } + + paths[m] = manifestData[m]; + }); + + return paths; + } } diff --git a/src/core/KnownTags.ts b/src/core/KnownTags.ts index 70f6690..2d52eae 100644 --- a/src/core/KnownTags.ts +++ b/src/core/KnownTags.ts @@ -39,7 +39,8 @@ export const WARP_TAGS = { UPLOADER_TX_ID: 'Uploader-Tx-Id', WARP_TESTNET: 'Warp-Testnet', MANIFEST: 'Contract-Manifest', - NONCE: 'Nonce' + NONCE: 'Nonce', + INPUT_FORMAT: 'Input-Format' } as const; export type WarpTags = ObjectValues; diff --git a/src/legacy/create-interaction-tx.ts b/src/legacy/create-interaction-tx.ts index 523c25f..1b58899 100644 --- a/src/legacy/create-interaction-tx.ts +++ b/src/legacy/create-interaction-tx.ts @@ -120,6 +120,7 @@ export function createInteractionTagsList( interactionTags.push(new Tag(SMART_WEAVE_TAGS.SDK, 'Warp')); interactionTags.push(new Tag(SMART_WEAVE_TAGS.CONTRACT_TX_ID, contractId)); interactionTags.push(new Tag(SMART_WEAVE_TAGS.INPUT, JSON.stringify(input))); + interactionTags.push(new Tag(WARP_TAGS.INPUT_FORMAT, 'tag')); if (isTestnet) { interactionTags.push(new Tag(WARP_TAGS.WARP_TESTNET, '1.0.0')); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e7c6933..5932985 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -119,3 +119,8 @@ export async function getJsonResponse(response: Promise): Promise(input: RequestInfo | URL, init?: RequestInit): Promise { return getJsonResponse(fetch(input, init)); } + +export function isTxIdValid(txId: string): boolean { + const validTxIdRegex = /[a-z0-9_-]{43}/i; + return validTxIdRegex.test(txId); +}