Skip to content

Commit

Permalink
chore: deprecate earning receiver address (#320)
Browse files Browse the repository at this point in the history
* chore: deprecate earning receiver address

* fix test

* fmt

* testutils

* fix comment
  • Loading branch information
shrimalmadhur authored Aug 6, 2024
1 parent dc23135 commit f9be5dc
Show file tree
Hide file tree
Showing 26 changed files with 1,182 additions and 453 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
GO_LINES_IGNORED_DIRS=contracts
GO_PACKAGES=./chainio/... ./crypto/... ./logging/... \
./types/... ./utils/... ./signer/... ./cmd/... \
./signerv2/...
./signerv2/... ./aws/... ./internal/... ./metrics/... \
./nodeapi/... ./cmd/... ./services/... ./testutils/...
GO_FOLDERS=$(shell echo ${GO_PACKAGES} | sed -e "s/\.\///g" | sed -e "s/\/\.\.\.//g")
help:
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
Expand Down
1 change: 0 additions & 1 deletion chainio/clients/elcontracts/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ func (r *ChainReader) GetOperatorDetails(opts *bind.CallOpts, operator types.Ope

return types.Operator{
Address: operator.Address,
EarningsReceiverAddress: operatorDetails.DeprecatedEarningsReceiver.Hex(),
StakerOptOutWindowBlocks: operatorDetails.StakerOptOutWindowBlocks,
DelegationApproverAddress: operatorDetails.DelegationApprover.Hex(),
}, nil
Expand Down
8 changes: 6 additions & 2 deletions chainio/clients/elcontracts/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,9 @@ func (w *ChainWriter) RegisterAsOperator(ctx context.Context, operator types.Ope

w.logger.Infof("registering operator %s to EigenLayer", operator.Address)
opDetails := delegationmanager.IDelegationManagerOperatorDetails{
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
// Earning receiver has been deprecated, so we just use the operator address as a dummy value
// Any reward related setup is via RewardsCoordinator contract
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.Address),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
}
Expand Down Expand Up @@ -194,7 +196,9 @@ func (w *ChainWriter) UpdateOperatorDetails(

w.logger.Infof("updating operator details of operator %s to EigenLayer", operator.Address)
opDetails := delegationmanager.IDelegationManagerOperatorDetails{
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.EarningsReceiverAddress),
// Earning receiver has been deprecated, so we just use the operator address as a dummy value
// Any reward related setup is via RewardsCoordinator contract
DeprecatedEarningsReceiver: gethcommon.HexToAddress(operator.Address),
DelegationApprover: gethcommon.HexToAddress(operator.DelegationApproverAddress),
StakerOptOutWindowBlocks: operator.StakerOptOutWindowBlocks,
}
Expand Down
9 changes: 7 additions & 2 deletions internal/fakes/eth_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package fakes
import (
"context"
"errors"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"math/big"
)

const (
Expand Down Expand Up @@ -90,6 +91,10 @@ func (f *EthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]t
return []types.Log{}, nil
}

func (f *EthClient) SubscribeFilterLogs(ctx context.Context, q ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
func (f *EthClient) SubscribeFilterLogs(
ctx context.Context,
q ethereum.FilterQuery,
ch chan<- types.Log,
) (ethereum.Subscription, error) {
return nil, nil
}
32 changes: 22 additions & 10 deletions metrics/collectors/economic/economic.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ type Collector struct {
operatorId types.OperatorId
quorumNames map[types.QuorumNum]string
// metrics
// TODO(samlaf): I feel like eigenlayer-core metrics like slashingStatus and delegatedShares, which are not avs specific,
// TODO(samlaf): I feel like eigenlayer-core metrics like slashingStatus and delegatedShares, which are not avs
// specific,
// should not be here, and should instead be collected by some eigenlayer-cli daemon or something, since
// otherwise every avs will be exporting these same metrics for no reason.

Expand Down Expand Up @@ -85,8 +86,9 @@ func NewCollector(
avsRegistryReader: avsRegistryReader,
logger: logger,
operatorAddr: operatorAddr,
// we don't fetch operatorId here because operator might not yet be registered (and hence not have an operatorId)
// we cache operatorId dynamically in the collect function() inside, which allows constructing collector before registering operator
// we don't fetch operatorId here because operator might not yet be registered (and hence not have an
// operatorId) we cache operatorId dynamically in the collect function() inside, which allows constructing
// collector before registering operator
operatorId: [32]byte{},
quorumNames: quorumNames,
slashingStatus: prometheus.NewDesc(
Expand Down Expand Up @@ -145,10 +147,12 @@ func (ec *Collector) initOperatorId() error {
// constant metrics with the results
func (ec *Collector) Collect(ch chan<- prometheus.Metric) {
// collect slashingStatus metric
// TODO(samlaf): note that this call is not avs specific, so every avs will have the same value if the operator has been slashed
// TODO(samlaf): note that this call is not avs specific, so every avs will have the same value if the operator has
// been slashed
// if we want instead to only output 1 if the operator has been slashed for a specific avs, we have 2 choices:
// 1. keep this collector format but query the OperatorFrozen event from a subgraph
// 2. subscribe to the event and keep a local state of whether the operator has been slashed, exporting it via normal prometheus instrumentation
// 2. subscribe to the event and keep a local state of whether the operator has been slashed, exporting it via
// normal prometheus instrumentation
operatorIsFrozen, err := ec.elReader.OperatorIsFrozen(nil, ec.operatorAddr)
if err != nil {
ec.logger.Error("Failed to get slashing incurred", "err", err)
Expand All @@ -163,7 +167,11 @@ func (ec *Collector) Collect(ch chan<- prometheus.Metric) {
// collect registeredStake metric
err = ec.initOperatorId()
if err != nil {
ec.logger.Warn("Failed to fetch and cache operator id. Skipping collection of registeredStake metric.", "err", err)
ec.logger.Warn(
"Failed to fetch and cache operator id. Skipping collection of registeredStake metric.",
"err",
err,
)
} else {
// probably should start using the avsregistry service instead of avsRegistryReader so that we can
// swap out backend for a subgraph eventually
Expand All @@ -189,14 +197,18 @@ func (ec *Collector) Collect(ch chan<- prometheus.Metric) {
// // We'll emit all 3 units in case this is needed for whatever reason
// // might want to change this behavior if this is emitting too many metrics
// sharesWeiFloat, _ := sharesWei.Float64()
// // TODO(samlaf): add the token name.. probably need to have a hardcoded dict per env (mainnet, goerli, etc)? Is it really that important..?
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue, sharesWeiFloat, strategyAddr.String(), "wei", "token")
// // TODO(samlaf): add the token name.. probably need to have a hardcoded dict per env (mainnet, goerli, etc)? Is
// it really that important..?
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue,
// sharesWeiFloat, strategyAddr.String(), "wei", "token")

// sharesGweiFloat, _ := sharesWei.Div(sharesWei, big.NewInt(1e9)).Float64()
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue, sharesGweiFloat, strategyAddr.String(), "gwei", "token")
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue, sharesGweiFloat,
// strategyAddr.String(), "gwei", "token")

// sharesEtherFloat, _ := sharesWei.Div(sharesWei, big.NewInt(1e18)).Float64()
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue, sharesEtherFloat, strategyAddr.String(), "ether", "token")
// ch <- prometheus.MustNewConstMetric(ec.delegatedShares, prometheus.GaugeValue, sharesEtherFloat,
// strategyAddr.String(), "ether", "token")
// }
// }
}
6 changes: 5 additions & 1 deletion metrics/collectors/rpc_calls/rpc_calls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,9 @@ func TestRpcCallsCollector(t *testing.T) {
// assert.Equal(t, 1.0, testutil.ToFloat64(suite.metrics.rpcRequestDurationSeconds))

rpcCallsCollector.AddRPCRequestTotal("testmethod", "testclient/testversion")
assert.Equal(t, 1.0, testutil.ToFloat64(rpcCallsCollector.rpcRequestTotal.WithLabelValues("testmethod", "testclient/testversion")))
assert.Equal(
t,
1.0,
testutil.ToFloat64(rpcCallsCollector.rpcRequestTotal.WithLabelValues("testmethod", "testclient/testversion")),
)
}
11 changes: 7 additions & 4 deletions metrics/eigenmetrics.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Package metrics implements the avs node prometheus metrics spec: https://docs.eigenlayer.xyz/eigenlayer/avs-guides/spec/metrics/metrics-prom-spec
// Package metrics implements the avs node prometheus metrics spec:
// https://docs.eigenlayer.xyz/eigenlayer/avs-guides/spec/metrics/metrics-prom-spec
package metrics

import (
Expand Down Expand Up @@ -28,7 +29,8 @@ var _ Metrics = (*EigenMetrics)(nil)

// Follows the structure from https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#hdr-A_Basic_Example
// TODO(samlaf): I think each avs runs in a separate docker bridge network.
// In order for prometheus to scrape the metrics does the address need to be 0.0.0.0:port to accept connections from other networks?
// In order for prometheus to scrape the metrics does the address need to be 0.0.0.0:port to accept connections from
// other networks?
func NewEigenMetrics(avsName, ipPortAddress string, reg prometheus.Registerer, logger logging.Logger) *EigenMetrics {

metrics := &EigenMetrics{
Expand Down Expand Up @@ -61,8 +63,9 @@ func (m *EigenMetrics) initMetrics() {
// Performance score starts as 100, and goes down if node doesn't perform well
m.performanceScore.Set(100)

// TODO(samlaf): should we initialize the feeEarnedTotal? This would require the user to pass in a list of tokens for which to initialize the metric
// same for rpcRequestDurationSeconds and rpcRequestTotal... we could initialize them to be 0 on every json-rpc... but is that really necessary?
// TODO(samlaf): should we initialize the feeEarnedTotal? This would require the user to pass in a list of tokens
// for which to initialize the metric same for rpcRequestDurationSeconds and rpcRequestTotal... we could initialize
// them to be 0 on every json-rpc... but is that really necessary?
}

// AddEigenFeeEarnedTotal adds the fee earned to the total fee earned metric
Expand Down
12 changes: 10 additions & 2 deletions metrics/eigenmetrics_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,17 @@ func ExampleEigenMetrics() {
0: "ethQuorum",
1: "someOtherTokenQuorum",
}
// We must register the economic metrics separately because they are exported metrics (from jsonrpc or subgraph calls)
// We must register the economic metrics separately because they are exported metrics (from jsonrpc or subgraph
// calls)
// and not instrumented metrics: see https://prometheus.io/docs/instrumenting/writing_clientlibs/#overall-structure
economicMetricsCollector := economic.NewCollector(clients.ElChainReader, clients.AvsRegistryChainReader, "exampleAvs", logger, operatorEcdsaAddr, quorumNames)
economicMetricsCollector := economic.NewCollector(
clients.ElChainReader,
clients.AvsRegistryChainReader,
"exampleAvs",
logger,
operatorEcdsaAddr,
quorumNames,
)
reg.MustRegister(economicMetricsCollector)

rpcCallsCollector := rpccalls.NewCollector("exampleAvs", reg)
Expand Down
3 changes: 2 additions & 1 deletion metrics/eigenmetrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func (suite *MetricsTestSuite) TestEigenMetricsServerIntegration() {
assert.NoError(suite.T(), err)

// We only check for "eigen_performance_score" since it's the only metric that doesn't have a label
// the other metrics have labels (they are vectors) so they don't appear in the output unless we use them once at least
// the other metrics have labels (they are vectors) so they don't appear in the output unless we use them once at
// least
assert.Contains(suite.T(), string(body), "eigen_fees_earned_total")
assert.Contains(suite.T(), string(body), "eigen_performance_score")
}
Expand Down
3 changes: 2 additions & 1 deletion metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import (
)

// Metrics is the interface for the EigenMetrics server
// it only wraps 2 of the 6 methods required by the spec (https://docs.eigenlayer.xyz/eigenlayer/avs-guides/spec/metrics/metrics-prom-spec)
// it only wraps 2 of the 6 methods required by the spec
// (https://docs.eigenlayer.xyz/eigenlayer/avs-guides/spec/metrics/metrics-prom-spec)
// the others are implemented by the economics and rpc_calls collectors,
// and need to be registered with the metrics server prometheus registry
type Metrics interface {
Expand Down
7 changes: 6 additions & 1 deletion nodeapi/nodeapi_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ func ExampleNodeApi() {
nodeApi := nodeapi.NewNodeApi("testAvs", "v0.0.1", "localhost:8080", logger)
// register a service with the nodeApi. This could be a db, a cache, a queue, etc.
// see https://docs.eigenlayer.xyz/eigenlayer/avs-guides/spec/api/#get-eigennodeservices
nodeApi.RegisterNewService("testServiceId", "testServiceName", "testServiceDescription", nodeapi.ServiceStatusInitializing)
nodeApi.RegisterNewService(
"testServiceId",
"testServiceName",
"testServiceDescription",
nodeapi.ServiceStatusInitializing,
)

// this starts the nodeApi server in a goroutine, so no need to wrap it in a go func
nodeApi.Start()
Expand Down
27 changes: 23 additions & 4 deletions nodeapi/nodeapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ func TestNodeHandler(t *testing.T) {
assert.NoError(t, err)

assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Equal(t, "{\"node_name\":\"testAvs\",\"node_version\":\"v0.0.1\",\"spec_version\":\"v0.0.1\"}\n", string(data))
assert.Equal(
t,
"{\"node_name\":\"testAvs\",\"node_version\":\"v0.0.1\",\"spec_version\":\"v0.0.1\"}\n",
string(data),
)
}

func TestHealthHandler(t *testing.T) {
Expand Down Expand Up @@ -107,7 +111,12 @@ func TestServicesHandler(t *testing.T) {
"one service": {
nodeApi: func() *NodeApi {
nodeApi := NewNodeApi("testAvs", "v0.0.1", "localhost:8080", logger)
nodeApi.RegisterNewService("testServiceId", "testServiceName", "testServiceDescription", ServiceStatusUp)
nodeApi.RegisterNewService(
"testServiceId",
"testServiceName",
"testServiceDescription",
ServiceStatusUp,
)
return nodeApi
}(),
wantStatusCode: http.StatusOK,
Expand All @@ -116,8 +125,18 @@ func TestServicesHandler(t *testing.T) {
"two services": {
nodeApi: func() *NodeApi {
nodeApi := NewNodeApi("testAvs", "v0.0.1", "localhost:8080", logger)
nodeApi.RegisterNewService("testServiceId", "testServiceName", "testServiceDescription", ServiceStatusUp)
nodeApi.RegisterNewService("testServiceId2", "testServiceName2", "testServiceDescription2", ServiceStatusDown)
nodeApi.RegisterNewService(
"testServiceId",
"testServiceName",
"testServiceDescription",
ServiceStatusUp,
)
nodeApi.RegisterNewService(
"testServiceId2",
"testServiceName2",
"testServiceDescription2",
ServiceStatusDown,
)
return nodeApi
}(),
wantStatusCode: http.StatusOK,
Expand Down
28 changes: 22 additions & 6 deletions services/avsregistry/avsregistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,32 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)

// AvsRegistryServicemService is a service that indexes the Avs Registry contracts and provides a way to query for operator state
// AvsRegistryServicemService is a service that indexes the Avs Registry contracts and provides a way to query for
// operator state
// at certain blocks, including operatorIds, pubkeys, and staking status in each quorum.
type AvsRegistryService interface {
// GetOperatorsAvsState returns the state of an avs wrt to a list of quorums at a certain block.
// The state includes the operatorId, pubkey, and staking amount in each quorum.
GetOperatorsAvsStateAtBlock(ctx context.Context, quorumNumbers types.QuorumNums, blockNumber types.BlockNum) (map[types.OperatorId]types.OperatorAvsState, error)
GetOperatorsAvsStateAtBlock(
ctx context.Context,
quorumNumbers types.QuorumNums,
blockNumber types.BlockNum,
) (map[types.OperatorId]types.OperatorAvsState, error)
// GetQuorumsAvsStateAtBlock returns the aggregated data for a list of quorums at a certain block.
// The aggregated data includes the aggregated pubkey and total stake in each quorum.
// This information is derivable from the Operators Avs State (returned from GetOperatorsAvsStateAtBlock), but this function is provided for convenience.
GetQuorumsAvsStateAtBlock(ctx context.Context, quorumNumbers types.QuorumNums, blockNumber types.BlockNum) (map[types.QuorumNum]types.QuorumAvsState, error)
// GetCheckSignaturesIndices returns the registry indices of the nonsigner operators specified by nonSignerOperatorIds who were registered at referenceBlockNumber.
GetCheckSignaturesIndices(opts *bind.CallOpts, referenceBlockNumber types.BlockNum, quorumNumbers types.QuorumNums, nonSignerOperatorIds []types.OperatorId) (opstateretriever.OperatorStateRetrieverCheckSignaturesIndices, error)
// This information is derivable from the Operators Avs State (returned from GetOperatorsAvsStateAtBlock), but this
// function is provided for convenience.
GetQuorumsAvsStateAtBlock(
ctx context.Context,
quorumNumbers types.QuorumNums,
blockNumber types.BlockNum,
) (map[types.QuorumNum]types.QuorumAvsState, error)
// GetCheckSignaturesIndices returns the registry indices of the nonsigner operators specified by
// nonSignerOperatorIds who were registered at referenceBlockNumber.
GetCheckSignaturesIndices(
opts *bind.CallOpts,
referenceBlockNumber types.BlockNum,
quorumNumbers types.QuorumNums,
nonSignerOperatorIds []types.OperatorId,
) (opstateretriever.OperatorStateRetrieverCheckSignaturesIndices, error)
}
Loading

0 comments on commit f9be5dc

Please sign in to comment.