Skip to content

Implement EIP7805: Fork-choice enforced Inclusion Lists #14754

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions api/server/structs/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,66 @@ func SignedBLSChangesFromConsensus(src []*eth.SignedBLSToExecutionChange) []*Sig
return changes
}

func SignedInclusionListFromConsensus(src *eth.SignedInclusionList) *SignedInclusionList {
transactions := make([]string, len(src.Message.Transactions))
for i, transaction := range src.Message.Transactions {
transactions[i] = hexutil.Encode(transaction)
}

return &SignedInclusionList{
Message: &InclusionList{
Slot: fmt.Sprintf("%d", src.Message.Slot),
ValidatorIndex: fmt.Sprintf("%d", src.Message.ValidatorIndex),
InclusionListCommitteeRoot: hexutil.Encode(src.Message.InclusionListCommitteeRoot),
Transactions: transactions,
},
Signature: hexutil.Encode(src.Signature),
}
}

func (s *SignedInclusionList) ToConsensus() (*eth.SignedInclusionList, error) {
message, err := s.Message.ToConsensus()
if err != nil {
return nil, server.NewDecodeError(err, "Message")
}
signature, err := bytesutil.DecodeHexWithLength(s.Signature, fieldparams.BLSSignatureLength)
if err != nil {
return nil, server.NewDecodeError(err, "Signature")
}
return &eth.SignedInclusionList{
Message: message,
Signature: signature,
}, nil
}

func (s *InclusionList) ToConsensus() (*eth.InclusionList, error) {
slot, err := strconv.ParseUint(s.Slot, 10, 64)
if err != nil {
return nil, server.NewDecodeError(err, "Slot")
}
validatorIndex, err := strconv.ParseUint(s.ValidatorIndex, 10, 64)
if err != nil {
return nil, server.NewDecodeError(err, "ValidatorIndex")
}
inclusionListCommitteeRoot, err := bytesutil.DecodeHexWithLength(s.InclusionListCommitteeRoot, fieldparams.RootLength)
if err != nil {
return nil, server.NewDecodeError(err, "InclusionListCommitteeRoot")
}
transactions := make([][]byte, len(s.Transactions))
for i, transaction := range s.Transactions {
transactions[i], err = bytesutil.DecodeHexWithMaxLength(transaction, fieldparams.MaxBytesPerTxLength)
if err != nil {
return nil, server.NewDecodeError(err, fmt.Sprintf("Transactions[%d]", i))
}
}
return &eth.InclusionList{
Slot: primitives.Slot(slot),
ValidatorIndex: primitives.ValidatorIndex(validatorIndex),
InclusionListCommitteeRoot: inclusionListCommitteeRoot,
Transactions: transactions,
}, nil
}

func (s *Fork) ToConsensus() (*eth.Fork, error) {
previousVersion, err := bytesutil.DecodeHexWithLength(s.PreviousVersion, 4)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions api/server/structs/endpoints_events.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ type BlobSidecarEvent struct {
VersionedHash string `json:"versioned_hash"`
}

type InclusionListEvent struct {
Version string `json:"version"`
Data *SignedInclusionList `json:"data"`
}

type LightClientFinalityUpdateEvent struct {
Version string `json:"version"`
Data *LightClientFinalityUpdate `json:"data"`
Expand Down
12 changes: 12 additions & 0 deletions api/server/structs/other.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,3 +262,15 @@ type PendingConsolidation struct {
SourceIndex string `json:"source_index"`
TargetIndex string `json:"target_index"`
}

type SignedInclusionList struct {
Message *InclusionList `json:"message"`
Signature string `json:"signature"`
}

type InclusionList struct {
Slot string `json:"slot"`
ValidatorIndex string `json:"validator_index"`
InclusionListCommitteeRoot string `json:"inclusion_list_committee_root"`
Transactions []string `json:"transactions"`
}
1 change: 1 addition & 0 deletions beacon-chain/blockchain/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
"forkchoice_update_execution.go",
"head.go",
"head_sync_committee_info.go",
"inclusion_list.go",
"init_sync_process_block.go",
"log.go",
"merge_ascii_art.go",
Expand Down
1 change: 1 addition & 0 deletions beacon-chain/blockchain/chain_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type ForkchoiceFetcher interface {
Ancestor(context.Context, []byte, primitives.Slot) ([]byte, error)
CachedHeadRoot() [32]byte
GetProposerHead() [32]byte
GetAttesterHead() [32]byte
SetForkChoiceGenesisTime(uint64)
UpdateHead(context.Context, primitives.Slot)
HighestReceivedBlockSlot() primitives.Slot
Expand Down
7 changes: 7 additions & 0 deletions beacon-chain/blockchain/chain_info_forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ func (s *Service) GetProposerHead() [32]byte {
return s.cfg.ForkChoiceStore.GetProposerHead()
}

// GetAttesterHead returns the corresponding value from forkchoice
func (s *Service) GetAttesterHead() [32]byte {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.GetAttesterHead()
}

// SetForkChoiceGenesisTime sets the genesis time in Forkchoice
func (s *Service) SetForkChoiceGenesisTime(timestamp uint64) {
s.cfg.ForkChoiceStore.Lock()
Expand Down
15 changes: 14 additions & 1 deletion beacon-chain/blockchain/execution_engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,12 +267,25 @@ func (s *Service) notifyNewPayload(ctx context.Context, preStateVersion int,
return false, errors.New("nil execution requests")
}
}
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests)

var txs [][]byte
// Post-FOCIL, only consider the inclusion list constraint if it matches the current slot.
if slots.ToEpoch(s.CurrentSlot()) >= params.BeaconConfig().Eip7805ForkEpoch && s.CurrentSlot() == blk.Block().Slot() {
txs = s.inclusionListCache.Get(blk.Block().Slot() - 1)
}
lastValidHash, err = s.cfg.ExecutionEngineCaller.NewPayload(ctx, payload, versionedHashes, parentRoot, requests, txs)

switch {
case err == nil:
newPayloadValidNodeCount.Inc()
return true, nil
case errors.Is(err, execution.ErrBadInclusionListPayloadStatus):
log.WithFields(logrus.Fields{
"slot": blk.Block().Slot(),
"parentRoot": fmt.Sprintf("%#x", parentRoot),
}).Info("Called new payload but inclusion list didn't satisfy")
blk.Block().MarkInclusionListNotSatisfied() // Cache the block root that fails to satisfy the inclusion list constraint.
return true, nil
case errors.Is(err, execution.ErrAcceptedSyncingPayloadStatus):
newPayloadOptimisticNodeCount.Inc()
log.WithFields(logrus.Fields{
Expand Down
72 changes: 72 additions & 0 deletions beacon-chain/blockchain/inclusion_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package blockchain

import (
"context"
"fmt"
"time"

"github.com/OffchainLabs/prysm/v6/config/params"
"github.com/OffchainLabs/prysm/v6/time/slots"
"github.com/sirupsen/logrus"
)

const updateInclusionListBlockInterval = time.Second

// Routine that updates block building with inclusion lists one second before the slot starts.
func (s *Service) updateBlockWithInclusionListRoutine() {
if err := s.waitForSync(); err != nil {
log.WithError(err).Error("Failed to wait for initial sync")
return
}

interval := time.Second*time.Duration(params.BeaconConfig().SecondsPerSlot) - updateInclusionListBlockInterval
ticker := slots.NewSlotTickerWithIntervals(s.genesisTime, []time.Duration{interval})

for {
select {
case <-s.ctx.Done():
return
case <-ticker.C():
s.updateBlockWithInclusionList(context.Background())
}
}
}

// Updates block building with inclusion lists, the current payload ID, and the new upload ID.
func (s *Service) updateBlockWithInclusionList(ctx context.Context) {
currentSlot := s.CurrentSlot()

// Skip update if not in or past the FOCIL fork epoch.
if slots.ToEpoch(currentSlot) < params.BeaconConfig().Eip7805ForkEpoch {
return
}

s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()

headRoot := s.headRoot()
id, found := s.cfg.PayloadIDCache.PayloadID(currentSlot+1, headRoot)
if !found {
return
}

txs := s.inclusionListCache.Get(currentSlot)
if len(txs) == 0 {
log.WithField("slot", currentSlot).Warn("No inclusion list transactions found to update block")
return
}

newID, err := s.cfg.ExecutionEngineCaller.UpdatePayloadWithInclusionList(ctx, id, txs)
if err != nil {
log.WithError(err).Error("Failed to update block with inclusion list")
return
}

log.WithFields(logrus.Fields{
"slot": currentSlot,
"headRoot": fmt.Sprintf("%x", headRoot),
"txs": len(txs),
}).Info("Updated block with inclusion list")

s.cfg.PayloadIDCache.Set(currentSlot+1, headRoot, *newID)
}
7 changes: 7 additions & 0 deletions beacon-chain/blockchain/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,10 @@ func WithLightClientStore(lcs *lightclient.Store) Option {
return nil
}
}

func WithInclusionListCache(c *cache.InclusionLists) Option {
return func(s *Service) error {
s.inclusionListCache = c
return nil
}
}
8 changes: 8 additions & 0 deletions beacon-chain/blockchain/receive_attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ func (s *Service) spawnProcessAttestationsRoutine() {
s.cfg.ForkChoiceStore.Unlock()

s.UpdateHead(s.ctx, slotInterval.Slot)

// Prune inclusion list that's more than 1 epoch old.
// Mean at the second 0 of slot 100, we prune the inclusion list of slot 98.
cachedSlot := primitives.Slot(0)
if slotInterval.Slot > 2 {
cachedSlot = slotInterval.Slot - 2
}
s.inclusionListCache.Delete(cachedSlot)
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/blockchain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type Service struct {
dataColumnStorage *filesystem.DataColumnStorage
slasherEnabled bool
lcStore *lightClient.Store
inclusionListCache *cache.InclusionLists
}

// config options for the service.
Expand Down Expand Up @@ -219,6 +220,7 @@ func (s *Service) Start() {
}
s.spawnProcessAttestationsRoutine()
go s.runLateBlockTasks()
go s.updateBlockWithInclusionListRoutine()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes it better to have a routine here instead of updating a payload when a validator has a proposer role? Easier implementation?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The routine is a no op unless the validator has a proposer role regardless

}

// Stop the blockchain service's main event loop and associated goroutines.
Expand Down
10 changes: 10 additions & 0 deletions beacon-chain/blockchain/testing/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,16 @@ func (s *ChainService) GetProposerHead() [32]byte {
return [32]byte{}
}

// GetAttesterHead mocks the same method in the chain service
func (s *ChainService) GetAttesterHead() [32]byte {
if s.ForkChoiceStore != nil {
return s.ForkChoiceStore.GetAttesterHead()
}
var rootArr [32]byte
copy(rootArr[:], s.Root)
return rootArr
}

// SetForkChoiceGenesisTime mocks the same method in the chain service
func (s *ChainService) SetForkChoiceGenesisTime(timestamp uint64) {
if s.ForkChoiceStore != nil {
Expand Down
2 changes: 2 additions & 0 deletions beacon-chain/cache/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"common.go",
"doc.go",
"error.go",
"inclusion_list.go",
"interfaces.go",
"payload_id.go",
"proposer_indices.go",
Expand Down Expand Up @@ -75,6 +76,7 @@ go_test(
"checkpoint_state_test.go",
"committee_fuzz_test.go",
"committee_test.go",
"inclusion_list_test.go",
"payload_id_test.go",
"private_access_test.go",
"proposer_indices_test.go",
Expand Down
Loading
Loading