-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
4 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
) | ||
|
||
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 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
|
||
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")) | ||
} |