From 8a6d351c2202be9620ae41e60114fa56ff38475a Mon Sep 17 00:00:00 2001 From: andri lim Date: Tue, 25 Dec 2018 14:31:12 +0700 Subject: [PATCH] add internal transactions dump --- nimbus/tracer.nim | 37 ++++++++++++++++++-------------- nimbus/vm/stack.nim | 4 ++++ nimbus/vm/transaction_tracer.nim | 12 +++++++++++ nimbus/vm_state.nim | 8 +++++++ nimbus/vm_types.nim | 2 ++ 5 files changed, 47 insertions(+), 16 deletions(-) diff --git a/nimbus/tracer.nim b/nimbus/tracer.nim index 0657ed4c4..bb70ac328 100644 --- a/nimbus/tracer.nim +++ b/nimbus/tracer.nim @@ -52,6 +52,7 @@ const recipientName = "recipient" minerName = "miner" uncleName = "uncle" + internalTxName = "internalTx" proc traceTransaction*(db: BaseChainDB, header: BlockHeader, body: BlockBody, txIndex: int, tracerFlags: set[TracerFlags] = {}): JsonNode = @@ -63,9 +64,11 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader, captureDB = newCaptureDB(db.db, memoryDB) captureTrieDB = trieDB captureDB captureChainDB = newBaseChainDB(captureTrieDB, false) # prune or not prune? - vmState = newBaseVMState(parent, captureChainDB, tracerFlags) + vmState = newBaseVMState(parent, captureChainDB, tracerFlags + {EnableAccount}) var stateDb = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie) + var stateBefore = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie) + # stateDb and stateBefore will not the same after transaction execution if header.txRoot == BLANK_ROOT_HASH: return newJNull() assert(body.transactions.calcTxRoot == header.txRoot) assert(body.transactions.len != 0) @@ -85,6 +88,7 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader, before.captureAccount(stateDb, sender, senderName) before.captureAccount(stateDb, recipient, recipientName) before.captureAccount(stateDb, header.coinbase, minerName) + stateDiff["beforeRoot"] = %($vmState.blockHeader.stateRoot) let txFee = processTransaction(stateDb, tx, sender, vmState) stateDb.addBalance(header.coinbase, txFee) @@ -94,8 +98,16 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader, after.captureAccount(stateDb, sender, senderName) after.captureAccount(stateDb, recipient, recipientName) after.captureAccount(stateDb, header.coinbase, minerName) + stateDiff["afterRoot"] = %($vmState.blockHeader.stateRoot) break + # internal transactions: + for idx, acc in tracedAccountsPairs(vmState): + before.captureAccount(stateBefore, acc, internalTxName & $idx) + + for idx, acc in tracedAccountsPairs(vmState): + after.captureAccount(stateDb, acc, internalTxName & $idx) + result = vmState.getTracingResult() result["gas"] = %gasUsed result["statediff"] = stateDiff @@ -105,20 +117,6 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader, result.dumpMemoryDB(memoryDB) proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody, dumpState = false): JsonNode = - # TODO: scan tracing result and find internal transaction address - # then do account dump as usual, before and after - # cons: unreliable and prone to error. - # pros: no need to map account secure key back to address. - - # alternative: capture state trie using captureDB, and then dump every - # account for this block. - # cons: we need to map account secure key back to account address. - # pros: no account will be missed, we will get all account touched by this block - # we can turn this address capture/mapping only during dumping block state and - # disable it during normal operation. - # also the order of capture needs to be reversed, collecting addresses *after* - # processing block then using that addresses to collect accounts from parent block - let parent = db.getParentHeader(header) memoryDB = newMemoryDB() @@ -126,7 +124,7 @@ proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody, dump captureTrieDB = trieDB captureDB captureChainDB = newBaseChainDB(captureTrieDB, false) # we only need stack dump if we want to scan for internal transaction address - vmState = newBaseVMState(parent, captureChainDB, {EnableTracing, DisableMemory, DisableStorage}) + vmState = newBaseVMState(parent, captureChainDB, {EnableTracing, DisableMemory, DisableStorage, EnableAccount}) var before = newJArray() @@ -158,6 +156,13 @@ proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody, dump for idx, uncle in body.uncles: after.captureAccount(stateAfter, uncle.coinbase, uncleName & $idx) + # internal transactions: + for idx, acc in tracedAccountsPairs(vmState): + before.captureAccount(stateBefore, acc, internalTxName & $idx) + + for idx, acc in tracedAccountsPairs(vmState): + after.captureAccount(stateAfter, acc, internalTxName & $idx) + result = %{"before": before, "after": after} if dumpState: diff --git a/nimbus/vm/stack.nim b/nimbus/vm/stack.nim index 17d281845..9ea76a1ab 100644 --- a/nimbus/vm/stack.nim +++ b/nimbus/vm/stack.nim @@ -122,3 +122,7 @@ proc peek*(stack: Stack): UInt256 = proc `$`*(stack: Stack): string = let values = stack.values.mapIt(&" {$it}").join("\n") &"Stack:\n{values}" + +proc `[]`*(stack: Stack, i: BackwardsIndex, T: typedesc): T = + # This should be used only for tracer/test/debugging + fromStackElement(stack.values[i], result) diff --git a/nimbus/vm/transaction_tracer.nim b/nimbus/vm/transaction_tracer.nim index bb5c47a03..8d6dcb578 100644 --- a/nimbus/vm/transaction_tracer.nim +++ b/nimbus/vm/transaction_tracer.nim @@ -18,6 +18,7 @@ proc initTracer*(tracer: var TransactionTracer, flags: set[TracerFlags] = {}) = tracer.trace["structLogs"] = newJArray() tracer.flags = flags + tracer.accounts = @[] proc traceOpCodeStarted*(tracer: var TransactionTracer, c: BaseComputation, op: Op) = if unlikely tracer.trace.isNil: @@ -48,6 +49,17 @@ proc traceOpCodeStarted*(tracer: var TransactionTracer, c: BaseComputation, op: mem.add(%c.memory.bytes.toOpenArray(i * chunkLen, (i + 1) * chunkLen - 1).toHex()) j["memory"] = mem + if TracerFlags.EnableAccount in tracer.flags: + case op + of Call, CallCode, DelegateCall, StaticCall: + assert(c.stack.values.len > 2) + tracer.accounts.add c.stack[^2, EthAddress] + of SelfDestruct: + assert(c.stack.values.len > 1) + tracer.accounts.add c.stack[^1, EthAddress] + else: + discard + proc traceOpCodeEnded*(tracer: var TransactionTracer, c: BaseComputation, op: Op) = let j = tracer.trace["structLogs"].elems[^1] diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index c91e6da4b..dab5af93a 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -134,3 +134,11 @@ proc enableTracing*(vmState: BaseVMState) = proc disableTracing*(vmState: BaseVMState) = vmState.tracingEnabled = false + +iterator tracedAccounts*(vmState: BaseVMState): EthAddress = + for acc in vmState.tracer.accounts: + yield acc + +iterator tracedAccountsPairs*(vmState: BaseVMState): (int, EthAddress) = + for idx, acc in vmState.tracer.accounts: + yield (idx, acc) diff --git a/nimbus/vm_types.nim b/nimbus/vm_types.nim index b8a8a2102..7f0527e67 100644 --- a/nimbus/vm_types.nim +++ b/nimbus/vm_types.nim @@ -34,11 +34,13 @@ type DisableMemory DisableStack DisableState + EnableAccount TransactionTracer* = object trace*: JsonNode gasRemaining*: GasInt flags*: set[TracerFlags] + accounts*: seq[EthAddress] OpcodeExecutor* = proc(computation: var BaseComputation)