2018-12-03 10:54:19 +00:00
|
|
|
import
|
2018-12-03 16:22:08 +00:00
|
|
|
db/[db_chain, state_db, capturedb], eth_common, utils, json,
|
2018-12-04 11:42:55 +00:00
|
|
|
constants, vm_state, vm_types, transaction, p2p/executor,
|
2018-12-12 03:40:37 +00:00
|
|
|
eth_trie/db, nimcrypto, strutils, ranges, ./utils/addresses,
|
2018-12-12 09:41:18 +00:00
|
|
|
chronicles
|
2018-12-03 16:22:08 +00:00
|
|
|
|
|
|
|
proc getParentHeader(self: BaseChainDB, header: BlockHeader): BlockHeader =
|
|
|
|
self.getBlockHeader(header.parentHash)
|
|
|
|
|
|
|
|
proc prefixHex(x: openArray[byte]): string =
|
|
|
|
"0x" & toHex(x, true)
|
2018-12-03 10:54:19 +00:00
|
|
|
|
2018-12-12 03:40:37 +00:00
|
|
|
proc getSender(tx: Transaction): EthAddress =
|
|
|
|
if not tx.getSender(result):
|
|
|
|
raise newException(ValueError, "Could not get sender")
|
|
|
|
|
|
|
|
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)
|
2018-12-11 09:53:05 +00:00
|
|
|
let account = db.getAccount(address)
|
2018-12-12 03:40:37 +00:00
|
|
|
jaccount["nonce"] = %(account.nonce.toHex)
|
|
|
|
jaccount["balance"] = %(account.balance.toHex)
|
2018-12-11 09:53:05 +00:00
|
|
|
|
|
|
|
let code = db.getCode(address)
|
2018-12-12 09:41:18 +00:00
|
|
|
jaccount["codeHash"] = %(($account.codeHash).toLowerAscii)
|
2018-12-12 03:40:37 +00:00
|
|
|
jaccount["code"] = %(toHex(code.toOpenArray, true))
|
2018-12-12 09:41:18 +00:00
|
|
|
jaccount["storageRoot"] = %(($account.storageRoot).toLowerAscii)
|
2018-12-11 09:53:05 +00:00
|
|
|
|
|
|
|
var storage = newJObject()
|
|
|
|
for key, value in db.storage(address):
|
|
|
|
storage[key.dumpHex] = %(value.dumpHex)
|
2018-12-12 03:40:37 +00:00
|
|
|
jaccount["storage"] = storage
|
2018-12-11 09:53:05 +00:00
|
|
|
|
2018-12-12 03:40:37 +00:00
|
|
|
n.add jaccount
|
2018-12-11 09:53:05 +00:00
|
|
|
|
2018-12-12 15:18:46 +00:00
|
|
|
proc dumpMemoryDB*(node: JsonNode, memoryDB: TrieDatabaseRef) =
|
|
|
|
var n = newJObject()
|
|
|
|
for k, v in pairsInMemoryDB(memoryDB):
|
|
|
|
n[k.prefixHex] = %v.prefixHex
|
|
|
|
node["state"] = n
|
|
|
|
|
2018-12-12 03:40:37 +00:00
|
|
|
const
|
|
|
|
senderName = "sender"
|
|
|
|
recipientName = "recipient"
|
|
|
|
minerName = "miner"
|
|
|
|
uncleName = "uncle"
|
2018-12-11 09:53:05 +00:00
|
|
|
|
2018-12-03 10:54:19 +00:00
|
|
|
proc traceTransaction*(db: BaseChainDB, header: BlockHeader,
|
2018-12-04 11:42:55 +00:00
|
|
|
body: BlockBody, txIndex: int, tracerFlags: set[TracerFlags] = {}): JsonNode =
|
2018-12-03 16:22:08 +00:00
|
|
|
let
|
2018-12-04 01:53:21 +00:00
|
|
|
parent = db.getParentHeader(header)
|
2018-12-04 02:01:56 +00:00
|
|
|
# we add a memory layer between backend/lower layer db
|
2018-12-04 01:53:21 +00:00
|
|
|
# and capture state db snapshot during transaction execution
|
2018-12-03 16:22:08 +00:00
|
|
|
memoryDB = newMemoryDB()
|
|
|
|
captureDB = newCaptureDB(db.db, memoryDB)
|
|
|
|
captureTrieDB = trieDB captureDB
|
2018-12-04 02:01:56 +00:00
|
|
|
captureChainDB = newBaseChainDB(captureTrieDB, false) # prune or not prune?
|
2018-12-11 09:27:22 +00:00
|
|
|
vmState = newBaseVMState(parent, captureChainDB, tracerFlags)
|
2018-12-04 02:01:56 +00:00
|
|
|
|
2018-12-04 01:53:21 +00:00
|
|
|
var stateDb = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie)
|
2018-12-12 15:18:46 +00:00
|
|
|
if header.txRoot == BLANK_ROOT_HASH: return newJNull()
|
2018-12-04 01:53:21 +00:00
|
|
|
assert(body.transactions.calcTxRoot == header.txRoot)
|
2018-12-03 10:54:19 +00:00
|
|
|
assert(body.transactions.len != 0)
|
|
|
|
|
2018-12-11 09:53:05 +00:00
|
|
|
var
|
|
|
|
gasUsed: GasInt
|
2018-12-12 09:41:18 +00:00
|
|
|
before = newJArray()
|
|
|
|
after = newJArray()
|
2018-12-11 09:53:05 +00:00
|
|
|
stateDiff = %{"before": before, "after": after}
|
|
|
|
|
2018-12-03 10:54:19 +00:00
|
|
|
for idx, tx in body.transactions:
|
2018-12-12 03:40:37 +00:00
|
|
|
let sender = tx.getSender
|
|
|
|
let recipient = tx.getRecipient
|
2018-12-11 09:53:05 +00:00
|
|
|
|
2018-12-12 03:40:37 +00:00
|
|
|
if idx == txIndex:
|
|
|
|
vmState.enableTracing()
|
|
|
|
before.captureAccount(stateDb, sender, senderName)
|
|
|
|
before.captureAccount(stateDb, recipient, recipientName)
|
|
|
|
before.captureAccount(stateDb, header.coinbase, minerName)
|
2018-12-11 09:53:05 +00:00
|
|
|
|
2018-12-12 03:40:37 +00:00
|
|
|
let txFee = processTransaction(stateDb, tx, sender, vmState)
|
2018-12-12 09:41:18 +00:00
|
|
|
stateDb.addBalance(header.coinbase, txFee)
|
2018-12-12 03:40:37 +00:00
|
|
|
|
|
|
|
if idx == txIndex:
|
|
|
|
gasUsed = (txFee div tx.gasPrice.u256).truncate(GasInt)
|
|
|
|
after.captureAccount(stateDb, sender, senderName)
|
|
|
|
after.captureAccount(stateDb, recipient, recipientName)
|
|
|
|
after.captureAccount(stateDb, header.coinbase, minerName)
|
|
|
|
break
|
2018-12-03 10:54:19 +00:00
|
|
|
|
2018-12-03 16:22:08 +00:00
|
|
|
result = vmState.getTracingResult()
|
|
|
|
result["gas"] = %gasUsed
|
2018-12-11 09:53:05 +00:00
|
|
|
result["statediff"] = stateDiff
|
2018-12-03 16:22:08 +00:00
|
|
|
|
2018-12-04 01:53:21 +00:00
|
|
|
# now we dump captured state db
|
2018-12-04 02:01:56 +00:00
|
|
|
if TracerFlags.DisableState notin tracerFlags:
|
2018-12-12 15:18:46 +00:00
|
|
|
result.dumpMemoryDB(memoryDB)
|
2018-12-11 10:05:49 +00:00
|
|
|
|
2018-12-12 15:18:46 +00:00
|
|
|
proc dumpBlockState*(db: BaseChainDB, header: BlockHeader, body: BlockBody, dumpState = false): JsonNode =
|
2018-12-12 03:40:37 +00:00
|
|
|
# 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
|
2018-12-12 09:41:18 +00:00
|
|
|
before = newJArray()
|
|
|
|
after = newJArray()
|
2018-12-12 03:40:37 +00:00
|
|
|
stateBefore = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie)
|
|
|
|
stateAfter = newAccountStateDB(captureTrieDB, header.stateRoot, db.pruneTrie)
|
|
|
|
|
2018-12-12 09:41:18 +00:00
|
|
|
for idx, tx in body.transactions:
|
2018-12-12 03:40:37 +00:00
|
|
|
let sender = tx.getSender
|
|
|
|
let recipient = tx.getRecipient
|
2018-12-12 09:41:18 +00:00
|
|
|
before.captureAccount(stateBefore, sender, senderName & $idx)
|
|
|
|
before.captureAccount(stateBefore, recipient, recipientName & $idx)
|
2018-12-12 03:40:37 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
|
2018-12-12 09:41:18 +00:00
|
|
|
for idx, tx in body.transactions:
|
2018-12-12 03:40:37 +00:00
|
|
|
let sender = tx.getSender
|
|
|
|
let recipient = tx.getRecipient
|
2018-12-12 09:41:18 +00:00
|
|
|
after.captureAccount(stateAfter, sender, senderName & $idx)
|
|
|
|
after.captureAccount(stateAfter, recipient, recipientName & $idx)
|
2018-12-12 03:40:37 +00:00
|
|
|
|
|
|
|
after.captureAccount(stateAfter, header.coinbase, minerName)
|
|
|
|
|
|
|
|
for idx, uncle in body.uncles:
|
|
|
|
after.captureAccount(stateAfter, uncle.coinbase, uncleName & $idx)
|
|
|
|
|
|
|
|
result = %{"before": before, "after": after}
|
2018-12-12 04:16:40 +00:00
|
|
|
|
2018-12-12 15:18:46 +00:00
|
|
|
if dumpState:
|
|
|
|
result.dumpMemoryDB(memoryDB)
|
|
|
|
|
2018-12-12 04:16:40 +00:00
|
|
|
proc traceBlock*(db: BaseChainDB, header: BlockHeader, body: BlockBody, tracerFlags: set[TracerFlags] = {}): JsonNode =
|
|
|
|
let
|
|
|
|
parent = db.getParentHeader(header)
|
|
|
|
memoryDB = newMemoryDB()
|
|
|
|
captureDB = newCaptureDB(db.db, memoryDB)
|
|
|
|
captureTrieDB = trieDB captureDB
|
|
|
|
captureChainDB = newBaseChainDB(captureTrieDB, false)
|
|
|
|
vmState = newBaseVMState(parent, captureChainDB, tracerFlags + {EnableTracing})
|
|
|
|
|
|
|
|
var
|
|
|
|
stateDb = newAccountStateDB(captureTrieDB, parent.stateRoot, db.pruneTrie)
|
|
|
|
|
2018-12-12 15:18:46 +00:00
|
|
|
if header.txRoot == BLANK_ROOT_HASH: return newJNull()
|
2018-12-12 04:16:40 +00:00
|
|
|
assert(body.transactions.calcTxRoot == header.txRoot)
|
|
|
|
assert(body.transactions.len != 0)
|
|
|
|
|
|
|
|
var gasUsed = GasInt(0)
|
|
|
|
|
|
|
|
for tx in body.transactions:
|
|
|
|
let
|
|
|
|
sender = tx.getSender
|
|
|
|
txFee = processTransaction(stateDb, tx, sender, vmState)
|
|
|
|
gasUsed = gasUsed + (txFee div tx.gasPrice.u256).truncate(GasInt)
|
|
|
|
|
|
|
|
result = vmState.getTracingResult()
|
2018-12-12 04:31:53 +00:00
|
|
|
result["gas"] = %gasUsed
|
|
|
|
|
2018-12-12 15:18:46 +00:00
|
|
|
if TracerFlags.DisableState notin tracerFlags:
|
|
|
|
result.dumpMemoryDB(memoryDB)
|
|
|
|
|
2018-12-12 04:31:53 +00:00
|
|
|
proc dumpDebuggingMetaData*(db: BaseChainDB, header: BlockHeader, body: BlockBody) =
|
|
|
|
# TODO: tidying this up and make it available to debugging tool
|
|
|
|
let ttrace = traceTransaction(db, header, body, body.transactions.len - 1, {})
|
|
|
|
trace "NIMBUS TRACE", transactionTrace=ttrace.pretty()
|
|
|
|
let dump = dumpBlockState(db, header, body)
|
|
|
|
trace "NIMBUS STATE DUMP", dump=dump.pretty()
|
|
|
|
let blockTrace = traceBlock(db, header, body, {})
|
|
|
|
trace "NIMBUS BLOCK TRACE", blockTrace=blockTrace
|
|
|
|
# dump receipt #195
|