From 4d35e776da91cc34abaf68d6120023c557201ee9 Mon Sep 17 00:00:00 2001 From: a Date: Sun, 11 Jun 2023 16:50:02 -0500 Subject: [PATCH] [caplin] abstract transition (#7661) start of abstracting the transition layer. incremental movements. --- cl/phase1/core/transition/block_transition.go | 211 ----- cl/phase1/core/transition/operations.go | 281 ------ .../core/transition/process_attestations.go | 260 ------ .../process_bls_to_execution_change.go | 65 -- cl/phase1/core/transition/process_epoch.go | 103 --- .../core/transition/process_slashings.go | 56 -- cl/phase1/core/transition/process_slots.go | 183 ---- .../core/transition/process_sync_aggregate.go | 93 -- cl/phase1/core/transition/processing.go | 120 --- cl/phase1/forkchoice/fork_graph/fork_graph.go | 2 +- cl/phase1/forkchoice/on_block.go | 5 +- cl/phase1/forkchoice/utils.go | 9 +- cl/phase1/stages/stages_beacon_state.go | 2 +- .../consensus_tests/epoch_processing.go | 30 +- cl/spectest/consensus_tests/finality.go | 5 +- cl/spectest/consensus_tests/operations.go | 19 +- cl/spectest/consensus_tests/sanity.go | 7 +- cl/spectest/consensus_tests/transition.go | 5 +- cl/spectest/tests_test.go | 3 +- cl/transition/compat.go | 19 + .../impl/eth2}/block_processing_test.go | 13 +- cl/transition/impl/eth2/impl.go | 11 + cl/transition/impl/eth2/operations.go | 872 ++++++++++++++++++ .../finalization_and_justification.go | 20 +- .../process_effective_balance_update.go | 2 +- .../impl/eth2/statechange/process_epoch.go | 49 + .../eth2/statechange}/process_epoch_test.go | 28 +- .../statechange/process_historical_roots.go | 59 ++ .../process_historical_roots_update.go | 44 + .../statechange}/process_inactivity_scores.go | 16 +- .../statechange}/process_registry_updates.go | 12 +- .../process_rewards_and_penalties.go | 56 +- .../eth2/statechange/process_slashings.go | 93 ++ .../process_sync_committee_update.go | 8 +- .../process_sync_committee_update_test.go | 25 +- .../impl/eth2/statechange}/resets.go | 2 +- .../block_processing/capella_block.ssz_snappy | Bin .../block_processing/capella_state.ssz_snappy | Bin .../effective_balances_expected.ssz_snappy | Bin .../effective_balances_test_state.ssz_snappy | Bin .../eth1_data_reset_expected_test.ssz_snappy | Bin .../eth1_data_reset_state_test.ssz_snappy | Bin .../historical_roots_expected_test.ssz_snappy | Bin .../historical_roots_state_test.ssz_snappy | Bin ...inactivity_scores_expected_test.ssz_snappy | Bin .../inactivity_scores_state_test.ssz_snappy | Bin ...tion_and_finality_expected_test.ssz_snappy | Bin ...ication_and_finality_state_test.ssz_snappy | Bin ...tion_flag_updates_expected_test.ssz_snappy | Bin ...ipation_flag_updates_state_test.ssz_snappy | Bin ...andao_mixes_reset_expected_test.ssz_snappy | Bin .../randao_mixes_reset_state_test.ssz_snappy | Bin .../registry_updates_test_expected.ssz_snappy | Bin .../registry_updates_test_state.ssz_snappy | Bin .../rewards_penalty_test_expected.ssz_snappy | Bin .../rewards_penalty_test_state.ssz_snappy | Bin .../slashings_expected_test.ssz_snappy | Bin .../slashings_reset_expected_test.ssz_snappy | Bin .../slashings_reset_state_test.ssz_snappy | Bin .../slashings_state_test.ssz_snappy | Bin .../impl/eth2/utils.go} | 57 +- cl/transition/impl/eth2/validation.go | 54 ++ cl/transition/impl/funcmap/impl.go | 94 ++ cl/transition/machine/block.go | 141 +++ cl/transition/machine/helpers.go | 11 + cl/transition/machine/machine.go | 48 + cl/transition/machine/transition.go | 32 + spectest/case.go | 3 + spectest/suite.go | 4 +- 69 files changed, 1694 insertions(+), 1538 deletions(-) delete mode 100644 cl/phase1/core/transition/block_transition.go delete mode 100644 cl/phase1/core/transition/operations.go delete mode 100644 cl/phase1/core/transition/process_attestations.go delete mode 100644 cl/phase1/core/transition/process_bls_to_execution_change.go delete mode 100644 cl/phase1/core/transition/process_epoch.go delete mode 100644 cl/phase1/core/transition/process_slashings.go delete mode 100644 cl/phase1/core/transition/process_slots.go delete mode 100644 cl/phase1/core/transition/process_sync_aggregate.go delete mode 100644 cl/phase1/core/transition/processing.go create mode 100644 cl/transition/compat.go rename cl/{phase1/core/transition => transition/impl/eth2}/block_processing_test.go (51%) create mode 100644 cl/transition/impl/eth2/impl.go create mode 100644 cl/transition/impl/eth2/operations.go rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/finalization_and_justification.go (86%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_effective_balance_update.go (98%) create mode 100644 cl/transition/impl/eth2/statechange/process_epoch.go rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_epoch_test.go (90%) create mode 100644 cl/transition/impl/eth2/statechange/process_historical_roots.go create mode 100644 cl/transition/impl/eth2/statechange/process_historical_roots_update.go rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_inactivity_scores.go (53%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_registry_updates.go (87%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_rewards_and_penalties.go (74%) create mode 100644 cl/transition/impl/eth2/statechange/process_slashings.go rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_sync_committee_update.go (63%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/process_sync_committee_update_test.go (61%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/resets.go (97%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/block_processing/capella_block.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/block_processing/capella_state.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/effective_balances_expected.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/effective_balances_test_state.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/eth1_data_reset_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/eth1_data_reset_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/historical_roots_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/historical_roots_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/inactivity_scores_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/inactivity_scores_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/justification_and_finality_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/justification_and_finality_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/participation_flag_updates_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/participation_flag_updates_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/randao_mixes_reset_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/randao_mixes_reset_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/registry_updates_test_expected.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/registry_updates_test_state.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/rewards_penalty_test_expected.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/rewards_penalty_test_state.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/slashings_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/slashings_reset_expected_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/slashings_reset_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition => transition/impl/eth2/statechange}/test_data/epoch_processing/slashings_state_test.ssz_snappy (100%) rename cl/{phase1/core/transition/process_blob_kzg_commitments.go => transition/impl/eth2/utils.go} (63%) create mode 100644 cl/transition/impl/eth2/validation.go create mode 100644 cl/transition/impl/funcmap/impl.go create mode 100644 cl/transition/machine/block.go create mode 100644 cl/transition/machine/helpers.go create mode 100644 cl/transition/machine/machine.go create mode 100644 cl/transition/machine/transition.go diff --git a/cl/phase1/core/transition/block_transition.go b/cl/phase1/core/transition/block_transition.go deleted file mode 100644 index 400f27128..000000000 --- a/cl/phase1/core/transition/block_transition.go +++ /dev/null @@ -1,211 +0,0 @@ -package transition - -import ( - "errors" - "fmt" - - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/metrics/methelp" -) - -// processBlock takes a block and transitions the state to the next slot, using the provided execution payload if enabled. -func processBlock(state *state2.BeaconState, signedBlock *cltypes.SignedBeaconBlock, fullValidation bool) error { - block := signedBlock.Block - version := state.Version() - // Check the state version is correct. - if signedBlock.Version() != version { - return fmt.Errorf("processBlock: wrong state version for block at slot %d", block.Slot) - } - - h := methelp.NewHistTimer("beacon_process_block") - - c := h.Tag("process_step", "block_header") - // Process the block header. - if err := ProcessBlockHeader(state, block, fullValidation); err != nil { - return fmt.Errorf("processBlock: failed to process block header: %v", err) - } - c.PutSince() - - // Process execution payload if enabled. - if version >= clparams.BellatrixVersion && executionEnabled(state, block.Body.ExecutionPayload) { - if state.Version() >= clparams.CapellaVersion { - // Process withdrawals in the execution payload. - c = h.Tag("process_step", "withdrawals") - if err := ProcessWithdrawals(state, block.Body.ExecutionPayload.Withdrawals, fullValidation); err != nil { - return fmt.Errorf("processBlock: failed to process withdrawals: %v", err) - } - c.PutSince() - } - - // Process the execution payload. - c = h.Tag("process_step", "execution_payload") - if err := ProcessExecutionPayload(state, block.Body.ExecutionPayload); err != nil { - return fmt.Errorf("processBlock: failed to process execution payload: %v", err) - } - c.PutSince() - } - - // Process RANDAO reveal. - c = h.Tag("process_step", "randao_reveal") - if err := ProcessRandao(state, block.Body.RandaoReveal, block.ProposerIndex, fullValidation); err != nil { - return fmt.Errorf("processBlock: failed to process RANDAO reveal: %v", err) - } - c.PutSince() - - // Process Eth1 data. - c = h.Tag("process_step", "eth1_data") - if err := ProcessEth1Data(state, block.Body.Eth1Data); err != nil { - return fmt.Errorf("processBlock: failed to process Eth1 data: %v", err) - } - c.PutSince() - - // Process block body operations. - c = h.Tag("process_step", "operations") - if err := processOperations(state, block.Body, fullValidation); err != nil { - return fmt.Errorf("processBlock: failed to process block body operations: %v", err) - } - c.PutSince() - - // Process sync aggregate in case of Altair version. - if version >= clparams.AltairVersion { - c = h.Tag("process_step", "sync_aggregate") - if err := ProcessSyncAggregate(state, block.Body.SyncAggregate, fullValidation); err != nil { - return fmt.Errorf("processBlock: failed to process sync aggregate: %v", err) - } - c.PutSince() - } - - if version >= clparams.DenebVersion && fullValidation { - c = h.Tag("process_step", "blob_kzg_commitments") - verified, err := VerifyKzgCommitmentsAgainstTransactions(block.Body.ExecutionPayload.Transactions, block.Body.BlobKzgCommitments) - if err != nil { - return fmt.Errorf("processBlock: failed to process blob kzg commitments: %w", err) - } - if !verified { - return fmt.Errorf("processBlock: failed to process blob kzg commitments: commitments are not equal") - } - c.PutSince() - } - - h.PutSince() - return nil -} - -func processOperations(state *state2.BeaconState, blockBody *cltypes.BeaconBody, fullValidation bool) error { - if blockBody.Deposits.Len() != int(maximumDeposits(state)) { - return errors.New("outstanding deposits do not match maximum deposits") - } - h := methelp.NewHistTimer("beacon_process_block_operations") - - // Process each proposer slashing - var err error - c := h.Tag("operation", "proposer_slashings") - if err := solid.RangeErr[*cltypes.ProposerSlashing](blockBody.ProposerSlashings, func(index int, slashing *cltypes.ProposerSlashing, length int) error { - if err = ProcessProposerSlashing(state, slashing); err != nil { - return fmt.Errorf("ProcessProposerSlashing: %s", err) - } - return nil - }); err != nil { - return err - } - c.PutSince() - - c = h.Tag("operation", "attester_slashings") - if err := solid.RangeErr[*cltypes.AttesterSlashing](blockBody.AttesterSlashings, func(index int, slashing *cltypes.AttesterSlashing, length int) error { - if err = ProcessAttesterSlashing(state, slashing); err != nil { - return fmt.Errorf("ProcessAttesterSlashing: %s", err) - } - return nil - }); err != nil { - return err - } - - c.PutSince() - - // Process each attestations - c = h.Tag("operation", "attestations", "validation", "false") - if fullValidation { - c = h.Tag("operation", "attestations", "validation", "true") - } - if err := ProcessAttestations(state, blockBody.Attestations, fullValidation); err != nil { - return fmt.Errorf("ProcessAttestation: %s", err) - } - c.PutSince() - - // Process each deposit - c = h.Tag("operation", "deposit") - if err := solid.RangeErr[*cltypes.Deposit](blockBody.Deposits, func(index int, deposit *cltypes.Deposit, length int) error { - if err = ProcessDeposit(state, deposit, fullValidation); err != nil { - return fmt.Errorf("ProcessDeposit: %s", err) - } - return nil - }); err != nil { - return err - } - c.PutSince() - - // Process each voluntary exit. - c = h.Tag("operation", "voluntary_exit") - if err := solid.RangeErr[*cltypes.SignedVoluntaryExit](blockBody.VoluntaryExits, func(index int, exit *cltypes.SignedVoluntaryExit, length int) error { - if err = ProcessVoluntaryExit(state, exit, fullValidation); err != nil { - return fmt.Errorf("ProcessVoluntaryExit: %s", err) - } - return nil - }); err != nil { - return err - } - c.PutSince() - if state.Version() < clparams.CapellaVersion { - return nil - } - // Process each execution change. this will only have entries after the capella fork. - c = h.Tag("operation", "execution_change") - if err := solid.RangeErr[*cltypes.SignedBLSToExecutionChange](blockBody.ExecutionChanges, func(index int, addressChange *cltypes.SignedBLSToExecutionChange, length int) error { - if err := ProcessBlsToExecutionChange(state, addressChange, fullValidation); err != nil { - return fmt.Errorf("ProcessBlsToExecutionChange: %s", err) - } - return nil - }); err != nil { - return err - } - c.PutSince() - return nil -} - -func maximumDeposits(state *state2.BeaconState) (maxDeposits uint64) { - maxDeposits = state.Eth1Data().DepositCount - state.Eth1DepositIndex() - if maxDeposits > state.BeaconConfig().MaxDeposits { - maxDeposits = state.BeaconConfig().MaxDeposits - } - return -} - -// ProcessExecutionPayload sets the latest payload header accordinly. -func ProcessExecutionPayload(s *state2.BeaconState, payload *cltypes.Eth1Block) error { - if state2.IsMergeTransitionComplete(s.BeaconState) { - if payload.ParentHash != s.LatestExecutionPayloadHeader().BlockHash { - return fmt.Errorf("ProcessExecutionPayload: invalid eth1 chain. mismatching parent") - } - } - if payload.PrevRandao != s.GetRandaoMixes(state2.Epoch(s.BeaconState)) { - return fmt.Errorf("ProcessExecutionPayload: randao mix mismatches with mix digest") - } - if payload.Time != state2.ComputeTimestampAtSlot(s.BeaconState, s.Slot()) { - return fmt.Errorf("ProcessExecutionPayload: invalid Eth1 timestamp") - } - payloadHeader, err := payload.PayloadHeader() - if err != nil { - return err - } - s.SetLatestExecutionPayloadHeader(payloadHeader) - return nil -} - -func executionEnabled(s *state2.BeaconState, payload *cltypes.Eth1Block) bool { - return (!state2.IsMergeTransitionComplete(s.BeaconState) && payload.BlockHash != libcommon.Hash{}) || state2.IsMergeTransitionComplete(s.BeaconState) -} diff --git a/cl/phase1/core/transition/operations.go b/cl/phase1/core/transition/operations.go deleted file mode 100644 index c79d05466..000000000 --- a/cl/phase1/core/transition/operations.go +++ /dev/null @@ -1,281 +0,0 @@ -package transition - -import ( - "errors" - "fmt" - - "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - "github.com/Giulio2002/bls" - "github.com/ledgerwatch/log/v3" - - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/fork" - "github.com/ledgerwatch/erigon/cl/utils" - "github.com/ledgerwatch/erigon/core/types" -) - -func ProcessProposerSlashing(s *state2.BeaconState, propSlashing *cltypes.ProposerSlashing) error { - h1 := propSlashing.Header1.Header - h2 := propSlashing.Header2.Header - - if h1.Slot != h2.Slot { - return fmt.Errorf("non-matching slots on proposer slashing: %d != %d", h1.Slot, h2.Slot) - } - - if h1.ProposerIndex != h2.ProposerIndex { - return fmt.Errorf("non-matching proposer indices proposer slashing: %d != %d", h1.ProposerIndex, h2.ProposerIndex) - } - - h1Root, err := h1.HashSSZ() - if err != nil { - return fmt.Errorf("unable to hash header1: %v", err) - } - h2Root, err := h2.HashSSZ() - if err != nil { - return fmt.Errorf("unable to hash header2: %v", err) - } - if h1Root == h2Root { - return fmt.Errorf("propose slashing headers are the same: %v == %v", h1Root, h2Root) - } - - proposer, err := s.ValidatorForValidatorIndex(int(h1.ProposerIndex)) - if err != nil { - return err - } - if !proposer.IsSlashable(state2.Epoch(s.BeaconState)) { - return fmt.Errorf("proposer is not slashable: %v", proposer) - } - - for _, signedHeader := range []*cltypes.SignedBeaconBlockHeader{propSlashing.Header1, propSlashing.Header2} { - domain, err := s.GetDomain(s.BeaconConfig().DomainBeaconProposer, state2.GetEpochAtSlot(s.BeaconConfig(), signedHeader.Header.Slot)) - if err != nil { - return fmt.Errorf("unable to get domain: %v", err) - } - signingRoot, err := fork.ComputeSigningRoot(signedHeader.Header, domain) - if err != nil { - return fmt.Errorf("unable to compute signing root: %v", err) - } - pk := proposer.PublicKey() - valid, err := bls.Verify(signedHeader.Signature[:], signingRoot[:], pk[:]) - if err != nil { - return fmt.Errorf("unable to verify signature: %v", err) - } - if !valid { - return fmt.Errorf("invalid signature: signature %v, root %v, pubkey %v", signedHeader.Signature[:], signingRoot[:], pk) - } - } - - // Set whistleblower index to 0 so current proposer gets reward. - s.SlashValidator(h1.ProposerIndex, nil) - return nil -} - -func ProcessAttesterSlashing(s *state2.BeaconState, attSlashing *cltypes.AttesterSlashing) error { - att1 := attSlashing.Attestation_1 - att2 := attSlashing.Attestation_2 - - if !cltypes.IsSlashableAttestationData(att1.Data, att2.Data) { - return fmt.Errorf("attestation data not slashable: %+v; %+v", att1.Data, att2.Data) - } - - valid, err := state2.IsValidIndexedAttestation(s.BeaconState, att1) - if err != nil { - return fmt.Errorf("error calculating indexed attestation 1 validity: %v", err) - } - if !valid { - return fmt.Errorf("invalid indexed attestation 1") - } - - valid, err = state2.IsValidIndexedAttestation(s.BeaconState, att2) - if err != nil { - return fmt.Errorf("error calculating indexed attestation 2 validity: %v", err) - } - if !valid { - return fmt.Errorf("invalid indexed attestation 2") - } - - slashedAny := false - currentEpoch := state2.GetEpochAtSlot(s.BeaconConfig(), s.Slot()) - for _, ind := range solid.IntersectionOfSortedSets(att1.AttestingIndices, att2.AttestingIndices) { - validator, err := s.ValidatorForValidatorIndex(int(ind)) - if err != nil { - return err - } - if validator.IsSlashable(currentEpoch) { - err := s.SlashValidator(ind, nil) - if err != nil { - return fmt.Errorf("unable to slash validator: %d", ind) - } - slashedAny = true - } - } - - if !slashedAny { - return fmt.Errorf("no validators slashed") - } - return nil -} - -func ProcessDeposit(s *state2.BeaconState, deposit *cltypes.Deposit, fullValidation bool) error { - if deposit == nil { - return nil - } - depositLeaf, err := deposit.Data.HashSSZ() - if err != nil { - return err - } - depositIndex := s.Eth1DepositIndex() - eth1Data := s.Eth1Data() - rawProof := []common.Hash{} - deposit.Proof.Range(func(_ int, l common.Hash, _ int) bool { - rawProof = append(rawProof, l) - return true - }) - // Validate merkle proof for deposit leaf. - if fullValidation && !utils.IsValidMerkleBranch( - depositLeaf, - rawProof, - s.BeaconConfig().DepositContractTreeDepth+1, - depositIndex, - eth1Data.Root, - ) { - return fmt.Errorf("processDepositForAltair: Could not validate deposit root") - } - - // Increment index - s.SetEth1DepositIndex(depositIndex + 1) - publicKey := deposit.Data.PubKey - amount := deposit.Data.Amount - // Check if pub key is in validator set - validatorIndex, has := s.ValidatorIndexByPubkey(publicKey) - if !has { - // Agnostic domain. - domain, err := fork.ComputeDomain(s.BeaconConfig().DomainDeposit[:], utils.Uint32ToBytes4(s.BeaconConfig().GenesisForkVersion), [32]byte{}) - if err != nil { - return err - } - depositMessageRoot, err := deposit.Data.MessageHash() - if err != nil { - return err - } - signedRoot := utils.Keccak256(depositMessageRoot[:], domain) - // Perform BLS verification and if successful noice. - valid, err := bls.Verify(deposit.Data.Signature[:], signedRoot[:], publicKey[:]) - // Literally you can input it trash. - if !valid || err != nil { - log.Debug("Validator BLS verification failed", "valid", valid, "err", err) - return nil - } - // Append validator - s.AddValidator(state2.ValidatorFromDeposit(s.BeaconConfig(), deposit), amount) - // Altair forward - if s.Version() >= clparams.AltairVersion { - s.AddCurrentEpochParticipationFlags(cltypes.ParticipationFlags(0)) - s.AddPreviousEpochParticipationFlags(cltypes.ParticipationFlags(0)) - s.AddInactivityScore(0) - } - return nil - } - // Increase the balance if exists already - return state2.IncreaseBalance(s.BeaconState, validatorIndex, amount) -} - -// ProcessVoluntaryExit takes a voluntary exit and applies state transition. -func ProcessVoluntaryExit(s *state2.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit, fullValidation bool) error { - // Sanity checks so that we know it is good. - voluntaryExit := signedVoluntaryExit.VolunaryExit - currentEpoch := state2.Epoch(s.BeaconState) - validator, err := s.ValidatorForValidatorIndex(int(voluntaryExit.ValidatorIndex)) - if err != nil { - return err - } - if !validator.Active(currentEpoch) { - return errors.New("ProcessVoluntaryExit: validator is not active") - } - if validator.ExitEpoch() != s.BeaconConfig().FarFutureEpoch { - return errors.New("ProcessVoluntaryExit: another exit for the same validator is already getting processed") - } - if currentEpoch < voluntaryExit.Epoch { - return errors.New("ProcessVoluntaryExit: exit is happening in the future") - } - if currentEpoch < validator.ActivationEpoch()+s.BeaconConfig().ShardCommitteePeriod { - return errors.New("ProcessVoluntaryExit: exit is happening too fast") - } - - // We can skip it in some instances if we want to optimistically sync up. - if fullValidation { - domain, err := s.GetDomain(s.BeaconConfig().DomainVoluntaryExit, voluntaryExit.Epoch) - if err != nil { - return err - } - signingRoot, err := fork.ComputeSigningRoot(voluntaryExit, domain) - if err != nil { - return err - } - pk := validator.PublicKey() - valid, err := bls.Verify(signedVoluntaryExit.Signature[:], signingRoot[:], pk[:]) - if err != nil { - return err - } - if !valid { - return errors.New("ProcessVoluntaryExit: BLS verification failed") - } - } - // Do the exit (same process in slashing). - return s.InitiateValidatorExit(voluntaryExit.ValidatorIndex) -} - -// ProcessWithdrawals processes withdrawals by decreasing the balance of each validator -// and updating the next withdrawal index and validator index. -func ProcessWithdrawals(s *state2.BeaconState, withdrawals *solid.ListSSZ[*types.Withdrawal], fullValidation bool) error { - // Get the list of withdrawals, the expected withdrawals (if performing full validation), - // and the beacon configuration. - beaconConfig := s.BeaconConfig() - numValidators := uint64(s.ValidatorLength()) - - // Check if full validation is required and verify expected withdrawals. - if fullValidation { - expectedWithdrawals := state2.ExpectedWithdrawals(s.BeaconState) - if len(expectedWithdrawals) != withdrawals.Len() { - return fmt.Errorf("ProcessWithdrawals: expected %d withdrawals, but got %d", len(expectedWithdrawals), withdrawals.Len()) - } - if err := solid.RangeErr[*types.Withdrawal](withdrawals, func(i int, w *types.Withdrawal, _ int) error { - if !expectedWithdrawals[i].Equal(w) { - return fmt.Errorf("ProcessWithdrawals: withdrawal %d does not match expected withdrawal", i) - } - return nil - }); err != nil { - return err - } - } - - if err := solid.RangeErr[*types.Withdrawal](withdrawals, func(_ int, w *types.Withdrawal, _ int) error { - if err := state2.DecreaseBalance(s.BeaconState, w.Validator, w.Amount); err != nil { - return err - } - return nil - }); err != nil { - return err - } - - // Update next withdrawal index based on number of withdrawals. - if withdrawals.Len() > 0 { - lastWithdrawalIndex := withdrawals.Get(withdrawals.Len() - 1).Index - s.SetNextWithdrawalIndex(lastWithdrawalIndex + 1) - } - - // Update next withdrawal validator index based on number of withdrawals. - if withdrawals.Len() == int(beaconConfig.MaxWithdrawalsPerPayload) { - lastWithdrawalValidatorIndex := withdrawals.Get(withdrawals.Len()-1).Validator + 1 - s.SetNextWithdrawalValidatorIndex(lastWithdrawalValidatorIndex % numValidators) - } else { - nextIndex := s.NextWithdrawalValidatorIndex() + beaconConfig.MaxValidatorsPerWithdrawalsSweep - s.SetNextWithdrawalValidatorIndex(nextIndex % numValidators) - } - - return nil -} diff --git a/cl/phase1/core/transition/process_attestations.go b/cl/phase1/core/transition/process_attestations.go deleted file mode 100644 index 55491897c..000000000 --- a/cl/phase1/core/transition/process_attestations.go +++ /dev/null @@ -1,260 +0,0 @@ -package transition - -import ( - "errors" - "fmt" - - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/utils" - "github.com/ledgerwatch/erigon/metrics/methelp" - "golang.org/x/exp/slices" -) - -func ProcessAttestations(s *state2.BeaconState, attestations *solid.ListSSZ[*solid.Attestation], fullValidation bool) error { - attestingIndiciesSet := make([][]uint64, attestations.Len()) - h := methelp.NewHistTimer("beacon_process_attestations") - baseRewardPerIncrement := s.BaseRewardPerIncrement() - - c := h.Tag("attestation_step", "process") - var err error - if err := solid.RangeErr[*solid.Attestation](attestations, func(i int, a *solid.Attestation, _ int) error { - if attestingIndiciesSet[i], err = processAttestation(s, a, baseRewardPerIncrement); err != nil { - return err - } - return nil - }); err != nil { - return err - } - if err != nil { - return err - } - var valid bool - c.PutSince() - if fullValidation { - c = h.Tag("attestation_step", "validate") - valid, err = verifyAttestations(s, attestations, attestingIndiciesSet) - if err != nil { - return err - } - if !valid { - return errors.New("ProcessAttestation: wrong bls data") - } - c.PutSince() - } - - return nil -} - -func processAttestationPostAltair(s *state2.BeaconState, attestation *solid.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { - data := attestation.AttestantionData() - currentEpoch := state2.Epoch(s.BeaconState) - stateSlot := s.Slot() - beaconConfig := s.BeaconConfig() - - h := methelp.NewHistTimer("beacon_process_attestation_post_altair") - - c := h.Tag("step", "get_participation_flag") - participationFlagsIndicies, err := s.GetAttestationParticipationFlagIndicies(attestation.AttestantionData(), stateSlot-data.Slot()) - if err != nil { - return nil, err - } - c.PutSince() - - c = h.Tag("step", "get_attesting_indices") - - attestingIndicies, err := s.GetAttestingIndicies(attestation.AttestantionData(), attestation.AggregationBits(), true) - if err != nil { - return nil, err - } - - c.PutSince() - - var proposerRewardNumerator uint64 - - isCurrentEpoch := data.Target().Epoch() == currentEpoch - - c = h.Tag("step", "update_attestation") - for _, attesterIndex := range attestingIndicies { - val, err := s.ValidatorEffectiveBalance(int(attesterIndex)) - if err != nil { - return nil, err - } - - baseReward := (val / beaconConfig.EffectiveBalanceIncrement) * baseRewardPerIncrement - for flagIndex, weight := range beaconConfig.ParticipationWeights() { - flagParticipation := s.EpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex)) - if !slices.Contains(participationFlagsIndicies, uint8(flagIndex)) || flagParticipation.HasFlag(flagIndex) { - continue - } - s.SetEpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex), flagParticipation.Add(flagIndex)) - proposerRewardNumerator += baseReward * weight - } - } - c.PutSince() - // Reward proposer - c = h.Tag("step", "get_proposer_index") - proposer, err := s.GetBeaconProposerIndex() - if err != nil { - return nil, err - } - c.PutSince() - proposerRewardDenominator := (beaconConfig.WeightDenominator - beaconConfig.ProposerWeight) * beaconConfig.WeightDenominator / beaconConfig.ProposerWeight - reward := proposerRewardNumerator / proposerRewardDenominator - return attestingIndicies, state2.IncreaseBalance(s.BeaconState, proposer, reward) -} - -// processAttestationsPhase0 implements the rules for phase0 processing. -func processAttestationPhase0(s *state2.BeaconState, attestation *solid.Attestation) ([]uint64, error) { - data := attestation.AttestantionData() - committee, err := s.GetBeaconCommitee(data.Slot(), data.ValidatorIndex()) - if err != nil { - return nil, err - } - - if len(committee) != utils.GetBitlistLength(attestation.AggregationBits()) { - return nil, fmt.Errorf("processAttestationPhase0: mismatching aggregation bits size") - } - // Cached so it is performant. - proposerIndex, err := s.GetBeaconProposerIndex() - if err != nil { - return nil, err - } - // Create the attestation to add to pending attestations - pendingAttestation := solid.NewPendingAttestionFromParameters( - attestation.AggregationBits(), - data, - s.Slot()-data.Slot(), - proposerIndex, - ) - - isCurrentAttestation := data.Target().Epoch() == state2.Epoch(s.BeaconState) - // Depending of what slot we are on we put in either the current justified or previous justified. - if isCurrentAttestation { - if !data.Source().Equal(s.CurrentJustifiedCheckpoint()) { - return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") - } - s.AddCurrentEpochAtteastation(pendingAttestation) - } else { - if !data.Source().Equal(s.PreviousJustifiedCheckpoint()) { - return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") - } - s.AddPreviousEpochAttestation(pendingAttestation) - } - // Not required by specs but needed if we want performant epoch transition. - indicies, err := s.GetAttestingIndicies(attestation.AttestantionData(), attestation.AggregationBits(), true) - if err != nil { - return nil, err - } - epochRoot, err := state2.GetBlockRoot(s.BeaconState, attestation.AttestantionData().Target().Epoch()) - if err != nil { - return nil, err - } - slotRoot, err := s.GetBlockRootAtSlot(attestation.AttestantionData().Slot()) - if err != nil { - return nil, err - } - // Basically we flag all validators we are currently attesting. will be important for rewards/finalization processing. - for _, index := range indicies { - minCurrentInclusionDelayAttestation, err := s.ValidatorMinCurrentInclusionDelayAttestation(int(index)) - if err != nil { - return nil, err - } - - minPreviousInclusionDelayAttestation, err := s.ValidatorMinPreviousInclusionDelayAttestation(int(index)) - if err != nil { - return nil, err - } - // NOTE: does not affect state root. - // We need to set it to currents or previouses depending on which attestation we process. - if isCurrentAttestation { - if minCurrentInclusionDelayAttestation == nil || - minCurrentInclusionDelayAttestation.InclusionDelay() > pendingAttestation.InclusionDelay() { - if err := s.SetValidatorMinCurrentInclusionDelayAttestation(int(index), pendingAttestation); err != nil { - return nil, err - } - } - if err := s.SetValidatorIsCurrentMatchingSourceAttester(int(index), true); err != nil { - return nil, err - } - if attestation.AttestantionData().Target().BlockRoot() == epochRoot { - if err := s.SetValidatorIsCurrentMatchingTargetAttester(int(index), true); err != nil { - return nil, err - } - } else { - continue - } - if attestation.AttestantionData().BeaconBlockRoot() == slotRoot { - if err := s.SetValidatorIsCurrentMatchingHeadAttester(int(index), true); err != nil { - return nil, err - } - } - } else { - if minPreviousInclusionDelayAttestation == nil || - minPreviousInclusionDelayAttestation.InclusionDelay() > pendingAttestation.InclusionDelay() { - if err := s.SetValidatorMinPreviousInclusionDelayAttestation(int(index), pendingAttestation); err != nil { - return nil, err - } - } - if err := s.SetValidatorIsPreviousMatchingSourceAttester(int(index), true); err != nil { - return nil, err - } - if attestation.AttestantionData().Target().BlockRoot() != epochRoot { - continue - } - if err := s.SetValidatorIsPreviousMatchingTargetAttester(int(index), true); err != nil { - return nil, err - } - if attestation.AttestantionData().BeaconBlockRoot() == slotRoot { - if err := s.SetValidatorIsPreviousMatchingHeadAttester(int(index), true); err != nil { - return nil, err - } - } - } - } - return indicies, nil -} - -// ProcessAttestation takes an attestation and process it. -func processAttestation(s *state2.BeaconState, attestation *solid.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { - data := attestation.AttestantionData() - currentEpoch := state2.Epoch(s.BeaconState) - previousEpoch := state2.PreviousEpoch(s.BeaconState) - stateSlot := s.Slot() - beaconConfig := s.BeaconConfig() - // Prelimary checks. - if (data.Target().Epoch() != currentEpoch && data.Target().Epoch() != previousEpoch) || data.Target().Epoch() != state2.GetEpochAtSlot(s.BeaconConfig(), data.Slot()) { - return nil, errors.New("ProcessAttestation: attestation with invalid epoch") - } - if data.Slot()+beaconConfig.MinAttestationInclusionDelay > stateSlot || stateSlot > data.Slot()+beaconConfig.SlotsPerEpoch { - return nil, errors.New("ProcessAttestation: attestation slot not in range") - } - if data.ValidatorIndex() >= s.CommitteeCount(data.Target().Epoch()) { - return nil, errors.New("ProcessAttestation: attester index out of range") - } - // check if we need to use rules for phase0 or post-altair. - if s.Version() == clparams.Phase0Version { - return processAttestationPhase0(s, attestation) - } - return processAttestationPostAltair(s, attestation, baseRewardPerIncrement) -} - -func verifyAttestations(s *state2.BeaconState, attestations *solid.ListSSZ[*solid.Attestation], attestingIndicies [][]uint64) (bool, error) { - var err error - valid := true - attestations.Range(func(idx int, a *solid.Attestation, _ int) bool { - indexedAttestation := state2.GetIndexedAttestation(a, attestingIndicies[idx]) - valid, err = state2.IsValidIndexedAttestation(s.BeaconState, indexedAttestation) - if err != nil { - return false - } - if !valid { - return false - } - return true - }) - - return valid, err -} diff --git a/cl/phase1/core/transition/process_bls_to_execution_change.go b/cl/phase1/core/transition/process_bls_to_execution_change.go deleted file mode 100644 index 8ac006b96..000000000 --- a/cl/phase1/core/transition/process_bls_to_execution_change.go +++ /dev/null @@ -1,65 +0,0 @@ -package transition - -import ( - "bytes" - "fmt" - - "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - "github.com/Giulio2002/bls" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/fork" - "github.com/ledgerwatch/erigon/cl/utils" -) - -// ProcessBlsToExecutionChange processes a BLSToExecutionChange message by updating a validator's withdrawal credentials. -func ProcessBlsToExecutionChange(state *state.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange, fullValidation bool) error { - change := signedChange.Message - - beaconConfig := state.BeaconConfig() - validator, err := state.ValidatorForValidatorIndex(int(change.ValidatorIndex)) - if err != nil { - return err - } - - // Perform full validation if requested. - wc := validator.WithdrawalCredentials() - if fullValidation { - // Check the validator's withdrawal credentials prefix. - if wc[0] != beaconConfig.BLSWithdrawalPrefixByte { - return fmt.Errorf("invalid withdrawal credentials prefix") - } - - // Check the validator's withdrawal credentials against the provided message. - hashedFrom := utils.Keccak256(change.From[:]) - if !bytes.Equal(hashedFrom[1:], wc[1:]) { - return fmt.Errorf("invalid withdrawal credentials") - } - - // Compute the signing domain and verify the message signature. - domain, err := fork.ComputeDomain(beaconConfig.DomainBLSToExecutionChange[:], utils.Uint32ToBytes4(beaconConfig.GenesisForkVersion), state.GenesisValidatorsRoot()) - if err != nil { - return err - } - signedRoot, err := fork.ComputeSigningRoot(change, domain) - if err != nil { - return err - } - valid, err := bls.Verify(signedChange.Signature[:], signedRoot[:], change.From[:]) - if err != nil { - return err - } - if !valid { - return fmt.Errorf("invalid signature") - } - } - credentials := wc - // Reset the validator's withdrawal credentials. - credentials[0] = beaconConfig.ETH1AddressWithdrawalPrefixByte - copy(credentials[1:], make([]byte, 11)) - copy(credentials[12:], change.To[:]) - - // Update the state with the modified validator. - state.SetWithdrawalCredentialForValidatorAtIndex(int(change.ValidatorIndex), credentials) - return nil -} diff --git a/cl/phase1/core/transition/process_epoch.go b/cl/phase1/core/transition/process_epoch.go deleted file mode 100644 index ce0cae47f..000000000 --- a/cl/phase1/core/transition/process_epoch.go +++ /dev/null @@ -1,103 +0,0 @@ -package transition - -import ( - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - "github.com/ledgerwatch/erigon/cl/phase1/core/state" -) - -// ProcessEpoch process epoch transition. -func ProcessEpoch(state *state.BeaconState) error { - if err := ProcessJustificationBitsAndFinality(state); err != nil { - return err - } - if state.Version() >= clparams.AltairVersion { - if err := ProcessInactivityScores(state); err != nil { - return err - } - } - if err := ProcessRewardsAndPenalties(state); err != nil { - return err - } - if err := ProcessRegistryUpdates(state); err != nil { - return err - } - if err := ProcessSlashings(state); err != nil { - return err - } - ProcessEth1DataReset(state) - if err := ProcessEffectiveBalanceUpdates(state); err != nil { - return err - } - ProcessSlashingsReset(state) - ProcessRandaoMixesReset(state) - if err := ProcessHistoricalRootsUpdate(state); err != nil { - return err - } - if state.Version() == clparams.Phase0Version { - if err := ProcessParticipationRecordUpdates(state); err != nil { - return err - } - } - - if state.Version() >= clparams.AltairVersion { - ProcessParticipationFlagUpdates(state) - if err := ProcessSyncCommitteeUpdate(state); err != nil { - return err - } - } - return nil -} - -func ProcessParticipationRecordUpdates(state *state.BeaconState) error { - state.SetPreviousEpochAttestations(state.CurrentEpochAttestations()) - state.ResetCurrentEpochAttestations() - var err error - // Also mark all current attesters as previous - state.ForEachValidator(func(_ solid.Validator, idx, total int) bool { - - var oldCurrentMatchingSourceAttester, oldCurrentMatchingTargetAttester, oldCurrentMatchingHeadAttester bool - var oldMinCurrentInclusionDelayAttestation *solid.PendingAttestation - - if oldCurrentMatchingSourceAttester, err = state.ValidatorIsCurrentMatchingSourceAttester(idx); err != nil { - return false - } - if oldCurrentMatchingTargetAttester, err = state.ValidatorIsCurrentMatchingTargetAttester(idx); err != nil { - return false - } - if oldCurrentMatchingHeadAttester, err = state.ValidatorIsCurrentMatchingHeadAttester(idx); err != nil { - return false - } - if oldMinCurrentInclusionDelayAttestation, err = state.ValidatorMinCurrentInclusionDelayAttestation(idx); err != nil { - return false - } - // Previous sources/target/head - if err = state.SetValidatorIsPreviousMatchingSourceAttester(idx, oldCurrentMatchingSourceAttester); err != nil { - return false - } - if err = state.SetValidatorIsPreviousMatchingTargetAttester(idx, oldCurrentMatchingTargetAttester); err != nil { - return false - } - if err = state.SetValidatorIsPreviousMatchingHeadAttester(idx, oldCurrentMatchingHeadAttester); err != nil { - return false - } - if err = state.SetValidatorMinPreviousInclusionDelayAttestation(idx, oldMinCurrentInclusionDelayAttestation); err != nil { - return false - } - // Current sources/target/head - if err = state.SetValidatorIsCurrentMatchingSourceAttester(idx, false); err != nil { - return false - } - if err = state.SetValidatorIsCurrentMatchingTargetAttester(idx, false); err != nil { - return false - } - if err = state.SetValidatorIsCurrentMatchingHeadAttester(idx, false); err != nil { - return false - } - if err = state.SetValidatorMinCurrentInclusionDelayAttestation(idx, nil); err != nil { - return false - } - return true - }) - return err -} diff --git a/cl/phase1/core/transition/process_slashings.go b/cl/phase1/core/transition/process_slashings.go deleted file mode 100644 index ec2f5788d..000000000 --- a/cl/phase1/core/transition/process_slashings.go +++ /dev/null @@ -1,56 +0,0 @@ -package transition - -import ( - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" -) - -func processSlashings(s *state2.BeaconState, slashingMultiplier uint64) error { - // Get the current epoch - epoch := state2.Epoch(s.BeaconState) - // Get the total active balance - totalBalance := s.GetTotalActiveBalance() - // Calculate the total slashing amount - // by summing all slashings and multiplying by the provided multiplier - slashing := state2.GetTotalSlashingAmount(s.BeaconState) * slashingMultiplier - // Adjust the total slashing amount to be no greater than the total active balance - if totalBalance < slashing { - slashing = totalBalance - } - beaconConfig := s.BeaconConfig() - // Apply penalties to validators who have been slashed and reached the withdrawable epoch - var err error - s.ForEachValidator(func(validator solid.Validator, i, total int) bool { - if !validator.Slashed() || epoch+beaconConfig.EpochsPerSlashingsVector/2 != validator.WithdrawableEpoch() { - return true - } - // Get the effective balance increment - increment := beaconConfig.EffectiveBalanceIncrement - // Calculate the penalty numerator by multiplying the validator's effective balance by the total slashing amount - penaltyNumerator := validator.EffectiveBalance() / increment * slashing - // Calculate the penalty by dividing the penalty numerator by the total balance and multiplying by the increment - penalty := penaltyNumerator / totalBalance * increment - // Decrease the validator's balance by the calculated penalty - if err = state2.DecreaseBalance(s.BeaconState, uint64(i), penalty); err != nil { - return false - } - return true - }) - if err != nil { - return err - } - return nil -} - -func ProcessSlashings(state *state2.BeaconState) error { - // Depending on the version of the state, use different multipliers - switch state.Version() { - case clparams.Phase0Version: - return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplier) - case clparams.AltairVersion: - return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplierAltair) - default: - return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplierBellatrix) - } -} diff --git a/cl/phase1/core/transition/process_slots.go b/cl/phase1/core/transition/process_slots.go deleted file mode 100644 index 8632bc68f..000000000 --- a/cl/phase1/core/transition/process_slots.go +++ /dev/null @@ -1,183 +0,0 @@ -package transition - -import ( - "fmt" - "time" - - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - "github.com/Giulio2002/bls" - libcommon "github.com/ledgerwatch/erigon-lib/common" - "github.com/ledgerwatch/erigon/cl/clparams" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/fork" - "github.com/ledgerwatch/erigon/cl/utils" - "github.com/ledgerwatch/log/v3" -) - -func TransitionState(s *state2.BeaconState, block *cltypes.SignedBeaconBlock, fullValidation bool) error { - currentBlock := block.Block - if err := ProcessSlots(s, currentBlock.Slot); err != nil { - return err - } - - if fullValidation { - valid, err := verifyBlockSignature(s, block) - if err != nil { - return fmt.Errorf("error validating block signature: %v", err) - } - if !valid { - return fmt.Errorf("block not valid") - } - } - // Transition block - if err := processBlock(s, block, fullValidation); err != nil { - return err - } - if fullValidation { - expectedStateRoot, err := s.HashSSZ() - if err != nil { - return fmt.Errorf("unable to generate state root: %v", err) - } - if expectedStateRoot != currentBlock.StateRoot { - return fmt.Errorf("expected state root differs from received state root") - } - } - - s.SetPreviousStateRoot(currentBlock.StateRoot) - return nil -} - -// transitionSlot is called each time there is a new slot to process -func transitionSlot(s *state2.BeaconState) error { - slot := s.Slot() - previousStateRoot := s.PreviousStateRoot() - var err error - if previousStateRoot == (libcommon.Hash{}) { - previousStateRoot, err = s.HashSSZ() - if err != nil { - return err - } - } - - beaconConfig := s.BeaconConfig() - - s.SetStateRootAt(int(slot%beaconConfig.SlotsPerHistoricalRoot), previousStateRoot) - - latestBlockHeader := s.LatestBlockHeader() - if latestBlockHeader.Root == [32]byte{} { - latestBlockHeader.Root = previousStateRoot - s.SetLatestBlockHeader(&latestBlockHeader) - } - blockHeader := s.LatestBlockHeader() - - previousBlockRoot, err := (&blockHeader).HashSSZ() - if err != nil { - return err - } - s.SetBlockRootAt(int(slot%beaconConfig.SlotsPerHistoricalRoot), previousBlockRoot) - return nil -} - -func ProcessSlots(s *state2.BeaconState, slot uint64) error { - beaconConfig := s.BeaconConfig() - sSlot := s.Slot() - if slot <= sSlot { - return fmt.Errorf("new slot: %d not greater than s slot: %d", slot, sSlot) - } - // Process each slot. - for i := sSlot; i < slot; i++ { - err := transitionSlot(s) - if err != nil { - return fmt.Errorf("unable to process slot transition: %v", err) - } - // TODO(Someone): Add epoch transition. - if (sSlot+1)%beaconConfig.SlotsPerEpoch == 0 { - start := time.Now() - if err := ProcessEpoch(s); err != nil { - return err - } - log.Debug("Processed new epoch successfully", "epoch", state2.Epoch(s.BeaconState), "process_epoch_elpsed", time.Since(start)) - } - // TODO: add logic to process epoch updates. - sSlot += 1 - s.SetSlot(sSlot) - if sSlot%beaconConfig.SlotsPerEpoch != 0 { - continue - } - if state2.Epoch(s.BeaconState) == beaconConfig.AltairForkEpoch { - if err := s.UpgradeToAltair(); err != nil { - return err - } - } - if state2.Epoch(s.BeaconState) == beaconConfig.BellatrixForkEpoch { - if err := s.UpgradeToBellatrix(); err != nil { - return err - } - } - if state2.Epoch(s.BeaconState) == beaconConfig.CapellaForkEpoch { - if err := s.UpgradeToCapella(); err != nil { - return err - } - } - if state2.Epoch(s.BeaconState) == beaconConfig.DenebForkEpoch { - if err := s.UpgradeToDeneb(); err != nil { - return err - } - } - } - return nil -} - -func verifyBlockSignature(s *state2.BeaconState, block *cltypes.SignedBeaconBlock) (bool, error) { - proposer, err := s.ValidatorForValidatorIndex(int(block.Block.ProposerIndex)) - if err != nil { - return false, err - } - domain, err := s.GetDomain(s.BeaconConfig().DomainBeaconProposer, state2.Epoch(s.BeaconState)) - if err != nil { - return false, err - } - sigRoot, err := fork.ComputeSigningRoot(block.Block, domain) - if err != nil { - return false, err - } - pk := proposer.PublicKey() - return bls.Verify(block.Signature[:], sigRoot[:], pk[:]) -} - -// ProcessHistoricalRootsUpdate updates the historical root data structure by computing a new historical root batch when it is time to do so. -func ProcessHistoricalRootsUpdate(s *state2.BeaconState) error { - nextEpoch := state2.Epoch(s.BeaconState) + 1 - beaconConfig := s.BeaconConfig() - blockRoots := s.BlockRoots() - stateRoots := s.StateRoots() - - // Check if it's time to compute the historical root batch. - if nextEpoch%(beaconConfig.SlotsPerHistoricalRoot/beaconConfig.SlotsPerEpoch) != 0 { - return nil - } - - // Compute historical root batch. - blockRootsLeaf, err := blockRoots.HashSSZ() - if err != nil { - return err - } - stateRootsLeaf, err := stateRoots.HashSSZ() - if err != nil { - return err - } - - // Add the historical summary or root to the s. - if s.Version() >= clparams.CapellaVersion { - s.AddHistoricalSummary(&cltypes.HistoricalSummary{ - BlockSummaryRoot: blockRootsLeaf, - StateSummaryRoot: stateRootsLeaf, - }) - } else { - historicalRoot := utils.Keccak256(blockRootsLeaf[:], stateRootsLeaf[:]) - s.AddHistoricalRoot(historicalRoot) - } - - return nil -} diff --git a/cl/phase1/core/transition/process_sync_aggregate.go b/cl/phase1/core/transition/process_sync_aggregate.go deleted file mode 100644 index bfafc00ab..000000000 --- a/cl/phase1/core/transition/process_sync_aggregate.go +++ /dev/null @@ -1,93 +0,0 @@ -package transition - -import ( - "errors" - - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - "github.com/Giulio2002/bls" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/fork" - "github.com/ledgerwatch/erigon/cl/utils" -) - -// processSyncAggregate applies all the logic in the spec function `process_sync_aggregate` except -// verifying the BLS signatures. It returns the modified beacons state and the list of validators' -// public keys that voted, for future signature verification. -func processSyncAggregate(s *state2.BeaconState, sync *cltypes.SyncAggregate) ([][]byte, error) { - currentSyncCommittee := s.CurrentSyncCommittee() - - if currentSyncCommittee == nil { - return nil, errors.New("nil current sync committee in s") - } - committeeKeys := currentSyncCommittee.GetCommittee() - if len(sync.SyncCommiteeBits)*8 > len(committeeKeys) { - return nil, errors.New("bits length exceeds committee length") - } - var votedKeys [][]byte - - proposerReward, participantReward, err := s.SyncRewards() - if err != nil { - return nil, err - } - - proposerIndex, err := s.GetBeaconProposerIndex() - if err != nil { - return nil, err - } - - syncAggregateBits := sync.SyncCommiteeBits - earnedProposerReward := uint64(0) - currPubKeyIndex := 0 - for i := range syncAggregateBits { - for bit := 1; bit <= 128; bit *= 2 { - vIdx, exists := s.ValidatorIndexByPubkey(committeeKeys[currPubKeyIndex]) - // Impossible scenario. - if !exists { - return nil, errors.New("validator public key does not exist in state") - } - if syncAggregateBits[i]&byte(bit) > 0 { - votedKeys = append(votedKeys, committeeKeys[currPubKeyIndex][:]) - if err := state2.IncreaseBalance(s.BeaconState, vIdx, participantReward); err != nil { - return nil, err - } - earnedProposerReward += proposerReward - } else { - if err := state2.DecreaseBalance(s.BeaconState, vIdx, participantReward); err != nil { - return nil, err - } - } - currPubKeyIndex++ - } - } - - return votedKeys, state2.IncreaseBalance(s.BeaconState, proposerIndex, earnedProposerReward) -} - -func ProcessSyncAggregate(s *state2.BeaconState, sync *cltypes.SyncAggregate, fullValidation bool) error { - votedKeys, err := processSyncAggregate(s, sync) - if err != nil { - return err - } - if fullValidation { - previousSlot := s.PreviousSlot() - - domain, err := fork.Domain(s.Fork(), state2.GetEpochAtSlot(s.BeaconConfig(), previousSlot), s.BeaconConfig().DomainSyncCommittee, s.GenesisValidatorsRoot()) - if err != nil { - return nil - } - blockRoot, err := s.GetBlockRootAtSlot(previousSlot) - if err != nil { - return err - } - msg := utils.Keccak256(blockRoot[:], domain) - isValid, err := bls.VerifyAggregate(sync.SyncCommiteeSignature[:], msg[:], votedKeys) - if err != nil { - return err - } - if !isValid { - return errors.New("ProcessSyncAggregate: cannot validate sync committee signature") - } - } - return nil -} diff --git a/cl/phase1/core/transition/processing.go b/cl/phase1/core/transition/processing.go deleted file mode 100644 index 669f9cc03..000000000 --- a/cl/phase1/core/transition/processing.go +++ /dev/null @@ -1,120 +0,0 @@ -package transition - -import ( - "encoding/binary" - "fmt" - - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - - libcommon "github.com/ledgerwatch/erigon-lib/common" - - "github.com/Giulio2002/bls" - "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/utils" -) - -func computeSigningRootEpoch(epoch uint64, domain []byte) (libcommon.Hash, error) { - b := make([]byte, 32) - binary.LittleEndian.PutUint64(b, epoch) - return utils.Keccak256(b, domain), nil -} - -func ProcessBlockHeader(state *state2.BeaconState, block *cltypes.BeaconBlock, fullValidation bool) error { - if fullValidation { - if block.Slot != state.Slot() { - return fmt.Errorf("state slot: %d, not equal to block slot: %d", state.Slot(), block.Slot) - } - if block.Slot <= state.LatestBlockHeader().Slot { - return fmt.Errorf("slock slot: %d, not greater than latest block slot: %d", block.Slot, state.LatestBlockHeader().Slot) - } - propInd, err := state.GetBeaconProposerIndex() - if err != nil { - return fmt.Errorf("error in GetBeaconProposerIndex: %v", err) - } - if block.ProposerIndex != propInd { - return fmt.Errorf("block proposer index: %d, does not match beacon proposer index: %d", block.ProposerIndex, propInd) - } - blockHeader := state.LatestBlockHeader() - latestRoot, err := (&blockHeader).HashSSZ() - if err != nil { - return fmt.Errorf("unable to hash tree root of latest block header: %v", err) - } - if block.ParentRoot != latestRoot { - return fmt.Errorf("block parent root: %x, does not match latest block root: %x", block.ParentRoot, latestRoot) - } - } - - bodyRoot, err := block.Body.HashSSZ() - if err != nil { - return fmt.Errorf("unable to hash tree root of block body: %v", err) - } - state.SetLatestBlockHeader(&cltypes.BeaconBlockHeader{ - Slot: block.Slot, - ProposerIndex: block.ProposerIndex, - ParentRoot: block.ParentRoot, - BodyRoot: bodyRoot, - }) - - proposer, err := state.ValidatorForValidatorIndex(int(block.ProposerIndex)) - if err != nil { - return err - } - if proposer.Slashed() { - return fmt.Errorf("proposer: %d is slashed", block.ProposerIndex) - } - return nil -} - -func ProcessRandao(s *state2.BeaconState, randao [96]byte, proposerIndex uint64, fullValidation bool) error { - epoch := state2.Epoch(s.BeaconState) - proposer, err := s.ValidatorForValidatorIndex(int(proposerIndex)) - if err != nil { - return err - } - if fullValidation { - domain, err := s.GetDomain(s.BeaconConfig().DomainRandao, epoch) - if err != nil { - return fmt.Errorf("ProcessRandao: unable to get domain: %v", err) - } - signingRoot, err := computeSigningRootEpoch(epoch, domain) - if err != nil { - return fmt.Errorf("ProcessRandao: unable to compute signing root: %v", err) - } - pk := proposer.PublicKey() - valid, err := bls.Verify(randao[:], signingRoot[:], pk[:]) - if err != nil { - return fmt.Errorf("ProcessRandao: unable to verify public key: %x, with signing root: %x, and signature: %x, %v", pk[:], signingRoot[:], randao[:], err) - } - if !valid { - return fmt.Errorf("ProcessRandao: invalid signature: public key: %x, signing root: %x, signature: %x", pk[:], signingRoot[:], randao[:]) - } - } - - randaoMixes := s.GetRandaoMixes(epoch) - randaoHash := utils.Keccak256(randao[:]) - mix := [32]byte{} - for i := range mix { - mix[i] = randaoMixes[i] ^ randaoHash[i] - } - s.SetRandaoMixAt(int(epoch%s.BeaconConfig().EpochsPerHistoricalVector), mix) - return nil -} - -func ProcessEth1Data(state *state2.BeaconState, eth1Data *cltypes.Eth1Data) error { - state.AddEth1DataVote(eth1Data) - newVotes := state.Eth1DataVotes() - - // Count how many times body.Eth1Data appears in the votes. - numVotes := 0 - newVotes.Range(func(index int, value *cltypes.Eth1Data, length int) bool { - if eth1Data.Equal(value) { - numVotes += 1 - } - return true - }) - - if uint64(numVotes*2) > state.BeaconConfig().EpochsPerEth1VotingPeriod*state.BeaconConfig().SlotsPerEpoch { - state.SetEth1Data(eth1Data) - } - return nil -} diff --git a/cl/phase1/forkchoice/fork_graph/fork_graph.go b/cl/phase1/forkchoice/fork_graph/fork_graph.go index aa83ec283..8221ab2c7 100644 --- a/cl/phase1/forkchoice/fork_graph/fork_graph.go +++ b/cl/phase1/forkchoice/fork_graph/fork_graph.go @@ -6,7 +6,7 @@ import ( "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/cl/cltypes/solid" "github.com/ledgerwatch/erigon/cl/phase1/core/state" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" + "github.com/ledgerwatch/erigon/cl/transition" "github.com/ledgerwatch/log/v3" "golang.org/x/exp/slices" ) diff --git a/cl/phase1/forkchoice/on_block.go b/cl/phase1/forkchoice/on_block.go index cd6860e6a..c1c3b4015 100644 --- a/cl/phase1/forkchoice/on_block.go +++ b/cl/phase1/forkchoice/on_block.go @@ -2,10 +2,9 @@ package forkchoice import ( "fmt" - "github.com/ledgerwatch/erigon/cl/freezer" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" "github.com/ledgerwatch/erigon/cl/phase1/forkchoice/fork_graph" + "github.com/ledgerwatch/erigon/cl/transition/impl/eth2/statechange" "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/log/v3" @@ -74,7 +73,7 @@ func (f *ForkChoiceStore) OnBlock(block *cltypes.SignedBeaconBlock, newPayload, justificationBits = lastProcessedState.JustificationBits().Copy() ) // Eagerly compute unrealized justification and finality - if err := transition.ProcessJustificationBitsAndFinality(lastProcessedState); err != nil { + if err := statechange.ProcessJustificationBitsAndFinality(lastProcessedState); err != nil { return err } f.updateUnrealizedCheckpoints(lastProcessedState.CurrentJustifiedCheckpoint().Copy(), lastProcessedState.FinalizedCheckpoint().Copy()) diff --git a/cl/phase1/forkchoice/utils.go b/cl/phase1/forkchoice/utils.go index 2627341f9..ada018f16 100644 --- a/cl/phase1/forkchoice/utils.go +++ b/cl/phase1/forkchoice/utils.go @@ -2,12 +2,11 @@ package forkchoice import ( "fmt" - - "github.com/ledgerwatch/erigon/cl/cltypes/solid" - "github.com/ledgerwatch/erigon/cl/phase1/core/state" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" + "github.com/ledgerwatch/erigon/cl/transition" libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/log/v3" ) @@ -85,7 +84,7 @@ func (f *ForkChoiceStore) getCheckpointState(checkpoint solid.Checkpoint) (*chec if baseState.Slot() < f.computeStartSlotAtEpoch(checkpoint.Epoch()) { log.Debug("Long checkpoint detected") // If we require to change it then process the future epoch - if err := transition.ProcessSlots(baseState, f.computeStartSlotAtEpoch(checkpoint.Epoch())); err != nil { + if err := transition.DefaultMachine.ProcessSlots(baseState, f.computeStartSlotAtEpoch(checkpoint.Epoch())); err != nil { return nil, err } } diff --git a/cl/phase1/stages/stages_beacon_state.go b/cl/phase1/stages/stages_beacon_state.go index cee0fa059..eeb043d72 100644 --- a/cl/phase1/stages/stages_beacon_state.go +++ b/cl/phase1/stages/stages_beacon_state.go @@ -2,10 +2,10 @@ package stages import ( "context" + "github.com/ledgerwatch/erigon/cl/transition" "github.com/ledgerwatch/erigon/cl/phase1/core/rawdb" state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" "github.com/ledgerwatch/erigon/cl/phase1/execution_client" libcommon "github.com/ledgerwatch/erigon-lib/common" diff --git a/cl/spectest/consensus_tests/epoch_processing.go b/cl/spectest/consensus_tests/epoch_processing.go index 7544baee8..eb3b0e4c5 100644 --- a/cl/spectest/consensus_tests/epoch_processing.go +++ b/cl/spectest/consensus_tests/epoch_processing.go @@ -1,13 +1,12 @@ package consensus_tests import ( + "github.com/ledgerwatch/erigon/cl/transition/impl/eth2/statechange" "io/fs" "os" "testing" "github.com/ledgerwatch/erigon/cl/phase1/core/state" - transition2 "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - "github.com/ledgerwatch/erigon/spectest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,58 +51,57 @@ func (b *EpochProcessing) Run(t *testing.T, root fs.FS, c spectest.TestCase) (er } var effectiveBalancesUpdateTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessEffectiveBalanceUpdates(s) + return statechange.ProcessEffectiveBalanceUpdates(s) }) var eth1DataResetTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessEth1DataReset(s) + statechange.ProcessEth1DataReset(s) return nil }) var historicalRootsUpdateTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessHistoricalRootsUpdate(s) - return nil + return statechange.ProcessHistoricalRootsUpdate(s) }) var inactivityUpdateTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessInactivityScores(s) + return statechange.ProcessInactivityScores(s) }) var justificationFinalizationTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessJustificationBitsAndFinality(s) + return statechange.ProcessJustificationBitsAndFinality(s) }) var participationFlagUpdatesTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessParticipationFlagUpdates(s) + statechange.ProcessParticipationFlagUpdates(s) return nil }) var participationRecordUpdatesTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessParticipationRecordUpdates(s) + return statechange.ProcessParticipationRecordUpdates(s) }) var randaoMixesTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessRandaoMixesReset(s) + statechange.ProcessRandaoMixesReset(s) return nil }) var registryUpdatesTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessRegistryUpdates(s) + return statechange.ProcessRegistryUpdates(s) }) var rewardsAndPenaltiesTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessRewardsAndPenalties(s) + return statechange.ProcessRewardsAndPenalties(s) }) var slashingsTest = NewEpochProcessing(func(s *state.BeaconState) error { - return transition2.ProcessSlashings(s) + return statechange.ProcessSlashings(s) }) var slashingsResetTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessSlashingsReset(s) + statechange.ProcessSlashingsReset(s) return nil }) var recordsResetTest = NewEpochProcessing(func(s *state.BeaconState) error { - transition2.ProcessParticipationRecordUpdates(s) + statechange.ProcessParticipationRecordUpdates(s) return nil }) diff --git a/cl/spectest/consensus_tests/finality.go b/cl/spectest/consensus_tests/finality.go index 0faf1995c..1c72fcfb0 100644 --- a/cl/spectest/consensus_tests/finality.go +++ b/cl/spectest/consensus_tests/finality.go @@ -2,11 +2,10 @@ package consensus_tests import ( "fmt" + "github.com/ledgerwatch/erigon/cl/transition/machine" "io/fs" "testing" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - "github.com/ledgerwatch/erigon/spectest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,7 +25,7 @@ var FinalityFinality = spectest.HandlerFunc(func(t *testing.T, root fs.FS, c spe } startSlot := testState.Slot() for _, block := range blocks { - if err := transition.TransitionState(testState, block, true); err != nil { + if err := machine.TransitionState(c.Machine, testState, block); err != nil { require.NoError(t, fmt.Errorf("cannot transition state: %w. slot=%d. start_slot=%d", err, block.Block.Slot, startSlot)) } } diff --git a/cl/spectest/consensus_tests/operations.go b/cl/spectest/consensus_tests/operations.go index c4c1c755d..1aaca37cc 100644 --- a/cl/spectest/consensus_tests/operations.go +++ b/cl/spectest/consensus_tests/operations.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/ledgerwatch/erigon/cl/cltypes/solid" - transition2 "github.com/ledgerwatch/erigon/cl/phase1/core/transition" "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/spectest" @@ -39,7 +38,7 @@ func operationAttestationHandler(t *testing.T, root fs.FS, c spectest.TestCase) if err := spectest.ReadSszOld(root, att, c.Version(), attestationFileName); err != nil { return err } - if err := transition2.ProcessAttestations(preState, solid.NewDynamicListSSZFromList([]*solid.Attestation{att}, 128), true); err != nil { + if err := c.Machine.ProcessAttestations(preState, solid.NewDynamicListSSZFromList([]*solid.Attestation{att}, 128)); err != nil { if expectedError { return nil } @@ -69,7 +68,7 @@ func operationAttesterSlashingHandler(t *testing.T, root fs.FS, c spectest.TestC if err := spectest.ReadSszOld(root, att, c.Version(), attesterSlashingFileName); err != nil { return err } - if err := transition2.ProcessAttesterSlashing(preState, att); err != nil { + if err := c.Machine.ProcessAttesterSlashing(preState, att); err != nil { if expectedError { return nil } @@ -99,7 +98,7 @@ func operationProposerSlashingHandler(t *testing.T, root fs.FS, c spectest.TestC if err := spectest.ReadSszOld(root, att, c.Version(), proposerSlashingFileName); err != nil { return err } - if err := transition2.ProcessProposerSlashing(preState, att); err != nil { + if err := c.Machine.ProcessProposerSlashing(preState, att); err != nil { if expectedError { return nil } @@ -129,7 +128,7 @@ func operationBlockHeaderHandler(t *testing.T, root fs.FS, c spectest.TestCase) if err := spectest.ReadSszOld(root, block, c.Version(), blockFileName); err != nil { return err } - if err := transition2.ProcessBlockHeader(preState, block, true); err != nil { + if err := c.Machine.ProcessBlockHeader(preState, block); err != nil { if expectedError { return nil } @@ -159,7 +158,7 @@ func operationDepositHandler(t *testing.T, root fs.FS, c spectest.TestCase) erro if err := spectest.ReadSszOld(root, deposit, c.Version(), depositFileName); err != nil { return err } - if err := transition2.ProcessDeposit(preState, deposit, true); err != nil { + if err := c.Machine.ProcessDeposit(preState, deposit); err != nil { if expectedError { return nil } @@ -189,7 +188,7 @@ func operationSyncAggregateHandler(t *testing.T, root fs.FS, c spectest.TestCase if err := spectest.ReadSszOld(root, agg, c.Version(), syncAggregateFileName); err != nil { return err } - if err := transition2.ProcessSyncAggregate(preState, agg, true); err != nil { + if err := c.Machine.ProcessSyncAggregate(preState, agg); err != nil { if expectedError { return nil } @@ -219,7 +218,7 @@ func operationVoluntaryExitHandler(t *testing.T, root fs.FS, c spectest.TestCase if err := spectest.ReadSszOld(root, vo, c.Version(), voluntaryExitFileName); err != nil { return err } - if err := transition2.ProcessVoluntaryExit(preState, vo, true); err != nil { + if err := c.Machine.ProcessVoluntaryExit(preState, vo); err != nil { if expectedError { return nil } @@ -249,7 +248,7 @@ func operationWithdrawalHandler(t *testing.T, root fs.FS, c spectest.TestCase) e if err := spectest.ReadSszOld(root, executionPayload, c.Version(), executionPayloadFileName); err != nil { return err } - if err := transition2.ProcessWithdrawals(preState, executionPayload.Withdrawals, true); err != nil { + if err := c.Machine.ProcessWithdrawals(preState, executionPayload.Withdrawals); err != nil { if expectedError { return nil } @@ -279,7 +278,7 @@ func operationSignedBlsChangeHandler(t *testing.T, root fs.FS, c spectest.TestCa if err := spectest.ReadSszOld(root, change, c.Version(), addressChangeFileName); err != nil { return err } - if err := transition2.ProcessBlsToExecutionChange(preState, change, true); err != nil { + if err := c.Machine.ProcessBlsToExecutionChange(preState, change); err != nil { if expectedError { return nil } diff --git a/cl/spectest/consensus_tests/sanity.go b/cl/spectest/consensus_tests/sanity.go index ddc718222..493f3ddcf 100644 --- a/cl/spectest/consensus_tests/sanity.go +++ b/cl/spectest/consensus_tests/sanity.go @@ -1,12 +1,11 @@ package consensus_tests import ( + "github.com/ledgerwatch/erigon/cl/transition/machine" "io/fs" "os" "testing" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/spectest" "github.com/stretchr/testify/assert" @@ -25,7 +24,7 @@ var SanitySlots = spectest.HandlerFunc(func(t *testing.T, root fs.FS, c spectest expectedState, err := spectest.ReadBeaconState(root, c.Version(), spectest.PostSsz) require.NoError(t, err) - err = transition.ProcessSlots(testState, expectedState.Slot()) + err = c.Machine.ProcessSlots(testState, expectedState.Slot()) require.NoError(t, err) expectedRoot, err := expectedState.HashSSZ() @@ -65,7 +64,7 @@ var SanityBlocks = spectest.HandlerFunc(func(t *testing.T, root fs.FS, c spectes var block *cltypes.SignedBeaconBlock for _, block = range blocks { - err = transition.TransitionState(testState, block, true) + err = machine.TransitionState(c.Machine, testState, block) if err != nil { break } diff --git a/cl/spectest/consensus_tests/transition.go b/cl/spectest/consensus_tests/transition.go index db31b8511..105ab477f 100644 --- a/cl/spectest/consensus_tests/transition.go +++ b/cl/spectest/consensus_tests/transition.go @@ -2,11 +2,10 @@ package consensus_tests import ( "fmt" + "github.com/ledgerwatch/erigon/cl/transition/machine" "io/fs" "testing" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/spectest" @@ -60,7 +59,7 @@ func (b *TransitionCore) Run(t *testing.T, root fs.FS, c spectest.TestCase) (err break } blockIndex++ - if err := transition.TransitionState(startState, block, true); err != nil { + if err := machine.TransitionState(c.Machine, startState, block); err != nil { return fmt.Errorf("cannot transition state: %s. slot=%d. start_slot=%d", err, block.Block.Slot, startSlot) } } diff --git a/cl/spectest/tests_test.go b/cl/spectest/tests_test.go index 0f89aa7cd..09a7a4234 100644 --- a/cl/spectest/tests_test.go +++ b/cl/spectest/tests_test.go @@ -5,6 +5,7 @@ package spectest import ( + "github.com/ledgerwatch/erigon/cl/transition" "os" "testing" @@ -14,5 +15,5 @@ import ( ) func Test(t *testing.T) { - spectest.RunCases(t, consensus_tests.TestFormats, os.DirFS("./tests")) + spectest.RunCases(t, consensus_tests.TestFormats, transition.ValidatingMachine, os.DirFS("./tests")) } diff --git a/cl/transition/compat.go b/cl/transition/compat.go new file mode 100644 index 000000000..dea4d8d05 --- /dev/null +++ b/cl/transition/compat.go @@ -0,0 +1,19 @@ +package transition + +import ( + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/transition/impl/eth2" + machine2 "github.com/ledgerwatch/erigon/cl/transition/machine" + + "github.com/ledgerwatch/erigon/cl/cltypes" +) + +var _ machine2.Interface = (*eth2.Impl)(nil) + +var DefaultMachine = ð2.Impl{} +var ValidatingMachine = ð2.Impl{FullValidation: true} + +func TransitionState(s *state.BeaconState, block *cltypes.SignedBeaconBlock, fullValidation bool) error { + cvm := ð2.Impl{FullValidation: fullValidation} + return machine2.TransitionState(cvm, s, block) +} diff --git a/cl/phase1/core/transition/block_processing_test.go b/cl/transition/impl/eth2/block_processing_test.go similarity index 51% rename from cl/phase1/core/transition/block_processing_test.go rename to cl/transition/impl/eth2/block_processing_test.go index ae4a67c19..436e915b6 100644 --- a/cl/phase1/core/transition/block_processing_test.go +++ b/cl/transition/impl/eth2/block_processing_test.go @@ -1,7 +1,8 @@ -package transition +package eth2_test import ( _ "embed" + "github.com/ledgerwatch/erigon/cl/transition" "testing" "github.com/ledgerwatch/erigon/cl/clparams" @@ -11,16 +12,16 @@ import ( "github.com/stretchr/testify/require" ) -//go:embed test_data/block_processing/capella_block.ssz_snappy +//go:embed statechange/test_data/block_processing/capella_block.ssz_snappy var capellaBlock []byte -//go:embed test_data/block_processing/capella_state.ssz_snappy +//go:embed statechange/test_data/block_processing/capella_state.ssz_snappy var capellaState []byte func TestBlockProcessing(t *testing.T) { - state := state.New(&clparams.MainnetBeaconConfig) - require.NoError(t, utils.DecodeSSZSnappy(state, capellaState, int(clparams.CapellaVersion))) + s := state.New(&clparams.MainnetBeaconConfig) + require.NoError(t, utils.DecodeSSZSnappy(s, capellaState, int(clparams.CapellaVersion))) block := &cltypes.SignedBeaconBlock{} require.NoError(t, utils.DecodeSSZSnappy(block, capellaBlock, int(clparams.CapellaVersion))) - require.NoError(t, TransitionState(state, block, true)) // All checks already made in transition state + require.NoError(t, transition.TransitionState(s, block, true)) // All checks already made in transition state } diff --git a/cl/transition/impl/eth2/impl.go b/cl/transition/impl/eth2/impl.go new file mode 100644 index 000000000..dcd233bca --- /dev/null +++ b/cl/transition/impl/eth2/impl.go @@ -0,0 +1,11 @@ +package eth2 + +import "github.com/ledgerwatch/erigon/cl/transition/machine" + +type Impl = impl + +var _ machine.Interface = (*impl)(nil) + +type impl struct { + FullValidation bool +} diff --git a/cl/transition/impl/eth2/operations.go b/cl/transition/impl/eth2/operations.go new file mode 100644 index 000000000..29771769f --- /dev/null +++ b/cl/transition/impl/eth2/operations.go @@ -0,0 +1,872 @@ +package eth2 + +import ( + "bytes" + "errors" + "fmt" + "github.com/ledgerwatch/erigon/cl/transition/impl/eth2/statechange" + "github.com/ledgerwatch/erigon/metrics/methelp" + "golang.org/x/exp/slices" + "reflect" + "time" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + + "github.com/Giulio2002/bls" + "github.com/ledgerwatch/log/v3" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/fork" + "github.com/ledgerwatch/erigon/cl/utils" + "github.com/ledgerwatch/erigon/core/types" +) + +func (I *impl) ProcessProposerSlashing(s *state.BeaconState, propSlashing *cltypes.ProposerSlashing) error { + h1 := propSlashing.Header1.Header + h2 := propSlashing.Header2.Header + + if h1.Slot != h2.Slot { + return fmt.Errorf("non-matching slots on proposer slashing: %d != %d", h1.Slot, h2.Slot) + } + + if h1.ProposerIndex != h2.ProposerIndex { + return fmt.Errorf("non-matching proposer indices proposer slashing: %d != %d", h1.ProposerIndex, h2.ProposerIndex) + } + + h1Root, err := h1.HashSSZ() + if err != nil { + return fmt.Errorf("unable to hash header1: %v", err) + } + h2Root, err := h2.HashSSZ() + if err != nil { + return fmt.Errorf("unable to hash header2: %v", err) + } + if h1Root == h2Root { + return fmt.Errorf("propose slashing headers are the same: %v == %v", h1Root, h2Root) + } + + proposer, err := s.ValidatorForValidatorIndex(int(h1.ProposerIndex)) + if err != nil { + return err + } + if !proposer.IsSlashable(state.Epoch(s.BeaconState)) { + return fmt.Errorf("proposer is not slashable: %v", proposer) + } + + for _, signedHeader := range []*cltypes.SignedBeaconBlockHeader{propSlashing.Header1, propSlashing.Header2} { + domain, err := s.GetDomain(s.BeaconConfig().DomainBeaconProposer, state.GetEpochAtSlot(s.BeaconConfig(), signedHeader.Header.Slot)) + if err != nil { + return fmt.Errorf("unable to get domain: %v", err) + } + signingRoot, err := fork.ComputeSigningRoot(signedHeader.Header, domain) + if err != nil { + return fmt.Errorf("unable to compute signing root: %v", err) + } + pk := proposer.PublicKey() + valid, err := bls.Verify(signedHeader.Signature[:], signingRoot[:], pk[:]) + if err != nil { + return fmt.Errorf("unable to verify signature: %v", err) + } + if !valid { + return fmt.Errorf("invalid signature: signature %v, root %v, pubkey %v", signedHeader.Signature[:], signingRoot[:], pk) + } + } + + // Set whistleblower index to 0 so current proposer gets reward. + s.SlashValidator(h1.ProposerIndex, nil) + return nil +} + +func (I *impl) ProcessAttesterSlashing(s *state.BeaconState, attSlashing *cltypes.AttesterSlashing) error { + att1 := attSlashing.Attestation_1 + att2 := attSlashing.Attestation_2 + + if !cltypes.IsSlashableAttestationData(att1.Data, att2.Data) { + return fmt.Errorf("attestation data not slashable: %+v; %+v", att1.Data, att2.Data) + } + + valid, err := state.IsValidIndexedAttestation(s.BeaconState, att1) + if err != nil { + return fmt.Errorf("error calculating indexed attestation 1 validity: %v", err) + } + if !valid { + return fmt.Errorf("invalid indexed attestation 1") + } + + valid, err = state.IsValidIndexedAttestation(s.BeaconState, att2) + if err != nil { + return fmt.Errorf("error calculating indexed attestation 2 validity: %v", err) + } + if !valid { + return fmt.Errorf("invalid indexed attestation 2") + } + + slashedAny := false + currentEpoch := state.GetEpochAtSlot(s.BeaconConfig(), s.Slot()) + for _, ind := range solid.IntersectionOfSortedSets( + solid.IterableSSZ[uint64](att1.AttestingIndices), + solid.IterableSSZ[uint64](att2.AttestingIndices)) { + validator, err := s.ValidatorForValidatorIndex(int(ind)) + if err != nil { + return err + } + if validator.IsSlashable(currentEpoch) { + err := s.SlashValidator(ind, nil) + if err != nil { + return fmt.Errorf("unable to slash validator: %d", ind) + } + slashedAny = true + } + } + + if !slashedAny { + return fmt.Errorf("no validators slashed") + } + return nil +} + +func (I *impl) ProcessDeposit(s *state.BeaconState, deposit *cltypes.Deposit) error { + if deposit == nil { + return nil + } + depositLeaf, err := deposit.Data.HashSSZ() + if err != nil { + return err + } + depositIndex := s.Eth1DepositIndex() + eth1Data := s.Eth1Data() + rawProof := []common.Hash{} + deposit.Proof.Range(func(_ int, l common.Hash, _ int) bool { + rawProof = append(rawProof, l) + return true + }) + // Validate merkle proof for deposit leaf. + if I.FullValidation && !utils.IsValidMerkleBranch( + depositLeaf, + rawProof, + s.BeaconConfig().DepositContractTreeDepth+1, + depositIndex, + eth1Data.Root, + ) { + return fmt.Errorf("processDepositForAltair: Could not validate deposit root") + } + + // Increment index + s.SetEth1DepositIndex(depositIndex + 1) + publicKey := deposit.Data.PubKey + amount := deposit.Data.Amount + // Check if pub key is in validator set + validatorIndex, has := s.ValidatorIndexByPubkey(publicKey) + if !has { + // Agnostic domain. + domain, err := fork.ComputeDomain(s.BeaconConfig().DomainDeposit[:], utils.Uint32ToBytes4(s.BeaconConfig().GenesisForkVersion), [32]byte{}) + if err != nil { + return err + } + depositMessageRoot, err := deposit.Data.MessageHash() + if err != nil { + return err + } + signedRoot := utils.Keccak256(depositMessageRoot[:], domain) + // Perform BLS verification and if successful noice. + valid, err := bls.Verify(deposit.Data.Signature[:], signedRoot[:], publicKey[:]) + // Literally you can input it trash. + if !valid || err != nil { + log.Debug("Validator BLS verification failed", "valid", valid, "err", err) + return nil + } + // Append validator + s.AddValidator(state.ValidatorFromDeposit(s.BeaconConfig(), deposit), amount) + // Altair forward + if s.Version() >= clparams.AltairVersion { + s.AddCurrentEpochParticipationFlags(cltypes.ParticipationFlags(0)) + s.AddPreviousEpochParticipationFlags(cltypes.ParticipationFlags(0)) + s.AddInactivityScore(0) + } + return nil + } + // Increase the balance if exists already + return state.IncreaseBalance(s.BeaconState, validatorIndex, amount) +} + +// ProcessVoluntaryExit takes a voluntary exit and applies state transition. +func (I *impl) ProcessVoluntaryExit(s *state.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit) error { + // Sanity checks so that we know it is good. + voluntaryExit := signedVoluntaryExit.VolunaryExit + currentEpoch := state.Epoch(s.BeaconState) + validator, err := s.ValidatorForValidatorIndex(int(voluntaryExit.ValidatorIndex)) + if err != nil { + return err + } + if !validator.Active(currentEpoch) { + return errors.New("ProcessVoluntaryExit: validator is not active") + } + if validator.ExitEpoch() != s.BeaconConfig().FarFutureEpoch { + return errors.New("ProcessVoluntaryExit: another exit for the same validator is already getting processed") + } + if currentEpoch < voluntaryExit.Epoch { + return errors.New("ProcessVoluntaryExit: exit is happening in the future") + } + if currentEpoch < validator.ActivationEpoch()+s.BeaconConfig().ShardCommitteePeriod { + return errors.New("ProcessVoluntaryExit: exit is happening too fast") + } + + // We can skip it in some instances if we want to optimistically sync up. + if I.FullValidation { + domain, err := s.GetDomain(s.BeaconConfig().DomainVoluntaryExit, voluntaryExit.Epoch) + if err != nil { + return err + } + signingRoot, err := fork.ComputeSigningRoot(voluntaryExit, domain) + if err != nil { + return err + } + pk := validator.PublicKey() + valid, err := bls.Verify(signedVoluntaryExit.Signature[:], signingRoot[:], pk[:]) + if err != nil { + return err + } + if !valid { + return errors.New("ProcessVoluntaryExit: BLS verification failed") + } + } + // Do the exit (same process in slashing). + return s.InitiateValidatorExit(voluntaryExit.ValidatorIndex) +} + +// ProcessWithdrawals processes withdrawals by decreasing the balance of each validator +// and updating the next withdrawal index and validator index. +func (I *impl) ProcessWithdrawals(s *state.BeaconState, withdrawals *solid.ListSSZ[*types.Withdrawal]) error { + // Get the list of withdrawals, the expected withdrawals (if performing full validation), + // and the beacon configuration. + beaconConfig := s.BeaconConfig() + numValidators := uint64(s.ValidatorLength()) + + // Check if full validation is required and verify expected withdrawals. + if I.FullValidation { + expectedWithdrawals := state.ExpectedWithdrawals(s.BeaconState) + if len(expectedWithdrawals) != withdrawals.Len() { + return fmt.Errorf("ProcessWithdrawals: expected %d withdrawals, but got %d", len(expectedWithdrawals), withdrawals.Len()) + } + if err := solid.RangeErr[*types.Withdrawal](withdrawals, func(i int, w *types.Withdrawal, _ int) error { + if !expectedWithdrawals[i].Equal(w) { + return fmt.Errorf("ProcessWithdrawals: withdrawal %d does not match expected withdrawal", i) + } + return nil + }); err != nil { + return err + } + } + + if err := solid.RangeErr[*types.Withdrawal](withdrawals, func(_ int, w *types.Withdrawal, _ int) error { + if err := state.DecreaseBalance(s.BeaconState, w.Validator, w.Amount); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + // Update next withdrawal index based on number of withdrawals. + if withdrawals.Len() > 0 { + lastWithdrawalIndex := withdrawals.Get(withdrawals.Len() - 1).Index + s.SetNextWithdrawalIndex(lastWithdrawalIndex + 1) + } + + // Update next withdrawal validator index based on number of withdrawals. + if withdrawals.Len() == int(beaconConfig.MaxWithdrawalsPerPayload) { + lastWithdrawalValidatorIndex := withdrawals.Get(withdrawals.Len()-1).Validator + 1 + s.SetNextWithdrawalValidatorIndex(lastWithdrawalValidatorIndex % numValidators) + } else { + nextIndex := s.NextWithdrawalValidatorIndex() + beaconConfig.MaxValidatorsPerWithdrawalsSweep + s.SetNextWithdrawalValidatorIndex(nextIndex % numValidators) + } + + return nil +} + +// ProcessExecutionPayload sets the latest payload header accordinly. +func (I *impl) ProcessExecutionPayload(s *state.BeaconState, payload *cltypes.Eth1Block) error { + if state.IsMergeTransitionComplete(s.BeaconState) { + if payload.ParentHash != s.LatestExecutionPayloadHeader().BlockHash { + return fmt.Errorf("ProcessExecutionPayload: invalid eth1 chain. mismatching parent") + } + } + if payload.PrevRandao != s.GetRandaoMixes(state.Epoch(s.BeaconState)) { + return fmt.Errorf("ProcessExecutionPayload: randao mix mismatches with mix digest") + } + if payload.Time != state.ComputeTimestampAtSlot(s.BeaconState, s.Slot()) { + return fmt.Errorf("ProcessExecutionPayload: invalid Eth1 timestamp") + } + payloadHeader, err := payload.PayloadHeader() + if err != nil { + return err + } + s.SetLatestExecutionPayloadHeader(payloadHeader) + return nil +} + +func (I *impl) ProcessSyncAggregate(s *state.BeaconState, sync *cltypes.SyncAggregate) error { + votedKeys, err := processSyncAggregate(s, sync) + if err != nil { + return err + } + if I.FullValidation { + previousSlot := s.PreviousSlot() + + domain, err := fork.Domain(s.Fork(), state.GetEpochAtSlot(s.BeaconConfig(), previousSlot), s.BeaconConfig().DomainSyncCommittee, s.GenesisValidatorsRoot()) + if err != nil { + return nil + } + blockRoot, err := s.GetBlockRootAtSlot(previousSlot) + if err != nil { + return err + } + msg := utils.Keccak256(blockRoot[:], domain) + isValid, err := bls.VerifyAggregate(sync.SyncCommiteeSignature[:], msg[:], votedKeys) + if err != nil { + return err + } + if !isValid { + return errors.New("ProcessSyncAggregate: cannot validate sync committee signature") + } + } + return nil +} + +// processSyncAggregate applies all the logic in the spec function `process_sync_aggregate` except +// verifying the BLS signatures. It returns the modified beacons state and the list of validators' +// public keys that voted, for future signature verification. +func processSyncAggregate(s *state.BeaconState, sync *cltypes.SyncAggregate) ([][]byte, error) { + currentSyncCommittee := s.CurrentSyncCommittee() + + if currentSyncCommittee == nil { + return nil, errors.New("nil current sync committee in s") + } + committeeKeys := currentSyncCommittee.GetCommittee() + if len(sync.SyncCommiteeBits)*8 > len(committeeKeys) { + return nil, errors.New("bits length exceeds committee length") + } + var votedKeys [][]byte + + proposerReward, participantReward, err := s.SyncRewards() + if err != nil { + return nil, err + } + + proposerIndex, err := s.GetBeaconProposerIndex() + if err != nil { + return nil, err + } + + syncAggregateBits := sync.SyncCommiteeBits + earnedProposerReward := uint64(0) + currPubKeyIndex := 0 + for i := range syncAggregateBits { + for bit := 1; bit <= 128; bit *= 2 { + vIdx, exists := s.ValidatorIndexByPubkey(committeeKeys[currPubKeyIndex]) + // Impossible scenario. + if !exists { + return nil, errors.New("validator public key does not exist in state") + } + if syncAggregateBits[i]&byte(bit) > 0 { + votedKeys = append(votedKeys, committeeKeys[currPubKeyIndex][:]) + if err := state.IncreaseBalance(s.BeaconState, vIdx, participantReward); err != nil { + return nil, err + } + earnedProposerReward += proposerReward + } else { + if err := state.DecreaseBalance(s.BeaconState, vIdx, participantReward); err != nil { + return nil, err + } + } + currPubKeyIndex++ + } + } + + return votedKeys, state.IncreaseBalance(s.BeaconState, proposerIndex, earnedProposerReward) +} + +// ProcessBlsToExecutionChange processes a BLSToExecutionChange message by updating a validator's withdrawal credentials. +func (I *impl) ProcessBlsToExecutionChange(s *state.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange) error { + change := signedChange.Message + + beaconConfig := s.BeaconConfig() + validator, err := s.ValidatorForValidatorIndex(int(change.ValidatorIndex)) + if err != nil { + return err + } + + // Perform full validation if requested. + wc := validator.WithdrawalCredentials() + if I.FullValidation { + // Check the validator's withdrawal credentials prefix. + if wc[0] != beaconConfig.BLSWithdrawalPrefixByte { + return fmt.Errorf("invalid withdrawal credentials prefix") + } + + // Check the validator's withdrawal credentials against the provided message. + hashedFrom := utils.Keccak256(change.From[:]) + if !bytes.Equal(hashedFrom[1:], wc[1:]) { + return fmt.Errorf("invalid withdrawal credentials") + } + + // Compute the signing domain and verify the message signature. + domain, err := fork.ComputeDomain(beaconConfig.DomainBLSToExecutionChange[:], utils.Uint32ToBytes4(beaconConfig.GenesisForkVersion), s.GenesisValidatorsRoot()) + if err != nil { + return err + } + signedRoot, err := fork.ComputeSigningRoot(change, domain) + if err != nil { + return err + } + valid, err := bls.Verify(signedChange.Signature[:], signedRoot[:], change.From[:]) + if err != nil { + return err + } + if !valid { + return fmt.Errorf("invalid signature") + } + } + credentials := wc + // Reset the validator's withdrawal credentials. + credentials[0] = beaconConfig.ETH1AddressWithdrawalPrefixByte + copy(credentials[1:], make([]byte, 11)) + copy(credentials[12:], change.To[:]) + + // Update the state with the modified validator. + s.SetWithdrawalCredentialForValidatorAtIndex(int(change.ValidatorIndex), credentials) + return nil +} + +func (I *impl) VerifyKzgCommitmentsAgainstTransactions(transactions *solid.TransactionsSSZ, kzgCommitments *solid.ListSSZ[*cltypes.KZGCommitment]) (bool, error) { + if I.FullValidation { + return true, nil + } + allVersionedHashes := []common.Hash{} + transactions.ForEach(func(tx []byte, idx, total int) bool { + if tx[0] != types.BlobTxType { + return true + } + + allVersionedHashes = append(allVersionedHashes, txPeekBlobVersionedHashes(tx)...) + return true + }) + + commitmentVersionedHash := []common.Hash{} + var err error + var versionedHash common.Hash + kzgCommitments.Range(func(index int, value *cltypes.KZGCommitment, length int) bool { + versionedHash, err = kzgCommitmentToVersionedHash(value) + if err != nil { + return false + } + + commitmentVersionedHash = append(commitmentVersionedHash, versionedHash) + return true + }) + if err != nil { + return false, err + } + + return reflect.DeepEqual(allVersionedHashes, commitmentVersionedHash), nil +} + +func (I *impl) ProcessAttestations(s *state.BeaconState, attestations *solid.ListSSZ[*solid.Attestation]) error { + attestingIndiciesSet := make([][]uint64, attestations.Len()) + h := methelp.NewHistTimer("beacon_process_attestations") + baseRewardPerIncrement := s.BaseRewardPerIncrement() + + c := h.Tag("attestation_step", "process") + var err error + if err := solid.RangeErr[*solid.Attestation](attestations, func(i int, a *solid.Attestation, _ int) error { + if attestingIndiciesSet[i], err = processAttestation(s, a, baseRewardPerIncrement); err != nil { + return err + } + return nil + }); err != nil { + return err + } + if err != nil { + return err + } + var valid bool + c.PutSince() + if I.FullValidation { + c = h.Tag("attestation_step", "validate") + valid, err = verifyAttestations(s, attestations, attestingIndiciesSet) + if err != nil { + return err + } + if !valid { + return errors.New("ProcessAttestation: wrong bls data") + } + c.PutSince() + } + + return nil +} + +func processAttestationPostAltair(s *state.BeaconState, attestation *solid.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { + data := attestation.AttestantionData() + currentEpoch := state.Epoch(s.BeaconState) + stateSlot := s.Slot() + beaconConfig := s.BeaconConfig() + + h := methelp.NewHistTimer("beacon_process_attestation_post_altair") + + c := h.Tag("step", "get_participation_flag") + participationFlagsIndicies, err := s.GetAttestationParticipationFlagIndicies(attestation.AttestantionData(), stateSlot-data.Slot()) + if err != nil { + return nil, err + } + c.PutSince() + + c = h.Tag("step", "get_attesting_indices") + + attestingIndicies, err := s.GetAttestingIndicies(attestation.AttestantionData(), attestation.AggregationBits(), true) + if err != nil { + return nil, err + } + + c.PutSince() + + var proposerRewardNumerator uint64 + + isCurrentEpoch := data.Target().Epoch() == currentEpoch + + c = h.Tag("step", "update_attestation") + for _, attesterIndex := range attestingIndicies { + val, err := s.ValidatorEffectiveBalance(int(attesterIndex)) + if err != nil { + return nil, err + } + + baseReward := (val / beaconConfig.EffectiveBalanceIncrement) * baseRewardPerIncrement + for flagIndex, weight := range beaconConfig.ParticipationWeights() { + flagParticipation := s.EpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex)) + if !slices.Contains(participationFlagsIndicies, uint8(flagIndex)) || flagParticipation.HasFlag(flagIndex) { + continue + } + s.SetEpochParticipationForValidatorIndex(isCurrentEpoch, int(attesterIndex), flagParticipation.Add(flagIndex)) + proposerRewardNumerator += baseReward * weight + } + } + c.PutSince() + // Reward proposer + c = h.Tag("step", "get_proposer_index") + proposer, err := s.GetBeaconProposerIndex() + if err != nil { + return nil, err + } + c.PutSince() + proposerRewardDenominator := (beaconConfig.WeightDenominator - beaconConfig.ProposerWeight) * beaconConfig.WeightDenominator / beaconConfig.ProposerWeight + reward := proposerRewardNumerator / proposerRewardDenominator + return attestingIndicies, state.IncreaseBalance(s.BeaconState, proposer, reward) +} + +// processAttestationsPhase0 implements the rules for phase0 processing. +func processAttestationPhase0(s *state.BeaconState, attestation *solid.Attestation) ([]uint64, error) { + data := attestation.AttestantionData() + committee, err := s.GetBeaconCommitee(data.Slot(), data.ValidatorIndex()) + if err != nil { + return nil, err + } + + if len(committee) != utils.GetBitlistLength(attestation.AggregationBits()) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching aggregation bits size") + } + // Cached so it is performant. + proposerIndex, err := s.GetBeaconProposerIndex() + if err != nil { + return nil, err + } + // Create the attestation to add to pending attestations + pendingAttestation := solid.NewPendingAttestionFromParameters( + attestation.AggregationBits(), + data, + s.Slot()-data.Slot(), + proposerIndex, + ) + + isCurrentAttestation := data.Target().Epoch() == state.Epoch(s.BeaconState) + // Depending of what slot we are on we put in either the current justified or previous justified. + if isCurrentAttestation { + if !data.Source().Equal(s.CurrentJustifiedCheckpoint()) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") + } + s.AddCurrentEpochAtteastation(pendingAttestation) + } else { + if !data.Source().Equal(s.PreviousJustifiedCheckpoint()) { + return nil, fmt.Errorf("processAttestationPhase0: mismatching sources") + } + s.AddPreviousEpochAttestation(pendingAttestation) + } + // Not required by specs but needed if we want performant epoch transition. + indicies, err := s.GetAttestingIndicies(attestation.AttestantionData(), attestation.AggregationBits(), true) + if err != nil { + return nil, err + } + epochRoot, err := state.GetBlockRoot(s.BeaconState, attestation.AttestantionData().Target().Epoch()) + if err != nil { + return nil, err + } + slotRoot, err := s.GetBlockRootAtSlot(attestation.AttestantionData().Slot()) + if err != nil { + return nil, err + } + // Basically we flag all validators we are currently attesting. will be important for rewards/finalization processing. + for _, index := range indicies { + minCurrentInclusionDelayAttestation, err := s.ValidatorMinCurrentInclusionDelayAttestation(int(index)) + if err != nil { + return nil, err + } + + minPreviousInclusionDelayAttestation, err := s.ValidatorMinPreviousInclusionDelayAttestation(int(index)) + if err != nil { + return nil, err + } + // NOTE: does not affect state root. + // We need to set it to currents or previouses depending on which attestation we process. + if isCurrentAttestation { + if minCurrentInclusionDelayAttestation == nil || + minCurrentInclusionDelayAttestation.InclusionDelay() > pendingAttestation.InclusionDelay() { + if err := s.SetValidatorMinCurrentInclusionDelayAttestation(int(index), pendingAttestation); err != nil { + return nil, err + } + } + if err := s.SetValidatorIsCurrentMatchingSourceAttester(int(index), true); err != nil { + return nil, err + } + if attestation.AttestantionData().Target().BlockRoot() == epochRoot { + if err := s.SetValidatorIsCurrentMatchingTargetAttester(int(index), true); err != nil { + return nil, err + } + } else { + continue + } + if attestation.AttestantionData().BeaconBlockRoot() == slotRoot { + if err := s.SetValidatorIsCurrentMatchingHeadAttester(int(index), true); err != nil { + return nil, err + } + } + } else { + if minPreviousInclusionDelayAttestation == nil || + minPreviousInclusionDelayAttestation.InclusionDelay() > pendingAttestation.InclusionDelay() { + if err := s.SetValidatorMinPreviousInclusionDelayAttestation(int(index), pendingAttestation); err != nil { + return nil, err + } + } + if err := s.SetValidatorIsPreviousMatchingSourceAttester(int(index), true); err != nil { + return nil, err + } + if attestation.AttestantionData().Target().BlockRoot() != epochRoot { + continue + } + if err := s.SetValidatorIsPreviousMatchingTargetAttester(int(index), true); err != nil { + return nil, err + } + if attestation.AttestantionData().BeaconBlockRoot() == slotRoot { + if err := s.SetValidatorIsPreviousMatchingHeadAttester(int(index), true); err != nil { + return nil, err + } + } + } + } + return indicies, nil +} + +// ProcessAttestation takes an attestation and process it. +func processAttestation(s *state.BeaconState, attestation *solid.Attestation, baseRewardPerIncrement uint64) ([]uint64, error) { + data := attestation.AttestantionData() + currentEpoch := state.Epoch(s.BeaconState) + previousEpoch := state.PreviousEpoch(s.BeaconState) + stateSlot := s.Slot() + beaconConfig := s.BeaconConfig() + // Prelimary checks. + if (data.Target().Epoch() != currentEpoch && data.Target().Epoch() != previousEpoch) || data.Target().Epoch() != state.GetEpochAtSlot(s.BeaconConfig(), data.Slot()) { + return nil, errors.New("ProcessAttestation: attestation with invalid epoch") + } + if data.Slot()+beaconConfig.MinAttestationInclusionDelay > stateSlot || stateSlot > data.Slot()+beaconConfig.SlotsPerEpoch { + return nil, errors.New("ProcessAttestation: attestation slot not in range") + } + if data.ValidatorIndex() >= s.CommitteeCount(data.Target().Epoch()) { + return nil, errors.New("ProcessAttestation: attester index out of range") + } + // check if we need to use rules for phase0 or post-altair. + if s.Version() == clparams.Phase0Version { + return processAttestationPhase0(s, attestation) + } + return processAttestationPostAltair(s, attestation, baseRewardPerIncrement) +} + +func verifyAttestations(s *state.BeaconState, attestations *solid.ListSSZ[*solid.Attestation], attestingIndicies [][]uint64) (bool, error) { + var err error + valid := true + attestations.Range(func(idx int, a *solid.Attestation, _ int) bool { + indexedAttestation := state.GetIndexedAttestation(a, attestingIndicies[idx]) + valid, err = state.IsValidIndexedAttestation(s.BeaconState, indexedAttestation) + if err != nil { + return false + } + if !valid { + return false + } + return true + }) + + return valid, err +} + +func (I *impl) ProcessBlockHeader(s *state.BeaconState, block *cltypes.BeaconBlock) error { + if I.FullValidation { + if block.Slot != s.Slot() { + return fmt.Errorf("state slot: %d, not equal to block slot: %d", s.Slot(), block.Slot) + } + if block.Slot <= s.LatestBlockHeader().Slot { + return fmt.Errorf("slock slot: %d, not greater than latest block slot: %d", block.Slot, s.LatestBlockHeader().Slot) + } + propInd, err := s.GetBeaconProposerIndex() + if err != nil { + return fmt.Errorf("error in GetBeaconProposerIndex: %v", err) + } + if block.ProposerIndex != propInd { + return fmt.Errorf("block proposer index: %d, does not match beacon proposer index: %d", block.ProposerIndex, propInd) + } + blockHeader := s.LatestBlockHeader() + latestRoot, err := (&blockHeader).HashSSZ() + if err != nil { + return fmt.Errorf("unable to hash tree root of latest block header: %v", err) + } + if block.ParentRoot != latestRoot { + return fmt.Errorf("block parent root: %x, does not match latest block root: %x", block.ParentRoot, latestRoot) + } + } + + bodyRoot, err := block.Body.HashSSZ() + if err != nil { + return fmt.Errorf("unable to hash tree root of block body: %v", err) + } + s.SetLatestBlockHeader(&cltypes.BeaconBlockHeader{ + Slot: block.Slot, + ProposerIndex: block.ProposerIndex, + ParentRoot: block.ParentRoot, + BodyRoot: bodyRoot, + }) + + proposer, err := s.ValidatorForValidatorIndex(int(block.ProposerIndex)) + if err != nil { + return err + } + if proposer.Slashed() { + return fmt.Errorf("proposer: %d is slashed", block.ProposerIndex) + } + return nil +} + +func (I *impl) ProcessRandao(s *state.BeaconState, randao [96]byte, proposerIndex uint64) error { + epoch := state.Epoch(s.BeaconState) + proposer, err := s.ValidatorForValidatorIndex(int(proposerIndex)) + if err != nil { + return err + } + if I.FullValidation { + domain, err := s.GetDomain(s.BeaconConfig().DomainRandao, epoch) + if err != nil { + return fmt.Errorf("ProcessRandao: unable to get domain: %v", err) + } + signingRoot, err := computeSigningRootEpoch(epoch, domain) + if err != nil { + return fmt.Errorf("ProcessRandao: unable to compute signing root: %v", err) + } + pk := proposer.PublicKey() + valid, err := bls.Verify(randao[:], signingRoot[:], pk[:]) + if err != nil { + return fmt.Errorf("ProcessRandao: unable to verify public key: %x, with signing root: %x, and signature: %x, %v", pk[:], signingRoot[:], randao[:], err) + } + if !valid { + return fmt.Errorf("ProcessRandao: invalid signature: public key: %x, signing root: %x, signature: %x", pk[:], signingRoot[:], randao[:]) + } + } + + randaoMixes := s.GetRandaoMixes(epoch) + randaoHash := utils.Keccak256(randao[:]) + mix := [32]byte{} + for i := range mix { + mix[i] = randaoMixes[i] ^ randaoHash[i] + } + s.SetRandaoMixAt(int(epoch%s.BeaconConfig().EpochsPerHistoricalVector), mix) + return nil +} + +func (I *impl) ProcessEth1Data(state *state.BeaconState, eth1Data *cltypes.Eth1Data) error { + state.AddEth1DataVote(eth1Data) + newVotes := state.Eth1DataVotes() + + // Count how many times body.Eth1Data appears in the votes. + numVotes := 0 + newVotes.Range(func(index int, value *cltypes.Eth1Data, length int) bool { + if eth1Data.Equal(value) { + numVotes += 1 + } + return true + }) + + if uint64(numVotes*2) > state.BeaconConfig().EpochsPerEth1VotingPeriod*state.BeaconConfig().SlotsPerEpoch { + state.SetEth1Data(eth1Data) + } + return nil +} + +func (I *impl) ProcessSlots(s *state.BeaconState, slot uint64) error { + beaconConfig := s.BeaconConfig() + sSlot := s.Slot() + if slot <= sSlot { + return fmt.Errorf("new slot: %d not greater than s slot: %d", slot, sSlot) + } + // Process each slot. + for i := sSlot; i < slot; i++ { + err := transitionSlot(s) + if err != nil { + return fmt.Errorf("unable to process slot transition: %v", err) + } + // TODO(Someone): Add epoch transition. + if (sSlot+1)%beaconConfig.SlotsPerEpoch == 0 { + start := time.Now() + if err := statechange.ProcessEpoch(s); err != nil { + return err + } + log.Debug("Processed new epoch successfully", "epoch", state.Epoch(s.BeaconState), "process_epoch_elpsed", time.Since(start)) + } + // TODO: add logic to process epoch updates. + sSlot += 1 + s.SetSlot(sSlot) + if sSlot%beaconConfig.SlotsPerEpoch != 0 { + continue + } + if state.Epoch(s.BeaconState) == beaconConfig.AltairForkEpoch { + if err := s.UpgradeToAltair(); err != nil { + return err + } + } + if state.Epoch(s.BeaconState) == beaconConfig.BellatrixForkEpoch { + if err := s.UpgradeToBellatrix(); err != nil { + return err + } + } + if state.Epoch(s.BeaconState) == beaconConfig.CapellaForkEpoch { + if err := s.UpgradeToCapella(); err != nil { + return err + } + } + if state.Epoch(s.BeaconState) == beaconConfig.DenebForkEpoch { + if err := s.UpgradeToDeneb(); err != nil { + return err + } + } + } + return nil +} diff --git a/cl/phase1/core/transition/finalization_and_justification.go b/cl/transition/impl/eth2/statechange/finalization_and_justification.go similarity index 86% rename from cl/phase1/core/transition/finalization_and_justification.go rename to cl/transition/impl/eth2/statechange/finalization_and_justification.go index df905e848..d4f625d15 100644 --- a/cl/phase1/core/transition/finalization_and_justification.go +++ b/cl/transition/impl/eth2/statechange/finalization_and_justification.go @@ -1,17 +1,17 @@ -package transition +package statechange import ( "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes" "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" ) // weighJustificationAndFinalization checks justification and finality of epochs and adds records to the state as needed. -func weighJustificationAndFinalization(s *state2.BeaconState, previousEpochTargetBalance, currentEpochTargetBalance uint64) error { +func weighJustificationAndFinalization(s *state.BeaconState, previousEpochTargetBalance, currentEpochTargetBalance uint64) error { totalActiveBalance := s.GetTotalActiveBalance() - currentEpoch := state2.Epoch(s.BeaconState) - previousEpoch := state2.PreviousEpoch(s.BeaconState) + currentEpoch := state.Epoch(s.BeaconState) + previousEpoch := state.PreviousEpoch(s.BeaconState) oldPreviousJustifiedCheckpoint := s.PreviousJustifiedCheckpoint() oldCurrentJustifiedCheckpoint := s.CurrentJustifiedCheckpoint() justificationBits := s.JustificationBits() @@ -23,7 +23,7 @@ func weighJustificationAndFinalization(s *state2.BeaconState, previousEpochTarge justificationBits[0] = false // Update justified checkpoint if super majority is reached on previous epoch if previousEpochTargetBalance*3 >= totalActiveBalance*2 { - checkPointRoot, err := state2.GetBlockRoot(s.BeaconState, previousEpoch) + checkPointRoot, err := state.GetBlockRoot(s.BeaconState, previousEpoch) if err != nil { return err } @@ -32,7 +32,7 @@ func weighJustificationAndFinalization(s *state2.BeaconState, previousEpochTarge justificationBits[1] = true } if currentEpochTargetBalance*3 >= totalActiveBalance*2 { - checkPointRoot, err := state2.GetBlockRoot(s.BeaconState, currentEpoch) + checkPointRoot, err := state.GetBlockRoot(s.BeaconState, currentEpoch) if err != nil { return err } @@ -58,9 +58,9 @@ func weighJustificationAndFinalization(s *state2.BeaconState, previousEpochTarge return nil } -func ProcessJustificationBitsAndFinality(s *state2.BeaconState) error { - currentEpoch := state2.Epoch(s.BeaconState) - previousEpoch := state2.PreviousEpoch(s.BeaconState) +func ProcessJustificationBitsAndFinality(s *state.BeaconState) error { + currentEpoch := state.Epoch(s.BeaconState) + previousEpoch := state.PreviousEpoch(s.BeaconState) beaconConfig := s.BeaconConfig() // Skip for first 2 epochs if currentEpoch <= beaconConfig.GenesisEpoch+1 { diff --git a/cl/phase1/core/transition/process_effective_balance_update.go b/cl/transition/impl/eth2/statechange/process_effective_balance_update.go similarity index 98% rename from cl/phase1/core/transition/process_effective_balance_update.go rename to cl/transition/impl/eth2/statechange/process_effective_balance_update.go index 7430bd66a..ce42a2cce 100644 --- a/cl/phase1/core/transition/process_effective_balance_update.go +++ b/cl/transition/impl/eth2/statechange/process_effective_balance_update.go @@ -1,4 +1,4 @@ -package transition +package statechange import ( "github.com/ledgerwatch/erigon/cl/cltypes/solid" diff --git a/cl/transition/impl/eth2/statechange/process_epoch.go b/cl/transition/impl/eth2/statechange/process_epoch.go new file mode 100644 index 000000000..fe103ea30 --- /dev/null +++ b/cl/transition/impl/eth2/statechange/process_epoch.go @@ -0,0 +1,49 @@ +package statechange + +import ( + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +// ProcessEpoch process epoch transition. +func ProcessEpoch(state *state.BeaconState) error { + if err := ProcessJustificationBitsAndFinality(state); err != nil { + return err + } + if state.Version() >= clparams.AltairVersion { + if err := ProcessInactivityScores(state); err != nil { + return err + } + } + if err := ProcessRewardsAndPenalties(state); err != nil { + return err + } + if err := ProcessRegistryUpdates(state); err != nil { + return err + } + if err := ProcessSlashings(state); err != nil { + return err + } + ProcessEth1DataReset(state) + if err := ProcessEffectiveBalanceUpdates(state); err != nil { + return err + } + ProcessSlashingsReset(state) + ProcessRandaoMixesReset(state) + if err := ProcessHistoricalRootsUpdate(state); err != nil { + return err + } + if state.Version() == clparams.Phase0Version { + if err := ProcessParticipationRecordUpdates(state); err != nil { + return err + } + } + + if state.Version() >= clparams.AltairVersion { + ProcessParticipationFlagUpdates(state) + if err := ProcessSyncCommitteeUpdate(state); err != nil { + return err + } + } + return nil +} diff --git a/cl/phase1/core/transition/process_epoch_test.go b/cl/transition/impl/eth2/statechange/process_epoch_test.go similarity index 90% rename from cl/phase1/core/transition/process_epoch_test.go rename to cl/transition/impl/eth2/statechange/process_epoch_test.go index 4fc13b4dc..19a13e3ef 100644 --- a/cl/phase1/core/transition/process_epoch_test.go +++ b/cl/transition/impl/eth2/statechange/process_epoch_test.go @@ -1,13 +1,11 @@ -package transition_test +package statechange import ( _ "embed" "testing" - "github.com/ledgerwatch/erigon/cl/phase1/core/state" - transition2 "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/erigon/cl/utils" "github.com/stretchr/testify/require" ) @@ -91,64 +89,64 @@ var startingSlashingsResetState []byte func TestProcessRewardsAndPenalties(t *testing.T) { runEpochTransitionConsensusTest(t, startingRewardsPenaltyState, expectedRewardsPenaltyState, func(s *state.BeaconState) error { - return transition2.ProcessRewardsAndPenalties(s) + return ProcessRewardsAndPenalties(s) }) } func TestProcessRegistryUpdates(t *testing.T) { runEpochTransitionConsensusTest(t, startingRegistryUpdatesState, expectedRegistryUpdatesState, func(s *state.BeaconState) error { - return transition2.ProcessRegistryUpdates(s) + return ProcessRegistryUpdates(s) }) } func TestProcessEffectiveBalances(t *testing.T) { runEpochTransitionConsensusTest(t, startingEffectiveBalancesState, expectedEffectiveBalancesState, func(s *state.BeaconState) error { - return transition2.ProcessEffectiveBalanceUpdates(s) + return ProcessEffectiveBalanceUpdates(s) }) } func TestProcessHistoricalRoots(t *testing.T) { runEpochTransitionConsensusTest(t, startingHistoricalRootsState, expectedHistoricalRootsState, func(s *state.BeaconState) error { - return transition2.ProcessHistoricalRootsUpdate(s) + return ProcessHistoricalRootsUpdate(s) }) } func TestProcessParticipationFlagUpdates(t *testing.T) { runEpochTransitionConsensusTest(t, startingParticipationFlagState, expectedParticipationFlagState, func(s *state.BeaconState) error { - transition2.ProcessParticipationFlagUpdates(s) + ProcessParticipationFlagUpdates(s) return nil }) } func TestProcessSlashings(t *testing.T) { runEpochTransitionConsensusTest(t, startingSlashingsState, expectedSlashingsState, func(s *state.BeaconState) error { - return transition2.ProcessSlashings(s) + return ProcessSlashings(s) }) } func TestProcessJustificationAndFinality(t *testing.T) { runEpochTransitionConsensusTest(t, startingJustificationAndFinalityState, expectedJustificationAndFinalityState, func(s *state.BeaconState) error { - return transition2.ProcessJustificationBitsAndFinality(s) + return ProcessJustificationBitsAndFinality(s) }) } func TestEth1DataReset(t *testing.T) { runEpochTransitionConsensusTest(t, startingEth1DataResetState, expectedEth1DataResetState, func(s *state.BeaconState) error { - transition2.ProcessEth1DataReset(s) + ProcessEth1DataReset(s) return nil }) } func TestRandaoMixesReset(t *testing.T) { runEpochTransitionConsensusTest(t, startingRandaoMixesResetState, expectedRandaoMixesResetState, func(s *state.BeaconState) error { - transition2.ProcessRandaoMixesReset(s) + ProcessRandaoMixesReset(s) return nil }) } func TestSlashingsReset(t *testing.T) { runEpochTransitionConsensusTest(t, startingSlashingsResetState, expectedSlashingsResetState, func(s *state.BeaconState) error { - transition2.ProcessSlashingsReset(s) + ProcessSlashingsReset(s) return nil }) } @@ -161,6 +159,6 @@ var startingInactivityScoresState []byte func TestInactivityScores(t *testing.T) { runEpochTransitionConsensusTest(t, startingInactivityScoresState, expectedInactivityScoresState, func(s *state.BeaconState) error { - return transition2.ProcessInactivityScores(s) + return ProcessInactivityScores(s) }) } diff --git a/cl/transition/impl/eth2/statechange/process_historical_roots.go b/cl/transition/impl/eth2/statechange/process_historical_roots.go new file mode 100644 index 000000000..9bb26f505 --- /dev/null +++ b/cl/transition/impl/eth2/statechange/process_historical_roots.go @@ -0,0 +1,59 @@ +package statechange + +import ( + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +func ProcessParticipationRecordUpdates(s *state.BeaconState) error { + s.SetPreviousEpochAttestations(s.CurrentEpochAttestations()) + s.ResetCurrentEpochAttestations() + var err error + // Also mark all current attesters as previous + s.ForEachValidator(func(_ solid.Validator, idx, total int) bool { + + var oldCurrentMatchingSourceAttester, oldCurrentMatchingTargetAttester, oldCurrentMatchingHeadAttester bool + var oldMinCurrentInclusionDelayAttestation *solid.PendingAttestation + + if oldCurrentMatchingSourceAttester, err = s.ValidatorIsCurrentMatchingSourceAttester(idx); err != nil { + return false + } + if oldCurrentMatchingTargetAttester, err = s.ValidatorIsCurrentMatchingTargetAttester(idx); err != nil { + return false + } + if oldCurrentMatchingHeadAttester, err = s.ValidatorIsCurrentMatchingHeadAttester(idx); err != nil { + return false + } + if oldMinCurrentInclusionDelayAttestation, err = s.ValidatorMinCurrentInclusionDelayAttestation(idx); err != nil { + return false + } + // Previous sources/target/head + if err = s.SetValidatorIsPreviousMatchingSourceAttester(idx, oldCurrentMatchingSourceAttester); err != nil { + return false + } + if err = s.SetValidatorIsPreviousMatchingTargetAttester(idx, oldCurrentMatchingTargetAttester); err != nil { + return false + } + if err = s.SetValidatorIsPreviousMatchingHeadAttester(idx, oldCurrentMatchingHeadAttester); err != nil { + return false + } + if err = s.SetValidatorMinPreviousInclusionDelayAttestation(idx, oldMinCurrentInclusionDelayAttestation); err != nil { + return false + } + // Current sources/target/head + if err = s.SetValidatorIsCurrentMatchingSourceAttester(idx, false); err != nil { + return false + } + if err = s.SetValidatorIsCurrentMatchingTargetAttester(idx, false); err != nil { + return false + } + if err = s.SetValidatorIsCurrentMatchingHeadAttester(idx, false); err != nil { + return false + } + if err = s.SetValidatorMinCurrentInclusionDelayAttestation(idx, nil); err != nil { + return false + } + return true + }) + return err +} diff --git a/cl/transition/impl/eth2/statechange/process_historical_roots_update.go b/cl/transition/impl/eth2/statechange/process_historical_roots_update.go new file mode 100644 index 000000000..9490a1c91 --- /dev/null +++ b/cl/transition/impl/eth2/statechange/process_historical_roots_update.go @@ -0,0 +1,44 @@ +package statechange + +import ( + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/utils" +) + +// ProcessHistoricalRootsUpdate updates the historical root data structure by computing a new historical root batch when it is time to do so. +func ProcessHistoricalRootsUpdate(s *state.BeaconState) error { + nextEpoch := state.Epoch(s.BeaconState) + 1 + beaconConfig := s.BeaconConfig() + blockRoots := s.BlockRoots() + stateRoots := s.StateRoots() + + // Check if it's time to compute the historical root batch. + if nextEpoch%(beaconConfig.SlotsPerHistoricalRoot/beaconConfig.SlotsPerEpoch) != 0 { + return nil + } + + // Compute historical root batch. + blockRootsLeaf, err := blockRoots.HashSSZ() + if err != nil { + return err + } + stateRootsLeaf, err := stateRoots.HashSSZ() + if err != nil { + return err + } + + // Add the historical summary or root to the s. + if s.Version() >= clparams.CapellaVersion { + s.AddHistoricalSummary(&cltypes.HistoricalSummary{ + BlockSummaryRoot: blockRootsLeaf, + StateSummaryRoot: stateRootsLeaf, + }) + } else { + historicalRoot := utils.Keccak256(blockRootsLeaf[:], stateRootsLeaf[:]) + s.AddHistoricalRoot(historicalRoot) + } + + return nil +} diff --git a/cl/phase1/core/transition/process_inactivity_scores.go b/cl/transition/impl/eth2/statechange/process_inactivity_scores.go similarity index 53% rename from cl/phase1/core/transition/process_inactivity_scores.go rename to cl/transition/impl/eth2/statechange/process_inactivity_scores.go index 0dbb8f438..70b5a5a3d 100644 --- a/cl/phase1/core/transition/process_inactivity_scores.go +++ b/cl/transition/impl/eth2/statechange/process_inactivity_scores.go @@ -1,28 +1,28 @@ -package transition +package statechange import ( - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/erigon/cl/utils" ) // ProcessInactivityScores will updates the inactivity registry of each validator. -func ProcessInactivityScores(s *state2.BeaconState) error { - if state2.Epoch(s.BeaconState) == s.BeaconConfig().GenesisEpoch { +func ProcessInactivityScores(s *state.BeaconState) error { + if state.Epoch(s.BeaconState) == s.BeaconConfig().GenesisEpoch { return nil } - previousEpoch := state2.PreviousEpoch(s.BeaconState) - for _, validatorIndex := range state2.EligibleValidatorsIndicies(s.BeaconState) { + previousEpoch := state.PreviousEpoch(s.BeaconState) + for _, validatorIndex := range state.EligibleValidatorsIndicies(s.BeaconState) { // retrieve validator inactivity score index. score, err := s.ValidatorInactivityScore(int(validatorIndex)) if err != nil { return err } - if state2.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, validatorIndex, int(s.BeaconConfig().TimelyTargetFlagIndex)) { + if state.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, validatorIndex, int(s.BeaconConfig().TimelyTargetFlagIndex)) { score -= utils.Min64(1, score) } else { score += s.BeaconConfig().InactivityScoreBias } - if !state2.InactivityLeaking(s.BeaconState) { + if !state.InactivityLeaking(s.BeaconState) { score -= utils.Min64(s.BeaconConfig().InactivityScoreRecoveryRate, score) } if err := s.SetValidatorInactivityScore(int(validatorIndex), score); err != nil { diff --git a/cl/phase1/core/transition/process_registry_updates.go b/cl/transition/impl/eth2/statechange/process_registry_updates.go similarity index 87% rename from cl/phase1/core/transition/process_registry_updates.go rename to cl/transition/impl/eth2/statechange/process_registry_updates.go index ae33d154d..8a3eda87d 100644 --- a/cl/phase1/core/transition/process_registry_updates.go +++ b/cl/transition/impl/eth2/statechange/process_registry_updates.go @@ -1,10 +1,10 @@ -package transition +package statechange import ( "sort" "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/erigon/cl/clparams" ) @@ -15,15 +15,15 @@ func computeActivationExitEpoch(beaconConfig *clparams.BeaconChainConfig, epoch } // ProcessRegistyUpdates updates every epoch the activation status of validators. Specs at: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#registry-updates. -func ProcessRegistryUpdates(s *state2.BeaconState) error { +func ProcessRegistryUpdates(s *state.BeaconState) error { beaconConfig := s.BeaconConfig() - currentEpoch := state2.Epoch(s.BeaconState) + currentEpoch := state.Epoch(s.BeaconState) // start also initializing the activation queue. activationQueue := make([]uint64, 0) // Process activation eligibility and ejections. var err error s.ForEachValidator(func(validator solid.Validator, validatorIndex, total int) bool { - if state2.IsValidatorEligibleForActivationQueue(s.BeaconState, validator) { + if state.IsValidatorEligibleForActivationQueue(s.BeaconState, validator) { s.SetActivationEligibilityEpochForValidatorAtIndex(validatorIndex, currentEpoch+1) } if validator.Active(currentEpoch) && validator.EffectiveBalance() <= beaconConfig.EjectionBalance { @@ -32,7 +32,7 @@ func ProcessRegistryUpdates(s *state2.BeaconState) error { } } // Insert in the activation queue in case. - if state2.IsValidatorEligibleForActivation(s.BeaconState, validator) { + if state.IsValidatorEligibleForActivation(s.BeaconState, validator) { activationQueue = append(activationQueue, uint64(validatorIndex)) } return true diff --git a/cl/phase1/core/transition/process_rewards_and_penalties.go b/cl/transition/impl/eth2/statechange/process_rewards_and_penalties.go similarity index 74% rename from cl/phase1/core/transition/process_rewards_and_penalties.go rename to cl/transition/impl/eth2/statechange/process_rewards_and_penalties.go index 4369717a6..2adca23fb 100644 --- a/cl/phase1/core/transition/process_rewards_and_penalties.go +++ b/cl/transition/impl/eth2/statechange/process_rewards_and_penalties.go @@ -1,18 +1,18 @@ -package transition +package statechange import ( "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes/solid" - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" ) -func processRewardsAndPenaltiesPostAltair(s *state2.BeaconState) (err error) { +func processRewardsAndPenaltiesPostAltair(s *state.BeaconState) (err error) { beaconConfig := s.BeaconConfig() weights := beaconConfig.ParticipationWeights() - eligibleValidators := state2.EligibleValidatorsIndicies(s.BeaconState) + eligibleValidators := state.EligibleValidatorsIndicies(s.BeaconState) // Initialize variables totalActiveBalance := s.GetTotalActiveBalance() - previousEpoch := state2.PreviousEpoch(s.BeaconState) + previousEpoch := state.PreviousEpoch(s.BeaconState) // Inactivity penalties denominator. inactivityPenaltyDenominator := beaconConfig.InactivityScoreBias * beaconConfig.GetPenaltyQuotient(s.Version()) // Make buffer for flag indexes total balances. @@ -20,7 +20,7 @@ func processRewardsAndPenaltiesPostAltair(s *state2.BeaconState) (err error) { // Compute all total balances for each enable unslashed validator indicies with all flags on. s.ForEachValidator(func(validator solid.Validator, validatorIndex, total int) bool { for i := range weights { - if state2.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, uint64(validatorIndex), i) { + if state.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, uint64(validatorIndex), i) { flagsTotalBalances[i] += validator.EffectiveBalance() } } @@ -40,20 +40,20 @@ func processRewardsAndPenaltiesPostAltair(s *state2.BeaconState) (err error) { return } for flagIdx := range weights { - if state2.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, index, flagIdx) { - if !state2.InactivityLeaking(s.BeaconState) { + if state.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, index, flagIdx) { + if !state.InactivityLeaking(s.BeaconState) { rewardNumerator := baseReward * rewardMultipliers[flagIdx] - if err := state2.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { + if err := state.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { return err } } } else if flagIdx != int(beaconConfig.TimelyHeadFlagIndex) { - if err := state2.DecreaseBalance(s.BeaconState, index, baseReward*weights[flagIdx]/beaconConfig.WeightDenominator); err != nil { + if err := state.DecreaseBalance(s.BeaconState, index, baseReward*weights[flagIdx]/beaconConfig.WeightDenominator); err != nil { return err } } } - if !state2.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, index, int(beaconConfig.TimelyTargetFlagIndex)) { + if !state.IsUnslashedParticipatingIndex(s.BeaconState, previousEpoch, index, int(beaconConfig.TimelyTargetFlagIndex)) { inactivityScore, err := s.ValidatorInactivityScore(int(index)) if err != nil { return err @@ -63,19 +63,19 @@ func processRewardsAndPenaltiesPostAltair(s *state2.BeaconState) (err error) { if err != nil { return err } - state2.DecreaseBalance(s.BeaconState, index, (effectiveBalance*inactivityScore)/inactivityPenaltyDenominator) + state.DecreaseBalance(s.BeaconState, index, (effectiveBalance*inactivityScore)/inactivityPenaltyDenominator) } } return } // processRewardsAndPenaltiesPhase0 process rewards and penalties for phase0 state. -func processRewardsAndPenaltiesPhase0(s *state2.BeaconState) (err error) { +func processRewardsAndPenaltiesPhase0(s *state.BeaconState) (err error) { beaconConfig := s.BeaconConfig() - if state2.Epoch(s.BeaconState) == beaconConfig.GenesisEpoch { + if state.Epoch(s.BeaconState) == beaconConfig.GenesisEpoch { return nil } - eligibleValidators := state2.EligibleValidatorsIndicies(s.BeaconState) + eligibleValidators := state.EligibleValidatorsIndicies(s.BeaconState) // Initialize variables rewardDenominator := s.GetTotalActiveBalance() / beaconConfig.EffectiveBalanceIncrement // Make buffer for flag indexes totTargetal balances. @@ -151,47 +151,47 @@ func processRewardsAndPenaltiesPhase0(s *state2.BeaconState) (err error) { } // If we attested then we reward the validator. - if state2.InactivityLeaking(s.BeaconState) { - if err := state2.IncreaseBalance(s.BeaconState, index, baseReward*attested); err != nil { + if state.InactivityLeaking(s.BeaconState) { + if err := state.IncreaseBalance(s.BeaconState, index, baseReward*attested); err != nil { return err } } else { if !currentValidator.Slashed() && previousMatchingSourceAttester { rewardNumerator := baseReward * unslashedMatchingSourceBalanceIncrements - if err := state2.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { + if err := state.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { return err } } if !currentValidator.Slashed() && previousMatchingTargetAttester { rewardNumerator := baseReward * unslashedMatchingTargetBalanceIncrements - if err := state2.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { + if err := state.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { return err } } if !currentValidator.Slashed() && previousMatchingHeadAttester { rewardNumerator := baseReward * unslashedMatchingHeadBalanceIncrements - if err := state2.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { + if err := state.IncreaseBalance(s.BeaconState, index, rewardNumerator/rewardDenominator); err != nil { return err } } } // Process inactivity of the network as a whole finalities. - if state2.InactivityLeaking(s.BeaconState) { + if state.InactivityLeaking(s.BeaconState) { proposerReward := baseReward / beaconConfig.ProposerRewardQuotient // Neutralize rewards. - if state2.DecreaseBalance(s.BeaconState, index, beaconConfig.BaseRewardsPerEpoch*baseReward-proposerReward); err != nil { + if state.DecreaseBalance(s.BeaconState, index, beaconConfig.BaseRewardsPerEpoch*baseReward-proposerReward); err != nil { return err } if currentValidator.Slashed() || !previousMatchingTargetAttester { // Increase penalities linearly if network is leaking. - if state2.DecreaseBalance(s.BeaconState, index, currentValidator.EffectiveBalance()*state2.FinalityDelay(s.BeaconState)/beaconConfig.InactivityPenaltyQuotient); err != nil { + if state.DecreaseBalance(s.BeaconState, index, currentValidator.EffectiveBalance()*state.FinalityDelay(s.BeaconState)/beaconConfig.InactivityPenaltyQuotient); err != nil { return err } } } // For each missed duty we penalize the validator. - if state2.DecreaseBalance(s.BeaconState, index, baseReward*missed); err != nil { + if state.DecreaseBalance(s.BeaconState, index, baseReward*missed); err != nil { return err } @@ -217,11 +217,11 @@ func processRewardsAndPenaltiesPhase0(s *state2.BeaconState) (err error) { } // Compute proposer reward. proposerReward := (baseReward / beaconConfig.ProposerRewardQuotient) - if err = state2.IncreaseBalance(s.BeaconState, attestation.ProposerIndex(), proposerReward); err != nil { + if err = state.IncreaseBalance(s.BeaconState, attestation.ProposerIndex(), proposerReward); err != nil { return false } maxAttesterReward := baseReward - proposerReward - if err = state2.IncreaseBalance(s.BeaconState, uint64(index), maxAttesterReward/attestation.InclusionDelay()); err != nil { + if err = state.IncreaseBalance(s.BeaconState, uint64(index), maxAttesterReward/attestation.InclusionDelay()); err != nil { return false } return true @@ -233,8 +233,8 @@ func processRewardsAndPenaltiesPhase0(s *state2.BeaconState) (err error) { } // ProcessRewardsAndPenalties applies rewards/penalties accumulated during previous epoch. -func ProcessRewardsAndPenalties(s *state2.BeaconState) error { - if state2.Epoch(s.BeaconState) == s.BeaconConfig().GenesisEpoch { +func ProcessRewardsAndPenalties(s *state.BeaconState) error { + if state.Epoch(s.BeaconState) == s.BeaconConfig().GenesisEpoch { return nil } if s.Version() == clparams.Phase0Version { diff --git a/cl/transition/impl/eth2/statechange/process_slashings.go b/cl/transition/impl/eth2/statechange/process_slashings.go new file mode 100644 index 000000000..7833a0537 --- /dev/null +++ b/cl/transition/impl/eth2/statechange/process_slashings.go @@ -0,0 +1,93 @@ +package statechange + +import ( + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +func processSlashings(s *state.BeaconState, slashingMultiplier uint64) error { + // Get the current epoch + epoch := state.Epoch(s.BeaconState) + // Get the total active balance + totalBalance := s.GetTotalActiveBalance() + // Calculate the total slashing amount + // by summing all slashings and multiplying by the provided multiplier + slashing := state.GetTotalSlashingAmount(s.BeaconState) * slashingMultiplier + // Adjust the total slashing amount to be no greater than the total active balance + if totalBalance < slashing { + slashing = totalBalance + } + beaconConfig := s.BeaconConfig() + // Apply penalties to validators who have been slashed and reached the withdrawable epoch + var err error + s.ForEachValidator(func(validator solid.Validator, i, total int) bool { + if !validator.Slashed() || epoch+beaconConfig.EpochsPerSlashingsVector/2 != validator.WithdrawableEpoch() { + return true + } + // Get the effective balance increment + increment := beaconConfig.EffectiveBalanceIncrement + // Calculate the penalty numerator by multiplying the validator's effective balance by the total slashing amount + penaltyNumerator := validator.EffectiveBalance() / increment * slashing + // Calculate the penalty by dividing the penalty numerator by the total balance and multiplying by the increment + penalty := penaltyNumerator / totalBalance * increment + // Decrease the validator's balance by the calculated penalty + if err = state.DecreaseBalance(s.BeaconState, uint64(i), penalty); err != nil { + return false + } + return true + }) + if err != nil { + return err + } + return nil +} + +func processSlashings2(s *state.BeaconState, slashingMultiplier uint64) error { + // Get the current epoch + epoch := state.Epoch(s.BeaconState) + // Get the total active balance + totalBalance := s.GetTotalActiveBalance() + // Calculate the total slashing amount + // by summing all slashings and multiplying by the provided multiplier + slashing := state.GetTotalSlashingAmount(s.BeaconState) * slashingMultiplier + // Adjust the total slashing amount to be no greater than the total active balance + if totalBalance < slashing { + slashing = totalBalance + } + beaconConfig := s.BeaconConfig() + // Apply penalties to validators who have been slashed and reached the withdrawable epoch + var err error + s.ForEachValidator(func(validator solid.Validator, i, total int) bool { + if !validator.Slashed() || epoch+beaconConfig.EpochsPerSlashingsVector/2 != validator.WithdrawableEpoch() { + return true + } + // Get the effective balance increment + increment := beaconConfig.EffectiveBalanceIncrement + // Calculate the penalty numerator by multiplying the validator's effective balance by the total slashing amount + penaltyNumerator := validator.EffectiveBalance() / increment * slashing + // Calculate the penalty by dividing the penalty numerator by the total balance and multiplying by the increment + penalty := penaltyNumerator / totalBalance * increment + // Decrease the validator's balance by the calculated penalty + if err = state.DecreaseBalance(s.BeaconState, uint64(i), penalty); err != nil { + return false + } + return true + }) + if err != nil { + return err + } + return nil +} + +func ProcessSlashings(state *state.BeaconState) error { + // Depending on the version of the state, use different multipliers + switch state.Version() { + case clparams.Phase0Version: + return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplier) + case clparams.AltairVersion: + return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplierAltair) + default: + return processSlashings(state, state.BeaconConfig().ProportionalSlashingMultiplierBellatrix) + } +} diff --git a/cl/phase1/core/transition/process_sync_committee_update.go b/cl/transition/impl/eth2/statechange/process_sync_committee_update.go similarity index 63% rename from cl/phase1/core/transition/process_sync_committee_update.go rename to cl/transition/impl/eth2/statechange/process_sync_committee_update.go index ec043b5a1..58bd15505 100644 --- a/cl/phase1/core/transition/process_sync_committee_update.go +++ b/cl/transition/impl/eth2/statechange/process_sync_committee_update.go @@ -1,12 +1,12 @@ -package transition +package statechange import ( - state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" ) // ProcessSyncCommitteeUpdate implements processing for the sync committee update. unfortunately there is no easy way to test it. -func ProcessSyncCommitteeUpdate(s *state2.BeaconState) error { - if (state2.Epoch(s.BeaconState)+1)%s.BeaconConfig().EpochsPerSyncCommitteePeriod != 0 { +func ProcessSyncCommitteeUpdate(s *state.BeaconState) error { + if (state.Epoch(s.BeaconState)+1)%s.BeaconConfig().EpochsPerSyncCommitteePeriod != 0 { return nil } // Set new current sync committee. diff --git a/cl/phase1/core/transition/process_sync_committee_update_test.go b/cl/transition/impl/eth2/statechange/process_sync_committee_update_test.go similarity index 61% rename from cl/phase1/core/transition/process_sync_committee_update_test.go rename to cl/transition/impl/eth2/statechange/process_sync_committee_update_test.go index 5d7d8d35e..a7f4b62fe 100644 --- a/cl/phase1/core/transition/process_sync_committee_update_test.go +++ b/cl/transition/impl/eth2/statechange/process_sync_committee_update_test.go @@ -1,14 +1,13 @@ -package transition_test +package statechange_test import ( "encoding/binary" + "github.com/ledgerwatch/erigon/cl/transition/impl/eth2/statechange" "testing" + "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/cl/cltypes/solid" "github.com/ledgerwatch/erigon/cl/phase1/core/state" - "github.com/ledgerwatch/erigon/cl/phase1/core/transition" - - "github.com/ledgerwatch/erigon/cl/clparams" "github.com/ledgerwatch/erigon/common" "github.com/stretchr/testify/require" ) @@ -18,7 +17,7 @@ func TestProcessSyncCommittee(t *testing.T) { var pk [48]byte copy(pk[:], pkBytes) validatorNum := 10_000 - state := state.New(&clparams.MainnetBeaconConfig) + s := state.New(&clparams.MainnetBeaconConfig) currentCommittee := &solid.SyncCommittee{} nextCommittee := &solid.SyncCommittee{} for i := 0; i < validatorNum; i++ { @@ -28,13 +27,13 @@ func TestProcessSyncCommittee(t *testing.T) { v.SetExitEpoch(clparams.MainnetBeaconConfig.FarFutureEpoch) v.SetPublicKey(pk) v.SetEffectiveBalance(2000000000) - state.AddValidator(v, 2000000000) + s.AddValidator(v, 2000000000) } - state.SetCurrentSyncCommittee(currentCommittee) - state.SetNextSyncCommittee(nextCommittee) - prevNextSyncCommittee := state.NextSyncCommittee() - state.SetSlot(8160) - require.NoError(t, transition.ProcessSyncCommitteeUpdate(state)) - require.Equal(t, state.CurrentSyncCommittee(), prevNextSyncCommittee) - require.NotEqual(t, state.NextSyncCommittee(), prevNextSyncCommittee) + s.SetCurrentSyncCommittee(currentCommittee) + s.SetNextSyncCommittee(nextCommittee) + prevNextSyncCommittee := s.NextSyncCommittee() + s.SetSlot(8160) + require.NoError(t, statechange.ProcessSyncCommitteeUpdate(s)) + require.Equal(t, s.CurrentSyncCommittee(), prevNextSyncCommittee) + require.NotEqual(t, s.NextSyncCommittee(), prevNextSyncCommittee) } diff --git a/cl/phase1/core/transition/resets.go b/cl/transition/impl/eth2/statechange/resets.go similarity index 97% rename from cl/phase1/core/transition/resets.go rename to cl/transition/impl/eth2/statechange/resets.go index 28f470b71..8e560946c 100644 --- a/cl/phase1/core/transition/resets.go +++ b/cl/transition/impl/eth2/statechange/resets.go @@ -1,4 +1,4 @@ -package transition +package statechange import ( state2 "github.com/ledgerwatch/erigon/cl/phase1/core/state" diff --git a/cl/phase1/core/transition/test_data/block_processing/capella_block.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/block_processing/capella_block.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/block_processing/capella_block.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/block_processing/capella_block.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/block_processing/capella_state.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/block_processing/capella_state.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/block_processing/capella_state.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/block_processing/capella_state.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/effective_balances_expected.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/effective_balances_expected.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/effective_balances_expected.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/effective_balances_expected.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/effective_balances_test_state.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/effective_balances_test_state.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/effective_balances_test_state.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/effective_balances_test_state.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/eth1_data_reset_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/eth1_data_reset_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/eth1_data_reset_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/eth1_data_reset_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/eth1_data_reset_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/eth1_data_reset_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/eth1_data_reset_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/eth1_data_reset_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/historical_roots_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/historical_roots_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/historical_roots_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/historical_roots_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/historical_roots_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/historical_roots_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/historical_roots_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/historical_roots_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/inactivity_scores_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/inactivity_scores_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/inactivity_scores_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/inactivity_scores_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/inactivity_scores_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/inactivity_scores_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/inactivity_scores_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/inactivity_scores_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/justification_and_finality_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/justification_and_finality_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/justification_and_finality_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/justification_and_finality_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/justification_and_finality_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/justification_and_finality_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/justification_and_finality_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/justification_and_finality_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/participation_flag_updates_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/participation_flag_updates_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/participation_flag_updates_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/participation_flag_updates_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/participation_flag_updates_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/participation_flag_updates_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/participation_flag_updates_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/participation_flag_updates_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/randao_mixes_reset_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/randao_mixes_reset_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/randao_mixes_reset_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/randao_mixes_reset_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/randao_mixes_reset_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/randao_mixes_reset_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/randao_mixes_reset_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/randao_mixes_reset_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/registry_updates_test_expected.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/registry_updates_test_expected.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/registry_updates_test_expected.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/registry_updates_test_expected.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/registry_updates_test_state.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/registry_updates_test_state.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/registry_updates_test_state.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/registry_updates_test_state.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/rewards_penalty_test_expected.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/rewards_penalty_test_expected.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/rewards_penalty_test_expected.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/rewards_penalty_test_expected.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/rewards_penalty_test_state.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/rewards_penalty_test_state.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/rewards_penalty_test_state.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/rewards_penalty_test_state.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/slashings_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/slashings_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/slashings_reset_expected_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_reset_expected_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/slashings_reset_expected_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_reset_expected_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/slashings_reset_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_reset_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/slashings_reset_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_reset_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/test_data/epoch_processing/slashings_state_test.ssz_snappy b/cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_state_test.ssz_snappy similarity index 100% rename from cl/phase1/core/transition/test_data/epoch_processing/slashings_state_test.ssz_snappy rename to cl/transition/impl/eth2/statechange/test_data/epoch_processing/slashings_state_test.ssz_snappy diff --git a/cl/phase1/core/transition/process_blob_kzg_commitments.go b/cl/transition/impl/eth2/utils.go similarity index 63% rename from cl/phase1/core/transition/process_blob_kzg_commitments.go rename to cl/transition/impl/eth2/utils.go index 5f2840a67..cb0843c03 100644 --- a/cl/phase1/core/transition/process_blob_kzg_commitments.go +++ b/cl/transition/impl/eth2/utils.go @@ -1,12 +1,10 @@ -package transition +package eth2 import ( "encoding/binary" - "reflect" - libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon/cl/cltypes" - "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" "github.com/ledgerwatch/erigon/cl/utils" "github.com/ledgerwatch/erigon/core/types" ) @@ -58,32 +56,39 @@ func txPeekBlobVersionedHashes(txBytes []byte) []libcommon.Hash { return versionedHashes } -func VerifyKzgCommitmentsAgainstTransactions(transactions *solid.TransactionsSSZ, kzgCommitments *solid.ListSSZ[*cltypes.KZGCommitment]) (bool, error) { - allVersionedHashes := []libcommon.Hash{} - transactions.ForEach(func(tx []byte, idx, total int) bool { - if tx[0] != types.BlobTxType { - return true - } +func computeSigningRootEpoch(epoch uint64, domain []byte) (libcommon.Hash, error) { + b := make([]byte, 32) + binary.LittleEndian.PutUint64(b, epoch) + return utils.Keccak256(b, domain), nil +} - allVersionedHashes = append(allVersionedHashes, txPeekBlobVersionedHashes(tx)...) - return true - }) - - commitmentVersionedHash := []libcommon.Hash{} +// transitionSlot is called each time there is a new slot to process +func transitionSlot(s *state.BeaconState) error { + slot := s.Slot() + previousStateRoot := s.PreviousStateRoot() var err error - var versionedHash libcommon.Hash - kzgCommitments.Range(func(index int, value *cltypes.KZGCommitment, length int) bool { - versionedHash, err = kzgCommitmentToVersionedHash(value) + if previousStateRoot == (libcommon.Hash{}) { + previousStateRoot, err = s.HashSSZ() if err != nil { - return false + return err } - - commitmentVersionedHash = append(commitmentVersionedHash, versionedHash) - return true - }) - if err != nil { - return false, err } - return reflect.DeepEqual(allVersionedHashes, commitmentVersionedHash), nil + beaconConfig := s.BeaconConfig() + + s.SetStateRootAt(int(slot%beaconConfig.SlotsPerHistoricalRoot), previousStateRoot) + + latestBlockHeader := s.LatestBlockHeader() + if latestBlockHeader.Root == [32]byte{} { + latestBlockHeader.Root = previousStateRoot + s.SetLatestBlockHeader(&latestBlockHeader) + } + blockHeader := s.LatestBlockHeader() + + previousBlockRoot, err := (&blockHeader).HashSSZ() + if err != nil { + return err + } + s.SetBlockRootAt(int(slot%beaconConfig.SlotsPerHistoricalRoot), previousBlockRoot) + return nil } diff --git a/cl/transition/impl/eth2/validation.go b/cl/transition/impl/eth2/validation.go new file mode 100644 index 000000000..baa54db1e --- /dev/null +++ b/cl/transition/impl/eth2/validation.go @@ -0,0 +1,54 @@ +package eth2 + +import ( + "fmt" + "github.com/Giulio2002/bls" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/fork" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +func (I *impl) VerifyTransition(s *state.BeaconState, currentBlock *cltypes.BeaconBlock) error { + if !I.FullValidation { + return nil + } + expectedStateRoot, err := s.HashSSZ() + if err != nil { + return fmt.Errorf("unable to generate state root: %v", err) + } + if expectedStateRoot != currentBlock.StateRoot { + return fmt.Errorf("expected state root differs from received state root") + } + return nil +} + +func (I *impl) VerifyBlockSignature(s *state.BeaconState, block *cltypes.SignedBeaconBlock) error { + if !I.FullValidation { + return nil + } + valid, err := verifyBlockSignature(s, block) + if err != nil { + return fmt.Errorf("error validating block signature: %v", err) + } + if !valid { + return fmt.Errorf("block not valid") + } + return nil +} + +func verifyBlockSignature(s *state.BeaconState, block *cltypes.SignedBeaconBlock) (bool, error) { + proposer, err := s.ValidatorForValidatorIndex(int(block.Block.ProposerIndex)) + if err != nil { + return false, err + } + domain, err := s.GetDomain(s.BeaconConfig().DomainBeaconProposer, state.Epoch(s.BeaconState)) + if err != nil { + return false, err + } + sigRoot, err := fork.ComputeSigningRoot(block.Block, domain) + if err != nil { + return false, err + } + pk := proposer.PublicKey() + return bls.Verify(block.Signature[:], sigRoot[:], pk[:]) +} diff --git a/cl/transition/impl/funcmap/impl.go b/cl/transition/impl/funcmap/impl.go new file mode 100644 index 000000000..e92d926f9 --- /dev/null +++ b/cl/transition/impl/funcmap/impl.go @@ -0,0 +1,94 @@ +package funcmap + +import ( + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/cl/transition/machine" + "github.com/ledgerwatch/erigon/core/types" +) + +var _ machine.Interface = (*Impl)(nil) + +type Impl struct { + FnVerifyBlockSignature func(s *state.BeaconState, block *cltypes.SignedBeaconBlock) error + FnVerifyTransition func(s *state.BeaconState, block *cltypes.BeaconBlock) error + FnProcessSlots func(s *state.BeaconState, slot uint64) error + FnProcessBlockHeader func(s *state.BeaconState, block *cltypes.BeaconBlock) error + FnProcessWithdrawals func(s *state.BeaconState, withdrawals *solid.ListSSZ[*types.Withdrawal]) error + FnProcessExecutionPayload func(s *state.BeaconState, payload *cltypes.Eth1Block) error + FnProcessRandao func(s *state.BeaconState, randao [96]byte, proposerIndex uint64) error + FnProcessEth1Data func(state *state.BeaconState, eth1Data *cltypes.Eth1Data) error + FnProcessSyncAggregate func(s *state.BeaconState, sync *cltypes.SyncAggregate) error + FnVerifyKzgCommitmentsAgainstTransactions func(transactions *solid.TransactionsSSZ, kzgCommitments *solid.ListSSZ[*cltypes.KZGCommitment]) (bool, error) + FnProcessProposerSlashing func(s *state.BeaconState, propSlashing *cltypes.ProposerSlashing) error + FnProcessAttesterSlashing func(s *state.BeaconState, attSlashing *cltypes.AttesterSlashing) error + FnProcessAttestations func(s *state.BeaconState, attestations *solid.ListSSZ[*solid.Attestation]) error + FnProcessDeposit func(s *state.BeaconState, deposit *cltypes.Deposit) error + FnProcessVoluntaryExit func(s *state.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit) error + FnProcessBlsToExecutionChange func(state *state.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange) error +} + +func (i Impl) VerifyBlockSignature(s *state.BeaconState, block *cltypes.SignedBeaconBlock) error { + return i.FnVerifyBlockSignature(s, block) +} + +func (i Impl) VerifyTransition(s *state.BeaconState, block *cltypes.BeaconBlock) error { + return i.FnVerifyTransition(s, block) +} + +func (i Impl) ProcessBlockHeader(s *state.BeaconState, block *cltypes.BeaconBlock) error { + return i.FnProcessBlockHeader(s, block) +} + +func (i Impl) ProcessWithdrawals(s *state.BeaconState, withdrawals *solid.ListSSZ[*types.Withdrawal]) error { + return i.FnProcessWithdrawals(s, withdrawals) +} + +func (i Impl) ProcessExecutionPayload(s *state.BeaconState, payload *cltypes.Eth1Block) error { + return i.FnProcessExecutionPayload(s, payload) +} + +func (i Impl) ProcessRandao(s *state.BeaconState, randao [96]byte, proposerIndex uint64) error { + return i.FnProcessRandao(s, randao, proposerIndex) +} + +func (i Impl) ProcessEth1Data(state *state.BeaconState, eth1Data *cltypes.Eth1Data) error { + return i.FnProcessEth1Data(state, eth1Data) +} + +func (i Impl) ProcessSyncAggregate(s *state.BeaconState, sync *cltypes.SyncAggregate) error { + return i.FnProcessSyncAggregate(s, sync) +} + +func (i Impl) VerifyKzgCommitmentsAgainstTransactions(transactions *solid.TransactionsSSZ, kzgCommitments *solid.ListSSZ[*cltypes.KZGCommitment]) (bool, error) { + return i.FnVerifyKzgCommitmentsAgainstTransactions(transactions, kzgCommitments) +} + +func (i Impl) ProcessProposerSlashing(s *state.BeaconState, propSlashing *cltypes.ProposerSlashing) error { + return i.FnProcessProposerSlashing(s, propSlashing) +} + +func (i Impl) ProcessAttesterSlashing(s *state.BeaconState, attSlashing *cltypes.AttesterSlashing) error { + return i.FnProcessAttesterSlashing(s, attSlashing) +} + +func (i Impl) ProcessAttestations(s *state.BeaconState, attestations *solid.ListSSZ[*solid.Attestation]) error { + return i.FnProcessAttestations(s, attestations) +} + +func (i Impl) ProcessDeposit(s *state.BeaconState, deposit *cltypes.Deposit) error { + return i.FnProcessDeposit(s, deposit) +} + +func (i Impl) ProcessVoluntaryExit(s *state.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit) error { + return i.FnProcessVoluntaryExit(s, signedVoluntaryExit) +} + +func (i Impl) ProcessBlsToExecutionChange(state *state.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange) error { + return i.FnProcessBlsToExecutionChange(state, signedChange) +} + +func (i Impl) ProcessSlots(s *state.BeaconState, slot uint64) error { + return i.FnProcessSlots(s, slot) +} diff --git a/cl/transition/machine/block.go b/cl/transition/machine/block.go new file mode 100644 index 000000000..f3dce1fe9 --- /dev/null +++ b/cl/transition/machine/block.go @@ -0,0 +1,141 @@ +package machine + +import ( + "errors" + "fmt" + + "github.com/ledgerwatch/erigon/cl/clparams" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/metrics/methelp" +) + +// ProcessBlock processes a block with the block processor +func ProcessBlock(impl BlockProcessor, s *state.BeaconState, signedBlock *cltypes.SignedBeaconBlock) error { + block := signedBlock.Block + version := s.Version() + // Check the state version is correct. + if signedBlock.Version() != version { + return fmt.Errorf("processBlock: wrong state version for block at slot %d", block.Slot) + } + h := methelp.NewHistTimer("beacon_process_block") + // Process the block header. + if err := impl.ProcessBlockHeader(s, block); err != nil { + return fmt.Errorf("processBlock: failed to process block header: %v", err) + } + // Process execution payload if enabled. + if version >= clparams.BellatrixVersion && executionEnabled(s, block.Body.ExecutionPayload) { + if s.Version() >= clparams.CapellaVersion { + // Process withdrawals in the execution payload. + if err := impl.ProcessWithdrawals(s, block.Body.ExecutionPayload.Withdrawals); err != nil { + return fmt.Errorf("processBlock: failed to process withdrawals: %v", err) + } + } + // Process the execution payload. + if err := impl.ProcessExecutionPayload(s, block.Body.ExecutionPayload); err != nil { + return fmt.Errorf("processBlock: failed to process execution payload: %v", err) + } + } + // Process RANDAO reveal. + if err := impl.ProcessRandao(s, block.Body.RandaoReveal, block.ProposerIndex); err != nil { + return fmt.Errorf("processBlock: failed to process RANDAO reveal: %v", err) + } + // Process Eth1 data. + if err := impl.ProcessEth1Data(s, block.Body.Eth1Data); err != nil { + return fmt.Errorf("processBlock: failed to process Eth1 data: %v", err) + } + // Process block body operations. + if err := ProcessOperations(impl, s, block.Body); err != nil { + return fmt.Errorf("processBlock: failed to process block body operations: %v", err) + } + // Process sync aggregate in case of Altair version. + if version >= clparams.AltairVersion { + if err := impl.ProcessSyncAggregate(s, block.Body.SyncAggregate); err != nil { + return fmt.Errorf("processBlock: failed to process sync aggregate: %v", err) + } + } + if version >= clparams.DenebVersion { + verified, err := impl.VerifyKzgCommitmentsAgainstTransactions(block.Body.ExecutionPayload.Transactions, block.Body.BlobKzgCommitments) + if err != nil { + return fmt.Errorf("processBlock: failed to process blob kzg commitments: %w", err) + } + if !verified { + return fmt.Errorf("processBlock: failed to process blob kzg commitments: commitments are not equal") + } + } + h.PutSince() + return nil +} + +// ProcessOperations is called by ProcessBlock and prcesses the block body operations +func ProcessOperations(impl BlockOperationProcessor, s *state.BeaconState, blockBody *cltypes.BeaconBody) error { + if blockBody.Deposits.Len() != int(maximumDeposits(s)) { + return errors.New("outstanding deposits do not match maximum deposits") + } + // Process each proposer slashing + var err error + if err := solid.RangeErr[*cltypes.ProposerSlashing](blockBody.ProposerSlashings, func(index int, slashing *cltypes.ProposerSlashing, length int) error { + if err = impl.ProcessProposerSlashing(s, slashing); err != nil { + return fmt.Errorf("ProcessProposerSlashing: %s", err) + } + return nil + }); err != nil { + return err + } + + if err := solid.RangeErr[*cltypes.AttesterSlashing](blockBody.AttesterSlashings, func(index int, slashing *cltypes.AttesterSlashing, length int) error { + if err = impl.ProcessAttesterSlashing(s, slashing); err != nil { + return fmt.Errorf("ProcessAttesterSlashing: %s", err) + } + return nil + }); err != nil { + return err + } + + // Process each attestations + if err := impl.ProcessAttestations(s, blockBody.Attestations); err != nil { + return fmt.Errorf("ProcessAttestation: %s", err) + } + + // Process each deposit + if err := solid.RangeErr[*cltypes.Deposit](blockBody.Deposits, func(index int, deposit *cltypes.Deposit, length int) error { + if err = impl.ProcessDeposit(s, deposit); err != nil { + return fmt.Errorf("ProcessDeposit: %s", err) + } + return nil + }); err != nil { + return err + } + + // Process each voluntary exit. + if err := solid.RangeErr[*cltypes.SignedVoluntaryExit](blockBody.VoluntaryExits, func(index int, exit *cltypes.SignedVoluntaryExit, length int) error { + if err = impl.ProcessVoluntaryExit(s, exit); err != nil { + return fmt.Errorf("ProcessVoluntaryExit: %s", err) + } + return nil + }); err != nil { + return err + } + if s.Version() < clparams.CapellaVersion { + return nil + } + // Process each execution change. this will only have entries after the capella fork. + if err := solid.RangeErr[*cltypes.SignedBLSToExecutionChange](blockBody.ExecutionChanges, func(index int, addressChange *cltypes.SignedBLSToExecutionChange, length int) error { + if err := impl.ProcessBlsToExecutionChange(s, addressChange); err != nil { + return fmt.Errorf("ProcessBlsToExecutionChange: %s", err) + } + return nil + }); err != nil { + return err + } + return nil +} + +func maximumDeposits(s *state.BeaconState) (maxDeposits uint64) { + maxDeposits = s.Eth1Data().DepositCount - s.Eth1DepositIndex() + if maxDeposits > s.BeaconConfig().MaxDeposits { + maxDeposits = s.BeaconConfig().MaxDeposits + } + return +} diff --git a/cl/transition/machine/helpers.go b/cl/transition/machine/helpers.go new file mode 100644 index 000000000..176efaa06 --- /dev/null +++ b/cl/transition/machine/helpers.go @@ -0,0 +1,11 @@ +package machine + +import ( + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +func executionEnabled(s *state.BeaconState, payload *cltypes.Eth1Block) bool { + return (!state.IsMergeTransitionComplete(s.BeaconState) && payload.BlockHash != common.Hash{}) || state.IsMergeTransitionComplete(s.BeaconState) +} diff --git a/cl/transition/machine/machine.go b/cl/transition/machine/machine.go new file mode 100644 index 000000000..5a6f07581 --- /dev/null +++ b/cl/transition/machine/machine.go @@ -0,0 +1,48 @@ +// Package machine is the interface for eth2 state transition +package machine + +import ( + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/cltypes/solid" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" + "github.com/ledgerwatch/erigon/core/types" +) + +type Interface interface { + BlockValidator + BlockProcessor + SlotProcessor +} + +type BlockProcessor interface { + BlockHeaderProcessor + BlockOperationProcessor +} + +type BlockValidator interface { + VerifyBlockSignature(s *state.BeaconState, block *cltypes.SignedBeaconBlock) error + VerifyTransition(s *state.BeaconState, block *cltypes.BeaconBlock) error +} + +type SlotProcessor interface { + ProcessSlots(s *state.BeaconState, slot uint64) error +} + +type BlockHeaderProcessor interface { + ProcessBlockHeader(s *state.BeaconState, block *cltypes.BeaconBlock) error + ProcessWithdrawals(s *state.BeaconState, withdrawals *solid.ListSSZ[*types.Withdrawal]) error + ProcessExecutionPayload(s *state.BeaconState, payload *cltypes.Eth1Block) error + ProcessRandao(s *state.BeaconState, randao [96]byte, proposerIndex uint64) error + ProcessEth1Data(state *state.BeaconState, eth1Data *cltypes.Eth1Data) error + ProcessSyncAggregate(s *state.BeaconState, sync *cltypes.SyncAggregate) error + VerifyKzgCommitmentsAgainstTransactions(transactions *solid.TransactionsSSZ, kzgCommitments *solid.ListSSZ[*cltypes.KZGCommitment]) (bool, error) +} + +type BlockOperationProcessor interface { + ProcessProposerSlashing(s *state.BeaconState, propSlashing *cltypes.ProposerSlashing) error + ProcessAttesterSlashing(s *state.BeaconState, attSlashing *cltypes.AttesterSlashing) error + ProcessAttestations(s *state.BeaconState, attestations *solid.ListSSZ[*solid.Attestation]) error + ProcessDeposit(s *state.BeaconState, deposit *cltypes.Deposit) error + ProcessVoluntaryExit(s *state.BeaconState, signedVoluntaryExit *cltypes.SignedVoluntaryExit) error + ProcessBlsToExecutionChange(state *state.BeaconState, signedChange *cltypes.SignedBLSToExecutionChange) error +} diff --git a/cl/transition/machine/transition.go b/cl/transition/machine/transition.go new file mode 100644 index 000000000..2a0672029 --- /dev/null +++ b/cl/transition/machine/transition.go @@ -0,0 +1,32 @@ +package machine + +import ( + "github.com/ledgerwatch/erigon/cl/cltypes" + "github.com/ledgerwatch/erigon/cl/phase1/core/state" +) + +// TransitionState will call impl..ProcessSlots, then impl.VerifyBlockSignature, then ProcessBlock, then impl.VerifyTransition +func TransitionState(impl Interface, s *state.BeaconState, block *cltypes.SignedBeaconBlock) error { + currentBlock := block.Block + if err := impl.ProcessSlots(s, currentBlock.Slot); err != nil { + return err + } + + if err := impl.VerifyBlockSignature(s, block); err != nil { + return err + } + + // Transition block + if err := ProcessBlock(impl, s, block); err != nil { + return err + } + + // perform validation + if err := impl.VerifyTransition(s, currentBlock); err != nil { + return err + } + + // if validation is successful, transition + s.SetPreviousStateRoot(currentBlock.StateRoot) + return nil +} diff --git a/spectest/case.go b/spectest/case.go index 2940701ab..b541abe83 100644 --- a/spectest/case.go +++ b/spectest/case.go @@ -1,6 +1,7 @@ package spectest import ( + "github.com/ledgerwatch/erigon/cl/transition/machine" "io/fs" "os" "strings" @@ -16,6 +17,8 @@ type TestCase struct { HandlerName string SuiteName string CaseName string + + Machine machine.Interface } func (t *TestCase) Version() clparams.StateVersion { diff --git a/spectest/suite.go b/spectest/suite.go index 757fc868d..fe10c65f3 100644 --- a/spectest/suite.go +++ b/spectest/suite.go @@ -1,6 +1,7 @@ package spectest import ( + "github.com/ledgerwatch/erigon/cl/transition/machine" "io/fs" "path/filepath" "testing" @@ -9,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func RunCases(t *testing.T, app Appendix, root fs.FS) { +func RunCases(t *testing.T, app Appendix, machineImpl machine.Interface, root fs.FS) { cases, err := ReadTestCases(root) require.Nil(t, err, "reading cases") // prepare for gore..... @@ -59,6 +60,7 @@ func RunCases(t *testing.T, app Appendix, root fs.FS) { value.SuiteName, value.CaseName, )) + value.Machine = machineImpl require.NoError(t, err) err = handler.Run(t, subfs, value) require.NoError(t, err)