import std/[options], stint, chronicles, chronos, stew/byteutils, nimcrypto/sysrand, web3/ethtypes, ./wd_history, ../helper, ../test_env, ../engine_client, ../types, ../base_spec, ../../../nimbus/common/common, ../../../nimbus/utils/utils, ../../../nimbus/common/chain_config, ../../../nimbus/beacon/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) # WARM_STORAGE_READ_COST p.expectStorageEqual(PUSH0_ADDRESS, latestPayloadNumber) # tx succeeded else: # Pre-Shanghai r.expectStorageEqual(WARM_COINBASE_ADDRESS, 2600.u256) # COLD_ACCOUNT_ACCESS_COST p.expectStorageEqual(PUSH0_ADDRESS, 0.u256) # 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 var h: common.BlockHeader let r = env.client.latestHeader(h) testCond r.isOk: error "failed to ge latest header", msg=r.error testCond h.withdrawalsRoot.isNone: error "genesis should not contains wdsRoot" else: # Genesis is post shanghai, it should contain EmptyWithdrawalsRoot var h: common.BlockHeader let r = env.client.latestHeader(h) testCond r.isOk: error "failed to ge latest header", msg=r.error testCond h.withdrawalsRoot.isSome: error "genesis should contains wdsRoot" testCond h.withdrawalsRoot.get == EMPTY_ROOT_HASH: error "genesis should contains wdsRoot==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(h, 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