From 8616257a0d9da9048ff451bae0d4e6b59beeba01 Mon Sep 17 00:00:00 2001 From: ppedziwiatr Date: Mon, 3 Jan 2022 01:46:49 +0100 Subject: [PATCH] perf: unnecessary call to arweave.networkInfo when block height is request from client #70 --- .../regression/test-cases/read-state.json | 1 - src/contract/Contract.ts | 2 +- src/contract/HandlerBasedContract.ts | 34 +++++++++++++------ .../modules/impl/ContractDefinitionLoader.ts | 26 +++++++++++--- src/core/modules/impl/ContractHandlerApi.ts | 7 ++-- .../modules/impl/DefaultStateEvaluator.ts | 9 +++-- .../impl/RedstoneGatewayInteractionsLoader.ts | 1 + src/logging/Benchmark.ts | 4 +++ .../CacheableContractInteractionsLoader.ts | 4 +++ src/utils/utils.ts | 10 ++++++ tools/contract.ts | 29 +++++++++------- yarn.lock | 14 +------- 12 files changed, 90 insertions(+), 51 deletions(-) diff --git a/src/__tests__/regression/test-cases/read-state.json b/src/__tests__/regression/test-cases/read-state.json index eb7107d11..bd049b7c4 100644 --- a/src/__tests__/regression/test-cases/read-state.json +++ b/src/__tests__/regression/test-cases/read-state.json @@ -86,7 +86,6 @@ "2caOW6ol9T8LHCMO8tVAx4GHRwv1q3fFc79KzKOtoww", "HdZBZa0GfOEUYCubwvoSyxGUUgPmgy7RJb5l77T21bE", "7Dp5r-UpZLDvHqsDbZbqWhCBwbYdJMKBuC3tFC-FF7U", - "YLVpmhSq5JmLltfg6R-5fL04rIRPrlSU22f6RQ6VyYE", "EOtTxCGktZe_J2DTM0D5h04crjlpjgeygA1R6Pmo_qM", "92Tq6BKm6pvVkKW8_6Fb13QWTdUzBRLnG9scMBNWYZ4", "w27141UQGgrCFhkiw9tL7A0-qWMQjbapU3mq2TfI4Cg", diff --git a/src/contract/Contract.ts b/src/contract/Contract.ts index 9e36ac94e..839fc992a 100644 --- a/src/contract/Contract.ts +++ b/src/contract/Contract.ts @@ -146,7 +146,7 @@ export interface Contract { * "root" contract network info - so that the whole execution is performed with the * same network info) */ - getNetworkInfo(): NetworkInfoInterface; + getNetworkInfo(): Partial; /** * Get the block height requested by user for the given interaction with contract diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index 501e40568..d1d1eb0a0 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -45,7 +45,7 @@ export class HandlerBasedContract implements Contract { * Only the 'root' contract call should read this data from Arweave - all the inner calls ("child" contracts) * should reuse this data from the parent ("calling") contract. */ - private _networkInfo?: NetworkInfoInterface = null; + private _networkInfo?: Partial = null; private _rootBlockHeight: number = null; @@ -98,10 +98,10 @@ export class HandlerBasedContract implements Contract { contractTxId: this._contractTxId, currentTx }); + const initBenchmark = Benchmark.measure(); this.maybeResetRootContract(blockHeight); const { stateEvaluator } = this.smartweave; - const benchmark = Benchmark.measure(); const executionContext = await this.createExecutionContext(this._contractTxId, blockHeight); this.logger.info('Execution Context', { blockHeight: executionContext.blockHeight, @@ -109,10 +109,16 @@ export class HandlerBasedContract implements Contract { missingInteractions: executionContext.sortedInteractions.length, cachedStateHeight: executionContext.cachedState?.cachedHeight }); - this.logger.debug('context', benchmark.elapsed()); - benchmark.reset(); + initBenchmark.stop(); + + const stateBenchmark = Benchmark.measure(); const result = await stateEvaluator.eval(executionContext, currentTx || []); - this.logger.debug('state', benchmark.elapsed()); + stateBenchmark.stop(); + + this.logger.info('Benchmark', { + 'init time': initBenchmark.elapsed(), + 'evaluation time': stateBenchmark.elapsed() + }); return result as EvalStateResult; } @@ -232,7 +238,7 @@ export class HandlerBasedContract implements Contract { return this._callStack; } - getNetworkInfo(): NetworkInfoInterface { + getNetworkInfo(): Partial { return this._networkInfo; } @@ -281,9 +287,15 @@ export class HandlerBasedContract implements Contract { const benchmark = Benchmark.measure(); // if this is a "root" call (ie. original call from SmartWeave's client) if (this._parentContract == null) { - this.logger.debug('Reading network info for root call'); - currentNetworkInfo = await arweave.network.getInfo(); - this._networkInfo = currentNetworkInfo; + if (blockHeight) { + this._networkInfo = { + height: blockHeight + }; + } else { + this.logger.debug('Reading network info for root call'); + currentNetworkInfo = await arweave.network.getInfo(); + this._networkInfo = currentNetworkInfo; + } } else { // if that's a call from within contract's source code this.logger.debug('Reusing network info from the calling contract'); @@ -300,7 +312,6 @@ export class HandlerBasedContract implements Contract { blockHeight = currentNetworkInfo.height; } this.logger.debug('network info', benchmark.elapsed()); - benchmark.reset(); const cachedState = await stateEvaluator.latestAvailableState(contractTxId, blockHeight); @@ -309,6 +320,9 @@ export class HandlerBasedContract implements Contract { cachedBlockHeight = cachedState.cachedHeight; } + this.logger.debug('cache lookup', benchmark.elapsed()); + benchmark.reset(); + let contractDefinition, interactions = [], sortedInteractions = [], diff --git a/src/core/modules/impl/ContractDefinitionLoader.ts b/src/core/modules/impl/ContractDefinitionLoader.ts index 83f27acf9..7cca83c14 100644 --- a/src/core/modules/impl/ContractDefinitionLoader.ts +++ b/src/core/modules/impl/ContractDefinitionLoader.ts @@ -1,4 +1,12 @@ -import { ContractDefinition, DefinitionLoader, getTag, LoggerFactory, SmartWeaveTags, SwCache } from '@smartweave'; +import { + Benchmark, + ContractDefinition, + DefinitionLoader, + getTag, + LoggerFactory, + SmartWeaveTags, + SwCache +} from '@smartweave'; import Arweave from 'arweave'; import Transaction from 'arweave/web/lib/transaction'; @@ -8,7 +16,7 @@ export class ContractDefinitionLoader implements DefinitionLoader { constructor( private readonly arweave: Arweave, // TODO: cache should be removed from the core layer and implemented in a wrapper of the core implementation - private readonly cache?: SwCache> + protected readonly cache?: SwCache> ) {} async load(contractTxId: string, forcedSrcTxId?: string): Promise> { @@ -16,23 +24,33 @@ export class ContractDefinitionLoader implements DefinitionLoader { this.logger.debug('ContractDefinitionLoader: Hit from cache!'); return Promise.resolve(this.cache?.get(contractTxId) as ContractDefinition); } - + const benchmark = Benchmark.measure(); const contract = await this.doLoad(contractTxId, forcedSrcTxId); + this.logger.info(`Contract definition loaded in: ${benchmark.elapsed()}`); this.cache?.put(contractTxId, contract); return contract; } async doLoad(contractTxId: string, forcedSrcTxId?: string): Promise> { + const benchmark = Benchmark.measure(); const contractTx = await this.arweave.transactions.get(contractTxId); const owner = await this.arweave.wallets.ownerToAddress(contractTx.owner); + this.logger.debug('Contract tx and owner', benchmark.elapsed()); + benchmark.reset(); const contractSrcTxId = forcedSrcTxId ? forcedSrcTxId : getTag(contractTx, SmartWeaveTags.CONTRACT_SRC_TX_ID); - const minFee = getTag(contractTx, SmartWeaveTags.MIN_FEE); + this.logger.debug('Tags decoding', benchmark.elapsed()); + benchmark.reset(); + const contractSrcTx = await this.arweave.transactions.get(contractSrcTxId); + this.logger.debug('Contract src tx load', benchmark.elapsed()); + benchmark.reset(); + const src = contractSrcTx.get('data', { decode: true, string: true }); const initState = JSON.parse(await this.evalInitialState(contractTx)); + this.logger.debug('Parsing src and init state', benchmark.elapsed()); return { txId: contractTxId, diff --git a/src/core/modules/impl/ContractHandlerApi.ts b/src/core/modules/impl/ContractHandlerApi.ts index 8885c83da..5e1d8bd33 100644 --- a/src/core/modules/impl/ContractHandlerApi.ts +++ b/src/core/modules/impl/ContractHandlerApi.ts @@ -16,6 +16,7 @@ import { } from '@smartweave'; import BigNumber from 'bignumber.js'; import * as clarity from '@weavery/clarity'; +import * as v8 from "v8"; export class ContractHandlerApi implements HandlerApi { private readonly contractLogger: RedStoneLogger; @@ -63,11 +64,7 @@ export class ContractHandlerApi implements HandlerApi { this.assignWrite(executionContext, currentTx); this.assignRefreshState(executionContext); - // strangely - state is for some reason modified for some contracts (eg. YLVpmhSq5JmLltfg6R-5fL04rIRPrlSU22f6RQ6VyYE) - // when calling any async (even simple timeout) function here... - // that's (ie. deepCopy) a dumb workaround for this issue - // see https://github.com/ArweaveTeam/SmartWeave/pull/92 for more details - const handlerResult = deepCopy(await Promise.race([timeoutPromise, handler(stateCopy, interaction)])); + const handlerResult = await Promise.race([timeoutPromise, handler(stateCopy, interaction)]); if (handlerResult && (handlerResult.state || handlerResult.result)) { return { diff --git a/src/core/modules/impl/DefaultStateEvaluator.ts b/src/core/modules/impl/DefaultStateEvaluator.ts index ba6746b23..ef6f33f3e 100644 --- a/src/core/modules/impl/DefaultStateEvaluator.ts +++ b/src/core/modules/impl/DefaultStateEvaluator.ts @@ -70,7 +70,9 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { let lastEvaluatedInteraction = null; let errorMessage = null; - for (const missingInteraction of missingInteractions) { + const missingInteractionsLength = missingInteractions.length; + for (let i = 0; i < missingInteractionsLength; i++) { + const missingInteraction = missingInteractions[i]; const singleInteractionBenchmark = Benchmark.measure(); const interactionTx: GQLNodeInterface = missingInteraction.node; @@ -81,7 +83,6 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { }/${missingInteractions.length} [of all:${sortedInteractions.length}]` ); - // verifying whether state isn't already available for this exact interaction. const isInteractWrite = this.tagsParser.isInteractWrite(missingInteraction, contractDefinition.txId); this.logger.debug('interactWrite?:', isInteractWrite); @@ -163,8 +164,6 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { caller: interactionTx.owner.address }; - const intermediaryCacheHit = false; - const interactionData = { interaction, interactionTx, @@ -199,7 +198,7 @@ export abstract class DefaultStateEvaluator implements StateEvaluator { interactionCall.update({ cacheHit: false, - intermediaryCacheHit, + intermediaryCacheHit: false, outputState: stackTrace.saveState ? currentState : undefined, executionTime: singleInteractionBenchmark.elapsed(true) as number, valid: validity[interactionTx.id], diff --git a/src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts b/src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts index 7ab657b73..83dcc26ac 100644 --- a/src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts +++ b/src/core/modules/impl/RedstoneGatewayInteractionsLoader.ts @@ -7,6 +7,7 @@ import { GQLNodeInterface } from '@smartweave'; import 'isomorphic-fetch'; + interface Paging { total: string; limit: number; diff --git a/src/logging/Benchmark.ts b/src/logging/Benchmark.ts index 803126cdb..0788321c1 100644 --- a/src/logging/Benchmark.ts +++ b/src/logging/Benchmark.ts @@ -15,6 +15,10 @@ export class Benchmark { this.end = null; } + public stop() { + this.end = Date.now(); + } + public elapsed(rawValue = false): string | number { if (this.end === null) { this.end = Date.now(); diff --git a/src/plugins/CacheableContractInteractionsLoader.ts b/src/plugins/CacheableContractInteractionsLoader.ts index bf0813222..b09b5fff1 100644 --- a/src/plugins/CacheableContractInteractionsLoader.ts +++ b/src/plugins/CacheableContractInteractionsLoader.ts @@ -1,4 +1,5 @@ import { + Benchmark, BlockHeightKey, BlockHeightSwCache, EvaluationOptions, @@ -26,6 +27,7 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader { toBlockHeight: number, evaluationOptions?: EvaluationOptions ): Promise { + const benchmark = Benchmark.measure(); this.logger.debug('Loading interactions', { contractId, fromBlockHeight, @@ -73,6 +75,8 @@ export class CacheableContractInteractionsLoader implements InteractionsLoader { // - that's why "result" variable is not used here await this.cache.put(new BlockHeightKey(contractId, toBlockHeight), valueToCache); + this.logger.debug(`Interactions loaded in ${benchmark.elapsed()}`); + return result; } } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 375d50e3f..421d1c4de 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,11 +1,21 @@ /* eslint-disable */ import cloneDeep from 'lodash/cloneDeep'; +const isNode = new Function('try {return this===global;}catch(e){return false;}'); + +let v8 = null; +if (isNode()) { + v8 = require('v8'); +} + export const sleep = (ms: number): Promise => { return new Promise((resolve) => setTimeout(resolve, ms)); }; export const deepCopy = (input: unknown): any => { + if (v8) { + return v8.deserialize(v8.serialize(input)); + } return cloneDeep(input); // note: parse/stringify combination is slooow: https://jsben.ch/bWfk9 //return JSON.parse(JSON.stringify(input, mapReplacer), mapReviver); diff --git a/tools/contract.ts b/tools/contract.ts index 8b03c2243..1a479b518 100644 --- a/tools/contract.ts +++ b/tools/contract.ts @@ -9,8 +9,13 @@ import {SmartWeaveNodeFactory} from '../src/core/node/SmartWeaveNodeFactory'; const logger = LoggerFactory.INST.create('Contract'); -LoggerFactory.use(new TsLogFactory()); -LoggerFactory.INST.logLevel('info'); +// LoggerFactory.use(new TsLogFactory()); +LoggerFactory.INST.logLevel("error"); +LoggerFactory.INST.logLevel("debug", "ArweaveGatewayInteractionsLoader"); +LoggerFactory.INST.logLevel("debug", "HandlerBasedContract"); +LoggerFactory.INST.logLevel("debug", "ContractDefinitionLoader"); +LoggerFactory.INST.logLevel("debug", "CacheableContractInteractionsLoader"); + async function main() { @@ -23,35 +28,35 @@ async function main() { }); const contractTxId = 'Daj-MNSnH55TDfxqC7v4eq0lKzVIwh98srUaWqyuZtY'; + //const contractTxId = 't9T7DIOGxx4VWXoCEeYYarFYeERTpWIC1V3y-BPZgKE'; //const interactionsLoader = new FromFileInteractionsLoader(path.join(__dirname, 'data', 'interactions.json')); // const smartweave = SmartWeaveWebFactory.memCachedBased(arweave).setInteractionsLoader(interactionsLoader).build(); const smartweaveR = SmartWeaveWebFactory - .memCachedBased(arweave, 1) - .setInteractionsLoader(new RedstoneGatewayInteractionsLoader( - 'https://gateway.redstone.finance') - ).build(); + .memCachedBased(arweave, 1).build(); - const usedBefore = Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100 + /* const usedBefore = Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100 const contractR = smartweaveR.contract(contractTxId); const {state, validity} = await contractR.readState(); const usedAfter = Math.round((process.memoryUsage().heapUsed / 1024 / 1024) * 100) / 100 logger.warn("Heap used in MB", { usedBefore, usedAfter - }); + });*/ - const smartweave = SmartWeaveWebFactory.memCached(arweave, 1); - const contract = smartweave.contract(contractTxId); + const smartweave = SmartWeaveWebFactory.memCached(arweave); + const contract = smartweave.contract(contractTxId).setEvaluationOptions({ + updateCacheForEachInteraction: false + }); const result = await contract.readState(); //fs.writeFileSync(path.join(__dirname, 'data', 'validity.json'), JSON.stringify(validity)); //fs.writeFileSync(path.join(__dirname, 'data', 'validity_old.json'), JSON.stringify(result.validity)); - fs.writeFileSync(path.join(__dirname, 'data', 'state_redstone.json'), JSON.stringify(state)); - fs.writeFileSync(path.join(__dirname, 'data', 'state_arweave.json'), JSON.stringify(result.state)); + //fs.writeFileSync(path.join(__dirname, 'data', 'state_redstone.json'), JSON.stringify(state)); + //fs.writeFileSync(path.join(__dirname, 'data', 'state_arweave.json'), JSON.stringify(result.state)); // console.log('second read'); // await lootContract.readState(); diff --git a/yarn.lock b/yarn.lock index 988e41c98..27d7df955 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2108,13 +2108,6 @@ cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== -cli-table@^0.3.11: - version "0.3.11" - resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.11.tgz#ac69cdecbe81dccdba4889b9a18b7da312a9d3ee" - integrity sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ== - dependencies: - colors "1.0.3" - cli-width@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" @@ -2212,12 +2205,7 @@ colorette@2.0.16: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== -colors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= - -colors@^1.3.3, colors@^1.4.0: +colors@^1.3.3: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==