Skip to content

Commit

Permalink
feat: remote iw discovery
Browse files Browse the repository at this point in the history
  • Loading branch information
ppedziwiatr committed Jun 21, 2023
1 parent b61090c commit 3d7571e
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 36 deletions.
3 changes: 2 additions & 1 deletion src/contract/EvaluationOptionsEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ export class EvaluationOptionsEvaluator {
private readonly notConflictingEvaluationOptions: (keyof EvaluationOptions)[] = [
'useKVStorage',
'sourceType',
'useConstructor'
'useConstructor',
'remoteInternalWrite'
];

/**
Expand Down
80 changes: 61 additions & 19 deletions src/contract/HandlerBasedContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
} from '../core/modules/impl/HandlerExecutorFactory';
import { LexicographicalInteractionsSorter } from '../core/modules/impl/LexicographicalInteractionsSorter';
import { InteractionsSorter } from '../core/modules/InteractionsSorter';
import { DefaultEvaluationOptions, EvalStateResult, EvaluationOptions } from '../core/modules/StateEvaluator';
import {
DefaultEvaluationOptions,
EvalStateResult,
EvaluationOptions,
InternalWriteEvalResult
} from '../core/modules/StateEvaluator';
import { WARP_TAGS } from '../core/KnownTags';
import { Warp } from '../core/Warp';
import { createDummyTx, createInteractionTx } from '../legacy/create-interaction-tx';
Expand Down Expand Up @@ -689,7 +694,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true);
dummyTx.strict = strict;
if (vrf) {
Arweave.utils;
const vrfPlugin = this.warp.maybeLoadPlugin<void, VrfPluginFunctions>('vrf');
if (vrfPlugin) {
dummyTx.vrf = vrfPlugin.process().generateMockVrf(dummyTx.sortKey);
Expand Down Expand Up @@ -954,25 +958,63 @@ export class HandlerBasedContract<State> implements Contract<State> {
strict: boolean,
vrf: boolean
) {
const handlerResult = await this.callContract(
input,
'write',
undefined,
undefined,
tags,
transfer,
strict,
vrf,
false
);
const innerWrites = [];

if (this.evaluationOptions().remoteInternalWrite) {
// there's probably a less dumb way of doin' this.
const baseDreUrl = this.evaluationOptions().remoteStateSyncSource.split('/')[1];

const walletAddress = await this._signature.getAddress();
const iwEvalUrl = `https://${baseDreUrl}/internal-write?contractTxId=${this.txId()}&caller=${walletAddress}&vrf=${vrf}&strict=${strict}`;
const result = await getJsonResponse<InternalWriteEvalResult>(fetch(iwEvalUrl));
if (result.errorMessage) {
throw new Error(`Error while generating internal writes, cause: ${result.errorMessage}`);
}
const stringifiedContracts = stringify(result.contracts);
const verified = await Arweave.crypto.verify(
result.publicModulus,
Arweave.utils.stringToBuffer(stringifiedContracts),
Arweave.utils.b64UrlToBuffer(result.signature)
);
if (!verified) {
throw new Error('Could not verify the internal writes response from DRE');
}
innerWrites.push(...result.contracts);
tags.push(
{
name: WARP_TAGS.INTERACT_WRITE_SIG,
value: result.signature
},
{
name: WARP_TAGS.INTERACT_WRITE_SIGNER,
value: result.publicModulus
},
{
name: WARP_TAGS.INTERACT_WRITE_SIG_DATA,
value: stringifiedContracts
}
);
} else {
const handlerResult = await this.callContract(
input,
'write',
undefined,
undefined,
tags,
transfer,
strict,
vrf,
false
);

if (strict && handlerResult.type !== 'ok') {
throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage));
if (strict && handlerResult.type !== 'ok') {
throw Error('Cannot create interaction: ' + JSON.stringify(handlerResult.error || handlerResult.errorMessage));
}
const callStack: ContractCallRecord = this.getCallStack();
const innerWrites = this._innerWritesEvaluator.eval(callStack);
this.logger.debug('Input', input);
this.logger.debug('Callstack', callStack.print());
}
const callStack: ContractCallRecord = this.getCallStack();
const innerWrites = this._innerWritesEvaluator.eval(callStack);
this.logger.debug('Input', input);
this.logger.debug('Callstack', callStack.print());

innerWrites.forEach((contractTxId) => {
tags.push({
Expand Down
3 changes: 3 additions & 0 deletions src/core/KnownTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ export const WARP_TAGS = {
INIT_STATE: 'Init-State',
INIT_STATE_TX: 'Init-State-TX',
INTERACT_WRITE: 'Interact-Write',
INTERACT_WRITE_SIG: 'Interact-Write-Sig',
INTERACT_WRITE_SIGNER: 'Interact-Write-Signer',
INTERACT_WRITE_SIG_DATA: 'Interact-Write-Sig-Data',
WASM_LANG: 'Wasm-Lang',
WASM_LANG_VERSION: 'Wasm-Lang-Version',
WASM_META: 'Wasm-Meta',
Expand Down
13 changes: 13 additions & 0 deletions src/core/modules/StateEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ export class EvalStateResult<State> {

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

export type InternalWriteEvalResult = {
contracts: string[];
signature: string;
publicModulus: string;
errorMessage: string;
};

export class DefaultEvaluationOptions implements EvaluationOptions {
// default = true - still cannot decide whether true or false should be the default.
// "false" may lead to some fairly simple attacks on contract, if the contract
Expand Down Expand Up @@ -150,6 +157,8 @@ export class DefaultEvaluationOptions implements EvaluationOptions {
remoteStateSyncSource = 'https://dre-1.warp.cc/contract';

useConstructor = false;

remoteInternalWrite = false;
}

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

// remote source for fetching most recent contract state, only applicable if remoteStateSyncEnabled is set to true
remoteStateSyncSource: string;

// whether the internal writes discovery should evaluate locally - or by the trusted D.R.E. node.
// if set to 'true', the D.R.E. from the 'remoteStateSyncSource' will be used.
remoteInternalWrite: boolean;
}
43 changes: 28 additions & 15 deletions src/core/modules/impl/DefaultStateEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { TagsParser } from './TagsParser';
import { VrfPluginFunctions } from '../../WarpPlugin';
import { BasicSortKeyCache } from '../../../cache/BasicSortKeyCache';
import { InnerWritesEvaluator } from '../../../contract/InnerWritesEvaluator';
import stringify from "safe-stable-stringify";

type EvaluationProgressInput = {
contractTxId: string;
Expand Down Expand Up @@ -245,22 +246,34 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
}

if (internalWrites && contract.isRoot() && result.type === 'ok') {
const innerWritesEvaluator = new InnerWritesEvaluator();
const iwEvaluatorResult = [];
innerWritesEvaluator.evalForeignCalls(contract.txId(), interactionCall, iwEvaluatorResult, false);
const tagsInnerWrites = this.tagsParser.getInteractWritesContracts(missingInteraction);
if (
iwEvaluatorResult.length == tagsInnerWrites.length &&
tagsInnerWrites.every((elem) => iwEvaluatorResult.includes(elem))
) {
validity[missingInteraction.id] = result.type === 'ok';
currentState = result.state;
const iwSigData = this.tagsParser.getInternalWritesSigTags(missingInteraction);
if (iwSigData) {
const verified = await Arweave.crypto.verify(
iwSigData.publicModulus,
Arweave.utils.stringToBuffer(stringify(iwSigData.contracts)),
Arweave.utils.b64UrlToBuffer(iwSigData.signature)
);
if (!verified) {
throw new Error('Could not verify the internal writes response from DRE');
}
} else {
validity[missingInteraction.id] = false;
errorMessage = `[SDK] Inner writes do not match - tags: ${tagsInnerWrites}, evaluated: ${iwEvaluatorResult}`;
// console.error(errorMessage);
// console.dir(interactionCall, { depth: null });
errorMessages[missingInteraction.id] = errorMessage;
const innerWritesEvaluator = new InnerWritesEvaluator();
const iwEvaluatorResult = [];
innerWritesEvaluator.evalForeignCalls(contract.txId(), interactionCall, iwEvaluatorResult, false);
const tagsInnerWrites = this.tagsParser.getInteractWritesContracts(missingInteraction);
if (
iwEvaluatorResult.length == tagsInnerWrites.length &&
tagsInnerWrites.every((elem) => iwEvaluatorResult.includes(elem))
) {
validity[missingInteraction.id] = result.type === 'ok';
currentState = result.state;
} else {
validity[missingInteraction.id] = false;
errorMessage = `[SDK] Inner writes do not match - tags: ${tagsInnerWrites}, evaluated: ${iwEvaluatorResult}`;
// console.error(errorMessage);
// console.dir(interactionCall, { depth: null });
errorMessages[missingInteraction.id] = errorMessage;
}
}
} else {
validity[missingInteraction.id] = result.type === 'ok';
Expand Down
21 changes: 21 additions & 0 deletions src/core/modules/impl/TagsParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SMART_WEAVE_TAGS, WARP_TAGS } from '../../KnownTags';
import { GQLNodeInterface, GQLTagInterface } from '../../../legacy/gqlResult';
import { LoggerFactory } from '../../../logging/LoggerFactory';
import { Transaction } from '../../../utils/types/arweave-types';
import { InternalWriteEvalResult } from '../StateEvaluator';

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

getInternalWritesSigTags(interactionTransaction: GQLNodeInterface): InternalWriteEvalResult | null {
const iwSigTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIG);
if (iwSigTag) {
const iwSignerTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIGNER);
const iwSigDataTag = this.findTag(interactionTransaction, WARP_TAGS.INTERACT_WRITE_SIG_DATA);
return {
contracts: JSON.parse(iwSigDataTag),
signature: iwSigTag,
publicModulus: iwSignerTag,
errorMessage: null
};
} else {
return null;
}
}

getContractsWithInputs(interactionTransaction: GQLNodeInterface): Map<string, GQLTagInterface> {
const result = new Map<string, GQLTagInterface>();

Expand Down Expand Up @@ -118,4 +135,8 @@ export class TagsParser {
return t.name == WARP_TAGS.REQUEST_VRF && t.value === 'true';
});
}

private findTag(interactionTransaction: GQLNodeInterface, tagName: string): string | undefined {
return interactionTransaction.tags.find((tag) => tag.name === tagName)?.value;
}
}
2 changes: 1 addition & 1 deletion src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export async function getJsonResponse<T>(response: Promise<Response>): Promise<T
try {
r = await response;
} catch (e) {
throw new Error(`Error while communicating with gateway: ${JSON.stringify(e)}`);
throw new Error(`Error while communicating with server: ${JSON.stringify(e)}`);
}

if (!r?.ok) {
Expand Down

0 comments on commit 3d7571e

Please sign in to comment.