feat: input in interaction data (#482)

This commit is contained in:
Asia
2023-12-14 11:03:24 +01:00
committed by GitHub
parent d8cb4cc181
commit 49ed1a1058
5 changed files with 80 additions and 9 deletions

View File

@@ -40,6 +40,7 @@ export interface DREContractStatusResponse<State> {
export type WarpOptions = { export type WarpOptions = {
vrf?: boolean; vrf?: boolean;
disableBundling?: boolean; disableBundling?: boolean;
manifestData?: { [path: string]: string };
}; };
export type ArweaveOptions = { export type ArweaveOptions = {

View File

@@ -16,7 +16,7 @@ import {
} from '../core/modules/impl/LexicographicalInteractionsSorter'; } from '../core/modules/impl/LexicographicalInteractionsSorter';
import { InteractionsSorter } from '../core/modules/InteractionsSorter'; import { InteractionsSorter } from '../core/modules/InteractionsSorter';
import { DefaultEvaluationOptions, EvalStateResult, EvaluationOptions } from '../core/modules/StateEvaluator'; 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 { Warp } from '../core/Warp';
import { createDummyTx, createInteractionTagsList, createInteractionTx } from '../legacy/create-interaction-tx'; import { createDummyTx, createInteractionTagsList, createInteractionTx } from '../legacy/create-interaction-tx';
import { GQLNodeInterface } from '../legacy/gqlResult'; import { GQLNodeInterface } from '../legacy/gqlResult';
@@ -24,7 +24,7 @@ import { Benchmark } from '../logging/Benchmark';
import { LoggerFactory } from '../logging/LoggerFactory'; import { LoggerFactory } from '../logging/LoggerFactory';
import { Evolve } from '../plugins/Evolve'; import { Evolve } from '../plugins/Evolve';
import { ArweaveWrapper } from '../utils/ArweaveWrapper'; import { ArweaveWrapper } from '../utils/ArweaveWrapper';
import { getJsonResponse, isBrowser, sleep, stripTrailingSlash } from '../utils/utils'; import { getJsonResponse, isBrowser, isTxIdValid, sleep, stripTrailingSlash } from '../utils/utils';
import { import {
BenchmarkStats, BenchmarkStats,
Contract, Contract,
@@ -46,6 +46,15 @@ import { Buffer, Crypto } from 'warp-isomorphic';
import { VrfPluginFunctions } from '../core/WarpPlugin'; import { VrfPluginFunctions } from '../core/WarpPlugin';
import { createData, DataItem, Signer, tagsExceedLimit } from 'warp-arbundles'; import { createData, DataItem, Signer, tagsExceedLimit } from 'warp-arbundles';
interface InteractionManifestData {
[path: string]: string;
}
interface InteractionDataField<Input> {
input?: Input;
manifest?: InteractionManifestData;
}
/** /**
* An implementation of {@link Contract} that is backwards compatible with current style * An implementation of {@link Contract} that is backwards compatible with current style
* of writing SW contracts (ie. using the "handle" function). * of writing SW contracts (ie. using the "handle" function).
@@ -76,6 +85,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
private _children: HandlerBasedContract<unknown>[] = []; private _children: HandlerBasedContract<unknown>[] = [];
private _interactionState; private _interactionState;
private _dreStates = new Map<string, SortKeyCacheResult<EvalStateResult<State>>>(); private _dreStates = new Map<string, SortKeyCacheResult<EvalStateResult<State>>>();
private maxInteractionDataItemSizeBytes: number;
constructor( constructor(
private readonly _contractTxId: string, private readonly _contractTxId: string,
@@ -332,6 +342,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
const effectiveVrf = options?.vrf === true; const effectiveVrf = options?.vrf === true;
const effectiveDisableBundling = options?.disableBundling === true; const effectiveDisableBundling = options?.disableBundling === true;
const effectiveReward = options?.reward; const effectiveReward = options?.reward;
const effectiveManifestData = options?.manifestData;
const bundleInteraction = interactionsLoader.type() == 'warp' && !effectiveDisableBundling; const bundleInteraction = interactionsLoader.type() == 'warp' && !effectiveDisableBundling;
@@ -358,7 +369,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
return await this.bundleInteraction(input, { return await this.bundleInteraction(input, {
tags: effectiveTags, tags: effectiveTags,
strict: effectiveStrict, strict: effectiveStrict,
vrf: effectiveVrf vrf: effectiveVrf,
manifestData: effectiveManifestData
}); });
} else { } else {
const interactionTx = await this.createInteraction( const interactionTx = await this.createInteraction(
@@ -398,16 +410,25 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags: Tags; tags: Tags;
strict: boolean; strict: boolean;
vrf: boolean; vrf: boolean;
manifestData: InteractionManifestData;
} }
): Promise<WriteInteractionResponse | null> { ): Promise<WriteInteractionResponse | null> {
this.logger.info('Bundle interaction input', input); 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( const interactionDataItem = await this.createInteractionDataItem(
input, input,
options.tags, options.tags,
emptyTransfer, emptyTransfer,
options.strict, options.strict,
options.vrf options.vrf,
options.manifestData
); );
const response = this._warpFetchWrapper.fetch( const response = this._warpFetchWrapper.fetch(
@@ -436,7 +457,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags: Tags, tags: Tags,
transfer: ArTransfer, transfer: ArTransfer,
strict: boolean, strict: boolean,
vrf = false vrf = false,
manifestData: InteractionManifestData
) { ) {
if (this._evaluationOptions.internalWrites) { if (this._evaluationOptions.internalWrites) {
// it modifies tags // it modifies tags
@@ -447,18 +469,33 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags.push(new Tag(WARP_TAGS.REQUEST_VRF, 'true')); tags.push(new Tag(WARP_TAGS.REQUEST_VRF, 'true'));
} }
const interactionTags = createInteractionTagsList( let interactionTags = createInteractionTagsList(
this._contractTxId, this._contractTxId,
input, input,
this.warp.environment === 'testnet', this.warp.environment === 'testnet',
tags tags
); );
let data: InteractionDataField<Input> | string;
if (tagsExceedLimit(interactionTags)) { 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<Input>),
manifest: this.createManifest(manifestData)
};
}
data = data ? JSON.stringify(data) : Math.random().toString().slice(-4);
const bundlerSigner = this._signature.bundlerSigner; const bundlerSigner = this._signature.bundlerSigner;
if (!bundlerSigner) { if (!bundlerSigner) {
@@ -475,6 +512,14 @@ export class HandlerBasedContract<State> implements Contract<State> {
await interactionDataItem.sign(bundlerSigner); 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) { if (!this._evaluationOptions.internalWrites && strict) {
await this.checkInteractionInStrictMode(interactionDataItem.owner, input, tags, transfer, strict, vrf); await this.checkInteractionInStrictMode(interactionDataItem.owner, input, tags, transfer, strict, vrf);
} }
@@ -1142,4 +1187,22 @@ export class HandlerBasedContract<State> implements Contract<State> {
} }
this._children = []; 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;
}
} }

View File

@@ -39,7 +39,8 @@ export const WARP_TAGS = {
UPLOADER_TX_ID: 'Uploader-Tx-Id', UPLOADER_TX_ID: 'Uploader-Tx-Id',
WARP_TESTNET: 'Warp-Testnet', WARP_TESTNET: 'Warp-Testnet',
MANIFEST: 'Contract-Manifest', MANIFEST: 'Contract-Manifest',
NONCE: 'Nonce' NONCE: 'Nonce',
INPUT_FORMAT: 'Input-Format'
} as const; } as const;
export type WarpTags = ObjectValues<typeof WARP_TAGS>; export type WarpTags = ObjectValues<typeof WARP_TAGS>;

View File

@@ -120,6 +120,7 @@ export function createInteractionTagsList<Input>(
interactionTags.push(new Tag(SMART_WEAVE_TAGS.SDK, 'Warp')); 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.CONTRACT_TX_ID, contractId));
interactionTags.push(new Tag(SMART_WEAVE_TAGS.INPUT, JSON.stringify(input))); interactionTags.push(new Tag(SMART_WEAVE_TAGS.INPUT, JSON.stringify(input)));
interactionTags.push(new Tag(WARP_TAGS.INPUT_FORMAT, 'tag'));
if (isTestnet) { if (isTestnet) {
interactionTags.push(new Tag(WARP_TAGS.WARP_TESTNET, '1.0.0')); interactionTags.push(new Tag(WARP_TAGS.WARP_TESTNET, '1.0.0'));
} }

View File

@@ -119,3 +119,8 @@ export async function getJsonResponse<T>(response: Promise<Response>): Promise<T
export async function safeGet<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> { export async function safeGet<T>(input: RequestInfo | URL, init?: RequestInit): Promise<T> {
return getJsonResponse(fetch(input, init)); return getJsonResponse(fetch(input, init));
} }
export function isTxIdValid(txId: string): boolean {
const validTxIdRegex = /[a-z0-9_-]{43}/i;
return validTxIdRegex.test(txId);
}