From 8dc7ac4284eb1ced6a893759470b57b639e54723 Mon Sep 17 00:00:00 2001 From: ppe Date: Mon, 25 Apr 2022 16:41:37 +0200 Subject: [PATCH] feat: syncState with the execution network --- src/contract/Contract.ts | 9 +++- src/contract/HandlerBasedContract.ts | 24 +++++++++ src/core/modules/StateEvaluator.ts | 5 ++ .../modules/impl/CacheableStateEvaluator.ts | 11 ++++ .../modules/impl/DefaultStateEvaluator.ts | 8 +++ src/legacy/create-tx.ts | 4 +- tools/contract.ts | 1 - tools/sync-state.ts | 50 +++++++++++++++++++ 8 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 tools/sync-state.ts diff --git a/src/contract/Contract.ts b/src/contract/Contract.ts index 35fe44f..3dcd9ea 100644 --- a/src/contract/Contract.ts +++ b/src/contract/Contract.ts @@ -8,7 +8,7 @@ import { InteractionResult, Tags } from '@smartweave'; -import { NetworkInfoInterface } from 'arweave/node/network'; +import {NetworkInfoInterface} from 'arweave/node/network'; export type CurrentTx = { interactionTxId: string; contractTxId: string }; export type BenchmarkStats = { gatewayCommunication: number; stateEvaluation: number; total: number }; @@ -203,4 +203,11 @@ export interface Contract { * calculates state hash using stable stringify */ stateHash(state: State): string; + + /** + * this method allows to sync the state of the local SDK state with the Execution Network - + * to make features like "viewStates" possible when using EN; + * @param nodeAddress - distributed execution network node address + */ + syncState(nodeAddress: string): Promise; } diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index 97f734f..c731aaa 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -677,4 +677,28 @@ export class HandlerBasedContract implements Contract { return hash.digest('hex'); } + + async syncState(nodeAddress: string): Promise { + const { stateEvaluator } = this.smartweave; + const response = await fetch(`${nodeAddress}/state?id=${this._contractTxId}&validity=true&safeHeight=true`) + .then((res) => { + return res.ok ? res.json() : Promise.reject(res); + }) + .catch((error) => { + if (error.body?.message) { + this.logger.error(error.body.message); + } + throw new Error(`Unable to retrieve state. ${error.status}: ${error.body?.message}`); + }); + + await stateEvaluator.syncState( + this._contractTxId, + response.height, + response.lastTransactionId, + response.state, + response.validity + ); + + return this; + } } diff --git a/src/core/modules/StateEvaluator.ts b/src/core/modules/StateEvaluator.ts index 713d381..aea5895 100644 --- a/src/core/modules/StateEvaluator.ts +++ b/src/core/modules/StateEvaluator.ts @@ -65,6 +65,11 @@ export interface StateEvaluator { * allows to manually flush state cache into underneath storage. */ flushCache(): Promise; + + /** + * allows to syncState with an external state source (like RedStone Distributed Execution Network) + */ + syncState(contractTxId: string, blockHeight: number, transactionId: string, state: any, validity: any): Promise; } export class EvalStateResult { diff --git a/src/core/modules/impl/CacheableStateEvaluator.ts b/src/core/modules/impl/CacheableStateEvaluator.ts index d51b63e..3f7179d 100644 --- a/src/core/modules/impl/CacheableStateEvaluator.ts +++ b/src/core/modules/impl/CacheableStateEvaluator.ts @@ -220,4 +220,15 @@ export class CacheableStateEvaluator extends DefaultStateEvaluator { async flushCache(): Promise { return await this.cache.flush(); } + + async syncState( + contractTxId: string, + blockHeight: number, + transactionId: string, + state: any, + validity: any + ): Promise { + const stateToCache = new EvalStateResult(state, validity, transactionId); + await this.cache.put(new BlockHeightKey(contractTxId, blockHeight), stateToCache); + } } diff --git a/src/core/modules/impl/DefaultStateEvaluator.ts b/src/core/modules/impl/DefaultStateEvaluator.ts index a675e0c..ec3b266 100644 --- a/src/core/modules/impl/DefaultStateEvaluator.ts +++ b/src/core/modules/impl/DefaultStateEvaluator.ts @@ -300,4 +300,12 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { ): Promise; abstract flushCache(): Promise; + + abstract syncState( + contractTxId: string, + blockHeight: number, + transactionId: string, + state: any, + validity: any + ): Promise; } diff --git a/src/legacy/create-tx.ts b/src/legacy/create-tx.ts index d6c1e95..b5ee2e2 100644 --- a/src/legacy/create-tx.ts +++ b/src/legacy/create-tx.ts @@ -42,7 +42,9 @@ export async function createTx( interactionTx.addTag(SmartWeaveTags.CONTRACT_TX_ID, contractId); interactionTx.addTag(SmartWeaveTags.INPUT, JSON.stringify(input)); - await arweave.transactions.sign(interactionTx, wallet); + if (wallet) { + await arweave.transactions.sign(interactionTx, wallet); + } return interactionTx; } diff --git a/tools/contract.ts b/tools/contract.ts index 60f6f1f..60f8af9 100644 --- a/tools/contract.ts +++ b/tools/contract.ts @@ -10,7 +10,6 @@ import { import * as fs from 'fs'; import knex from 'knex'; import os from 'os'; -import { readJSON } from '../../redstone-smartweave-examples/src/_utils'; import { TsLogFactory } from '../src/logging/node/TsLogFactory'; import path from "path"; import stringify from "safe-stable-stringify"; diff --git a/tools/sync-state.ts b/tools/sync-state.ts new file mode 100644 index 0000000..026b4e5 --- /dev/null +++ b/tools/sync-state.ts @@ -0,0 +1,50 @@ +/* eslint-disable */ +import Arweave from 'arweave'; +import {LoggerFactory, SmartWeaveNodeFactory} from '../src'; +import fs from 'fs'; +import {JWKInterface} from 'arweave/node/lib/wallet'; + +async function main() { + LoggerFactory.INST.logLevel('info'); + const logger = LoggerFactory.INST.create('deploy'); + + const arweave = Arweave.init({ + host: 'arweave.net', + port: 443, + protocol: 'https' + }); + + try { + const smartweave = SmartWeaveNodeFactory + .memCachedBased(arweave) + .useRedStoneGateway() + .build(); + + const contract = await smartweave.contract("qg5BIOUraunoi6XJzbCC-TgIAypcXyXlVprgg0zRRDE") + .syncState("http://134.209.84.136:8080"); + + const result = await contract + .viewState({ + function: "getNodeDetails", data: { + address: "33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA" + } + }); + + logger.info("Result", result); + + } catch (e) { + logger.error(e) + } + +} + +export function readJSON(path: string): JWKInterface { + const content = fs.readFileSync(path, "utf-8"); + try { + return JSON.parse(content); + } catch (e) { + throw new Error(`File "${path}" does not contain a valid JSON`); + } +} + +main().catch((e) => console.error(e));