Files
prysm/beacon-chain/sync/backfill/verify_column.go
kasey 61de11e2c4 Backfill data columns (#15580)
**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>
2025-12-02 15:19:32 +00:00

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,
}
}