-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Implement EIP7805: Fork-choice enforced Inclusion Lists #14754
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package blockchain | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/prysmaticlabs/prysm/v5/config/params" | ||
"github.com/prysmaticlabs/prysm/v5/time/slots" | ||
) | ||
|
||
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().FocilForkEpoch { | ||
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) | ||
newID, err := s.cfg.ExecutionEngineCaller.UpdatePayloadWithInclusionList(ctx, id, txs) | ||
if err != nil { | ||
log.WithError(err).Error("Failed to update block with inclusion list") | ||
return | ||
} | ||
|
||
s.cfg.PayloadIDCache.Set(currentSlot+1, headRoot, *newID) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,24 +47,26 @@ import ( | |
// Service represents a service that handles the internal | ||
// logic of managing the full PoS beacon chain. | ||
type Service struct { | ||
cfg *config | ||
ctx context.Context | ||
cancel context.CancelFunc | ||
genesisTime time.Time | ||
head *head | ||
headLock sync.RWMutex | ||
originBlockRoot [32]byte // genesis root, or weak subjectivity checkpoint root, depending on how the node is initialized | ||
boundaryRoots [][32]byte | ||
checkpointStateCache *cache.CheckpointStateCache | ||
initSyncBlocks map[[32]byte]interfaces.ReadOnlySignedBeaconBlock | ||
initSyncBlocksLock sync.RWMutex | ||
wsVerifier *WeakSubjectivityVerifier | ||
clockSetter startup.ClockSetter | ||
clockWaiter startup.ClockWaiter | ||
syncComplete chan struct{} | ||
blobNotifiers *blobNotifierMap | ||
blockBeingSynced *currentlySyncingBlock | ||
blobStorage *filesystem.BlobStorage | ||
cfg *config | ||
ctx context.Context | ||
cancel context.CancelFunc | ||
genesisTime time.Time | ||
head *head | ||
headLock sync.RWMutex | ||
originBlockRoot [32]byte // genesis root, or weak subjectivity checkpoint root, depending on how the node is initialized | ||
boundaryRoots [][32]byte | ||
checkpointStateCache *cache.CheckpointStateCache | ||
initSyncBlocks map[[32]byte]interfaces.ReadOnlySignedBeaconBlock | ||
initSyncBlocksLock sync.RWMutex | ||
wsVerifier *WeakSubjectivityVerifier | ||
clockSetter startup.ClockSetter | ||
clockWaiter startup.ClockWaiter | ||
syncComplete chan struct{} | ||
blobNotifiers *blobNotifierMap | ||
blockBeingSynced *currentlySyncingBlock | ||
blobStorage *filesystem.BlobStorage | ||
inclusionListCache *cache.InclusionLists | ||
badInclusionListBlock [32]byte | ||
} | ||
|
||
// config options for the service. | ||
|
@@ -215,6 +217,7 @@ func (s *Service) Start() { | |
} | ||
s.spawnProcessAttestationsRoutine() | ||
go s.runLateBlockTasks() | ||
go s.updateBlockWithInclusionListRoutine() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package cache | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have unit tests? |
||
|
||
import ( | ||
"crypto/sha256" | ||
"sync" | ||
|
||
"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" | ||
) | ||
|
||
type InclusionLists struct { | ||
mu sync.RWMutex | ||
ils map[primitives.Slot]map[primitives.ValidatorIndex]struct { | ||
txs [][]byte | ||
seenTwice bool | ||
} | ||
} | ||
|
||
// NewInclusionLists initializes a new InclusionLists instance. | ||
func NewInclusionLists() *InclusionLists { | ||
return &InclusionLists{ | ||
ils: make(map[primitives.Slot]map[primitives.ValidatorIndex]struct { | ||
txs [][]byte | ||
seenTwice bool | ||
}), | ||
} | ||
} | ||
|
||
// Add adds a set of transactions for a specific slot and validator index. | ||
func (i *InclusionLists) Add(slot primitives.Slot, validatorIndex primitives.ValidatorIndex, txs [][]byte) { | ||
i.mu.Lock() | ||
defer i.mu.Unlock() | ||
|
||
if _, ok := i.ils[slot]; !ok { | ||
i.ils[slot] = make(map[primitives.ValidatorIndex]struct { | ||
txs [][]byte | ||
seenTwice bool | ||
}) | ||
} | ||
|
||
entry := i.ils[slot][validatorIndex] | ||
if entry.seenTwice { | ||
return // No need to modify if already marked as seen twice. | ||
} | ||
|
||
if entry.txs == nil { | ||
entry.txs = txs | ||
} else { | ||
entry.seenTwice = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any chance the same IL received from different peer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Libp2p covers such case, it filters by content so we should not be importing same IL from different peer |
||
entry.txs = nil // Clear transactions to save space if seen twice. | ||
} | ||
i.ils[slot][validatorIndex] = entry | ||
} | ||
|
||
// Get retrieves unique transactions for a specific slot. | ||
func (i *InclusionLists) Get(slot primitives.Slot) [][]byte { | ||
i.mu.RLock() | ||
defer i.mu.RUnlock() | ||
|
||
ils, exists := i.ils[slot] | ||
if !exists { | ||
return nil | ||
} | ||
|
||
var uniqueTxs [][]byte | ||
seen := make(map[[32]byte]struct{}) | ||
for _, entry := range ils { | ||
for _, tx := range entry.txs { | ||
hash := sha256.Sum256(tx) | ||
if _, duplicate := seen[hash]; !duplicate { | ||
uniqueTxs = append(uniqueTxs, tx) | ||
seen[hash] = struct{}{} | ||
} | ||
} | ||
} | ||
return uniqueTxs | ||
} | ||
|
||
// Delete removes all inclusion lists for a specific slot. | ||
func (i *InclusionLists) Delete(slot primitives.Slot) { | ||
i.mu.Lock() | ||
defer i.mu.Unlock() | ||
|
||
delete(i.ils, slot) | ||
} | ||
|
||
// SeenTwice checks if a validator's transactions were marked as seen twice for a specific slot. | ||
func (i *InclusionLists) SeenTwice(slot primitives.Slot, idx primitives.ValidatorIndex) bool { | ||
i.mu.RLock() | ||
defer i.mu.RUnlock() | ||
|
||
ils, exists := i.ils[slot] | ||
if !exists { | ||
return false | ||
} | ||
|
||
entry, exists := ils[idx] | ||
return exists && entry.seenTwice | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it sufficient to cache only the latest
badInclusionListBlock
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so