implement dumpBlockState

This commit is contained in:
andri lim 2018-12-12 10:40:37 +07:00 committed by zah
parent 35c1c7e075
commit 6dc4b0be9a
3 changed files with 114 additions and 37 deletions

View File

@ -82,9 +82,11 @@ method persistBlocks*(c: Chain, headers: openarray[BlockHeader], bodies: openarr
let success = processBlock(c.db, head, headers[i], bodies[i], vmState) let success = processBlock(c.db, head, headers[i], bodies[i], vmState)
if not success: if not success:
# TODO: move this back into tracer.nim and produce a nice bundle of
# debugging tool metadata
let ttrace = traceTransaction(c.db, headers[i], bodies[i], bodies[i].transactions.len - 1, {}) let ttrace = traceTransaction(c.db, headers[i], bodies[i], bodies[i].transactions.len - 1, {})
trace "NIMBUS TRACE", transactionTrace=ttrace.pretty() trace "NIMBUS TRACE", transactionTrace=ttrace.pretty()
let dump = dumpBlockState(headers[i], bodies[i]) let dump = dumpBlockState(c.db, headers[i], bodies[i])
trace "NIMBUS STATE DUMP", dump=dump.pretty() trace "NIMBUS STATE DUMP", dump=dump.pretty()
assert(success) assert(success)

View File

@ -72,7 +72,7 @@ proc setupDebugRpc*(chainDB: BaseChainDB, rpcsrv: RpcServer) =
blockHash = chainDB.getBlockHash(header.blockNumber) blockHash = chainDB.getBlockHash(header.blockNumber)
body = getBlockBody(blockHash) body = getBlockBody(blockHash)
dumpBlockState(header, body) dumpBlockState(chainDB, header, body)
rpcsrv.rpc("debug_dumpBlockStateByHash") do(data: EthHashStr) -> JsonNode: rpcsrv.rpc("debug_dumpBlockStateByHash") do(data: EthHashStr) -> JsonNode:
## Retrieves the state that corresponds to the block number and returns ## Retrieves the state that corresponds to the block number and returns
@ -85,4 +85,4 @@ proc setupDebugRpc*(chainDB: BaseChainDB, rpcsrv: RpcServer) =
blockHash = chainDB.getBlockHash(header.blockNumber) blockHash = chainDB.getBlockHash(header.blockNumber)
body = getBlockBody(blockHash) body = getBlockBody(blockHash)
dumpBlockState(header, body) dumpBlockState(chainDB, header, body)

View File

@ -1,7 +1,8 @@
import import
db/[db_chain, state_db, capturedb], eth_common, utils, json, db/[db_chain, state_db, capturedb], eth_common, utils, json,
constants, vm_state, vm_types, transaction, p2p/executor, constants, vm_state, vm_types, transaction, p2p/executor,
eth_trie/db, nimcrypto, strutils, ranges, ./utils/addresses eth_trie/db, nimcrypto, strutils, ranges, ./utils/addresses,
sets
proc getParentHeader(self: BaseChainDB, header: BlockHeader): BlockHeader = proc getParentHeader(self: BaseChainDB, header: BlockHeader): BlockHeader =
self.getBlockHeader(header.parentHash) self.getBlockHeader(header.parentHash)
@ -9,33 +10,42 @@ proc getParentHeader(self: BaseChainDB, header: BlockHeader): BlockHeader =
proc prefixHex(x: openArray[byte]): string = proc prefixHex(x: openArray[byte]): string =
"0x" & toHex(x, true) "0x" & toHex(x, true)
proc accountToJson(db: AccountStateDB, address: EthAddress, name: string): JsonNode = proc getSender(tx: Transaction): EthAddress =
result = newJObject() if not tx.getSender(result):
result["name"] = %name raise newException(ValueError, "Could not get sender")
result["address"] = %($address)
proc getRecipient(tx: Transaction): EthAddress =
if tx.isContractCreation:
let sender = tx.getSender()
result = generateAddress(sender, tx.accountNonce)
else:
result = tx.to
proc captureAccount(n: JsonNode, db: AccountStateDB, address: EthAddress, name: string) =
var jaccount = newJObject()
jaccount["name"] = %name
jaccount["address"] = %($address)
let account = db.getAccount(address) let account = db.getAccount(address)
result["nonce"] = %(account.nonce.toHex) jaccount["nonce"] = %(account.nonce.toHex)
result["balance"] = %(account.balance.toHex) jaccount["balance"] = %(account.balance.toHex)
let code = db.getCode(address) let code = db.getCode(address)
result["codeHash"] = %($account.codeHash) jaccount["codeHash"] = %($account.codeHash)
result["code"] = %(toHex(code.toOpenArray, true)) jaccount["code"] = %(toHex(code.toOpenArray, true))
result["storageRoot"] = %($account.storageRoot) jaccount["storageRoot"] = %($account.storageRoot)
var storage = newJObject() var storage = newJObject()
for key, value in db.storage(address): for key, value in db.storage(address):
storage[key.dumpHex] = %(value.dumpHex) storage[key.dumpHex] = %(value.dumpHex)
result["storage"] = storage jaccount["storage"] = storage
proc captureStateAccount(n: JsonNode, db: AccountStateDB, sender: EthAddress, header: BlockHeader, tx: Transaction) = n.add jaccount
n.add accountToJson(db, sender, "sender")
n.add accountToJson(db, header.coinbase, "miner")
if tx.isContractCreation: const
let contractAddress = generateAddress(sender, tx.accountNonce) senderName = "sender"
n.add accountToJson(db, contractAddress, "contract") recipientName = "recipient"
else: minerName = "miner"
n.add accountToJson(db, tx.to, "recipient") uncleName = "uncle"
proc traceTransaction*(db: BaseChainDB, header: BlockHeader, proc traceTransaction*(db: BaseChainDB, header: BlockHeader,
body: BlockBody, txIndex: int, tracerFlags: set[TracerFlags] = {}): JsonNode = body: BlockBody, txIndex: int, tracerFlags: set[TracerFlags] = {}): JsonNode =
@ -61,21 +71,24 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader,
stateDiff = %{"before": before, "after": after} stateDiff = %{"before": before, "after": after}
for idx, tx in body.transactions: for idx, tx in body.transactions:
var sender: EthAddress let sender = tx.getSender
if tx.getSender(sender): let recipient = tx.getRecipient
if idx == txIndex: if idx == txIndex:
vmState.enableTracing() vmState.enableTracing()
before.captureStateAccount(stateDb, sender, header, tx) before.captureAccount(stateDb, sender, senderName)
before.captureAccount(stateDb, recipient, recipientName)
before.captureAccount(stateDb, header.coinbase, minerName)
let txFee = processTransaction(stateDb, tx, sender, vmState) let txFee = processTransaction(stateDb, tx, sender, vmState)
gasUsed = (txFee div tx.gasPrice.u256).truncate(GasInt)
if idx == txIndex: if idx == txIndex:
gasUsed = (txFee div tx.gasPrice.u256).truncate(GasInt)
vmState.disableTracing() vmState.disableTracing()
after.captureStateAccount(stateDb, sender, header, tx) after.captureAccount(stateDb, sender, senderName)
after.captureAccount(stateDb, recipient, recipientName)
after.captureAccount(stateDb, header.coinbase, minerName)
break break
else:
assert(false, "Could not get sender")
result = vmState.getTracingResult() result = vmState.getTracingResult()
result["gas"] = %gasUsed result["gas"] = %gasUsed
@ -88,5 +101,67 @@ proc traceTransaction*(db: BaseChainDB, header: BlockHeader,
n[k.prefixHex] = %v.prefixHex n[k.prefixHex] = %v.prefixHex
result["state"] = n result["state"] = n
proc dumpBlockState*(header: BlockHeader, body: BlockBody): JsonNode = proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody): JsonNode =
discard # 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()
captureDB = newCaptureDB(db.db, memoryDB)
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})
var
before = newJObject()
after = newJObject()
stateBefore = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie)
stateAfter = newAccountStateDB(captureTrieDB, header.stateRoot, db.pruneTrie)
accountSet = initSet[EthAddress]()
for tx in body.transactions:
let sender = tx.getSender
let recipient = tx.getRecipient
if sender notin accountSet:
before.captureAccount(stateBefore, sender, senderName)
accountSet.incl sender
if recipient notin accountSet:
before.captureAccount(stateBefore, recipient, recipientName)
accountSet.incl recipient
before.captureAccount(stateBefore, header.coinbase, minerName)
for idx, uncle in body.uncles:
before.captureAccount(stateBefore, uncle.coinbase, uncleName & $idx)
discard captureChainDB.processBlock(parent, header, body, vmState)
for tx in body.transactions:
let sender = tx.getSender
let recipient = tx.getRecipient
if sender notin accountSet:
after.captureAccount(stateAfter, sender, senderName)
accountSet.incl sender
if recipient notin accountSet:
after.captureAccount(stateAfter, recipient, recipientName)
accountSet.incl recipient
after.captureAccount(stateAfter, header.coinbase, minerName)
for idx, uncle in body.uncles:
after.captureAccount(stateAfter, uncle.coinbase, uncleName & $idx)
result = %{"before": before, "after": after}