mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-01-06 22:23:56 -05:00
**What type of PR is this?** Feature **What does this PR do? Why is it needed?** Adds data column support to backfill. **Acknowledgements** - [x] I have read [CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md). - [x] I have included a uniquely named [changelog fragment file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd). - [x] I have added a description to this PR with sufficient context for reviewers to understand this PR. --------- Co-authored-by: Kasey <kasey@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Preston Van Loon <preston@pvl.dev>
176 lines
4.8 KiB
Go
176 lines
4.8 KiB
Go
package backfill
|
|
|
|
import (
|
|
"io"
|
|
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
|
|
"github.com/OffchainLabs/prysm/v7/beacon-chain/das"
|
|
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
|
|
"github.com/libp2p/go-libp2p/core/peer"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type columnBisector struct {
|
|
rootKeys map[[32]byte]rootKey
|
|
columnSource map[rootKey]map[uint64]peer.ID
|
|
bisected map[peer.ID][]blocks.RODataColumn
|
|
pidIter []peer.ID
|
|
current int
|
|
next int
|
|
downscore peerDownscorer
|
|
errs []error
|
|
failures map[rootKey]peerdas.ColumnIndices
|
|
}
|
|
|
|
type rootKey *[32]byte
|
|
|
|
var errColumnVerification = errors.New("column verification failed")
|
|
var errBisectInconsistent = errors.New("state of bisector inconsistent with columns to bisect")
|
|
|
|
func (c *columnBisector) addPeerColumns(pid peer.ID, columns ...blocks.RODataColumn) {
|
|
for _, col := range columns {
|
|
c.setColumnSource(c.rootKey(col.BlockRoot()), col.Index, pid)
|
|
}
|
|
}
|
|
|
|
// failuresFor returns the set of column indices that failed verification
|
|
// for the given block root.
|
|
func (c *columnBisector) failuresFor(root [32]byte) peerdas.ColumnIndices {
|
|
return c.failures[c.rootKey(root)]
|
|
}
|
|
|
|
func (c *columnBisector) failingRoots() [][32]byte {
|
|
roots := make([][32]byte, 0, len(c.failures))
|
|
for rk := range c.failures {
|
|
roots = append(roots, *rk)
|
|
}
|
|
return roots
|
|
}
|
|
|
|
func (c *columnBisector) setColumnSource(rk rootKey, idx uint64, pid peer.ID) {
|
|
if c.columnSource == nil {
|
|
c.columnSource = make(map[rootKey]map[uint64]peer.ID)
|
|
}
|
|
if c.columnSource[rk] == nil {
|
|
c.columnSource[rk] = make(map[uint64]peer.ID)
|
|
}
|
|
c.columnSource[rk][idx] = pid
|
|
}
|
|
|
|
func (c *columnBisector) clearColumnSource(rk rootKey, idx uint64) {
|
|
if c.columnSource == nil {
|
|
return
|
|
}
|
|
if c.columnSource[rk] == nil {
|
|
return
|
|
}
|
|
delete(c.columnSource[rk], idx)
|
|
if len(c.columnSource[rk]) == 0 {
|
|
delete(c.columnSource, rk)
|
|
}
|
|
}
|
|
|
|
func (c *columnBisector) rootKey(root [32]byte) rootKey {
|
|
ptr, ok := c.rootKeys[root]
|
|
if ok {
|
|
return ptr
|
|
}
|
|
c.rootKeys[root] = &root
|
|
return c.rootKeys[root]
|
|
}
|
|
|
|
func (c *columnBisector) peerFor(col blocks.RODataColumn) (peer.ID, error) {
|
|
r := c.columnSource[c.rootKey(col.BlockRoot())]
|
|
if len(r) == 0 {
|
|
return "", errors.Wrap(errBisectInconsistent, "root not tracked")
|
|
}
|
|
if pid, ok := r[col.Index]; ok {
|
|
return pid, nil
|
|
}
|
|
return "", errors.Wrap(errBisectInconsistent, "index not tracked for root")
|
|
}
|
|
|
|
// reset prepares the columnBisector to be used to retry failed columns.
|
|
// it resets the peer sources of the failed columns and clears the failure records.
|
|
func (c *columnBisector) reset() {
|
|
// reset all column sources for failed columns
|
|
for rk, indices := range c.failures {
|
|
for _, idx := range indices.ToSlice() {
|
|
c.clearColumnSource(rk, idx)
|
|
}
|
|
}
|
|
c.failures = make(map[rootKey]peerdas.ColumnIndices)
|
|
c.errs = nil
|
|
}
|
|
|
|
// Bisect initializes columnBisector with the set of columns to bisect.
|
|
func (c *columnBisector) Bisect(columns []blocks.RODataColumn) (das.BisectionIterator, error) {
|
|
for _, col := range columns {
|
|
pid, err := c.peerFor(col)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "could not lookup peer for column")
|
|
}
|
|
c.bisected[pid] = append(c.bisected[pid], col)
|
|
}
|
|
c.pidIter = make([]peer.ID, 0, len(c.bisected))
|
|
for pid := range c.bisected {
|
|
c.pidIter = append(c.pidIter, pid)
|
|
}
|
|
// The implementation of Next() assumes these are equal in
|
|
// the base case.
|
|
c.current, c.next = 0, 0
|
|
return c, nil
|
|
}
|
|
|
|
// Next implements an iterator for the columnBisector.
|
|
// Each batch is from a single peer.
|
|
func (c *columnBisector) Next() ([]blocks.RODataColumn, error) {
|
|
if c.next >= len(c.pidIter) {
|
|
return nil, io.EOF
|
|
}
|
|
c.current = c.next
|
|
pid := c.pidIter[c.current]
|
|
cols := c.bisected[pid]
|
|
c.next += 1
|
|
return cols, nil
|
|
}
|
|
|
|
// Error implements das.Bisector.
|
|
func (c *columnBisector) Error() error {
|
|
if len(c.errs) > 0 {
|
|
return errColumnVerification
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// OnError implements das.Bisector.
|
|
func (c *columnBisector) OnError(err error) {
|
|
c.errs = append(c.errs, err)
|
|
pid := c.pidIter[c.current]
|
|
c.downscore(pid, "column verification error", err)
|
|
|
|
// Track which roots failed by examining columns from the current peer
|
|
columns := c.bisected[pid]
|
|
for _, col := range columns {
|
|
root := col.BlockRoot()
|
|
rk := c.rootKey(root)
|
|
if c.failures[rk] == nil {
|
|
c.failures[rk] = make(peerdas.ColumnIndices)
|
|
}
|
|
c.failures[rk][col.Index] = struct{}{}
|
|
}
|
|
}
|
|
|
|
var _ das.Bisector = &columnBisector{}
|
|
var _ das.BisectionIterator = &columnBisector{}
|
|
|
|
func newColumnBisector(downscorer peerDownscorer) *columnBisector {
|
|
return &columnBisector{
|
|
rootKeys: make(map[[32]byte]rootKey),
|
|
columnSource: make(map[rootKey]map[uint64]peer.ID),
|
|
bisected: make(map[peer.ID][]blocks.RODataColumn),
|
|
failures: make(map[rootKey]peerdas.ColumnIndices),
|
|
downscore: downscorer,
|
|
}
|
|
}
|