From e5c3fb83723f6c60d32d5f5b47a330bd96f59b13 Mon Sep 17 00:00:00 2001 From: Juan Bono Date: Fri, 16 Aug 2024 12:26:26 -0300 Subject: [PATCH] Add read only builder (#326) --- chainio/clients/avsregistry/builder.go | 42 ++++++++++ chainio/clients/builder.go | 110 +++++++++++++++++++++---- chainio/clients/elcontracts/builder.go | 30 +++++++ 3 files changed, 164 insertions(+), 18 deletions(-) diff --git a/chainio/clients/avsregistry/builder.go b/chainio/clients/avsregistry/builder.go index c46b96bd..d9ad5dc2 100644 --- a/chainio/clients/avsregistry/builder.go +++ b/chainio/clients/avsregistry/builder.go @@ -7,6 +7,48 @@ import ( "github.com/Layr-Labs/eigensdk-go/logging" ) +// Build an AVS registry client with the given configuration, +// HTTP and WS clients, and logger, but without a private key. +// +// This is useful for read-only operations. +func BuildReadClients( + config Config, + client eth.HttpBackend, + wsClient eth.WsBackend, + logger logging.Logger, +) (*ChainReader, *ChainSubscriber, *ContractBindings, error) { + avsBindings, err := NewBindingsFromConfig( + config, + client, + logger, + ) + + if err != nil { + return nil, nil, nil, err + } + + chainReader := NewChainReader( + avsBindings.RegistryCoordinatorAddr, + avsBindings.BlsApkRegistryAddr, + avsBindings.RegistryCoordinator, + avsBindings.OperatorStateRetriever, + avsBindings.StakeRegistry, + logger, + client, + ) + + chainSubscriber, err := NewSubscriberFromConfig( + config, + wsClient, + logger, + ) + if err != nil { + return nil, nil, nil, err + } + + return chainReader, chainSubscriber, avsBindings, nil +} + func BuildClients( config Config, client eth.HttpBackend, diff --git a/chainio/clients/builder.go b/chainio/clients/builder.go index 31d9559f..7cd0c029 100644 --- a/chainio/clients/builder.go +++ b/chainio/clients/builder.go @@ -29,28 +29,99 @@ type BuildAllConfig struct { PromMetricsIpPortAddress string } -// Clients is a struct that holds all the clients that are needed to interact with the AVS and EL contracts. -// TODO: this is confusing right now because clients are not instrumented clients, but -// we return metrics and prometheus reg, so user has to build instrumented clients at the call -// site if they need them. We should probably separate into two separate constructors, one -// for non-instrumented clients that doesn't return metrics/reg, and another instrumented-constructor -// that returns instrumented clients and the metrics/reg. -type Clients struct { +// ReadClients is a struct that holds only the read clients for interacting with the AVS and EL contracts. +type ReadClients struct { AvsRegistryChainReader *avsregistry.ChainReader AvsRegistryChainSubscriber *avsregistry.ChainSubscriber - AvsRegistryChainWriter *avsregistry.ChainWriter ElChainReader *elcontracts.ChainReader - ElChainWriter *elcontracts.ChainWriter EthHttpClient eth.HttpBackend EthWsClient eth.WsBackend - Wallet wallet.Wallet - TxManager txmgr.TxManager AvsRegistryContractBindings *avsregistry.ContractBindings EigenlayerContractBindings *elcontracts.ContractBindings Metrics *metrics.EigenMetrics // exposes main avs node spec metrics that need to be incremented by avs code and used to start the metrics server - PrometheusRegistry *prometheus.Registry // Used if avs teams need to register avs-specific metrics + PrometheusRegistry *prometheus.Registry +} + +// Clients is a struct that holds all the clients that are needed to interact with the AVS and EL contracts. +type Clients struct { + ReadClients + Wallet wallet.Wallet + TxManager txmgr.TxManager + ElChainWriter *elcontracts.ChainWriter + AvsRegistryChainWriter *avsregistry.ChainWriter +} + +// BuildReadClients creates all the read clients needed to interact with the AVS and EL contracts. +func BuildReadClients( + config BuildAllConfig, + logger logging.Logger, +) (*ReadClients, error) { + config.validate(logger) + + // Create the metrics server + promReg := prometheus.NewRegistry() + eigenMetrics := metrics.NewEigenMetrics(config.AvsName, config.PromMetricsIpPortAddress, promReg, logger) + + // creating two types of Eth clients: HTTP and WS + ethHttpClient, err := ethclient.Dial(config.EthHttpUrl) + if err != nil { + return nil, utils.WrapError("Failed to create Eth Http client", err) + } + + ethWsClient, err := ethclient.Dial(config.EthWsUrl) + if err != nil { + return nil, utils.WrapError("Failed to create Eth WS client", err) + } + + // creating AVS clients: Reader + avsRegistryChainReader, avsRegistryChainSubscriber, avsRegistryContractBindings, err := avsregistry.BuildReadClients( + avsregistry.Config{ + RegistryCoordinatorAddress: gethcommon.HexToAddress(config.RegistryCoordinatorAddr), + OperatorStateRetrieverAddress: gethcommon.HexToAddress(config.OperatorStateRetrieverAddr), + }, + ethHttpClient, + ethWsClient, + logger, + ) + if err != nil { + return nil, utils.WrapError("Failed to create AVS Registry Reader and Writer", err) + } + + // creating EL clients: Reader and EigenLayer Contract Bindings + elChainReader, elContractBindings, err := elcontracts.BuildReadClients( + elcontracts.Config{ + DelegationManagerAddress: avsRegistryContractBindings.DelegationManagerAddr, + AvsDirectoryAddress: avsRegistryContractBindings.AvsDirectoryAddr, + }, + ethHttpClient, + logger, + eigenMetrics, + ) + if err != nil { + return nil, utils.WrapError("Failed to create EL Reader and Writer", err) + } + + readClients := ReadClients{ + ElChainReader: elChainReader, + AvsRegistryChainReader: avsRegistryChainReader, + AvsRegistryChainSubscriber: avsRegistryChainSubscriber, + EthHttpClient: ethHttpClient, + EthWsClient: ethWsClient, + EigenlayerContractBindings: elContractBindings, + AvsRegistryContractBindings: avsRegistryContractBindings, + Metrics: eigenMetrics, + PrometheusRegistry: promReg, + } + return &readClients, nil } +// BuildAll creates all the clients needed to interact with the AVS and EL contracts. For both read and write +// operations. +// TODO: this is confusing right now because clients are not instrumented clients, but +// we return metrics and prometheus reg, so user has to build instrumented clients at the call +// site if they need them. We should probably separate into two separate constructors, one +// for non-instrumented clients that doesn't return metrics/reg, and another instrumented-constructor +// that returns instrumented clients and the metrics/reg. func BuildAll( config BuildAllConfig, ecdsaPrivateKey *ecdsa.PrivateKey, @@ -120,22 +191,25 @@ func BuildAll( return nil, utils.WrapError("Failed to create EL Reader and Writer", err) } - return &Clients{ + readClients := ReadClients{ ElChainReader: elChainReader, - ElChainWriter: elChainWriter, AvsRegistryChainReader: avsRegistryChainReader, AvsRegistryChainSubscriber: avsRegistryChainSubscriber, - AvsRegistryChainWriter: avsRegistryChainWriter, EthHttpClient: ethHttpClient, EthWsClient: ethWsClient, - Wallet: pkWallet, - TxManager: txMgr, EigenlayerContractBindings: elContractBindings, AvsRegistryContractBindings: avsRegistryContractBindings, Metrics: eigenMetrics, PrometheusRegistry: promReg, - }, nil + } + return &Clients{ + ReadClients: readClients, + ElChainWriter: elChainWriter, + AvsRegistryChainWriter: avsRegistryChainWriter, + Wallet: pkWallet, + TxManager: txMgr, + }, nil } // Very basic validation that makes sure all fields are nonempty diff --git a/chainio/clients/elcontracts/builder.go b/chainio/clients/elcontracts/builder.go index 957d3ec0..60f546b9 100644 --- a/chainio/clients/elcontracts/builder.go +++ b/chainio/clients/elcontracts/builder.go @@ -7,6 +7,36 @@ import ( "github.com/Layr-Labs/eigensdk-go/metrics" ) +// Returns a tuple of reader clients with the given: +// configuration, HTTP client, logger and metrics. +func BuildReadClients( + config Config, + client eth.HttpBackend, + logger logging.Logger, + eigenMetrics *metrics.EigenMetrics, +) (*ChainReader, *ContractBindings, error) { + elContractBindings, err := NewBindingsFromConfig( + config, + client, + logger, + ) + if err != nil { + return nil, nil, err + } + + elChainReader := NewChainReader( + elContractBindings.Slasher, + elContractBindings.DelegationManager, + elContractBindings.StrategyManager, + elContractBindings.AvsDirectory, + elContractBindings.RewardsCoordinator, + logger, + client, + ) + + return elChainReader, elContractBindings, nil +} + func BuildClients( config Config, client eth.HttpBackend,