Ordered trie for trie root computations (#6610)

Gives a 100x speed boost for this operation which happens during
optimistic sync when computing the block hash.

Co-authored-by: Etan Kissling <etan@nimbus.team>
This commit is contained in:
Jacek Sieka 2024-10-08 22:10:50 +02:00 committed by GitHub
parent e2d65a39a3
commit 7990cc2d1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 56 additions and 125 deletions

View File

@ -9,11 +9,11 @@
import import
std/[json, sequtils, times], std/[json, sequtils, times],
eth/common/[eth_types_rlp, transaction], eth/common/eth_types_rlp,
eth/keys, eth/keys,
eth/p2p/discoveryv5/random2, eth/p2p/discoveryv5/random2,
eth/rlp, eth/rlp,
eth/trie/[db, hexary], eth/trie/ordered_trie,
json_rpc/jsonmarshal, json_rpc/jsonmarshal,
secp256k1, secp256k1,
web3/[engine_api_types, eth_api_types, conversions], web3/[engine_api_types, eth_api_types, conversions],
@ -1210,6 +1210,12 @@ type
withdrawalRequests: seq[ETHWithdrawalRequest] withdrawalRequests: seq[ETHWithdrawalRequest]
consolidationRequests: seq[ETHConsolidationRequest] consolidationRequests: seq[ETHConsolidationRequest]
template append*(
w: var RlpWriter, v:
ETHWithdrawal | ETHDepositRequest | ETHWithdrawalRequest |
ETHConsolidationRequest) =
w.appendRawBytes(v.bytes)
proc ETHExecutionBlockHeaderCreateFromJson( proc ETHExecutionBlockHeaderCreateFromJson(
executionHash: ptr Eth2Digest, executionHash: ptr Eth2Digest,
blockHeaderJson: cstring): ptr ETHExecutionBlockHeader {.exported.} = blockHeaderJson: cstring): ptr ETHExecutionBlockHeader {.exported.} =
@ -1357,13 +1363,8 @@ proc ETHExecutionBlockHeaderCreateFromJson(
amount: wd.amount, amount: wd.amount,
bytes: rlpBytes) bytes: rlpBytes)
var tr = initHexaryTrie(newMemoryDB()) let tr = orderedTrieRoot(wds)
for i, wd in wds: if tr != data.withdrawalsRoot.get.asEth2Digest:
try:
tr.put(rlp.encode(i.uint), wd.bytes)
except RlpError:
raiseAssert "Unreachable"
if tr.rootHash() != data.withdrawalsRoot.get.asEth2Digest:
return nil return nil
# Construct deposit requests # Construct deposit requests
@ -1460,28 +1461,14 @@ proc ETHExecutionBlockHeaderCreateFromJson(
data.consolidationRequests.isSome: data.consolidationRequests.isSome:
doAssert data.requestsRoot.isSome # Checked above doAssert data.requestsRoot.isSome # Checked above
var var b = OrderedTrieRootBuilder.init(
tr = initHexaryTrie(newMemoryDB()) depositRequests.len + withdrawalRequests.len + consolidationRequests.len)
i = 0'u64
for req in depositRequests: b.add(depositRequests)
try: b.add(withdrawalRequests)
tr.put(rlp.encode(i.uint), req.bytes) b.add(consolidationRequests)
except RlpError:
raiseAssert "Unreachable" if b.rootHash() != data.requestsRoot.get.asEth2Digest:
inc i
for req in withdrawalRequests:
try:
tr.put(rlp.encode(i.uint), req.bytes)
except RlpError:
raiseAssert "Unreachable"
inc i
for req in consolidationRequests:
try:
tr.put(rlp.encode(i.uint), req.bytes)
except RlpError:
raiseAssert "Unreachable"
inc i
if tr.rootHash() != data.requestsRoot.get.asEth2Digest:
return nil return nil
let executionBlockHeader = ETHExecutionBlockHeader.new() let executionBlockHeader = ETHExecutionBlockHeader.new()
@ -1653,6 +1640,9 @@ type
signature: seq[byte] signature: seq[byte]
bytes: TypedTransaction bytes: TypedTransaction
template append*(w: var RlpWriter, v: ETHTransaction) =
w.appendRawBytes(distinctBase v.bytes)
proc ETHTransactionsCreateFromJson( proc ETHTransactionsCreateFromJson(
transactionsRoot: ptr Eth2Digest, transactionsRoot: ptr Eth2Digest,
transactionsJson: cstring): ptr seq[ETHTransaction] {.exported.} = transactionsJson: cstring): ptr seq[ETHTransaction] {.exported.} =
@ -1900,13 +1890,7 @@ proc ETHTransactionsCreateFromJson(
signature: @rawSig, signature: @rawSig,
bytes: rlpBytes.TypedTransaction) bytes: rlpBytes.TypedTransaction)
var tr = initHexaryTrie(newMemoryDB()) if orderedTrieRoot(txs) != transactionsRoot[]:
for i, transaction in txs:
try:
tr.put(rlp.encode(i.uint), distinctBase(transaction.bytes))
except RlpError:
raiseAssert "Unreachable"
if tr.rootHash() != transactionsRoot[]:
return nil return nil
let transactions = seq[ETHTransaction].new() let transactions = seq[ETHTransaction].new()
@ -2466,6 +2450,9 @@ type
logs: seq[ETHLog] logs: seq[ETHLog]
bytes: seq[byte] bytes: seq[byte]
template append*(w: var RlpWriter, v: ETHReceipt) =
w.appendRawBytes(v.bytes)
proc ETHReceiptsCreateFromJson( proc ETHReceiptsCreateFromJson(
receiptsRoot: ptr Eth2Digest, receiptsRoot: ptr Eth2Digest,
receiptsJson: cstring, receiptsJson: cstring,
@ -2610,13 +2597,7 @@ proc ETHReceiptsCreateFromJson(
data: it.data)), data: it.data)),
bytes: rlpBytes) bytes: rlpBytes)
var tr = initHexaryTrie(newMemoryDB()) if orderedTrieRoot(recs) != receiptsRoot[]:
for i, rec in recs:
try:
tr.put(rlp.encode(i.uint), rec.bytes)
except RlpError:
raiseAssert "Unreachable"
if tr.rootHash() != receiptsRoot[]:
return nil return nil
let receipts = seq[ETHReceipt].new() let receipts = seq[ETHReceipt].new()

View File

@ -14,7 +14,7 @@ import
stew/[byteutils, endians2, objects], stew/[byteutils, endians2, objects],
chronicles, chronicles,
eth/common/[eth_types, eth_types_rlp], eth/common/[eth_types, eth_types_rlp],
eth/rlp, eth/trie/[db, hexary], eth/rlp, eth/trie/ordered_trie,
# Internal # Internal
"."/[eth2_merkleization, forks, ssz_codec] "."/[eth2_merkleization, forks, ssz_codec]
@ -23,7 +23,7 @@ import
# generics sandwich where rlp/writer.append() is not seen, by a caller outside # generics sandwich where rlp/writer.append() is not seen, by a caller outside
# this module via compute_execution_block_hash() called from block_processor. # this module via compute_execution_block_hash() called from block_processor.
export export
eth2_merkleization, forks, rlp, ssz_codec eth2_merkleization, forks, ssz_codec, rlp, eth_types_rlp.append
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.7/specs/phase0/weak-subjectivity.md#constants # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.7/specs/phase0/weak-subjectivity.md#constants
const ETH_TO_GWEI = 1_000_000_000.Gwei const ETH_TO_GWEI = 1_000_000_000.Gwei
@ -440,67 +440,42 @@ func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 =
let slots_since_genesis = slot - GENESIS_SLOT let slots_since_genesis = slot - GENESIS_SLOT
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT
proc computeTransactionsTrieRoot*( template append*(w: var RlpWriter, v: bellatrix.Transaction) =
payload: ForkyExecutionPayload): ExecutionHash256 = w.appendRawBytes(distinctBase v)
if payload.transactions.len == 0:
return EMPTY_ROOT_HASH
var tr = initHexaryTrie(newMemoryDB()) template append*(w: var RlpWriter, withdrawal: capella.Withdrawal) =
for i, transaction in payload.transactions: w.appendRecordType(ExecutionWithdrawal(
try:
# Transactions are already RLP encoded
tr.put(rlp.encode(i.uint), distinctBase(transaction))
except RlpError as exc:
raiseAssert "HexaryTrie.put failed: " & $exc.msg
tr.rootHash()
func toExecutionWithdrawal(
withdrawal: capella.Withdrawal): ExecutionWithdrawal =
ExecutionWithdrawal(
index: withdrawal.index, index: withdrawal.index,
validatorIndex: withdrawal.validator_index, validatorIndex: withdrawal.validator_index,
address: EthAddress withdrawal.address.data, address: EthAddress withdrawal.address.data,
amount: distinctBase(withdrawal.amount)) amount: distinctBase(withdrawal.amount)))
proc rlpEncode(withdrawal: capella.Withdrawal): seq[byte] = proc computeTransactionsTrieRoot(
# TODO if this encode call is in a generic function, nim doesn't find the payload: ForkyExecutionPayload): ExecutionHash256 =
# right `append` to use with `Address` (!) orderedTrieRoot(payload.transactions.asSeq)
rlp.encode(toExecutionWithdrawal(withdrawal))
# https://eips.ethereum.org/EIPS/eip-4895 # https://eips.ethereum.org/EIPS/eip-4895
proc computeWithdrawalsTrieRoot*( proc computeWithdrawalsTrieRoot(
payload: capella.ExecutionPayload | deneb.ExecutionPayload | payload: capella.ExecutionPayload | deneb.ExecutionPayload |
electra.ExecutionPayload): ExecutionHash256 = electra.ExecutionPayload): ExecutionHash256 =
if payload.withdrawals.len == 0: orderedTrieRoot(payload.withdrawals.asSeq)
return EMPTY_ROOT_HASH
var tr = initHexaryTrie(newMemoryDB()) func append*(w: var RlpWriter, request: electra.DepositRequest) =
for i, withdrawal in payload.withdrawals: w.append ExecutionDepositRequest(
try:
tr.put(rlp.encode(i.uint), rlpEncode(withdrawal))
except RlpError as exc:
raiseAssert "HexaryTrie.put failed: " & $exc.msg
tr.rootHash()
func toExecutionDepositRequest*(
request: electra.DepositRequest): ExecutionDepositRequest =
ExecutionDepositRequest(
pubkey: Bytes48 request.pubkey.blob, pubkey: Bytes48 request.pubkey.blob,
withdrawalCredentials: Bytes32 request.withdrawal_credentials.data, withdrawalCredentials: Bytes32 request.withdrawal_credentials.data,
amount: distinctBase(request.amount), amount: distinctBase(request.amount),
signature: Bytes96 request.signature.blob, signature: Bytes96 request.signature.blob,
index: request.index) index: request.index)
func toExecutionWithdrawalRequest*( func append*(w: var RlpWriter, request: electra.WithdrawalRequest) =
request: electra.WithdrawalRequest): ExecutionWithdrawalRequest = w.append ExecutionWithdrawalRequest(
ExecutionWithdrawalRequest(
sourceAddress: Address request.source_address.data, sourceAddress: Address request.source_address.data,
validatorPubkey: Bytes48 request.validator_pubkey.blob, validatorPubkey: Bytes48 request.validator_pubkey.blob,
amount: distinctBase(request.amount)) amount: distinctBase(request.amount))
func toExecutionConsolidationRequest*( func append*(w: var RlpWriter, request: electra.ConsolidationRequest) =
request: electra.ConsolidationRequest): ExecutionConsolidationRequest = w.append ExecutionConsolidationRequest(
ExecutionConsolidationRequest(
sourceAddress: Address request.source_address.data, sourceAddress: Address request.source_address.data,
sourcePubkey: Bytes48 request.source_pubkey.blob, sourcePubkey: Bytes48 request.source_pubkey.blob,
targetPubkey: Bytes48 request.target_pubkey.blob) targetPubkey: Bytes48 request.target_pubkey.blob)
@ -508,47 +483,22 @@ func toExecutionConsolidationRequest*(
# https://eips.ethereum.org/EIPS/eip-7685 # https://eips.ethereum.org/EIPS/eip-7685
proc computeRequestsTrieRoot( proc computeRequestsTrieRoot(
requests: electra.ExecutionRequests): ExecutionHash256 = requests: electra.ExecutionRequests): ExecutionHash256 =
if requests.deposits.len == 0 and let n =
requests.withdrawals.len == 0 and requests.deposits.len +
requests.consolidations.len == 0: requests.withdrawals.len +
return EMPTY_ROOT_HASH requests.consolidations.len
var var b = OrderedTrieRootBuilder.init(n)
tr = initHexaryTrie(newMemoryDB())
i = 0'u64
static: static:
doAssert DEPOSIT_REQUEST_TYPE < WITHDRAWAL_REQUEST_TYPE doAssert DEPOSIT_REQUEST_TYPE < WITHDRAWAL_REQUEST_TYPE
doAssert WITHDRAWAL_REQUEST_TYPE < CONSOLIDATION_REQUEST_TYPE doAssert WITHDRAWAL_REQUEST_TYPE < CONSOLIDATION_REQUEST_TYPE
# EIP-6110 b.add(requests.deposits.asSeq) # EIP-6110
for request in requests.deposits: b.add(requests.withdrawals.asSeq) # EIP-7002
try: b.add(requests.consolidations.asSeq) # EIP-7251
tr.put(rlp.encode(i.uint), rlp.encode(
toExecutionDepositRequest(request)))
except RlpError as exc:
raiseAssert "HexaryTree.put failed: " & $exc.msg
inc i
# EIP-7002 b.rootHash()
for request in requests.withdrawals:
try:
tr.put(rlp.encode(i.uint), rlp.encode(
toExecutionWithdrawalRequest(request)))
except RlpError as exc:
raiseAssert "HexaryTree.put failed: " & $exc.msg
inc i
# EIP-7251
for request in requests.consolidations:
try:
tr.put(rlp.encode(i.uint), rlp.encode(
toExecutionConsolidationRequest(request)))
except RlpError as exc:
raiseAssert "HexaryTree.put failed: " & $exc.msg
inc i
tr.rootHash()
proc blockToBlockHeader*(blck: ForkyBeaconBlock): ExecutionBlockHeader = proc blockToBlockHeader*(blck: ForkyBeaconBlock): ExecutionBlockHeader =
template payload: auto = blck.body.execution_payload template payload: auto = blck.body.execution_payload
@ -561,7 +511,7 @@ proc blockToBlockHeader*(blck: ForkyBeaconBlock): ExecutionBlockHeader =
txRoot = payload.computeTransactionsTrieRoot() txRoot = payload.computeTransactionsTrieRoot()
withdrawalsRoot = withdrawalsRoot =
when typeof(payload).kind >= ConsensusFork.Capella: when typeof(payload).kind >= ConsensusFork.Capella:
Opt.some payload.computeWithdrawalsTrieRoot() Opt.some orderedTrieRoot(payload.withdrawals.asSeq)
else: else:
Opt.none(ExecutionHash256) Opt.none(ExecutionHash256)
blobGasUsed = blobGasUsed =

2
vendor/nim-eth vendored

@ -1 +1 @@
Subproject commit cea821df604cc89304afee261278b76d050222b6 Subproject commit 00c91a1dcaf488046bbc9b9fcbd430934312930f

2
vendor/nim-stew vendored

@ -1 +1 @@
Subproject commit 763147cf821ea105936502e029ac9f5cfad86568 Subproject commit 45b0e9d1579b06aedfe4ffe39e832ec5e88f9377