From 41a3b62fe260824ba0f1503ef6c225c53b05014f Mon Sep 17 00:00:00 2001 From: Etan Kissling Date: Mon, 8 Apr 2024 15:28:46 +0200 Subject: [PATCH] update `wss_sim` for Bellatrix, Capella and Deneb (#6170) The `wss_sim` was not properly maintained since Bellatrix. The missing functionality is now added, including: - Bellatrix: Connect to an EL for execution payload production - Capella: Correct withdrawals processing, is mandatory to do - Deneb: Dump blob sidecars into the output directory See https://ethresear.ch/t/insecura-my-consensus-for-the-pyrmont-network/11833 --- beacon_chain/sszdump.nim | 9 ++ research/wss_sim.nim | 255 +++++++++++++++++++++++++++------------ 2 files changed, 184 insertions(+), 80 deletions(-) diff --git a/beacon_chain/sszdump.nim b/beacon_chain/sszdump.nim index 295c81b5a..4ac4a2383 100644 --- a/beacon_chain/sszdump.nim +++ b/beacon_chain/sszdump.nim @@ -36,6 +36,15 @@ proc dump*(dir: string, v: ForkySignedBeaconBlock) = logErrors: SSZ.saveFile(dir / &"block-{v.message.slot}-{shortLog(v.root)}.ssz", v) +proc dump*(dir: string, v: BlobSidecar) = + logErrors: + let + slot = v.signed_block_header.message.slot + blck = shortLog(v.signed_block_header.message.hash_tree_root()) + index = v.index + root = shortLog(v.hash_tree_root()) + SSZ.saveFile(dir / &"blob-{slot}-{blck}-{index}-{root}.ssz", v) + proc dump*(dir: string, v: ForkyHashedBeaconState) = mixin saveFile logErrors: diff --git a/research/wss_sim.nim b/research/wss_sim.nim index 6aa171cc6..7ea00ba5d 100644 --- a/research/wss_sim.nim +++ b/research/wss_sim.nim @@ -17,14 +17,19 @@ import confutils, stew/io2, ../tests/testblockutil, + ../beacon_chain/el/el_manager, ../beacon_chain/networking/network_metadata, ../beacon_chain/[beacon_clock, sszdump], ../beacon_chain/spec/eth2_apis/eth2_rest_serialization, ../beacon_chain/spec/datatypes/[phase0, altair, bellatrix], ../beacon_chain/spec/[ - beaconstate, crypto, forks, helpers, signatures, state_transition], + beaconstate, crypto, engine_authentication, forks, helpers, + signatures, state_transition], ../beacon_chain/validators/[keystore_management, validator_pool] +from ../beacon_chain/gossip_processing/block_processor import + newExecutionPayload + template findIt*(s: openArray, predicate: untyped): int = var res = -1 for i, it {.inject.} in s: @@ -45,15 +50,96 @@ from ../beacon_chain/spec/datatypes/capella import SignedBeaconBlock from ../beacon_chain/spec/datatypes/deneb import SignedBeaconBlock cli do(validatorsDir: string, secretsDir: string, - startState: string, network: string): + startState: string, startBlock: string, + network: string, elUrl: string, jwtSecret: string, + suggestedFeeRecipient: string, graffiti = "insecura"): let - cfg = getMetadataForNetwork(network).cfg - state = + metadata = getMetadataForNetwork(network) + cfg = metadata.cfg + state = block: + let data = readAllBytes(startState) + if data.isErr: + fatal "failed to read hashed beacon state", err = $data.error + quit QuitFailure try: - newClone(readSszForkedHashedBeaconState( - cfg, readAllBytes(startState).tryGet())) - except CatchableError: - raiseAssert "failed to read hashed beacon state" + newClone(readSszForkedHashedBeaconState(cfg, data.get)) + except SerializationError as exc: + fatal "failed to parse hashed beacon state", err = exc.msg + quit QuitFailure + blck = block: + let data = readAllBytes(startBlock) + if data.isErr: + fatal "failed to read signed beacon block", err = $data.error + quit QuitFailure + try: + newClone(readSszForkedSignedBeaconBlock(cfg, data.get)) + except SerializationError as exc: + fatal "failed to parse signed beacon block", err = exc.msg + quit QuitFailure + engineApiUrl = block: + let + jwtSecretFile = + try: + InputFile.parseCmdArg(jwtSecret) + except ValueError as exc: + fatal "failed to read JWT secret file", err = exc.msg + quit QuitFailure + jwtSecret = loadJwtSecretFile(jwtSecretFile) + if jwtSecret.isErr: + fatal "failed to parse JWT secret file", err = jwtSecret.error + quit QuitFailure + let finalUrl = EngineApiUrlConfigValue(url: elUrl) + .toFinalUrl(Opt.some jwtSecret.get) + if finalUrl.isErr: + fatal "failed to read EL URL", err = finalUrl.error + quit QuitFailure + finalUrl.get + elManager = ELManager.new( + cfg, + metadata.depositContractBlock, + metadata.depositContractBlockHash, + db = nil, + @[engineApiUrl], + metadata.eth1Network) + feeRecipient = + try: + Address.fromHex(suggestedFeeRecipient) + except ValueError as exc: + fatal "failed to parse suggested fee recipient", err = exc.msg + quit QuitFailure + graffitiValue = + try: + GraffitiBytes.init(graffiti) + except ValueError as exc: + fatal "failed to parse graffiti", err = exc.msg + quit QuitFailure + + # Sync EL to initial state. Note that to construct the new branch, the EL + # should not have advanced to a later block via `engine_forkchoiceUpdated`. + # The EL may otherwise refuse to produce new heads + elManager.start(syncChain = false) + withBlck(blck[]): + when consensusFork >= ConsensusFork.Bellatrix: + if forkyBlck.message.is_execution_block: + template payload(): auto = forkyBlck.message.body.execution_payload + if not payload.block_hash.isZero: + notice "Syncing EL", elUrl, jwtSecret + while true: + waitFor noCancel sleepAsync(chronos.seconds(2)) + (waitFor noCancel elManager + .newExecutionPayload(forkyBlck.message)).isOkOr: + continue + + let (status, _) = waitFor noCancel elManager.forkchoiceUpdated( + headBlockHash = payload.block_hash, + safeBlockHash = payload.block_hash, + finalizedBlockHash = ZERO_HASH, + payloadAttributes = none(consensusFork.PayloadAttributes)) + if status != PayloadExecutionStatus.valid: + continue + + notice "EL synced", elUrl, jwtSecret + break var clock = BeaconClock.init(getStateField(state[], genesis_time)).valueOr: @@ -72,7 +158,7 @@ cli do(validatorsDir: string, secretsDir: string, validators[idx.get()] = item.privateKey validatorKeys[pubkey] = item.privateKey else: - warn "Unkownn validator", pubkey + warn "Unknown validator", pubkey var blockRoot = withState(state[]): forkyState.latest_block_root @@ -159,80 +245,89 @@ cli do(validatorsDir: string, secretsDir: string, # should never fall back to default value validators.getOrDefault( proposer, default(ValidatorPrivKey))).toValidatorSig() - message = makeBeaconBlock( - cfg, - state[], - proposer, - randao_reveal, - getStateField(state[], eth1_data), - static(GraffitiBytes.init("insecura")), - blockAggregates, - @[], - BeaconBlockValidatorChanges(), - syncAggregate, - default(bellatrix.ExecutionPayloadForSigning), - noRollback, - cache).get() + withState(state[]): + let + payload = + when consensusFork >= ConsensusFork.Bellatrix: + let + executionHead = + forkyState.data.latest_execution_payload_header.block_hash + withdrawals = + when consensusFork >= ConsensusFork.Capella: + get_expected_withdrawals(forkyState.data) + else: + newSeq[Withdrawal]() - try: - case message.kind - of ConsensusFork.Phase0: - blockRoot = hash_tree_root(message.phase0Data) - let signedBlock = phase0.SignedBeaconBlock( - message: message.phase0Data, + var pl: consensusFork.ExecutionPayloadForSigning + while true: + pl = (waitFor noCancel elManager.getPayload( + consensusFork.ExecutionPayloadForSigning, + consensusHead = forkyState.latest_block_root, + headBlock = executionHead, + safeBlock = executionHead, + finalizedBlock = ZERO_HASH, + timestamp = compute_timestamp_at_slot( + forkyState.data, forkyState.data.slot), + randomData = get_randao_mix( + forkyState.data, get_current_epoch(forkyState.data)), + suggestedFeeRecipient = feeRecipient, + withdrawals = withdrawals)).valueOr: + waitFor noCancel sleepAsync(chronos.seconds(2)) + continue + break + pl + else: + default(bellatrix.ExecutionPayloadForSigning) + message = makeBeaconBlock( + cfg, + state[], + proposer, + randao_reveal, + forkyState.data.eth1_data, + graffitiValue, + blockAggregates, + @[], + BeaconBlockValidatorChanges(), + syncAggregate, + payload, + noRollback, + cache).get() + + blockRoot = message.forky(consensusFork).hash_tree_root() + let + proposerPrivkey = + try: + validators[proposer] + except KeyError as exc: + raiseAssert "Proposer key not available: " & exc.msg + signedBlock = consensusFork.SignedBeaconBlock( + message: message.forky(consensusFork), root: blockRoot, signature: get_block_signature( fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - of ConsensusFork.Altair: - blockRoot = hash_tree_root(message.altairData) - let signedBlock = altair.SignedBeaconBlock( - message: message.altairData, - root: blockRoot, - signature: get_block_signature( - fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - of ConsensusFork.Bellatrix: - blockRoot = hash_tree_root(message.bellatrixData) - let signedBlock = bellatrix.SignedBeaconBlock( - message: message.bellatrixData, - root: blockRoot, - signature: get_block_signature( - fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - of ConsensusFork.Capella: - blockRoot = hash_tree_root(message.capellaData) - let signedBlock = capella.SignedBeaconBlock( - message: message.capellaData, - root: blockRoot, - signature: get_block_signature( - fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - of ConsensusFork.Deneb: - blockRoot = hash_tree_root(message.denebData) - let signedBlock = deneb.SignedBeaconBlock( - message: message.denebData, - root: blockRoot, - signature: get_block_signature( - fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - of ConsensusFork.Electra: - blockRoot = hash_tree_root(message.electraData) - let signedBlock = electra.SignedBeaconBlock( - message: message.electraData, - root: blockRoot, - signature: get_block_signature( - fork, genesis_validators_root, slot, blockRoot, - validators[proposer]).toValidatorSig()) - dump(".", signedBlock) - except CatchableError: - raiseAssert "unreachable" - notice "Block proposed", message, blockRoot + proposerPrivkey).toValidatorSig()) + + dump(".", signedBlock) + when consensusFork >= ConsensusFork.Deneb: + let blobs = signedBlock.create_blob_sidecars( + payload.blobsBundle.proofs, payload.blobsBundle.blobs) + for blob in blobs: + dump(".", blob) + + notice "Block proposed", message, blockRoot + + when consensusFork >= ConsensusFork.Bellatrix: + while true: + let status = waitFor noCancel elManager + .newExecutionPayload(signedBlock.message) + if status.isNone: + waitFor noCancel sleepAsync(chronos.seconds(2)) + continue + doAssert status.get in [ + PayloadExecutionStatus.valid, + PayloadExecutionStatus.accepted, + PayloadExecutionStatus.syncing] + break aggregates.setLen(0) @@ -300,4 +395,4 @@ cli do(validatorsDir: string, secretsDir: string, syncAggregate.sync_committee_bits.setBit(i) if inited: - syncAggregate.sync_committee_signature = finish(agg).toValidatorSig() \ No newline at end of file + syncAggregate.sync_committee_signature = finish(agg).toValidatorSig()