implement dumpBlockState
This commit is contained in:
parent
35c1c7e075
commit
6dc4b0be9a
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}
|
||||||
|
|
Loading…
Reference in New Issue