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

AVS-215, AVS-259: Add Subgraphs #233

Merged
merged 12 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,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=
Expand Down
142 changes: 142 additions & 0 deletions services/operatorsinfo/operatorsinfo_subgraph.go
Original file line number Diff line number Diff line change
@@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

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

are the anys necessary? If so, can you add comments as to how they are being used and what they are supposed to be. Hard to understand what this is used if we throwaway all type information.

}
)

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

}
63 changes: 63 additions & 0 deletions services/operatorsinfo/operatorsinfo_subgraph_test.go
Original file line number Diff line number Diff line change
@@ -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
}
samlaf marked this conversation as resolved.
Show resolved Hide resolved

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"))
}
Loading