From 2ee592381b2426df6d8f52725d4ac5847255c7bb Mon Sep 17 00:00:00 2001 From: Potuz Date: Sun, 3 Nov 2024 09:46:47 -0300 Subject: [PATCH] Process blocks after ePBS These are some of the things that are left to be done - Process the payload - Change stategen to get the poststate of the block and the payload separately - Change the next slot cache to be safe for full/empty --- CHANGELOG.md | 2 + beacon-chain/blockchain/log.go | 53 +++++--- beacon-chain/blockchain/process_block.go | 7 +- beacon-chain/blockchain/process_block_test.go | 12 +- beacon-chain/blockchain/receive_block.go | 49 ++++--- beacon-chain/core/blocks/payload.go | 14 +- beacon-chain/core/blocks/payload_test.go | 9 +- beacon-chain/core/electra/BUILD.bazel | 1 - beacon-chain/core/epbs/BUILD.bazel | 14 ++ .../core/epbs/execution_payload_header.go | 51 +++++++ .../operations.go} | 21 ++- beacon-chain/core/epbs/payload_attestation.go | 125 ++++++++++++++++++ beacon-chain/core/transition/BUILD.bazel | 3 + .../core/transition/transition_epbs.go | 109 +++++++++++++++ .../transition/transition_no_verify_sig.go | 26 +--- beacon-chain/verification/BUILD.bazel | 1 + .../verification/execution_payload_header.go | 45 +------ .../blocks/signed_execution_payload_header.go | 6 + .../signed_execution_payload_header.go | 2 + .../shared/common/operations/block_header.go | 4 +- .../common/operations/execution_payload.go | 4 +- .../shared/common/operations/test_runner.go | 6 +- 22 files changed, 435 insertions(+), 129 deletions(-) create mode 100644 beacon-chain/core/epbs/execution_payload_header.go rename beacon-chain/core/{electra/transition_no_verify_sig.go => epbs/operations.go} (84%) create mode 100644 beacon-chain/core/epbs/payload_attestation.go create mode 100644 beacon-chain/core/transition/transition_epbs.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 0db4182d6207..c0428b031093 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ The format is based on Keep a Changelog, and this project adheres to Semantic Ve - Simplified `ExitedValidatorIndices`. - Simplified `EjectedValidatorIndices`. - `engine_newPayloadV4`,`engine_getPayloadV4` are changes due to new execution request serialization decisions, [PR](https://github.com/prysmaticlabs/prysm/pull/14580) +- Use ROBlock earlier in block syncing pipeline. +- Changed the signature of `ProcessPayload` ### Deprecated diff --git a/beacon-chain/blockchain/log.go b/beacon-chain/blockchain/log.go index b8f4e0d94168..83869607e33d 100644 --- a/beacon-chain/blockchain/log.go +++ b/beacon-chain/blockchain/log.go @@ -45,28 +45,44 @@ func logStateTransitionData(b interfaces.ReadOnlyBeaconBlock) error { } log = log.WithField("syncBitsCount", agg.SyncCommitteeBits.Count()) } - if b.Version() >= version.Bellatrix { - p, err := b.Body().Execution() + if b.Version() >= version.EPBS { + sh, err := b.Body().SignedExecutionPayloadHeader() if err != nil { return err } - log = log.WithField("payloadHash", fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash()))) - txs, err := p.Transactions() - switch { - case errors.Is(err, consensus_types.ErrUnsupportedField): - case err != nil: + header, err := sh.Header() + if err != nil { return err - default: - log = log.WithField("txCount", len(txs)) - txsPerSlotCount.Set(float64(len(txs))) } - } - if b.Version() >= version.Deneb { - kzgs, err := b.Body().BlobKzgCommitments() - if err != nil { - log.WithError(err).Error("Failed to get blob KZG commitments") - } else if len(kzgs) > 0 { - log = log.WithField("kzgCommitmentCount", len(kzgs)) + log = log.WithFields(logrus.Fields{"payloadHash": header.BlockHash(), + "builderIndex": header.BuilderIndex(), + "value": header.Value(), + "blobKzgCommitmentsRoot": header.BlobKzgCommitmentsRoot(), + }) + } else { + if b.Version() >= version.Bellatrix { + p, err := b.Body().Execution() + if err != nil { + return err + } + log = log.WithField("payloadHash", fmt.Sprintf("%#x", bytesutil.Trunc(p.BlockHash()))) + txs, err := p.Transactions() + switch { + case errors.Is(err, consensus_types.ErrUnsupportedField): + case err != nil: + return err + default: + log = log.WithField("txCount", len(txs)) + txsPerSlotCount.Set(float64(len(txs))) + } + } + if b.Version() >= version.Deneb { + kzgs, err := b.Body().BlobKzgCommitments() + if err != nil { + log.WithError(err).Error("Failed to get blob KZG commitments") + } else if len(kzgs) > 0 { + log = log.WithField("kzgCommitmentCount", len(kzgs)) + } } } log.Info("Finished applying state transition") @@ -112,6 +128,9 @@ func logBlockSyncStatus(block interfaces.ReadOnlyBeaconBlock, blockRoot [32]byte // logs payload related data every slot. func logPayload(block interfaces.ReadOnlyBeaconBlock) error { + if block.Version() >= version.EPBS { + return nil + } isExecutionBlk, err := blocks.IsExecutionBlock(block.Body()) if err != nil { return errors.Wrap(err, "could not determine if block is execution block") diff --git a/beacon-chain/blockchain/process_block.go b/beacon-chain/blockchain/process_block.go index 12a26d8ff9f4..e7adfe1ff511 100644 --- a/beacon-chain/blockchain/process_block.go +++ b/beacon-chain/blockchain/process_block.go @@ -67,7 +67,9 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { fcuArgs := &fcuConfig{} if s.inRegularSync() { - defer s.handleSecondFCUCall(cfg, fcuArgs) + if cfg.roblock.Version() < version.EPBS { + defer s.handleSecondFCUCall(cfg, fcuArgs) + } } defer s.sendLightClientFeeds(cfg) defer s.sendStateFeedOnBlock(cfg) @@ -99,6 +101,9 @@ func (s *Service) postBlockProcess(cfg *postBlockProcessConfig) error { s.logNonCanonicalBlockReceived(cfg.roblock.Root(), cfg.headRoot) return nil } + if cfg.roblock.Version() >= version.EPBS { + return nil + } if err := s.getFCUArgs(cfg, fcuArgs); err != nil { log.WithError(err).Error("Could not get forkchoice update argument") return nil diff --git a/beacon-chain/blockchain/process_block_test.go b/beacon-chain/blockchain/process_block_test.go index be70fee70ba6..99314103044a 100644 --- a/beacon-chain/blockchain/process_block_test.go +++ b/beacon-chain/blockchain/process_block_test.go @@ -1520,7 +1520,9 @@ func TestStore_NoViableHead_NewPayload(t *testing.T) { require.NoError(t, err) preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState) require.NoError(t, err) - _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root) + rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb) require.ErrorContains(t, "received an INVALID payload from execution engine", err) // Check that forkchoice's head and store's headroot are the previous head (since the invalid block did // not finish importing and it was never imported to forkchoice). Check @@ -1714,7 +1716,9 @@ func TestStore_NoViableHead_Liveness(t *testing.T) { require.NoError(t, err) preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState) require.NoError(t, err) - _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root) + rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb) require.ErrorContains(t, "received an INVALID payload from execution engine", err) // Check that forkchoice's head and store's headroot are the previous head (since the invalid block did @@ -1964,7 +1968,9 @@ func TestNoViableHead_Reboot(t *testing.T) { require.NoError(t, err) preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState) require.NoError(t, err) - _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, wsb, root) + rowsb, err := consensusblocks.NewROBlockWithRoot(wsb, root) + require.NoError(t, err) + _, err = service.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, rowsb) require.ErrorContains(t, "received an INVALID payload from execution engine", err) // Check that the headroot/state are not in DB and restart the node diff --git a/beacon-chain/blockchain/receive_block.go b/beacon-chain/blockchain/receive_block.go index 4d3c1d2409bf..d8756c848489 100644 --- a/beacon-chain/blockchain/receive_block.go +++ b/beacon-chain/blockchain/receive_block.go @@ -18,6 +18,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" consensus_blocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" + consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" @@ -96,13 +97,34 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig } currentCheckpoints := s.saveCurrentCheckpoints(preState) - postState, isValidPayload, err := s.validateExecutionAndConsensus(ctx, preState, blockCopy, blockRoot) + roblock, err := consensus_blocks.NewROBlockWithRoot(blockCopy, blockRoot) if err != nil { return err } - daWaitedTime, err := s.handleDA(ctx, blockCopy, blockRoot, avs) - if err != nil { - return err + var postState state.BeaconState + var isValidPayload bool + var daWaitedTime time.Duration + if blockCopy.Version() >= version.EPBS { + postState, err = s.validateStateTransition(ctx, preState, roblock) + if err != nil { + return errors.Wrap(err, "could not validate state transition") + } + optimistic, err := s.IsOptimisticForRoot(ctx, roblock.Block().ParentRoot()) + if err != nil { + return errors.Wrap(err, "could not check if parent is optimistic") + } + // if the parent is not optimistic then we can set the block as + // not optimistic. + isValidPayload = !optimistic + } else { + postState, isValidPayload, err = s.validateExecutionAndConsensus(ctx, preState, roblock) + if err != nil { + return err + } + daWaitedTime, err = s.handleDA(ctx, blockCopy, blockRoot, avs) + if err != nil { + return err + } } // Defragment the state before continuing block processing. s.defragmentState(postState) @@ -113,10 +135,6 @@ func (s *Service) ReceiveBlock(ctx context.Context, block interfaces.ReadOnlySig if err := s.savePostStateInfo(ctx, blockRoot, blockCopy, postState); err != nil { return errors.Wrap(err, "could not save post state info") } - roblock, err := consensus_blocks.NewROBlockWithRoot(blockCopy, blockRoot) - if err != nil { - return err - } args := &postBlockProcessConfig{ ctx: ctx, roblock: roblock, @@ -200,8 +218,7 @@ func (s *Service) updateCheckpoints( func (s *Service) validateExecutionAndConsensus( ctx context.Context, preState state.BeaconState, - block interfaces.SignedBeaconBlock, - blockRoot [32]byte, + block consensusblocks.ROBlock, ) (state.BeaconState, bool, error) { preStateVersion, preStateHeader, err := getStateVersionAndPayload(preState) if err != nil { @@ -220,7 +237,7 @@ func (s *Service) validateExecutionAndConsensus( var isValidPayload bool eg.Go(func() error { var err error - isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, block, blockRoot) + isValidPayload, err = s.validateExecutionOnBlock(ctx, preStateVersion, preStateHeader, block) if err != nil { return errors.Wrap(err, "could not notify the engine of the new payload") } @@ -571,16 +588,16 @@ func (s *Service) sendBlockAttestationsToSlasher(signed interfaces.ReadOnlySigne } // validateExecutionOnBlock notifies the engine of the incoming block execution payload and returns true if the payload is valid -func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, signed interfaces.ReadOnlySignedBeaconBlock, blockRoot [32]byte) (bool, error) { - isValidPayload, err := s.notifyNewPayload(ctx, ver, header, signed) +func (s *Service) validateExecutionOnBlock(ctx context.Context, ver int, header interfaces.ExecutionData, block consensusblocks.ROBlock) (bool, error) { + isValidPayload, err := s.notifyNewPayload(ctx, ver, header, block) if err != nil { s.cfg.ForkChoiceStore.Lock() - err = s.handleInvalidExecutionError(ctx, err, blockRoot, signed.Block().ParentRoot()) + err = s.handleInvalidExecutionError(ctx, err, block.Root(), block.Block().ParentRoot()) s.cfg.ForkChoiceStore.Unlock() return false, err } - if signed.Version() < version.Capella && isValidPayload { - if err := s.validateMergeTransitionBlock(ctx, ver, header, signed); err != nil { + if block.Block().Version() < version.Capella && isValidPayload { + if err := s.validateMergeTransitionBlock(ctx, ver, header, block); err != nil { return isValidPayload, err } } diff --git a/beacon-chain/core/blocks/payload.go b/beacon-chain/core/blocks/payload.go index b60761c9a3f8..d603bfce99bd 100644 --- a/beacon-chain/core/blocks/payload.go +++ b/beacon-chain/core/blocks/payload.go @@ -202,24 +202,24 @@ func ValidatePayload(st state.BeaconState, payload interfaces.ExecutionData) err // block_hash=payload.block_hash, // transactions_root=hash_tree_root(payload.transactions), // ) -func ProcessPayload(st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (state.BeaconState, error) { +func ProcessPayload(st state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error { payload, err := body.Execution() if err != nil { - return nil, err + return err } if err := verifyBlobCommitmentCount(body); err != nil { - return nil, err + return err } if err := ValidatePayloadWhenMergeCompletes(st, payload); err != nil { - return nil, err + return err } if err := ValidatePayload(st, payload); err != nil { - return nil, err + return err } if err := st.SetLatestExecutionPayloadHeader(payload); err != nil { - return nil, err + return err } - return st, nil + return nil } func verifyBlobCommitmentCount(body interfaces.ReadOnlyBeaconBlockBody) error { diff --git a/beacon-chain/core/blocks/payload_test.go b/beacon-chain/core/blocks/payload_test.go index 20992c949aa2..c56d725a95b2 100644 --- a/beacon-chain/core/blocks/payload_test.go +++ b/beacon-chain/core/blocks/payload_test.go @@ -587,8 +587,7 @@ func Test_ProcessPayload(t *testing.T) { ExecutionPayload: tt.payload, }) require.NoError(t, err) - st, err := blocks.ProcessPayload(st, body) - if err != nil { + if err := blocks.ProcessPayload(st, body); err != nil { require.Equal(t, tt.err.Error(), err.Error()) } else { require.Equal(t, tt.err, err) @@ -619,8 +618,7 @@ func Test_ProcessPayloadCapella(t *testing.T) { ExecutionPayload: payload, }) require.NoError(t, err) - _, err = blocks.ProcessPayload(st, body) - require.NoError(t, err) + require.NoError(t, blocks.ProcessPayload(st, body)) } func Test_ProcessPayload_Blinded(t *testing.T) { @@ -677,8 +675,7 @@ func Test_ProcessPayload_Blinded(t *testing.T) { ExecutionPayloadHeader: p, }) require.NoError(t, err) - st, err := blocks.ProcessPayload(st, body) - if err != nil { + if err := blocks.ProcessPayload(st, body); err != nil { require.Equal(t, tt.err.Error(), err.Error()) } else { require.Equal(t, tt.err, err) diff --git a/beacon-chain/core/electra/BUILD.bazel b/beacon-chain/core/electra/BUILD.bazel index 35ef921f3ef0..e195e7194491 100644 --- a/beacon-chain/core/electra/BUILD.bazel +++ b/beacon-chain/core/electra/BUILD.bazel @@ -10,7 +10,6 @@ go_library( "effective_balance_updates.go", "registry_updates.go", "transition.go", - "transition_no_verify_sig.go", "upgrade.go", "validator.go", "withdrawals.go", diff --git a/beacon-chain/core/epbs/BUILD.bazel b/beacon-chain/core/epbs/BUILD.bazel index 8800bf99019e..0bb170719c12 100644 --- a/beacon-chain/core/epbs/BUILD.bazel +++ b/beacon-chain/core/epbs/BUILD.bazel @@ -5,14 +5,28 @@ go_library( srcs = [ "attestation.go", "execution_payload_envelope.go", + "execution_payload_header.go", + "operations.go", + "payload_attestation.go", ], importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/core/altair:go_default_library", + "//beacon-chain/core/blocks:go_default_library", "//beacon-chain/core/electra:go_default_library", + "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/signing:go_default_library", + "//beacon-chain/core/validators:go_default_library", "//beacon-chain/state:go_default_library", + "//config/params:go_default_library", "//consensus-types/interfaces:go_default_library", + "//consensus-types/primitives:go_default_library", + "//crypto/bls:go_default_library", + "//network/forks:go_default_library", "//proto/engine/v1:go_default_library", + "//runtime/version:go_default_library", + "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", ], ) diff --git a/beacon-chain/core/epbs/execution_payload_header.go b/beacon-chain/core/epbs/execution_payload_header.go new file mode 100644 index 000000000000..440f10c7e99e --- /dev/null +++ b/beacon-chain/core/epbs/execution_payload_header.go @@ -0,0 +1,51 @@ +package epbs + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/crypto/bls" + "github.com/prysmaticlabs/prysm/v5/network/forks" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +// ValidatePayloadHeaderSignature validates the signature of the execution payload header. +func ValidatePayloadHeaderSignature(st state.ReadOnlyBeaconState, sh interfaces.ROSignedExecutionPayloadHeader) error { + h, err := sh.Header() + if err != nil { + return err + } + + pubkey := st.PubkeyAtIndex(h.BuilderIndex()) + pub, err := bls.PublicKeyFromBytes(pubkey[:]) + if err != nil { + return err + } + + s := sh.Signature() + sig, err := bls.SignatureFromBytes(s[:]) + if err != nil { + return err + } + + currentEpoch := slots.ToEpoch(h.Slot()) + f, err := forks.Fork(currentEpoch) + if err != nil { + return err + } + + domain, err := signing.Domain(f, currentEpoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot()) + if err != nil { + return err + } + root, err := sh.SigningRoot(domain) + if err != nil { + return err + } + if !sig.Verify(pub, root[:]) { + return signing.ErrSigFailedToVerify + } + + return nil +} diff --git a/beacon-chain/core/electra/transition_no_verify_sig.go b/beacon-chain/core/epbs/operations.go similarity index 84% rename from beacon-chain/core/electra/transition_no_verify_sig.go rename to beacon-chain/core/epbs/operations.go index c7b478b09857..8eb499c2abc4 100644 --- a/beacon-chain/core/electra/transition_no_verify_sig.go +++ b/beacon-chain/core/epbs/operations.go @@ -1,4 +1,4 @@ -package electra +package epbs import ( "context" @@ -6,9 +6,11 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/runtime/version" ) var ( @@ -62,11 +64,11 @@ func ProcessOperations( if err != nil { return nil, errors.Wrap(err, "could not process altair attester slashing") } - st, err = ProcessAttestationsNoVerifySignature(ctx, st, block) + st, err = electra.ProcessAttestationsNoVerifySignature(ctx, st, block) if err != nil { return nil, errors.Wrap(err, "could not process altair attestation") } - if _, err := ProcessDeposits(ctx, st, bb.Deposits()); err != nil { // new in electra + if _, err := electra.ProcessDeposits(ctx, st, bb.Deposits()); err != nil { // new in electra return nil, errors.Wrap(err, "could not process altair deposit") } st, err = ProcessVoluntaryExits(ctx, st, bb.VoluntaryExits()) @@ -77,20 +79,27 @@ func ProcessOperations( if err != nil { return nil, errors.Wrap(err, "could not process bls-to-execution changes") } + // new in ePBS + if block.Version() >= version.EPBS { + if err := ProcessPayloadAttestations(st, bb); err != nil { + return nil, err + } + return st, nil + } // new in electra requests, err := bb.ExecutionRequests() if err != nil { return nil, errors.Wrap(err, "could not get execution requests") } - st, err = ProcessDepositRequests(ctx, st, requests.Deposits) + st, err = electra.ProcessDepositRequests(ctx, st, requests.Deposits) if err != nil { return nil, errors.Wrap(err, "could not process deposit receipts") } - st, err = ProcessWithdrawalRequests(ctx, st, requests.Withdrawals) + st, err = electra.ProcessWithdrawalRequests(ctx, st, requests.Withdrawals) if err != nil { return nil, errors.Wrap(err, "could not process execution layer withdrawal requests") } - if err := ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil { + if err := electra.ProcessConsolidationRequests(ctx, st, requests.Consolidations); err != nil { return nil, fmt.Errorf("could not process consolidation requests: %w", err) } return st, nil diff --git a/beacon-chain/core/epbs/payload_attestation.go b/beacon-chain/core/epbs/payload_attestation.go new file mode 100644 index 000000000000..cee4050740e9 --- /dev/null +++ b/beacon-chain/core/epbs/payload_attestation.go @@ -0,0 +1,125 @@ +package epbs + +import ( + "bytes" + "context" + "time" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" +) + +func ProcessPayloadAttestations(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) error { + atts, err := body.PayloadAttestations() + if err != nil { + return err + } + if len(atts) == 0 { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) + defer cancel() + lbh := state.LatestBlockHeader() + proposerIndex := lbh.ProposerIndex + var participation []byte + if state.Slot()%32 == 0 { + participation, err = state.PreviousEpochParticipation() + } else { + participation, err = state.CurrentEpochParticipation() + } + if err != nil { + return err + } + totalBalance, err := helpers.TotalActiveBalance(state) + if err != nil { + return err + } + baseReward, err := altair.BaseRewardWithTotalBalance(state, proposerIndex, totalBalance) + if err != nil { + return err + } + lfs, err := state.LatestFullSlot() + if err != nil { + return err + } + + cfg := params.BeaconConfig() + sourceFlagIndex := cfg.TimelySourceFlagIndex + targetFlagIndex := cfg.TimelyTargetFlagIndex + headFlagIndex := cfg.TimelyHeadFlagIndex + penaltyNumerator := uint64(0) + rewardNumerator := uint64(0) + rewardDenominator := (cfg.WeightDenominator - cfg.ProposerWeight) * cfg.WeightDenominator / cfg.ProposerWeight + + for _, att := range atts { + data := att.Data + if !bytes.Equal(data.BeaconBlockRoot, lbh.ParentRoot) { + return errors.New("invalid beacon block root in payload attestation data") + } + if data.Slot+1 != state.Slot() { + return errors.New("invalid data slot") + } + indexed, err := helpers.GetIndexedPayloadAttestation(ctx, state, data.Slot, att) + if err != nil { + return err + } + valid, err := helpers.IsValidIndexedPayloadAttestation(state, indexed) + if err != nil { + return err + } + if !valid { + return errors.New("invalid payload attestation") + } + payloadWasPreset := data.Slot == lfs + votedPresent := data.PayloadStatus == primitives.PAYLOAD_PRESENT + if votedPresent != payloadWasPreset { + for _, idx := range indexed.GetAttestingIndices() { + flags := participation[idx] + has, err := altair.HasValidatorFlag(flags, targetFlagIndex) + if err != nil { + return err + } + if has { + penaltyNumerator += baseReward * cfg.TimelyTargetWeight + } + has, err = altair.HasValidatorFlag(flags, sourceFlagIndex) + if err != nil { + return err + } + if has { + penaltyNumerator += baseReward * cfg.TimelySourceWeight + } + has, err = altair.HasValidatorFlag(flags, headFlagIndex) + if err != nil { + return err + } + if has { + penaltyNumerator += baseReward * cfg.TimelyHeadWeight + } + participation[idx] = 0 + } + } else { + for _, idx := range indexed.GetAttestingIndices() { + participation[idx] = (1 << headFlagIndex) | (1 << sourceFlagIndex) | (1 << targetFlagIndex) + rewardNumerator += baseReward * (cfg.TimelyHeadWeight + cfg.TimelySourceWeight + cfg.TimelyTargetWeight) + } + } + } + if penaltyNumerator > 0 { + if err := helpers.DecreaseBalance(state, proposerIndex, penaltyNumerator/rewardDenominator); err != nil { + return err + } + } + if rewardNumerator > 0 { + if err := helpers.IncreaseBalance(state, proposerIndex, penaltyNumerator/rewardDenominator); err != nil { + return err + } + } + return nil +} diff --git a/beacon-chain/core/transition/BUILD.bazel b/beacon-chain/core/transition/BUILD.bazel index be7d42ada04e..451e7dfbc07d 100644 --- a/beacon-chain/core/transition/BUILD.bazel +++ b/beacon-chain/core/transition/BUILD.bazel @@ -9,6 +9,7 @@ go_library( "state-bellatrix.go", "trailing_slot_state_cache.go", "transition.go", + "transition_epbs.go", "transition_no_verify_sig.go", ], importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition", @@ -20,6 +21,7 @@ go_library( "//beacon-chain/core/capella:go_default_library", "//beacon-chain/core/deneb:go_default_library", "//beacon-chain/core/electra:go_default_library", + "//beacon-chain/core/epbs:go_default_library", "//beacon-chain/core/epoch:go_default_library", "//beacon-chain/core/epoch/precompute:go_default_library", "//beacon-chain/core/execution:go_default_library", @@ -45,6 +47,7 @@ go_library( "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//runtime/version:go_default_library", + "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", diff --git a/beacon-chain/core/transition/transition_epbs.go b/beacon-chain/core/transition/transition_epbs.go new file mode 100644 index 000000000000..d79946733f9e --- /dev/null +++ b/beacon-chain/core/transition/transition_epbs.go @@ -0,0 +1,109 @@ +package transition + +import ( + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +func processExecution(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (err error) { + if body.Version() >= version.EPBS { + state, err = blocks.ProcessWithdrawals(state, nil) + if err != nil { + return errors.Wrap(err, "could not process withdrawals") + } + return processExecutionPayloadHeader(state, body) + } + enabled, err := blocks.IsExecutionEnabled(state, body) + if err != nil { + return errors.Wrap(err, "could not check if execution is enabled") + } + if !enabled { + return nil + } + executionData, err := body.Execution() + if err != nil { + return err + } + if state.Version() >= version.Capella { + state, err = blocks.ProcessWithdrawals(state, executionData) + if err != nil { + return errors.Wrap(err, "could not process withdrawals") + } + } + if err := blocks.ProcessPayload(state, body); err != nil { + return errors.Wrap(err, "could not process execution data") + } + return nil +} + +// This function verifies the signature as it is not necessarily signed by the +// proposer +func processExecutionPayloadHeader(state state.BeaconState, body interfaces.ReadOnlyBeaconBlockBody) (err error) { + sh, err := body.SignedExecutionPayloadHeader() + if err != nil { + return err + } + header, err := sh.Header() + if err != nil { + return err + } + if err := epbs.ValidatePayloadHeaderSignature(state, sh); err != nil { + return err + } + builderIndex := header.BuilderIndex() + builder, err := state.ValidatorAtIndex(builderIndex) + if err != nil { + return err + } + epoch := slots.ToEpoch(state.Slot()) + if builder.ActivationEpoch > epoch || epoch >= builder.ExitEpoch { + return errors.New("builder is not active") + } + if builder.Slashed { + return errors.New("builder is slashed") + } + amount := header.Value() + builderBalance, err := state.BalanceAtIndex(builderIndex) + if err != nil { + return err + } + if amount > primitives.Gwei(builderBalance) { + return errors.New("builder has insufficient balance") + } + // sate.Slot == block.Slot because of process_slot + if header.Slot() != state.Slot() { + return errors.New("incorrect header slot") + } + // the state latest block header has the parent root because of + // process_block_header + blockHeader := state.LatestBlockHeader() + if header.ParentBlockRoot() != [32]byte(blockHeader.ParentRoot) { + return errors.New("incorrect parent block root") + } + lbh, err := state.LatestBlockHash() + if err != nil { + return err + } + if header.ParentBlockHash() != [32]byte(lbh) { + return errors.New("incorrect latest block hash") + } + if err := state.UpdateBalancesAtIndex(builderIndex, builderBalance-uint64(amount)); err != nil { + return err + } + if err := helpers.IncreaseBalance(state, blockHeader.ProposerIndex, uint64(amount)); err != nil { + return err + } + headerEPBS, ok := header.Proto().(*enginev1.ExecutionPayloadHeaderEPBS) + if !ok { + return errors.New("not an ePBS execution payload header") + } + return state.SetLatestExecutionPayloadHeaderEPBS(headerEPBS) +} diff --git a/beacon-chain/core/transition/transition_no_verify_sig.go b/beacon-chain/core/transition/transition_no_verify_sig.go index 402d607837b3..7785ffc79220 100644 --- a/beacon-chain/core/transition/transition_no_verify_sig.go +++ b/beacon-chain/core/transition/transition_no_verify_sig.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/altair" b "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/blocks" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/electra" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/transition/interop" v "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/validators" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" @@ -272,7 +272,7 @@ func ProcessOperationsNoVerifyAttsSigs( return nil, err } } else { - state, err = electra.ProcessOperations(ctx, state, beaconBlock) + state, err = epbs.ProcessOperations(ctx, state, beaconBlock) if err != nil { return nil, err } @@ -318,27 +318,9 @@ func ProcessBlockForStateRoot( return nil, errors.Wrap(err, "could not process block header") } - enabled, err := b.IsExecutionEnabled(state, blk.Body()) - if err != nil { - return nil, errors.Wrap(err, "could not check if execution is enabled") + if err := processExecution(state, blk.Body()); err != nil { + return nil, errors.Wrap(err, "could not process execution") } - if enabled { - executionData, err := blk.Body().Execution() - if err != nil { - return nil, err - } - if state.Version() >= version.Capella { - state, err = b.ProcessWithdrawals(state, executionData) - if err != nil { - return nil, errors.Wrap(err, "could not process withdrawals") - } - } - state, err = b.ProcessPayload(state, blk.Body()) - if err != nil { - return nil, errors.Wrap(err, "could not process execution data") - } - } - randaoReveal := signed.Block().Body().RandaoReveal() state, err = b.ProcessRandaoNoVerify(state, randaoReveal[:]) if err != nil { diff --git a/beacon-chain/verification/BUILD.bazel b/beacon-chain/verification/BUILD.bazel index f147cac985ca..5822f4c51a88 100644 --- a/beacon-chain/verification/BUILD.bazel +++ b/beacon-chain/verification/BUILD.bazel @@ -26,6 +26,7 @@ go_library( visibility = ["//visibility:public"], deps = [ "//beacon-chain/blockchain/kzg:go_default_library", + "//beacon-chain/core/epbs:go_default_library", "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/signing:go_default_library", "//beacon-chain/core/transition:go_default_library", diff --git a/beacon-chain/verification/execution_payload_header.go b/beacon-chain/verification/execution_payload_header.go index 99a84e8989ca..86eeb98533b6 100644 --- a/beacon-chain/verification/execution_payload_header.go +++ b/beacon-chain/verification/execution_payload_header.go @@ -4,13 +4,12 @@ import ( "fmt" "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epbs" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" - "github.com/prysmaticlabs/prysm/v5/crypto/bls" - "github.com/prysmaticlabs/prysm/v5/network/forks" "github.com/prysmaticlabs/prysm/v5/time/slots" log "github.com/sirupsen/logrus" ) @@ -143,7 +142,7 @@ func (v *HeaderVerifier) VerifyParentBlockRootSeen(seen func([32]byte) bool) (er func (v *HeaderVerifier) VerifySignature() (err error) { defer v.record(RequireSignatureValid, &err) - err = validatePayloadHeaderSignature(v.st, v.h) + err = epbs.ValidatePayloadHeaderSignature(v.st, v.h) if err != nil { h, envErr := v.h.Header() if envErr != nil { @@ -201,43 +200,3 @@ func headerLogFields(h interfaces.ROExecutionPayloadHeaderEPBS) log.Fields { "value": h.Value(), } } - -// validatePayloadHeaderSignature validates the signature of the execution payload header. -func validatePayloadHeaderSignature(st state.ReadOnlyBeaconState, sh interfaces.ROSignedExecutionPayloadHeader) error { - h, err := sh.Header() - if err != nil { - return err - } - - pubkey := st.PubkeyAtIndex(h.BuilderIndex()) - pub, err := bls.PublicKeyFromBytes(pubkey[:]) - if err != nil { - return err - } - - s := sh.Signature() - sig, err := bls.SignatureFromBytes(s[:]) - if err != nil { - return err - } - - currentEpoch := slots.ToEpoch(h.Slot()) - f, err := forks.Fork(currentEpoch) - if err != nil { - return err - } - - domain, err := signing.Domain(f, currentEpoch, params.BeaconConfig().DomainBeaconBuilder, st.GenesisValidatorsRoot()) - if err != nil { - return err - } - root, err := sh.SigningRoot(domain) - if err != nil { - return err - } - if !sig.Verify(pub, root[:]) { - return signing.ErrSigFailedToVerify - } - - return nil -} diff --git a/consensus-types/blocks/signed_execution_payload_header.go b/consensus-types/blocks/signed_execution_payload_header.go index 846550220989..0fb7b4564193 100644 --- a/consensus-types/blocks/signed_execution_payload_header.go +++ b/consensus-types/blocks/signed_execution_payload_header.go @@ -6,6 +6,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" + "google.golang.org/protobuf/proto" ) type signedExecutionPayloadHeader struct { @@ -119,3 +120,8 @@ func (p executionPayloadHeaderEPBS) Value() primitives.Gwei { func (p executionPayloadHeaderEPBS) BlobKzgCommitmentsRoot() [32]byte { return [32]byte(p.p.BlobKzgCommitmentsRoot) } + +// Proto() returns the message type +func (p executionPayloadHeaderEPBS) Proto() proto.Message { + return p.p +} diff --git a/consensus-types/interfaces/signed_execution_payload_header.go b/consensus-types/interfaces/signed_execution_payload_header.go index 9ef4d0ab1d6c..ac17b8e73806 100644 --- a/consensus-types/interfaces/signed_execution_payload_header.go +++ b/consensus-types/interfaces/signed_execution_payload_header.go @@ -3,6 +3,7 @@ package interfaces import ( field_params "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "google.golang.org/protobuf/proto" ) type ROSignedExecutionPayloadHeader interface { @@ -22,4 +23,5 @@ type ROExecutionPayloadHeaderEPBS interface { Value() primitives.Gwei BlobKzgCommitmentsRoot() [32]byte IsNil() bool + Proto() proto.Message } diff --git a/testing/spectest/shared/common/operations/block_header.go b/testing/spectest/shared/common/operations/block_header.go index d38221535aff..c1f01e597ef1 100644 --- a/testing/spectest/shared/common/operations/block_header.go +++ b/testing/spectest/shared/common/operations/block_header.go @@ -56,10 +56,10 @@ func RunBlockHeaderTest(t *testing.T, config string, fork string, sszToBlock SSZ bodyRoot, err := block.Block().Body().HashTreeRoot() require.NoError(t, err) pr := block.Block().ParentRoot() - beaconState, err := blocks.ProcessBlockHeaderNoVerify(context.Background(), preBeaconState, block.Block().Slot(), block.Block().ProposerIndex(), pr[:], bodyRoot[:]) + _, err = blocks.ProcessBlockHeaderNoVerify(context.Background(), preBeaconState, block.Block().Slot(), block.Block().ProposerIndex(), pr[:], bodyRoot[:]) if postSSZExists { require.NoError(t, err) - comparePostState(t, postSSZFilepath, sszToState, preBeaconState, beaconState) + comparePostState(t, postSSZFilepath, sszToState, preBeaconState) } else { // Note: This doesn't test anything worthwhile. It essentially tests // that *any* error has occurred, not any specific error. diff --git a/testing/spectest/shared/common/operations/execution_payload.go b/testing/spectest/shared/common/operations/execution_payload.go index c305efae2cd0..652b500aa496 100644 --- a/testing/spectest/shared/common/operations/execution_payload.go +++ b/testing/spectest/shared/common/operations/execution_payload.go @@ -54,10 +54,10 @@ func RunExecutionPayloadTest(t *testing.T, config string, fork string, sszToBloc config := &ExecutionConfig{} require.NoError(t, utils.UnmarshalYaml(file, config), "Failed to Unmarshal") - gotState, err := blocks.ProcessPayload(preBeaconState, body) + err = blocks.ProcessPayload(preBeaconState, body) if postSSZExists { require.NoError(t, err) - comparePostState(t, postSSZFilepath, sszToState, preBeaconState, gotState) + comparePostState(t, postSSZFilepath, sszToState, preBeaconState) } else if config.Valid { // Note: This doesn't test anything worthwhile. It essentially tests // that *any* error has occurred, not any specific error. diff --git a/testing/spectest/shared/common/operations/test_runner.go b/testing/spectest/shared/common/operations/test_runner.go index e166c830f0dc..1fa5087841b7 100644 --- a/testing/spectest/shared/common/operations/test_runner.go +++ b/testing/spectest/shared/common/operations/test_runner.go @@ -50,10 +50,10 @@ func RunBlockOperationTest( } helpers.ClearCache() - beaconState, err := operationFn(context.Background(), preState, wsb) + _, err = operationFn(context.Background(), preState, wsb) if postSSZExists { require.NoError(t, err) - comparePostState(t, postSSZFilepath, sszToState, preState, beaconState) + comparePostState(t, postSSZFilepath, sszToState, preState) } else { // Note: This doesn't test anything worthwhile. It essentially tests // that *any* error has occurred, not any specific error. @@ -65,7 +65,7 @@ func RunBlockOperationTest( } } -func comparePostState(t *testing.T, postSSZFilepath string, sszToState SSZToState, want state.BeaconState, got state.BeaconState) { +func comparePostState(t *testing.T, postSSZFilepath string, sszToState SSZToState, want state.BeaconState) { postBeaconStateFile, err := os.ReadFile(postSSZFilepath) // #nosec G304 require.NoError(t, err) postBeaconStateSSZ, err := snappy.Decode(nil /* dst */, postBeaconStateFile)