From d3cc43d708b85d9e59a8c328e594fe26149970a9 Mon Sep 17 00:00:00 2001 From: kachapah <60323455+Sidu28@users.noreply.github.com> Date: Wed, 29 May 2024 07:05:53 -0700 Subject: [PATCH] AVS-215, AVS-259: Add Subgraphs (#233) * added querier * added grahql query * cleanup * added optimistic test * AVS-259: Subgraph test (#244) * cleanup * added optimistic test * fixed broken test * Subgraph testing (#245) * cleanup * added optimistic test * fixed broken test * chore: forge init * forge install: forge-std v1.8.2 * addeed integration test * added remappings * integration test * added integration test halpers * fixed * cleaned gir modules --- go.mod | 1 + go.sum | 2 + .../operatorsinfo/operatorsinfo_subgraph.go | 142 ++++++++++++++++++ .../operatorsinfo_subgraph_test.go | 63 ++++++++ 4 files changed, 208 insertions(+) create mode 100644 services/operatorsinfo/operatorsinfo_subgraph.go create mode 100644 services/operatorsinfo/operatorsinfo_subgraph_test.go diff --git a/go.mod b/go.mod index ae2a84e1..7e996799 100644 --- a/go.mod +++ b/go.mod @@ -33,6 +33,7 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/moby/sys/user v0.1.0 // indirect + github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect diff --git a/go.sum b/go.sum index fbe2c4c7..d95b471e 100644 --- a/go.sum +++ b/go.sum @@ -242,6 +242,8 @@ github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFt github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= diff --git a/services/operatorsinfo/operatorsinfo_subgraph.go b/services/operatorsinfo/operatorsinfo_subgraph.go new file mode 100644 index 00000000..4a6346a0 --- /dev/null +++ b/services/operatorsinfo/operatorsinfo_subgraph.go @@ -0,0 +1,142 @@ +package operatorsinfo + +import ( + "context" + "encoding/hex" + "errors" + "fmt" + + "github.com/Layr-Labs/eigensdk-go/crypto/bls" + "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/Layr-Labs/eigensdk-go/types" + "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/ethereum/go-ethereum/common" + "github.com/shurcooL/graphql" +) + +type ( + QueryOperatorByAddressGql struct { + Operator IndexedOperatorInfoGql `graphql:"operator(address: $address)"` + } + OperatorsInfoServiceSubgraph struct { + logger logging.Logger + client GraphQLQuerier + name string + } + SocketUpdates struct { + Socket graphql.String + } + IndexedOperatorInfoGql struct { + Address graphql.String + PubkeyG1_X graphql.String `graphql:"pubkeyG1_X"` + PubkeyG1_Y graphql.String `graphql:"pubkeyG1_Y"` + PubkeyG2_X []graphql.String `graphql:"pubkeyG2_X"` + PubkeyG2_Y []graphql.String `graphql:"pubkeyG2_Y"` + // Socket is the socket address of the operator, in the form "host:port" + SocketUpdates []SocketUpdates `graphql:"socketUpdates(first: 1, orderBy: blockNumber, orderDirection: desc)"` + } + IndexedOperatorInfo struct { + // PubKeyG1 and PubKeyG2 are the public keys of the operator, which are retrieved from the EigenDAPubKeyCompendium smart contract + PubkeyG1 *G1Point + PubkeyG2 *G2Point + // Socket is the socket address of the operator, in the form "host:port" + Socket string + } + G2Point struct { + *bn254.G2Affine + } + G1Point struct { + *bn254.G1Affine + } + GraphQLQuerier interface { + Query(ctx context.Context, q any, variables map[string]any) error + } +) + +var _ OperatorsInfoService = (*OperatorsInfoServiceSubgraph)(nil) + +// NewOperatorsInfoServiceSubgraph constructs a OperatorsInfoServiceSubgraph and starts it in a goroutine. +// It takes a context as argument because the "backfilling" of the database is done inside this constructor, +// so we wait for all past NewPubkeyRegistration/OperatorSocketUpdate events to be queried and the db to be filled before returning the service. +// The constructor is thus following a RAII-like pattern, of initializing the serving during construction. +// Using a separate initialize() function might lead to some users forgetting to call it and the service not behaving properly. +func NewOperatorsInfoServiceSubgraph( + ctx context.Context, + client GraphQLQuerier, + logger logging.Logger, +) *OperatorsInfoServiceSubgraph { + return &OperatorsInfoServiceSubgraph{ + logger: logger, + client: client, + name: "OperatorsInfoServiceSubgraph", + } +} + +// TODO(samlaf): we might want to also add an async version of this method that returns a channel of operator pubkeys? +func (ops *OperatorsInfoServiceSubgraph) GetOperatorInfo(ctx context.Context, operator common.Address) (operatorPubkeys types.OperatorInfo, operatorFound bool) { + operatorInfo, err := ops.getIndexedOperatorInfoByOperatorId(ctx, operator) + if err != nil { + return types.OperatorInfo{}, false + } + return *operatorInfo, true +} + +func (ops *OperatorsInfoServiceSubgraph) getIndexedOperatorInfoByOperatorId(ctx context.Context, operator common.Address) (*types.OperatorInfo, error) { + var ( + query QueryOperatorByAddressGql + variables = map[string]any{ + "id": graphql.String(fmt.Sprintf("0x%s", hex.EncodeToString(operator[:]))), + } + ) + err := ops.client.Query(ctx, &query, variables) + if err != nil { + ops.logger.Error("Error requesting info for operator", "err", err, "operator", hex.EncodeToString(operator[:])) + return nil, err + } + + return convertIndexedOperatorInfoGqlToOperatorInfo(&query.Operator) +} + +func convertIndexedOperatorInfoGqlToOperatorInfo(operator *IndexedOperatorInfoGql) (*types.OperatorInfo, error) { + + if len(operator.SocketUpdates) == 0 { + return nil, errors.New("no socket found for operator") + } + + pubkeyG1 := new(bn254.G1Affine) + _, err := pubkeyG1.X.SetString(string(operator.PubkeyG1_X)) + if err != nil { + return nil, err + } + _, err = pubkeyG1.Y.SetString(string(operator.PubkeyG1_Y)) + if err != nil { + return nil, err + } + + pubkeyG2 := new(bn254.G2Affine) + _, err = pubkeyG2.X.A1.SetString(string(operator.PubkeyG2_X[0])) + if err != nil { + return nil, err + } + _, err = pubkeyG2.X.A0.SetString(string(operator.PubkeyG2_X[1])) + if err != nil { + return nil, err + } + _, err = pubkeyG2.Y.A1.SetString(string(operator.PubkeyG2_Y[0])) + if err != nil { + return nil, err + } + _, err = pubkeyG2.Y.A0.SetString(string(operator.PubkeyG2_Y[1])) + if err != nil { + return nil, err + } + + return &types.OperatorInfo{ + Socket: types.Socket(string(operator.SocketUpdates[0].Socket)), + Pubkeys: types.OperatorPubkeys{ + G1Pubkey: &bls.G1Point{G1Affine: pubkeyG1}, + G2Pubkey: &bls.G2Point{G2Affine: pubkeyG2}, + }, + }, nil + +} diff --git a/services/operatorsinfo/operatorsinfo_subgraph_test.go b/services/operatorsinfo/operatorsinfo_subgraph_test.go new file mode 100644 index 00000000..40595f1c --- /dev/null +++ b/services/operatorsinfo/operatorsinfo_subgraph_test.go @@ -0,0 +1,63 @@ +package operatorsinfo + +import ( + "context" + "testing" + + "github.com/Layr-Labs/eigensdk-go/logging" + "github.com/Layr-Labs/eigensdk-go/types" + "github.com/ethereum/go-ethereum/common" + "github.com/shurcooL/graphql" + "github.com/stretchr/testify/assert" +) + +type mockGraphQLQuerier struct { + QueryFn func(ctx context.Context, q any, variables map[string]any) error +} + +func (m mockGraphQLQuerier) Query(ctx context.Context, q any, variables map[string]any) error { + return m.QueryFn(ctx, q, variables) +} + +func TestIndexedChainState_GetIndexedOperatorState(t *testing.T) { + logger, err := logging.NewZapLogger(logging.Development) + if err != nil { + t.Fatal(err) + } + + operatorAddress := common.Address{0x1} + + operatorsQueryCalled := false + querier := &mockGraphQLQuerier{} + querier.QueryFn = func(ctx context.Context, q any, variables map[string]any) error { + switch res := q.(type) { + case *QueryOperatorByAddressGql: + if operatorsQueryCalled { + return nil + } + res.Operator = IndexedOperatorInfoGql{ + Address: graphql.String(operatorAddress.String()), + PubkeyG1_X: "3336192159512049190945679273141887248666932624338963482128432381981287252980", + PubkeyG1_Y: "15195175002875833468883745675063986308012687914999552116603423331534089122704", + PubkeyG2_X: []graphql.String{ + "21597023645215426396093421944506635812143308313031252511177204078669540440732", + "11405255666568400552575831267661419473985517916677491029848981743882451844775", + }, + PubkeyG2_Y: []graphql.String{ + "9416989242565286095121881312760798075882411191579108217086927390793923664442", + "13612061731370453436662267863740141021994163834412349567410746669651828926551", + }, + SocketUpdates: []SocketUpdates{{Socket: "localhost:32006;32007"}}, + } + operatorsQueryCalled = true + return nil + default: + return nil + } + } + + cs := NewOperatorsInfoServiceSubgraph(context.Background(), querier, logger) + operatorPubkeys, success := cs.GetOperatorInfo(context.Background(), operatorAddress) + assert.True(t, success) + assert.Equal(t, operatorPubkeys.Socket, types.Socket("localhost:32006;32007")) +}