Skip to content

Commit 65c3785

Browse files
committed
feat: remote iw discovery
1 parent b61090c commit 65c3785

File tree

7 files changed

+129
-36
lines changed

7 files changed

+129
-36
lines changed

src/contract/EvaluationOptionsEvaluator.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ export class EvaluationOptionsEvaluator {
112112
private readonly notConflictingEvaluationOptions: (keyof EvaluationOptions)[] = [
113113
'useKVStorage',
114114
'sourceType',
115-
'useConstructor'
115+
'useConstructor',
116+
'remoteInternalWrite'
116117
];
117118

118119
/**

src/contract/HandlerBasedContract.ts

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ import {
1111
} from '../core/modules/impl/HandlerExecutorFactory';
1212
import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter';
1313
import { InteractionsSorter } from '../core/modules/InteractionsSorter';
14-
import { DefaultEvaluationOptions, EvalStateResult, EvaluationOptions } from '../core/modules/StateEvaluator';
14+
import {
15+
DefaultEvaluationOptions,
16+
EvalStateResult,
17+
EvaluationOptions,
18+
InternalWriteEvalResult
19+
} from '../core/modules/StateEvaluator';
1520
import { WARP_TAGS } from '../core/KnownTags';
1621
import { Warp } from '../core/Warp';
1722
import { createDummyTx, createInteractionTx } from '../legacy/create-interaction-tx';
@@ -689,7 +694,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
689694
dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true);
690695
dummyTx.strict = strict;
691696
if (vrf) {
692-
Arweave.utils;
693697
const vrfPlugin = this.warp.maybeLoadPlugin<void, VrfPluginFunctions>('vrf');
694698
if (vrfPlugin) {
695699
dummyTx.vrf = vrfPlugin.process().generateMockVrf(dummyTx.sortKey);
@@ -954,25 +958,63 @@ export class HandlerBasedContract<State> implements Contract<State> {
954958
strict: boolean,
955959
vrf: boolean
956960
) {
957-
const handlerResult = await this.callContract(
958-
input,
959-
'write',
960-
undefined,
961-
undefined,
962-
tags,
963-
transfer,
964-
strict,
965-
vrf,
966-
false
967-
);
961+
const innerWrites = [];
962+
963+
if (this.evaluationOptions().remoteInternalWrite) {
964+
// there's probably a less dumb way of doin' this.
965+
const baseDreUrl = this.evaluationOptions().remoteStateSyncSource.split('/')[1];
966+
967+
const walletAddress = await this._signature.getAddress();
968+
const iwEvalUrl = `https://${baseDreUrl}/internal-write?contractTxId=${this.txId()}&caller=${walletAddress}&vrf=${vrf}&strict=${strict}`;
969+
const result = await getJsonResponse<InternalWriteEvalResult>(fetch(iwEvalUrl));
970+
if (result.errorMessage) {
971+
throw new Error(`Error while generating internal writes, cause: ${result.errorMessage}`);
972+
}
973+
const stringifiedContracts = stringify(result.contracts);
974+
const verified = await Arweave.crypto.verify(
975+
result.publicModulus,
976+
Arweave.utils.stringToBuffer(stringifiedContracts),
977+
Arweave.utils.b64UrlToBuffer(result.signature)
978+
);
979+
if (!verified) {
980+
throw new Error('Could not verify the internal writes response from DRE');
981+
}
982+
innerWrites.push(...result.contracts);
983+
tags.push(
984+
{
985+
name: WARP_TAGS.INTERACT_WRITE_SIG,
986+
value: result.signature
987+
},
988+
{
989+
name: WARP_TAGS.INTERACT_WRITE_SIGNER,
990+
value: result.publicModulus
991+
},
992+
{
993+
name: WARP_TAGS.INTERACT_WRITE_SIG_DATA,
994+
value: stringifiedContracts
995+
}
996+
);
997+
} else {
998+
const handlerResult = await this.callContract(
999+
input,
1000+
'write',
1001+
undefined,
1002+
undefined,
1003+
tags,
1004+
transfer,
1005+
strict,
1006+
vrf,
1007+
false
1008+
);
9681009

969-
if (strict && handlerResult.type !== 'ok') {
970-
throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage));
1010+
if (strict && handlerResult.type !== 'ok') {
1011+
throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage));
1012+
}
1013+
const callStack: ContractCallRecord = this.getCallStack();
1014+
innerWrites.push(...this._innerWritesEvaluator.eval(callStack));
1015+
this.logger.debug('Input', input);
1016+
this.logger.debug('Callstack', callStack.print());
9711017
}
972-
const callStack: ContractCallRecord = this.getCallStack();
973-
const innerWrites = this._innerWritesEvaluator.eval(callStack);
974-
this.logger.debug('Input', input);
975-
this.logger.debug('Callstack', callStack.print());
9761018

9771019
innerWrites.forEach((contractTxId) => {
9781020
tags.push({

src/core/KnownTags.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ export const WARP_TAGS = {
3131
INIT_STATE: 'Init-State',
3232
INIT_STATE_TX: 'Init-State-TX',
3333
INTERACT_WRITE: 'Interact-Write',
34+
INTERACT_WRITE_SIG: 'Interact-Write-Sig',
35+
INTERACT_WRITE_SIGNER: 'Interact-Write-Signer',
36+
INTERACT_WRITE_SIG_DATA: 'Interact-Write-Sig-Data',
3437
WASM_LANG: 'Wasm-Lang',
3538
WASM_LANG_VERSION: 'Wasm-Lang-Version',
3639
WASM_META: 'Wasm-Meta',

src/core/modules/StateEvaluator.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ export class EvalStateResult<State> {
103103

104104
export type UnsafeClientOptions = 'allow' | 'skip' | 'throw';
105105

106+
export type InternalWriteEvalResult = {
107+
contracts: string[];
108+
signature: string;
109+
publicModulus: string;
110+
errorMessage: string;
111+
};
112+
106113
export class DefaultEvaluationOptions implements EvaluationOptions {
107114
// default = true - still cannot decide whether true or false should be the default.
108115
// "false" may lead to some fairly simple attacks on contract, if the contract
@@ -150,6 +157,8 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
150157
remoteStateSyncSource = 'https://dre-1.warp.cc/contract';
151158

152159
useConstructor = false;
160+
161+
remoteInternalWrite = false;
153162
}
154163

155164
// an interface for the contract EvaluationOptions - can be used to change the behaviour of some features.
@@ -238,4 +247,8 @@ export interface EvaluationOptions {
238247

239248
// remote source for fetching most recent contract state, only applicable if remoteStateSyncEnabled is set to true
240249
remoteStateSyncSource: string;
250+
251+
// whether the internal writes discovery should evaluate locally - or by the trusted D.R.E. node.
252+
// if set to 'true', the D.R.E. from the 'remoteStateSyncSource' will be used.
253+
remoteInternalWrite: boolean;
241254
}

src/core/modules/impl/DefaultStateEvaluator.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { TagsParser } from './TagsParser';
1414
import { VrfPluginFunctions } from '../../WarpPlugin';
1515
import { BasicSortKeyCache } from '../../../cache/BasicSortKeyCache';
1616
import { InnerWritesEvaluator } from '../../../contract/InnerWritesEvaluator';
17+
import stringify from 'safe-stable-stringify';
1718

1819
type EvaluationProgressInput = {
1920
contractTxId: string;
@@ -245,22 +246,34 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
245246
}
246247

247248
if (internalWrites && contract.isRoot() && result.type === 'ok') {
248-
const innerWritesEvaluator = new InnerWritesEvaluator();
249-
const iwEvaluatorResult = [];
250-
innerWritesEvaluator.evalForeignCalls(contract.txId(), interactionCall, iwEvaluatorResult, false);
251-
const tagsInnerWrites = this.tagsParser.getInteractWritesContracts(missingInteraction);
252-
if (
253-
iwEvaluatorResult.length == tagsInnerWrites.length &&
254-
tagsInnerWrites.every((elem) => iwEvaluatorResult.includes(elem))
255-
) {
256-
validity[missingInteraction.id] = result.type === 'ok';
257-
currentState = result.state;
249+
const iwSigData = this.tagsParser.getInternalWritesSigTags(missingInteraction);
250+
if (iwSigData) {
251+
const verified = await Arweave.crypto.verify(
252+
iwSigData.publicModulus,
253+
Arweave.utils.stringToBuffer(stringify(iwSigData.contracts)),
254+
Arweave.utils.b64UrlToBuffer(iwSigData.signature)
255+
);
256+
if (!verified) {
257+
throw new Error('Could not verify the internal writes response from DRE');
258+
}
258259
} else {
259-
validity[missingInteraction.id] = false;
260-
errorMessage = `[SDK] Inner writes do not match - tags: ${tagsInnerWrites}, evaluated: ${iwEvaluatorResult}`;
261-
// console.error(errorMessage);
262-
// console.dir(interactionCall, { depth: null });
263-
errorMessages[missingInteraction.id] = errorMessage;
260+
const innerWritesEvaluator = new InnerWritesEvaluator();
261+
const iwEvaluatorResult = [];
262+
innerWritesEvaluator.evalForeignCalls(contract.txId(), interactionCall, iwEvaluatorResult, false);
263+
const tagsInnerWrites = this.tagsParser.getInteractWritesContracts(missingInteraction);
264+
if (
265+
iwEvaluatorResult.length == tagsInnerWrites.length &&
266+
tagsInnerWrites.every((elem) => iwEvaluatorResult.includes(elem))
267+
) {
268+
validity[missingInteraction.id] = result.type === 'ok';
269+
currentState = result.state;
270+
} else {
271+
validity[missingInteraction.id] = false;
272+
errorMessage = `[SDK] Inner writes do not match - tags: ${tagsInnerWrites}, evaluated: ${iwEvaluatorResult}`;
273+
// console.error(errorMessage);
274+
// console.dir(interactionCall, { depth: null });
275+
errorMessages[missingInteraction.id] = errorMessage;
276+
}
264277
}
265278
} else {
266279
validity[missingInteraction.id] = result.type === 'ok';

src/core/modules/impl/TagsParser.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { SMART_WEAVE_TAGS, WARP_TAGS } from '../../KnownTags';
22
import { GQLNodeInterface, GQLTagInterface } from '../../../legacy/gqlResult';
33
import { LoggerFactory } from '../../../logging/LoggerFactory';
44
import { Transaction } from '../../../utils/types/arweave-types';
5+
import { InternalWriteEvalResult } from '../StateEvaluator';
56

67
/**
78
* A class that is responsible for retrieving "input" tag from the interaction transaction.
@@ -58,6 +59,22 @@ export class TagsParser {
5859
return interactionTransaction.tags.find((tag) => tag.name === SMART_WEAVE_TAGS.CONTRACT_TX_ID)?.value;
5960
}
6061

62+
getInternalWritesSigTags(interactionTransaction: GQLNodeInterface): InternalWriteEvalResult | null {
63+
const iwSigTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIG);
64+
if (iwSigTag) {
65+
const iwSignerTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIGNER);
66+
const iwSigDataTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIG_DATA);
67+
return {
68+
contracts: JSON.parse(iwSigDataTag),
69+
signature: iwSigTag,
70+
publicModulus: iwSignerTag,
71+
errorMessage: null
72+
};
73+
} else {
74+
return null;
75+
}
76+
}
77+
6178
getContractsWithInputs(interactionTransaction: GQLNodeInterface): Map<string, GQLTagInterface> {
6279
const result = new Map<string, GQLTagInterface>();
6380

@@ -118,4 +135,8 @@ export class TagsParser {
118135
return t.name == WARP_TAGS.REQUEST_VRF && t.value === 'true';
119136
});
120137
}
138+
139+
private findTag(interactionTransaction: GQLNodeInterface, tagName: string): string | undefined {
140+
return interactionTransaction.tags.find((tag) => tag.name === tagName)?.value;
141+
}
121142
}

src/utils/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export async function getJsonResponse<T>(response: Promise<Response>): Promise<T
9292
try {
9393
r = await response;
9494
} catch (e) {
95-
throw new Error(`Error while communicating with gateway: ${JSON.stringify(e)}`);
95+
throw new Error(`Error while communicating with server: ${JSON.stringify(e)}`);
9696
}
9797

9898
if (!r?.ok) {

0 commit comments

Comments
 (0)