Files
prysm/beacon-chain/verification/initializer.go
Potuz 6735c921f8 Dependent root instead of target (#15996)
* Add DepdenentRootForEpoch forkchoice helper

* Use dependent root in helpers to get head
2025-11-08 01:18:44 +00:00

160 lines
5.9 KiB
Go

package verification
import (
"context"
"sync"
"github.com/OffchainLabs/prysm/v7/beacon-chain/blockchain/kzg"
"github.com/OffchainLabs/prysm/v7/beacon-chain/core/peerdas"
forkchoicetypes "github.com/OffchainLabs/prysm/v7/beacon-chain/forkchoice/types"
"github.com/OffchainLabs/prysm/v7/beacon-chain/startup"
"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
fieldparams "github.com/OffchainLabs/prysm/v7/config/fieldparams"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/consensus-types/blocks"
"github.com/OffchainLabs/prysm/v7/consensus-types/primitives"
ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1"
"golang.org/x/sync/singleflight"
)
// Forkchoicer represents the forkchoice methods that the verifiers need.
// Note that forkchoice is used here in a lock-free fashion, assuming that a version of forkchoice
// is given that internally handles the details of locking the underlying store.
type Forkchoicer interface {
FinalizedCheckpoint() *forkchoicetypes.Checkpoint
HasNode([32]byte) bool
IsCanonical(root [32]byte) bool
Slot([32]byte) (primitives.Slot, error)
DependentRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
}
// StateByRooter describes a stategen-ish type that can produce arbitrary states by their root
type StateByRooter interface {
StateByRoot(ctx context.Context, blockRoot [32]byte) (state.BeaconState, error)
}
// HeadStateProvider describes a type that can provide access to the current head state and related methods.
// This interface matches blockchain.HeadFetcher but is defined here to avoid import cycles
// (blockchain package imports verification package).
type HeadStateProvider interface {
HeadRoot(ctx context.Context) ([]byte, error)
HeadSlot() primitives.Slot
HeadState(ctx context.Context) (state.BeaconState, error)
HeadStateReadOnly(ctx context.Context) (state.ReadOnlyBeaconState, error)
}
// sharedResources provides access to resources that are required by different verification types.
// for example, sidecar verification and block verification share the block signature verification cache.
type sharedResources struct {
clock *startup.Clock
fc Forkchoicer
sc signatureCache
pc proposerCache
sr StateByRooter
hsp HeadStateProvider
ic *inclusionProofCache
sg singleflight.Group
}
// Initializer is used to create different Verifiers.
// Verifiers require access to stateful data structures, like caches,
// and it is Initializer's job to provide access to those.
type Initializer struct {
shared *sharedResources
}
// NewBlobVerifier creates a BlobVerifier for a single blob, with the given set of requirements.
func (ini *Initializer) NewBlobVerifier(b blocks.ROBlob, reqs []Requirement) *ROBlobVerifier {
return &ROBlobVerifier{
sharedResources: ini.shared,
blob: b,
results: newResults(reqs...),
verifyBlobCommitment: kzg.Verify,
}
}
// NewDataColumnsVerifier creates a DataColumnVerifier for a slice of data columns, with the given set of requirements.
// WARNING: The returned verifier is not thread-safe, and should not be used concurrently.
func (ini *Initializer) NewDataColumnsVerifier(roDataColumns []blocks.RODataColumn, reqs []Requirement) *RODataColumnsVerifier {
return &RODataColumnsVerifier{
sharedResources: ini.shared,
dataColumns: roDataColumns,
results: newResults(reqs...),
verifyDataColumnsCommitment: peerdas.VerifyDataColumnsSidecarKZGProofs,
stateByRoot: make(map[[fieldparams.RootLength]byte]state.BeaconState),
}
}
// InitializerWaiter provides an Initializer once all dependent resources are ready
// via the WaitForInitializer method.
type InitializerWaiter struct {
sync.RWMutex
ready bool
cw startup.ClockWaiter
ini *Initializer
getFork forkLookup
}
type forkLookup func(targetEpoch primitives.Epoch) (*ethpb.Fork, error)
type InitializerOption func(waiter *InitializerWaiter)
// WithForkLookup allows tests to modify how Fork consensus type lookup works. Needed for spectests with weird Forks.
func WithForkLookup(fl forkLookup) InitializerOption {
return func(iw *InitializerWaiter) {
iw.getFork = fl
}
}
// NewInitializerWaiter creates an InitializerWaiter which can be used to obtain an Initializer once async dependencies are ready.
func NewInitializerWaiter(cw startup.ClockWaiter, fc Forkchoicer, sr StateByRooter, hsp HeadStateProvider, opts ...InitializerOption) *InitializerWaiter {
pc := newPropCache()
// signature cache is initialized in WaitForInitializer, since we need the genesis validators root, which can be obtained from startup.Clock.
shared := &sharedResources{
fc: fc,
pc: pc,
sr: sr,
hsp: hsp,
ic: newInclusionProofCache(defaultInclusionProofCacheSize),
}
iw := &InitializerWaiter{cw: cw, ini: &Initializer{shared: shared}}
for _, o := range opts {
o(iw)
}
if iw.getFork == nil {
iw.getFork = params.Fork
}
return iw
}
// WaitForInitializer ensures that asynchronous initialization of the shared resources the initializer
// depends on has completed before the underlying Initializer is accessible by client code.
func (w *InitializerWaiter) WaitForInitializer(ctx context.Context) (*Initializer, error) {
if err := w.waitForReady(ctx); err != nil {
return nil, err
}
// We wait until this point to initialize the signature cache because here we have access to the genesis validator root.
vr := w.ini.shared.clock.GenesisValidatorsRoot()
sc := newSigCache(vr[:], defaultSignatureCacheSize, w.getFork)
w.ini.shared.sc = sc
w.ini.shared.ic = newInclusionProofCache(defaultInclusionProofCacheSize)
return w.ini, nil
}
func (w *InitializerWaiter) waitForReady(ctx context.Context) error {
w.Lock()
defer w.Unlock()
if w.ready {
return nil
}
clock, err := w.cw.WaitForClock(ctx)
if err != nil {
return err
}
w.ini.shared.clock = clock
w.ready = true
return nil
}