From f088e5f57b0c916f97fc5daacfcbfb8affe0b1f9 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Mon, 11 Mar 2024 16:18:50 +0200 Subject: [PATCH] Consensus block value calculation for produceBlockV3 API call. (#5873) * allow specifying get_proposer_reward block root at state.slot * Add consensus_block_value calculation. * Address review comments. * Post-rebase adjustments. * Use proper state to calculate consensus block value. * Revert "allow specifying get_proposer_reward block root at state.slot" This reverts commit 9fef9a8199f63056060527ac2531acc3b0ed8dcb. * Fix post-revert problems. Return back to Gwei. * Adding test which is not working. * Do not use test suite if it does not have post-state. * Add debug logging. * Increase logging to track sources of balance changes. * Fix sync committee rewards/penalties calculation. * Revert "Increase logging to track sources of balance changes." This reverts commit 32feb20f2fdb66521401710866cd59ecc9951ef8. * Adopt new vision to block rewards. * Add block produce logging to VC. * Remove rewards.nim. * Eliminate toWei changes. * Improve UInt256 shortLog. * Fix conversion procedure. * Address review comments. * Fix test. * Revert "Fix test." This reverts commit 4948b2c1ec83fbe93f265fd4dea8cc678d48534c. --------- Co-authored-by: tersec Co-authored-by: Etan Kissling --- beacon_chain/rpc/rest_validator_api.nim | 4 +- beacon_chain/spec/state_transition.nim | 35 +++++- .../validator_client/block_service.nim | 31 ++++++ beacon_chain/validators/beacon_validators.nim | 103 +++++++++++------- 4 files changed, 125 insertions(+), 48 deletions(-) diff --git a/beacon_chain/rpc/rest_validator_api.nim b/beacon_chain/rpc/rest_validator_api.nim index ce4000628..f1f7d661a 100644 --- a/beacon_chain/rpc/rest_validator_api.nim +++ b/beacon_chain/rpc/rest_validator_api.nim @@ -672,8 +672,8 @@ proc installValidatorApiHandlers*(router: var RestRouter, node: BeaconNode) = message = (await PayloadType.makeBeaconBlockForHeadAndSlot( node, qrandao, proposer, qgraffiti, qhead, qslot)).valueOr: return RestApiResponse.jsonError(Http500, error) - executionValue = Opt.some(UInt256(message.blockValue)) - consensusValue = Opt.none(UInt256) + executionValue = Opt.some(UInt256(message.executionPayloadValue)) + consensusValue = Opt.some(UInt256(message.consensusBlockValue)) headers = consensusFork.getMaybeBlindedHeaders( isBlinded = false, executionValue, consensusValue) diff --git a/beacon_chain/spec/state_transition.nim b/beacon_chain/spec/state_transition.nim index cbe929886..79cd12bc7 100644 --- a/beacon_chain/spec/state_transition.nim +++ b/beacon_chain/spec/state_transition.nim @@ -48,7 +48,7 @@ import beaconstate, eth2_merkleization, forks, helpers, signatures, state_transition_block, state_transition_epoch, validator] -export results, extras +export results, extras, state_transition_block logScope: topics = "state_transition" @@ -382,7 +382,7 @@ func partialBeaconBlock*( res -proc makeBeaconBlock*( +proc makeBeaconBlockWithRewards*( cfg: RuntimeConfig, state: var ForkedHashedBeaconState, proposer_index: ValidatorIndex, @@ -403,13 +403,15 @@ proc makeBeaconBlock*( transactions_root: Opt[Eth2Digest], execution_payload_root: Opt[Eth2Digest], kzg_commitments: Opt[KzgCommitments]): - Result[ForkedBeaconBlock, cstring] = + Result[tuple[blck: ForkedBeaconBlock, rewards: BlockRewards], cstring] = ## Create a block for the given state. The latest block applied to it will ## be used for the parent_root value, and the slot will be take from ## state.slot meaning process_slots must be called up to the slot for which ## the block is to be created. - template makeBeaconBlock(kind: untyped): Result[ForkedBeaconBlock, cstring] = + template makeBeaconBlock( + kind: untyped + ): Result[tuple[blck: ForkedBeaconBlock, rewards: BlockRewards], cstring] = # To create a block, we'll first apply a partial block to the state, skipping # some validations. @@ -458,11 +460,10 @@ proc makeBeaconBlock*( else: static: raiseAssert "Unreachable" - state.`kind Data`.root = hash_tree_root(state.`kind Data`.data) blck.`kind Data`.state_root = state.`kind Data`.root - ok(blck) + ok((blck: blck, rewards: res.get)) const payloadFork = typeof(executionPayload).kind when payloadFork == ConsensusFork.Bellatrix: @@ -482,6 +483,28 @@ proc makeBeaconBlock*( else: {.error: "Unsupported fork".} +proc makeBeaconBlock*( + cfg: RuntimeConfig, state: var ForkedHashedBeaconState, + proposer_index: ValidatorIndex, randao_reveal: ValidatorSig, + eth1_data: Eth1Data, graffiti: GraffitiBytes, + attestations: seq[Attestation], deposits: seq[Deposit], + validator_changes: BeaconBlockValidatorChanges, + sync_aggregate: SyncAggregate, + executionPayload: ForkyExecutionPayloadForSigning, + rollback: RollbackForkedHashedProc, cache: var StateCache, + verificationFlags: UpdateFlags, + transactions_root: Opt[Eth2Digest], + execution_payload_root: Opt[Eth2Digest], + kzg_commitments: Opt[KzgCommitments]): + Result[ForkedBeaconBlock, cstring] = + let blockAndRewards = + ? makeBeaconBlockWithRewards( + cfg, state, proposer_index, randao_reveal, eth1_data, graffiti, + attestations, deposits, validator_changes, sync_aggregate, + executionPayload, rollback, cache, verificationFlags, transactions_root, + execution_payload_root, kzg_commitments) + ok(blockAndRewards.blck) + proc makeBeaconBlock*( cfg: RuntimeConfig, state: var ForkedHashedBeaconState, proposer_index: ValidatorIndex, randao_reveal: ValidatorSig, diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index 15baf6859..c5ee0d5dd 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -33,6 +33,19 @@ type blockRoot*: Eth2Digest data*: ForkedBlindedBeaconBlock +func shortLog(v: Opt[UInt256]): auto = + if v.isNone(): "" else: toString(v.get, 10) + +func shortLog(v: ForkedMaybeBlindedBeaconBlock): auto = + withForkyMaybeBlindedBlck(v): + when consensusFork < ConsensusFork.Deneb: + shortLog(forkyMaybeBlindedBlck) + else: + when isBlinded: + shortLog(forkyMaybeBlindedBlck) + else: + shortLog(forkyMaybeBlindedBlck.`block`) + proc proposeBlock(vc: ValidatorClientRef, slot: Slot, proposerKey: ValidatorPubKey) {.async.} @@ -237,6 +250,15 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, when isBlinded: let blockRoot = hash_tree_root(forkyMaybeBlindedBlck) + + debug "Block produced", + block_type = "blinded", + block_root = shortLog(blockRoot), + blck = shortLog(maybeBlock), + execution_value = shortLog(maybeBlock.executionValue), + consensus_value = shortLog(maybeBlock.consensusValue) + + let signingRoot = compute_block_signing_root(fork, genesisRoot, slot, blockRoot) notSlashable = vc.attachedValidators[] @@ -308,6 +330,15 @@ proc publishBlockV3(vc: ValidatorClientRef, currentSlot, slot: Slot, else: forkyMaybeBlindedBlck.`block` ) + + debug "Block produced", + block_type = "non-blinded", + block_root = shortLog(blockRoot), + blck = shortLog(maybeBlock), + execution_value = shortLog(maybeBlock.executionValue), + consensus_value = shortLog(maybeBlock.consensusValue) + + let signingRoot = compute_block_signing_root(fork, genesisRoot, slot, blockRoot) notSlashable = vc.attachedValidators[] diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index bd81bc329..6ed4f9046 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -76,13 +76,16 @@ declarePublicGauge(attached_validator_balance_total, logScope: topics = "beacval" type - EngineBid = tuple[ - blck: ForkedBeaconBlock, - blockValue: Wei, - blobsBundleOpt: Opt[BlobsBundle]] + EngineBid* = object + blck*: ForkedBeaconBlock + executionPayloadValue*: Wei + consensusBlockValue*: UInt256 + blobsBundleOpt*: Opt[BlobsBundle] - BuilderBid[SBBB] = tuple[ - blindedBlckPart: SBBB, blockValue: UInt256] + BuilderBid[SBBB] = object + blindedBlckPart*: SBBB + executionPayloadValue*: UInt256 + consensusBlockValue*: UInt256 ForkedBlockResult = Result[EngineBid, string] @@ -104,6 +107,11 @@ proc getValidator*(validators: auto, Opt.some ValidatorAndIndex(index: ValidatorIndex(idx), validator: validators[idx]) +func blockConsensusValue(r: BlockRewards): UInt256 {.noinit.} = + # Returns value of `block-consensus-value` in Wei units. + u256(r.attestations + r.sync_aggregate + + r.proposer_slashings + r.attester_slashings) * u256(1000000000) + proc addValidatorsFromWeb3Signer( node: BeaconNode, web3signerUrl: Web3SignerUrl, epoch: Epoch) {.async: (raises: [CancelledError]).} = @@ -494,7 +502,7 @@ proc makeBeaconBlockForHeadAndSlot*( slot, validator_index return err("Unable to get execution payload") - let blck = makeBeaconBlock( + let res = makeBeaconBlockWithRewards( node.dag.cfg, state[], validator_index, @@ -523,10 +531,16 @@ proc makeBeaconBlockForHeadAndSlot*( var blobsBundleOpt = Opt.none(BlobsBundle) when payload is deneb.ExecutionPayloadForSigning: blobsBundleOpt = Opt.some(payload.blobsBundle) - return if blck.isOk: - ok((blck.get, payload.blockValue, blobsBundleOpt)) + + if res.isOk: + ok(EngineBid( + blck: res.get().blck, + executionPayloadValue: payload.blockValue, + consensusBlockValue: res.get().rewards.blockConsensusValue(), + blobsBundleOpt: blobsBundleOpt + )) else: - err(blck.error) + err(res.error) proc makeBeaconBlockForHeadAndSlot*( PayloadType: type ForkyExecutionPayloadForSigning, node: BeaconNode, randao_reveal: ValidatorSig, @@ -578,11 +592,11 @@ proc getBlindedExecutionPayload[ when EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle: template builderBid: untyped = blindedHeader.data.message - return ok(( + return ok(BuilderBid[EPH]( blindedBlckPart: EPH( execution_payload_header: builderBid.header, blob_kzg_commitments: builderBid.blob_kzg_commitments), - blockValue: builderBid.value)) + executionPayloadValue: builderBid.value)) else: static: doAssert false @@ -691,7 +705,7 @@ proc getBlindedBlockParts[ node: BeaconNode, payloadBuilderClient: RestClientRef, head: BlockRef, pubkey: ValidatorPubKey, slot: Slot, randao: ValidatorSig, validator_index: ValidatorIndex, graffiti: GraffitiBytes): - Future[Result[(EPH, UInt256, ForkedBeaconBlock), string]] + Future[Result[(EPH, UInt256, UInt256, ForkedBeaconBlock), string]] {.async: (raises: [CancelledError]).} = let executionBlockHash = node.dag.loadExecutionBlockHash(head).valueOr: @@ -776,7 +790,8 @@ proc getBlindedBlockParts[ return ok( (executionPayloadHeader.get.blindedBlckPart, - executionPayloadHeader.get.blockValue, + executionPayloadHeader.get.executionPayloadValue, + forkedBlck.consensusBlockValue, forkedBlck.blck)) proc getBuilderBid[SBBB: deneb_mev.SignedBlindedBeaconBlock]( @@ -801,7 +816,8 @@ proc getBuilderBid[SBBB: deneb_mev.SignedBlindedBeaconBlock]( # These, together, get combined into the blinded block for signing and # proposal through the relay network. - let (executionPayloadHeader, bidValue, forkedBlck) = blindedBlockParts.get + let (executionPayloadHeader, bidValue, consensusValue, forkedBlck) = + blindedBlockParts.get let unsignedBlindedBlock = getUnsignedBlindedBeaconBlock[SBBB]( node, slot, validator_index, forkedBlck, executionPayloadHeader) @@ -809,7 +825,11 @@ proc getBuilderBid[SBBB: deneb_mev.SignedBlindedBeaconBlock]( if unsignedBlindedBlock.isErr: return err unsignedBlindedBlock.error() - return ok (unsignedBlindedBlock.get, bidValue) + ok(BuilderBid[SBBB]( + blindedBlckPart: unsignedBlindedBlock.get, + executionPayloadValue: bidValue, + consensusBlockValue: consensusValue + )) proc proposeBlockMEV( node: BeaconNode, payloadBuilderClient: RestClientRef, @@ -886,15 +906,20 @@ proc makeBlindedBeaconBlockForHeadAndSlot*[BBB: ForkyBlindedBeaconBlock]( # Don't try EL fallback -- VC specifically requested a blinded block return err("Unable to create blinded block") - let (executionPayloadHeader, bidValue, forkedBlck) = blindedBlockParts.get + let (executionPayloadHeader, bidValue, consensusValue, forkedBlck) = + blindedBlockParts.get withBlck(forkedBlck): when consensusFork >= ConsensusFork.Capella: when ((consensusFork == ConsensusFork.Deneb and EPH is deneb_mev.BlindedExecutionPayloadAndBlobsBundle) or (consensusFork == ConsensusFork.Capella and EPH is capella.ExecutionPayloadHeader)): - return ok (constructPlainBlindedBlock[BBB]( - forkyBlck, executionPayloadHeader), bidValue) + return ok( + BuilderBid[BBB]( + blindedBlckPart: + constructPlainBlindedBlock[BBB](forkyBlck, executionPayloadHeader), + executionPayloadValue: bidValue, + consensusBlockValue: consensusValue)) else: return err("makeBlindedBeaconBlockForHeadAndSlot: mismatched block/payload types") else: @@ -1013,8 +1038,8 @@ proc proposeBlockAux( if collectedBids.builderBid.isSome(): collectedBids.engineBid.isNone() or builderBetterBid( localBlockValueBoost, - collectedBids.builderBid.value().blockValue, - collectedBids.engineBid.value().blockValue) + collectedBids.builderBid.value().executionPayloadValue, + collectedBids.engineBid.value().executionPayloadValue) else: if not collectedBids.engineBid.isSome(): return head # errors logged in router @@ -1035,14 +1060,14 @@ proc proposeBlockAux( localBlockValueBoost, useBuilderBlock, builderBlockValue = - toString(collectedBids.builderBid.value().blockValue, 10), + toString(collectedBids.builderBid.value().executionPayloadValue, 10), engineBlockValue = - toString(collectedBids.engineBid.value().blockValue, 10) + toString(collectedBids.engineBid.value().executionPayloadValue, 10) elif payloadBuilderClient.isNil: discard # builder API not configured for this block else: info "Did not receive expected builder bid; using engine block", - engineBlockValue = collectedBids.engineBid.value().blockValue + engineBlockValue = collectedBids.engineBid.value().executionPayloadValue else: # Similar three cases: builder bid expected and absent, builder bid # expected and present, and builder bid not expected. However, only @@ -1051,7 +1076,7 @@ proc proposeBlockAux( if collectedBids.builderBid.isSome: info "Did not receive expected engine bid; using builder block", builderBlockValue = - collectedBids.builderBid.value().blockValue + collectedBids.builderBid.value().executionPayloadValue if useBuilderBlock: let @@ -1993,8 +2018,8 @@ proc makeMaybeBlindedBeaconBlockForHeadAndSlotImpl[ResultType]( if collectedBids.builderBid.isSome(): collectedBids.engineBid.isNone() or builderBetterBid( localBlockValueBoost, - collectedBids.builderBid.value().blockValue, - collectedBids.engineBid.value().blockValue) + collectedBids.builderBid.value().executionPayloadValue, + collectedBids.engineBid.value().executionPayloadValue) else: if not(collectedBids.engineBid.isSome): return ResultType.err("Engine bid is not available") @@ -2002,16 +2027,14 @@ proc makeMaybeBlindedBeaconBlockForHeadAndSlotImpl[ResultType]( engineBid = block: if useBuilderBlock: - let - blindedBid = collectedBids.builderBid.value() - payloadValue = blindedBid.blockValue - + let blindedBid = collectedBids.builderBid.value() return ResultType.ok(( - blck: consensusFork.MaybeBlindedBeaconBlock( - isBlinded: true, - blindedData: blindedBid.blindedBlckPart.message), - executionValue: Opt.some(payloadValue), - consensusValue: Opt.none(UInt256))) + blck: + consensusFork.MaybeBlindedBeaconBlock( + isBlinded: true, + blindedData: blindedBid.blindedBlckPart.message), + executionValue: Opt.some(blindedBid.executionPayloadValue), + consensusValue: Opt.some(blindedBid.consensusBlockValue))) collectedBids.engineBid.value() @@ -2027,15 +2050,15 @@ proc makeMaybeBlindedBeaconBlockForHeadAndSlotImpl[ResultType]( `block`: forkyBlck, kzg_proofs: blobsBundle.proofs, blobs: blobsBundle.blobs)), - executionValue: Opt.some(engineBid.blockValue), - consensusValue: Opt.none(UInt256))) + executionValue: Opt.some(engineBid.executionPayloadValue), + consensusValue: Opt.some(engineBid.consensusBlockValue))) else: ResultType.ok(( blck: consensusFork.MaybeBlindedBeaconBlock( isBlinded: false, data: forkyBlck), - executionValue: Opt.some(engineBid.blockValue), - consensusValue: Opt.none(UInt256))) + executionValue: Opt.some(engineBid.executionPayloadValue), + consensusValue: Opt.some(engineBid.consensusBlockValue))) proc makeMaybeBlindedBeaconBlockForHeadAndSlot*( node: BeaconNode, consensusFork: static ConsensusFork,