Skip to content

Commit

Permalink
bump nim-eth, extend empty block fallback for Capella
Browse files Browse the repository at this point in the history
Implements `emptyPayloadToBlockHeader` for Capella.
status-im/nim-eth#562
  • Loading branch information
etan-status committed Nov 25, 2022
1 parent 50d53e7 commit 1fc4f92
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 42 deletions.
7 changes: 4 additions & 3 deletions AllTests-mainnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,12 @@ OK: 12/12 Fail: 0/12 Skip: 0/12
OK: 1/1 Fail: 0/1 Skip: 0/1
## Spec helpers
```diff
+ build_empty_execution_payload OK
+ build_empty_execution_payload - Bellatrix OK
+ build_empty_execution_payload - Capella OK
+ build_proof - BeaconState OK
+ integer_squareroot OK
```
OK: 3/3 Fail: 0/3 Skip: 0/3
OK: 4/4 Fail: 0/4 Skip: 0/4
## Specific field types
```diff
+ root update OK
Expand Down Expand Up @@ -613,4 +614,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
OK: 9/9 Fail: 0/9 Skip: 0/9

---TOTAL---
OK: 338/343 Fail: 0/343 Skip: 5/343
OK: 339/344 Fail: 0/344 Skip: 5/344
116 changes: 94 additions & 22 deletions beacon_chain/spec/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import
# Status libraries
stew/[bitops2, byteutils, endians2, objects, saturation_arith],
chronicles,
eth/eip1559, eth/common/[eth_types, eth_types_rlp],
eth/eip1559, eth/common/[eth_types, eth_types_rlp], eth/trie/[db, hexary],
# Internal
./datatypes/[phase0, altair, bellatrix, capella],
"."/[eth2_merkleization, forks, ssz_codec]
Expand All @@ -29,6 +29,7 @@ export
forks, eth2_merkleization, ssz_codec

type
ExecutionWithdrawal = eth_types.Withdrawal
ExecutionBlockHeader = eth_types.BlockHeader

FinalityCheckpoints* = object
Expand Down Expand Up @@ -360,32 +361,101 @@ func compute_timestamp_at_slot*(state: ForkyBeaconState, slot: Slot): uint64 =
let slots_since_genesis = slot - GENESIS_SLOT
state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT

func gweiToWei*(gwei: Gwei): UInt256 =
gwei.u256 * 1_000_000_000.u256

func toExecutionWithdrawal*(
withdrawal: capella.Withdrawal): ExecutionWithdrawal =
ExecutionWithdrawal(
index: withdrawal.index,
validatorIndex: withdrawal.validatorIndex,
address: EthAddress withdrawal.address.data,
amount: gweiToWei withdrawal.amount)

# https://eips.ethereum.org/EIPS/eip-4895
proc computeWithdrawalsTrieRoot*(
payload: capella.ExecutionPayload): Hash256 =
if payload.withdrawals.len == 0:
return EMPTY_ROOT_HASH

var tr = initHexaryTrie(newMemoryDB())
for i, withdrawal in payload.withdrawals:
try:
tr.put(rlp.encode(i), rlp.encode(toExecutionWithdrawal(withdrawal)))
except RlpError as exc:
doAssert false, "HexaryTrie.put failed: " & $exc.msg
tr.rootHash()

proc emptyPayloadToBlockHeader*(
payload: bellatrix.ExecutionPayload): ExecutionBlockHeader =
payload: bellatrix.ExecutionPayload | capella.ExecutionPayload
): ExecutionBlockHeader =
static: # `GasInt` is signed. We only use it for hashing.
doAssert sizeof(GasInt) == sizeof(payload.gas_limit)
doAssert sizeof(GasInt) == sizeof(payload.gas_used)

## This function assumes that the payload is empty!
doAssert payload.transactions.len == 0

let
txRoot = EMPTY_ROOT_HASH
withdrawalsRoot =
when payload is bellatrix.ExecutionPayload:
none(Hash256)
else:
some payload.computeWithdrawalsTrieRoot()

ExecutionBlockHeader(
parentHash : payload.parent_hash,
ommersHash : EMPTY_UNCLE_HASH,
coinbase : EthAddress payload.fee_recipient.data,
stateRoot : payload.state_root,
txRoot : EMPTY_ROOT_HASH,
receiptRoot : payload.receipts_root,
bloom : payload.logs_bloom.data,
difficulty : default(DifficultyInt),
blockNumber : payload.block_number.u256,
gasLimit : GasInt payload.gas_limit,
gasUsed : GasInt payload.gas_used,
timestamp : fromUnix(int64.saturate payload.timestamp),
extraData : payload.extra_data.asSeq,
mixDigest : payload.prev_randao, # EIP-4399 redefine `mixDigest` -> `prevRandao`
nonce : default(BlockNonce),
fee : some payload.base_fee_per_gas
)
parentHash : payload.parent_hash,
ommersHash : EMPTY_UNCLE_HASH,
coinbase : EthAddress payload.fee_recipient.data,
stateRoot : payload.state_root,
txRoot : txRoot,
receiptRoot : payload.receipts_root,
bloom : payload.logs_bloom.data,
difficulty : default(DifficultyInt),
blockNumber : payload.block_number.u256,
gasLimit : cast[GasInt](payload.gas_limit),
gasUsed : cast[GasInt](payload.gas_used),
timestamp : fromUnix(int64.saturate payload.timestamp),
extraData : payload.extra_data.asSeq,
mixDigest : payload.prev_randao, # EIP-4399 `mixDigest` -> `prevRandao`
nonce : default(BlockNonce),
fee : some payload.base_fee_per_gas,
withdrawalsRoot: withdrawalsRoot)

func build_empty_execution_payload*(
state: bellatrix.BeaconState,
feeRecipient: Eth1Address): bellatrix.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions.
let
latest = state.latest_execution_payload_header
timestamp = compute_timestamp_at_slot(state, state.slot)
randao_mix = get_randao_mix(state, get_current_epoch(state))
base_fee = calcEip1599BaseFee(GasInt.saturate latest.gas_limit,
GasInt.saturate latest.gas_used,
latest.base_fee_per_gas)

var payload = bellatrix.ExecutionPayload(
parent_hash: latest.block_hash,
fee_recipient: bellatrix.ExecutionAddress(data: distinctBase(feeRecipient)),
state_root: latest.state_root, # no changes to the state
receipts_root: EMPTY_ROOT_HASH,
block_number: latest.block_number + 1,
prev_randao: randao_mix,
gas_limit: latest.gas_limit, # retain same limit
gas_used: 0, # empty block, 0 gas
timestamp: timestamp,
base_fee_per_gas: base_fee)

payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)

payload

func build_empty_execution_payload*[BS, EP](
state: BS, feeRecipient: Eth1Address): EP =
proc build_empty_execution_payload*(
state: capella.BeaconState,
feeRecipient: Eth1Address,
expectedWithdrawals: seq[capella.Withdrawal]): capella.ExecutionPayload =
## Assuming a pre-state of the same slot, build a valid ExecutionPayload
## without any transactions.
let
Expand All @@ -396,7 +466,7 @@ func build_empty_execution_payload*[BS, EP](
GasInt.saturate latest.gas_used,
latest.base_fee_per_gas)

var payload = EP(
var payload = capella.ExecutionPayload(
parent_hash: latest.block_hash,
fee_recipient: bellatrix.ExecutionAddress(data: distinctBase(feeRecipient)),
state_root: latest.state_root, # no changes to the state
Expand All @@ -407,6 +477,8 @@ func build_empty_execution_payload*[BS, EP](
gas_used: 0, # empty block, 0 gas
timestamp: timestamp,
base_fee_per_gas: base_fee)
for withdrawal in expectedWithdrawals:
doAssert payload.withdrawals.add withdrawal

payload.block_hash = rlpHash emptyPayloadToBlockHeader(payload)

Expand Down
12 changes: 8 additions & 4 deletions beacon_chain/validators/validator_duties.nim
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,15 @@ proc getExecutionPayload[T](

template empty_execution_payload(): auto =
withState(proposalState[]):
when stateFork >= BeaconStateFork.Bellatrix:
build_empty_execution_payload[typeof forkyState.data, T](
forkyState.data, feeRecipient)
when stateFork >= BeaconStateFork.Capella:
raiseAssert $capellaImplementationMissing # expected withdrawals
let expectedWithdrawals = newSeq[Withdrawal]() # TODO Pass into proc!
build_empty_execution_payload(
forkyState.data, feeRecipient, expectedWithdrawals)
elif stateFork >= BeaconStateFork.Bellatrix:
build_empty_execution_payload(forkyState.data, feeRecipient)
else:
default(T)
raiseAssert "Fork " & $stateFork & " lacks `ExecutionPayload`"

if node.eth1Monitor.isNil:
beacon_block_payload_errors.inc()
Expand Down
66 changes: 57 additions & 9 deletions tests/test_helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import
# Status libraries
stew/bitops2,
eth/common/eth_types as commonEthTypes, eth/common/eth_types_rlp,
web3/ethtypes,
# Beacon chain internals
../beacon_chain/spec/[forks, helpers, state_transition],
../beacon_chain/spec/datatypes/bellatrix,
../beacon_chain/spec/datatypes/[bellatrix, capella],
# Test utilities
./unittest2, mocking/mock_genesis

Expand Down Expand Up @@ -61,20 +62,67 @@ suite "Spec helpers":
i += 1
process(state, state.numLeaves)

test "build_empty_execution_payload":
test "build_empty_execution_payload - Bellatrix":
var cfg = defaultRuntimeConfig
cfg.ALTAIR_FORK_EPOCH = GENESIS_EPOCH
cfg.BELLATRIX_FORK_EPOCH = GENESIS_EPOCH

let state = newClone(initGenesisState(cfg = cfg).bellatrixData)

template testCase(recipient: Eth1Address): untyped =
block:
let payload = build_empty_execution_payload[
typeof state[].data, bellatrix.ExecutionPayload](
state[].data, recipient)
check payload.fee_recipient ==
bellatrix.ExecutionAddress(data: distinctBase(recipient))
proc testCase(recipient: Eth1Address) =
let payload = build_empty_execution_payload(state[].data, recipient)
check payload.fee_recipient ==
bellatrix.ExecutionAddress(data: distinctBase(recipient))

testCase default(Eth1Address)
testCase Eth1Address.fromHex("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")

test "build_empty_execution_payload - Capella":
var cfg = defaultRuntimeConfig
cfg.ALTAIR_FORK_EPOCH = GENESIS_EPOCH
cfg.BELLATRIX_FORK_EPOCH = GENESIS_EPOCH
cfg.CAPELLA_FORK_EPOCH = GENESIS_EPOCH

let
state = newClone(initGenesisState(cfg = cfg).capellaData)
recipient = Eth1Address.fromHex(
"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")

proc testCase(withdrawals: seq[capella.Withdrawal]) =
let payload = build_empty_execution_payload(
state[].data, recipient, withdrawals)
check payload.fee_recipient ==
bellatrix.ExecutionAddress(data: distinctBase(recipient))
for i, withdrawal in withdrawals:
check payload.withdrawals[i] == withdrawal

let elHeader = emptyPayloadToBlockHeader(payload)
check elHeader.withdrawalsRoot.isSome
if withdrawals.len == 0:
check elHeader.withdrawalsRoot.get == EMPTY_ROOT_HASH
else:
check elHeader.withdrawalsRoot.get != EMPTY_ROOT_HASH
check elHeader.blockHash == payload.block_hash

var bellatrixHeader = elHeader
bellatrixHeader.withdrawalsRoot.reset()
check elHeader.blockHash != rlpHash bellatrixHeader

testCase @[]
testCase @[
capella.Withdrawal(
index: 42,
validatorIndex: 1337,
address: bellatrix.ExecutionAddress(data: distinctBase(recipient)),
amount: 25.Gwei)]
testCase @[
capella.Withdrawal(
index: 1,
validatorIndex: 1,
address: bellatrix.ExecutionAddress(data: distinctBase(recipient)),
amount: 1.Gwei),
capella.Withdrawal(
index: 2,
validatorIndex: 2,
address: bellatrix.ExecutionAddress(data: distinctBase(recipient)),
amount: 2.Gwei)]
4 changes: 1 addition & 3 deletions tests/testblockutil.nim
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,7 @@ proc addTestBlock*(
cfg.BELLATRIX_FORK_EPOCH * SLOTS_PER_EPOCH + 10:
if is_merge_transition_complete(forkyState.data):
const feeRecipient = default(Eth1Address)
build_empty_execution_payload[
bellatrix.BeaconState, bellatrix.ExecutionPayload](
forkyState.data, feeRecipient)
build_empty_execution_payload(forkyState.data, feeRecipient)
else:
build_empty_merge_execution_payload(forkyState.data)
else:
Expand Down
2 changes: 1 addition & 1 deletion vendor/nim-eth
Submodule nim-eth updated 59 files
+1 −1 doc/rlp.md
+4 −4 doc/trie.md
+23 −16 eth/common/eth_types.nim
+21 −6 eth/common/eth_types_rlp.nim
+2 −2 eth/db/kvstore.nim
+4 −4 eth/db/kvstore_sqlite3.nim
+1 −1 eth/keyfile/keyfile.nim
+1 −1 eth/keys.nim
+2 −2 eth/net/nat.nim
+1 −1 eth/net/utils.nim
+47 −15 eth/p2p/auth.nim
+1 −1 eth/p2p/discovery.nim
+48 −12 eth/p2p/discoveryv5/dcli.nim
+1 −1 eth/p2p/discoveryv5/encoding.nim
+101 −73 eth/p2p/discoveryv5/enr.nim
+1 −1 eth/p2p/discoveryv5/ip_vote.nim
+1 −1 eth/p2p/discoveryv5/lru.nim
+1 −1 eth/p2p/discoveryv5/nodes_verification.nim
+3 −3 eth/p2p/discoveryv5/protocol.nim
+4 −4 eth/p2p/discoveryv5/routing_table.nim
+11 −10 eth/p2p/ecies.nim
+2 −2 eth/p2p/kademlia.nim
+8 −8 eth/p2p/p2p_protocol_dsl.nim
+1 −1 eth/p2p/peer_pool.nim
+16 −13 eth/p2p/private/p2p_types.nim
+11 −14 eth/p2p/rlpx.nim
+6 −10 eth/p2p/rlpxcrypt.nim
+9 −4 eth/rlp.nim
+1 −1 eth/rlp/writer.nim
+1 −1 eth/trie/db.nim
+4 −4 eth/utp/delay_histogram.nim
+2 −2 eth/utp/growable_buffer.nim
+8 −8 eth/utp/ledbat_congestion_control.nim
+4 −4 eth/utp/packets.nim
+3 −3 eth/utp/utp.nim
+2 −2 eth/utp/utp_discv5_protocol.nim
+3 −3 eth/utp/utp_protocol.nim
+7 −7 eth/utp/utp_router.nim
+66 −66 eth/utp/utp_socket.nim
+3 −3 tests/db/test_kvstore_sqlite3.nim
+1 −1 tests/fuzzing/discovery/fuzz.nim
+3 −3 tests/fuzzing/discoveryv5/fuzz_decode_packet.nim
+1 −1 tests/p2p/discv5_test_helper.nim
+79 −1 tests/p2p/test_auth.nim
+2 −2 tests/p2p/test_discovery.nim
+22 −3 tests/p2p/test_discoveryv5.nim
+4 −4 tests/p2p/test_discoveryv5_encoding.nim
+1 −1 tests/p2p/test_enr.nim
+16 −6 tests/p2p/test_rlpx_thunk.json
+2 −2 tests/p2p/test_routing_table.nim
+36 −4 tests/rlp/test_api_usage.nim
+13 −0 tests/rlp/test_common.nim
+3 −3 tests/utp/test_buffer.nim
+2 −2 tests/utp/test_clock_drift_calculator.nim
+12 −12 tests/utp/test_protocol.nim
+3 −3 tests/utp/test_protocol_integration.nim
+4 −4 tests/utp/test_utp_router.nim
+55 −55 tests/utp/test_utp_socket.nim
+3 −3 tests/utp/test_utp_socket_sack.nim

0 comments on commit 1fc4f92

Please sign in to comment.