Skip to content
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 cert verifier address provider #1368

Merged
merged 16 commits into from
Mar 7, 2025
16 changes: 12 additions & 4 deletions api/clients/v2/cert_verifier_address_provider.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package clients

import (
"context"

"github.com/ethereum/go-ethereum/common"
)

// CertVerifierAddressProvider defines an object which can translate block number to cert verifier address
//
// This provider uses block number as a key, since updates to a cert verifier address in a running system are
// coordinated by defining the block number at which a new cert verifier address takes effect.
// This provider uses reference block number as a key, since updates to a cert verifier address in a running system are
// coordinated by defining the reference block number at which a new cert verifier address takes effect. Specifically,
// a blob shall be verified by the latest defined cert verifier contract with a reference block number key that doesn't
// exceed the reference block number of the blob's batch.
type CertVerifierAddressProvider interface {
// GetCertVerifierAddress returns the EigenDACertVerifierAddress that is active at the input block number
GetCertVerifierAddress(blockNumber uint64) (string, error)
// GetCertVerifierAddress returns the EigenDACertVerifierAddress that is active at the input reference block number
GetCertVerifierAddress(ctx context.Context, referenceBlockNumber uint64) (common.Address, error)
}
6 changes: 3 additions & 3 deletions api/clients/v2/payloaddispersal/payload_disperser_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ type PayloadDisperserConfig struct {
ContractCallTimeout time.Duration
}

// GetDefaultPayloadDisperserConfig creates a PayloadDisperserConfig with default values
func GetDefaultPayloadDisperserConfig() *PayloadDisperserConfig {
// getDefaultPayloadDisperserConfig creates a PayloadDisperserConfig with default values
func getDefaultPayloadDisperserConfig() *PayloadDisperserConfig {
return &PayloadDisperserConfig{
PayloadClientConfig: *clients.GetDefaultPayloadClientConfig(),
DisperseBlobTimeout: 2 * time.Minute,
Expand All @@ -41,7 +41,7 @@ func GetDefaultPayloadDisperserConfig() *PayloadDisperserConfig {
// checkAndSetDefaults checks an existing config struct. If a given field is 0, and 0 is not an acceptable value, then
// this method sets it to the default.
func (dc *PayloadDisperserConfig) checkAndSetDefaults() error {
defaultConfig := GetDefaultPayloadDisperserConfig()
defaultConfig := getDefaultPayloadDisperserConfig()

if dc.DisperseBlobTimeout == 0 {
dc.DisperseBlobTimeout = defaultConfig.DisperseBlobTimeout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ type RelayPayloadRetrieverConfig struct {
RelayTimeout time.Duration
}

// GetDefaultRelayPayloadRetrieverConfig creates a RelayPayloadRetrieverConfig with default values
func GetDefaultRelayPayloadRetrieverConfig() *RelayPayloadRetrieverConfig {
// getDefaultRelayPayloadRetrieverConfig creates a RelayPayloadRetrieverConfig with default values
func getDefaultRelayPayloadRetrieverConfig() *RelayPayloadRetrieverConfig {
return &RelayPayloadRetrieverConfig{
PayloadClientConfig: *clients.GetDefaultPayloadClientConfig(),
RelayTimeout: 5 * time.Second,
Expand All @@ -26,7 +26,7 @@ func GetDefaultRelayPayloadRetrieverConfig() *RelayPayloadRetrieverConfig {
// checkAndSetDefaults checks an existing config struct. If a given field is 0, and 0 is not an acceptable value, then
// this method sets it to the default.
func (rc *RelayPayloadRetrieverConfig) checkAndSetDefaults() error {
defaultConfig := GetDefaultRelayPayloadRetrieverConfig()
defaultConfig := getDefaultRelayPayloadRetrieverConfig()
if rc.RelayTimeout == 0 {
rc.RelayTimeout = defaultConfig.RelayTimeout
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type ValidatorPayloadRetrieverConfig struct {
RetrievalTimeout time.Duration
}

// GetDefaultValidatorPayloadRetrieverConfig creates a ValidatorPayloadRetrieverConfig with default values
func GetDefaultValidatorPayloadRetrieverConfig() *ValidatorPayloadRetrieverConfig {
// getDefaultValidatorPayloadRetrieverConfig creates a ValidatorPayloadRetrieverConfig with default values
func getDefaultValidatorPayloadRetrieverConfig() *ValidatorPayloadRetrieverConfig {
return &ValidatorPayloadRetrieverConfig{
PayloadClientConfig: *clients.GetDefaultPayloadClientConfig(),
RetrievalTimeout: 30 * time.Second,
Expand All @@ -27,7 +27,7 @@ func GetDefaultValidatorPayloadRetrieverConfig() *ValidatorPayloadRetrieverConfi
// checkAndSetDefaults checks an existing config struct. If a given field is 0, and 0 is not an acceptable value, then
// this method sets it to the default.
func (rc *ValidatorPayloadRetrieverConfig) checkAndSetDefaults() error {
defaultConfig := GetDefaultValidatorPayloadRetrieverConfig()
defaultConfig := getDefaultValidatorPayloadRetrieverConfig()
if rc.RetrievalTimeout == 0 {
rc.RetrievalTimeout = defaultConfig.RetrievalTimeout
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import (
"github.com/Layr-Labs/eigensdk-go/logging"
)

// BlockNumberProvider is a utility for interacting with the ethereum block number
type BlockNumberProvider struct {
// BlockNumberMonitor is a utility for waiting for a certain ethereum block number
//
// TODO: this utility is not currently in use, but DO NOT delete it. It will be necessary for the upcoming
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// TODO: this utility is not currently in use, but DO NOT delete it. It will be necessary for the upcoming
// TODO(litt): this utility is not currently in use, but DO NOT delete it. It will be necessary for the upcoming

Copy link
Contributor

Choose a reason for hiding this comment

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

which litt? ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Litt specified, and disambiguated

// CertVerifierRouter effort
type BlockNumberMonitor struct {
logger logging.Logger
ethClient common.EthClient
// duration of interval when periodically polling the block number
Expand All @@ -23,29 +26,24 @@ type BlockNumberProvider struct {
pollingActive atomic.Bool
}

// NewBlockNumberProvider creates a new block number provider
func NewBlockNumberProvider(
// NewBlockNumberMonitor creates a new block number monitor
func NewBlockNumberMonitor(
logger logging.Logger,
ethClient common.EthClient,
pollIntervalDuration time.Duration,
) *BlockNumberProvider {
) (*BlockNumberMonitor, error) {
if pollIntervalDuration <= time.Duration(0) {
logger.Warn(
`Poll interval duration is <= 0. Therefore, any method calls made with this object that
rely on the internal client having reached a certain block number will fail if
the internal client is too far behind.`,
"pollIntervalDuration", pollIntervalDuration)
return nil, fmt.Errorf("input pollIntervalDuration (%v) must be greater than zero", pollIntervalDuration)
}

return &BlockNumberProvider{
return &BlockNumberMonitor{
logger: logger,
ethClient: ethClient,
pollIntervalDuration: pollIntervalDuration,
}
}, nil
}

// MaybeWaitForBlockNumber waits until the internal eth client has advanced to a certain targetBlockNumber, unless
// configured pollInterval is <= 0, in which case this method will NOT wait for the internal client to advance.
// WaitForBlockNumber waits until the internal eth client has advanced to a certain targetBlockNumber.
//
// This method will check the current block number of the internal client every pollInterval duration.
// It will return nil if the internal client advances to (or past) the targetBlockNumber. It will return an error
Expand All @@ -54,83 +52,72 @@ func NewBlockNumberProvider(
// This method is synchronized in a way that, if called by multiple goroutines, only a single goroutine will actually
// poll the internal eth client for the most recent block number. The goroutine responsible for polling at a given time
// updates an atomic integer, so that all goroutines may check the most recent block without duplicating work.
func (bnp *BlockNumberProvider) MaybeWaitForBlockNumber(ctx context.Context, targetBlockNumber uint64) error {
if bnp.pollIntervalDuration <= 0 {
// don't wait for the internal client to advance
return nil
func (bnm *BlockNumberMonitor) WaitForBlockNumber(ctx context.Context, targetBlockNumber uint64) error {
if bnm.pollIntervalDuration <= 0 {
return fmt.Errorf(
"pollIntervalDuration is <= 0: you ought to be using the provided constructor, which checks this")
}

if bnp.latestBlockNumber.Load() >= targetBlockNumber {
if bnm.latestBlockNumber.Load() >= targetBlockNumber {
// immediately return if the local client isn't behind the target block number
return nil
}

ticker := time.NewTicker(bnp.pollIntervalDuration)
ticker := time.NewTicker(bnm.pollIntervalDuration)
defer ticker.Stop()

polling := false
if bnp.pollingActive.CompareAndSwap(false, true) {
if bnm.pollingActive.CompareAndSwap(false, true) {
// no other goroutine is currently polling, so assume responsibility
polling = true
defer bnp.pollingActive.Store(false)
defer bnm.pollingActive.Store(false)
}

for {
select {
case <-ctx.Done():
return fmt.Errorf(
"timed out waiting for block number %d (latest block number observed was %d): %w",
targetBlockNumber, bnp.latestBlockNumber.Load(), ctx.Err())
targetBlockNumber, bnm.latestBlockNumber.Load(), ctx.Err())
case <-ticker.C:
if bnp.latestBlockNumber.Load() >= targetBlockNumber {
if bnm.latestBlockNumber.Load() >= targetBlockNumber {
return nil
}

if bnp.pollingActive.CompareAndSwap(false, true) {
if bnm.pollingActive.CompareAndSwap(false, true) {
// no other goroutine is currently polling, so assume responsibility
polling = true
defer bnp.pollingActive.Store(false)
defer bnm.pollingActive.Store(false)
}

if polling {
fetchedBlockNumber, err := bnp.FetchLatestBlockNumber(ctx)
blockNumber, err := bnm.ethClient.BlockNumber(ctx)
if err != nil {
return fmt.Errorf("get block number from eth client: %w", err)
}

bnm.latestBlockNumber.Store(blockNumber)

if err != nil {
bnp.logger.Debug(
bnm.logger.Debug(
"ethClient.BlockNumber returned an error",
"targetBlockNumber", targetBlockNumber,
"latestBlockNumber", bnp.latestBlockNumber.Load(),
"latestBlockNumber", bnm.latestBlockNumber.Load(),
"error", err)

// tolerate some failures here. if failure continues for too long, it will be caught by the timeout
continue
}

if fetchedBlockNumber >= targetBlockNumber {
if blockNumber >= targetBlockNumber {
return nil
}
}

bnp.logger.Debug(
bnm.logger.Debug(
"local client is behind the reference block number",
"targetBlockNumber", targetBlockNumber,
"actualBlockNumber", bnp.latestBlockNumber.Load())
"actualBlockNumber", bnm.latestBlockNumber.Load())
}
}
}

// FetchLatestBlockNumber fetches the latest block number from the eth client, and returns it.
//
// This method atomically stores the latest block number for internal use.
//
// Calling this method doesn't have an impact on the cadence of the standard block number polling that occurs
// in MaybeWaitForBlockNumber.
func (bnp *BlockNumberProvider) FetchLatestBlockNumber(ctx context.Context) (uint64, error) {
blockNumber, err := bnp.ethClient.BlockNumber(ctx)
if err != nil {
return 0, fmt.Errorf("get block number from eth client: %w", err)
}

bnp.latestBlockNumber.Store(blockNumber)

return blockNumber, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ func TestWaitForBlockNumber(t *testing.T) {

pollRate := time.Millisecond * 50

blockNumberProvider := NewBlockNumberProvider(logger, mockEthClient, pollRate)
blockNumberMonitor, err := NewBlockNumberMonitor(logger, mockEthClient, pollRate)
require.NoError(t, err)

// number of goroutines to start, each of which will call MaybeWaitForBlockNumber
// number of goroutines to start, each of which will call WaitForBlockNumber
callCount := 5

for i := uint64(0); i < uint64(callCount); i++ {
Expand All @@ -48,11 +49,11 @@ func TestWaitForBlockNumber(t *testing.T) {

if i == callCount-1 {
// the last call is set up to fail, by setting the target block to a number that will never be attained
err := blockNumberProvider.MaybeWaitForBlockNumber(timeoutCtx, uint64(i)+1)
err := blockNumberMonitor.WaitForBlockNumber(timeoutCtx, uint64(i)+1)
require.Error(t, err)
} else {
// all calls except the final call wait for a block number corresponding to their index
err := blockNumberProvider.MaybeWaitForBlockNumber(timeoutCtx, uint64(i))
err := blockNumberMonitor.WaitForBlockNumber(timeoutCtx, uint64(i))
require.NoError(t, err)
}
}(index)
Expand Down
Loading
Loading