Skip to content

Commit

Permalink
feat: input in interaction data
Browse files Browse the repository at this point in the history
  • Loading branch information
asiaziola committed Nov 22, 2023
1 parent 4ee509c commit 3c07646
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 9 deletions.
1 change: 1 addition & 0 deletions src/contract/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface DREContractStatusResponse<State> {
export type WarpOptions = {
vrf?: boolean;
disableBundling?: boolean;
manifestData?: { [path: string]: string };
};

export type ArweaveOptions = {
Expand Down
79 changes: 71 additions & 8 deletions src/contract/HandlerBasedContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import {
import { LexicographicalInteractionsSorter } 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';
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,
Expand All @@ -42,6 +42,15 @@ import { Crypto } from 'warp-isomorphic';
import { VrfPluginFunctions } from '../core/WarpPlugin';
import { createData, tagsExceedLimit, DataItem, Signer } 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
* of writing SW contracts (ie. using the "handle" function).
Expand Down Expand Up @@ -72,6 +81,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
private _children: HandlerBasedContract<unknown>[] = [];
private _interactionState;
private _dreStates = new Map<string, SortKeyCacheResult<EvalStateResult<State>>>();
private maxInteractionDataItemSizeBytes: number;

constructor(
private readonly _contractTxId: string,
Expand Down Expand Up @@ -259,6 +269,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
const effectiveVrf = options?.vrf === true;
const effectiveDisableBundling = options?.disableBundling === true;
const effectiveReward = options?.reward;
const effectiveManifestData = options?.manifestData;

const bundleInteraction = interactionsLoader.type() == 'warp' && !effectiveDisableBundling;

Expand All @@ -285,7 +296,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
return await this.bundleInteraction(input, {
tags: effectiveTags,
strict: effectiveStrict,
vrf: effectiveVrf
vrf: effectiveVrf,
manifestData: effectiveManifestData
});
} else {
const interactionTx = await this.createInteraction(
Expand Down Expand Up @@ -325,16 +337,25 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags: Tags;
strict: boolean;
vrf: boolean;
manifestData: InteractionManifestData;
}
): Promise<WriteInteractionResponse | null> {
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(
Expand Down Expand Up @@ -363,7 +384,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
tags: Tags,
transfer: ArTransfer,
strict: boolean,
vrf = false
vrf = false,
manifestData: InteractionManifestData
) {
if (this._evaluationOptions.internalWrites) {
// it modifies tags
Expand All @@ -374,18 +396,33 @@ export class HandlerBasedContract<State> implements Contract<State> {
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<Input> | 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
};
}

if (manifestData) {
data = {
...(data as InteractionData<Input>),
manifest: this.createManifest(manifestData)
};
}

const data = Math.random().toString().slice(-4);
data = data ? JSON.stringify(data) : Math.random().toString().slice(-4);

const bundlerSigner = this._signature.bundlerSigner;

if (!bundlerSigner) {
Expand All @@ -402,6 +439,14 @@ export class HandlerBasedContract<State> implements Contract<State> {
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);
}
Expand Down Expand Up @@ -1055,4 +1100,22 @@ export class HandlerBasedContract<State> implements Contract<State> {
}
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;
}
}
3 changes: 2 additions & 1 deletion src/core/KnownTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof WARP_TAGS>;
1 change: 1 addition & 0 deletions src/legacy/create-interaction-tx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ export function createInteractionTagsList<Input>(
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'));
}
Expand Down
5 changes: 5 additions & 0 deletions src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
return getJsonResponse(fetch(input, init));
}

export function isTxIdValid(txId: string): boolean {
const validTxIdRegex = /[a-z0-9_-]{43}/i;
return validTxIdRegex.test(txId);
}

0 comments on commit 3c07646

Please sign in to comment.