import std/[times, options], stint, chronicles, chronos, stew/byteutils, nimcrypto/sysrand, web3/ethtypes, ./wd_history, ../helper, ../test_env, ../engine_client, ../types, ../../../tools/common/helpers, ../../../nimbus/common/common, ../../../nimbus/utils/utils, ../../../nimbus/common/chain_config, ../../../nimbus/rpc/execution_types type WDBaseSpec* = ref object of BaseSpec timeIncrements*: int # Timestamp increments per block throughout the test wdForkHeight*: int # Withdrawals activation fork height 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 GenesisTimestamp = 0x1234 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, ] # Get the per-block timestamp increments configured for this test func getBlockTimeIncrements(ws: WDBaseSpec): int = if ws.timeIncrements == 0: return 1 ws.timeIncrements # Timestamp delta between genesis and the withdrawals fork func getWithdrawalsGenesisTimeDelta(ws: WDBaseSpec): int = ws.wdForkHeight * ws.getBlockTimeIncrements() # Calculates Shanghai fork timestamp given the amount of blocks that need to be # produced beforehand. func getWithdrawalsForkTime(ws: WDBaseSpec): int = GenesisTimestamp + ws.getWithdrawalsGenesisTimeDelta() # Generates the fork config, including withdrawals fork timestamp. func getForkConfig*(ws: WDBaseSpec): ChainConfig = result = getChainConfig("Shanghai") result.shanghaiTime = some(ws.getWithdrawalsForkTime().fromUnix) # Get the start account for all withdrawals. func getWithdrawalsStartAccount*(ws: WDBaseSpec): UInt256 = 0x1000.u256 func toAddress(x: UInt256): EthAddress = var mm = x.toByteArrayBE copyMem(result[0].addr, mm[11].addr, 20) # 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.wdForkHeight: # 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() # Changes the CL Mocker default time increments of 1 to the value specified # in the test spec. proc configureCLMock*(ws: WDBaseSpec, cl: CLMocker) = cl.blockTimestampIncrement = some(ws.getBlockTimeIncrements()) # Number of blocks to be produced (not counting genesis) before withdrawals # fork. func getPreWithdrawalsBlockCount*(ws: WDBaseSpec): int = if ws.wdForkHeight == 0: 0 else: ws.wdForkHeight - 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, t: TestEnv): bool = result = true let ok = waitFor t.clMock.waitForTTD() testCond ok # Check if we have pre-Shanghai blocks if ws.getWithdrawalsForkTime() > 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 = t.rpcClient.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 = t.rpcClient.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 = t.clMock.produceBlocks(ws.getPreWithdrawalsBlockCount, BlockProcessCallbacks( onPayloadProducerSelected: proc(): bool = # Send some transactions let numTx = ws.getTransactionCountPerPayload() for i in 0..= ws.wdForkHeight.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 = t.clMock.latestExecutedPayload.blockNumber.uint64 let res = ws.wdHistory.verifyWithdrawals(bnu, none(UInt256), t.rpcClient) testCond res.isOk: error "verify wd error", msg=res.error