# Nimbus # Copyright (c) 2023-2024 Status Research & Development GmbH # Licensed under either of # * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or # http://www.apache.org/licenses/LICENSE-2.0) # * MIT license ([LICENSE-MIT](LICENSE-MIT) or # http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except # according to those terms. import std/[options], stint, chronicles, chronos, stew/byteutils, web3/eth_api_types, ./wd_history, ../test_env, ../engine_client, ../types, ../base_spec, ../cancun/customizer, ../../../../nimbus/common/common, ../../../../nimbus/utils/utils, ../../../../nimbus/common/chain_config, web3/execution_types, ../../../../nimbus/beacon/web3_eth_conv type WDBaseSpec* = ref object of BaseSpec wdBlockCount*: int # Number of blocks on and after withdrawals fork activation wdPerBlock*: int # Number of withdrawals per block wdAbleAccountCount*: int # Number of accounts to withdraw to (round-robin) wdHistory*: WDHistory # Internal withdrawals history that keeps track of all withdrawals wdAmounts*: seq[uint64] # Amounts of withdrawn wei on each withdrawal (round-robin) txPerBlock*: Option[int] # Amount of test transactions to include in withdrawal blocks testCorrupedHashPayloads*: bool # Send a valid payload with corrupted hash skipBaseVerifications*: bool # For code reuse of the base spec procedure WithdrawalsForBlock = object wds*: seq[Withdrawal] nextIndex*: int const WARM_COINBASE_ADDRESS = hexToByteArray[20]("0x0101010101010101010101010101010101010101") PUSH0_ADDRESS = hexToByteArray[20]("0x0202020202020202020202020202020202020202") MAINNET_MAX_WITHDRAWAL_COUNT_PER_BLOCK* = 16 TX_CONTRACT_ADDRESSES = [ WARM_COINBASE_ADDRESS, PUSH0_ADDRESS, ] # Timestamp delta between genesis and the withdrawals fork func getWithdrawalsGenesisTimeDelta*(ws: WDBaseSpec): int = ws.forkHeight * ws.getBlockTimeIncrements() # Get the start account for all withdrawals. func getWithdrawalsStartAccount*(ws: WDBaseSpec): UInt256 = 0x1000.u256 # Adds bytecode that unconditionally sets an storage key to specified account range func addUnconditionalBytecode(g: Genesis, start, stop: UInt256) = var acc = start while acc= ws.forkHeight: # Shanghai r.expectStorageEqual(WARM_COINBASE_ADDRESS, 100.u256.w3FixedBytes) # WARM_STORAGE_READ_COST p.expectStorageEqual(PUSH0_ADDRESS, latestPayloadNumber.w3FixedBytes) # tx succeeded else: # Pre-Shanghai r.expectStorageEqual(WARM_COINBASE_ADDRESS, 2600.u256.w3FixedBytes) # COLD_ACCOUNT_ACCESS_COST p.expectStorageEqual(PUSH0_ADDRESS, 0.u256.w3FixedBytes) # tx must've failed ok() # Number of blocks to be produced (not counting genesis) before withdrawals # fork. func getPreWithdrawalsBlockCount*(ws: WDBaseSpec): int = if ws.forkHeight == 0: 0 else: ws.forkHeight - 1 # Number of payloads to be produced (pre and post withdrawals) during the entire test func getTotalPayloadCount*(ws: WDBaseSpec): int = ws.getPreWithdrawalsBlockCount() + ws.wdBlockCount # Generates a list of withdrawals based on current configuration func generateWithdrawalsForBlock*(ws: WDBaseSpec, nextIndex: int, startAccount: UInt256): WithdrawalsForBlock = let differentAccounts = ws.getWithdrawableAccountCount() var wdAmounts = ws.wdAmounts if wdAmounts.len == 0: wdAmounts.add(1) for i in 0 ..< ws.wdPerBlock: let nextAccount = startAccount + (nextIndex mod differentAccounts).u256 nextWithdrawal = Withdrawal( index: nextIndex.uint64, validatorIndex: nextIndex.uint64, address: nextAccount.toAddress, amount: wdAmounts[nextIndex mod wdAmounts.len] ) result.wds.add nextWithdrawal inc result.nextIndex # Base test case execution procedure for withdrawals proc execute*(ws: WDBaseSpec, env: TestEnv): bool = result = true let ok = waitFor env.clMock.waitForTTD() testCond ok # Check if we have pre-Shanghai blocks if ws.getForkTime() > GenesisTimestamp: # Check `latest` during all pre-shanghai blocks, none should # contain `withdrawalsRoot`, including genesis. # Genesis should not contain `withdrawalsRoot` either let r = env.client.latestHeader() r.expectWithdrawalsRoot(none(common.Hash256)) else: # Genesis is post shanghai, it should contain EmptyWithdrawalsRoot let r = env.client.latestHeader() r.expectWithdrawalsRoot(some(EMPTY_ROOT_HASH)) # Produce any blocks necessary to reach withdrawals fork var pbRes = env.clMock.produceBlocks(ws.getPreWithdrawalsBlockCount, BlockProcessCallbacks( onPayloadProducerSelected: proc(): bool = # Send some transactions let numTx = ws.getTransactionCountPerPayload() for i in 0..= ws.forkHeight.uint64: let wds = ws.wdHistory.getWithdrawals(bn) expectedWithdrawalsRoot = some(calcWithdrawalsRoot(wds.list)) #r.ExpectationDescription = fmt.Sprintf(` # Requested block %d to verify withdrawalsRoot with the # following withdrawals: # %s`, block, jsWithdrawals) r.expectWithdrawalsRoot(expectedWithdrawalsRoot) # Verify on `latest` let bnu = env.clMock.latestExecutedPayload.blockNumber.uint64 let res = ws.wdHistory.verifyWithdrawals(bnu, none(UInt256), env.client) testCond res.isOk: error "verify wd error", msg=res.error