diff --git a/.gitignore b/.gitignore index d9355ee9..5085b26d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,5 @@ target # Aggregator DB aggregator.db -# just for example -id_rsa +# Test artifacts +test_data \ No newline at end of file diff --git a/Makefile b/Makefile index 98e15a6e..e9220f3f 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ mocks: ## generates mocks for tests go generate ./... tests-unit: ## runs all unit tests - go test $$(go list ./... | grep -v /integration) -coverprofile=coverage.out -covermode=atomic --timeout 15s + go test $$(go list ./... | grep -v /integration) -coverprofile=coverage.out -covermode=atomic --timeout 18s go tool cover -html=coverage.out -o coverage.html tests-contract: ## runs all forge tests diff --git a/operator/avs_manager.go b/operator/avs_manager.go new file mode 100644 index 00000000..34843a22 --- /dev/null +++ b/operator/avs_manager.go @@ -0,0 +1,322 @@ +package operator + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/Layr-Labs/eigensdk-go/chainio/clients" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" + "github.com/Layr-Labs/eigensdk-go/chainio/clients/eth" + "github.com/Layr-Labs/eigensdk-go/chainio/txmgr" + "github.com/Layr-Labs/eigensdk-go/crypto/bls" + sdklogging "github.com/Layr-Labs/eigensdk-go/logging" + eigenSdkTypes "github.com/Layr-Labs/eigensdk-go/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + opsetupdatereg "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLOperatorSetUpdateRegistry" + registryrollup "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLRegistryRollup" + taskmanager "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLTaskManager" + "github.com/NethermindEth/near-sffl/core/chainio" + "github.com/NethermindEth/near-sffl/types" +) + +type AvsManagerer interface { + Start(ctx context.Context, operatorAddr common.Address) error + DepositIntoStrategy(operatorAddr common.Address, strategyAddr common.Address, amount *big.Int) error + RegisterOperatorWithEigenlayer(operatorAddr common.Address) error + RegisterOperatorWithAvs( + client eth.EthClient, + operatorEcdsaKeyPair *ecdsa.PrivateKey, + blsKeyPair *bls.KeyPair, + ) error + ProcessCheckpointTaskCreatedLog(checkpointTaskCreatedLog *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated) taskmanager.CheckpointTaskResponse + + GetOperatorId(options *bind.CallOpts, address common.Address) ([32]byte, error) + GetCheckpointTaskResponseChan() <-chan taskmanager.CheckpointTaskResponse + GetOperatorSetUpdateChan() <-chan registryrollup.OperatorSetUpdateMessage +} + +type AvsManager struct { + avsWriter *chainio.AvsWriter + avsReader chainio.AvsReaderer + avsSubscriber chainio.AvsSubscriberer + eigenlayerReader elcontracts.ELReader + eigenlayerWriter elcontracts.ELWriter + + // receive new tasks in this chan (typically from listening to onchain event) + checkpointTaskCreatedChan chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated + // receive operator set updates in this chan + operatorSetUpdateChan chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock + + // Sends message for operator to sign + checkpointTaskResponseCreatedChan chan taskmanager.CheckpointTaskResponse + operatorSetUpdateMessageChan chan registryrollup.OperatorSetUpdateMessage + + logger sdklogging.Logger +} + +func NewAvsManager(config *types.NodeConfig, ethRpcClient eth.EthClient, ethWsClient eth.EthClient, sdkClients *clients.Clients, txManager *txmgr.SimpleTxManager, logger sdklogging.Logger) (*AvsManager, error) { + avsWriter, err := chainio.BuildAvsWriter( + txManager, common.HexToAddress(config.AVSRegistryCoordinatorAddress), + common.HexToAddress(config.OperatorStateRetrieverAddress), ethRpcClient, logger, + ) + if err != nil { + logger.Error("Cannot create AvsWriter", "err", err) + return nil, err + } + + avsReader, err := chainio.BuildAvsReader( + common.HexToAddress(config.AVSRegistryCoordinatorAddress), + common.HexToAddress(config.OperatorStateRetrieverAddress), + ethRpcClient, logger) + if err != nil { + logger.Error("Cannot create AvsReader", "err", err) + return nil, err + } + + avsSubscriber, err := chainio.BuildAvsSubscriber(common.HexToAddress(config.AVSRegistryCoordinatorAddress), + common.HexToAddress(config.OperatorStateRetrieverAddress), ethWsClient, logger, + ) + if err != nil { + logger.Error("Cannot create AvsSubscriber", "err", err) + return nil, err + } + + return &AvsManager{ + avsReader: avsReader, + avsWriter: avsWriter, + avsSubscriber: avsSubscriber, + eigenlayerReader: sdkClients.ElChainReader, + eigenlayerWriter: sdkClients.ElChainWriter, + checkpointTaskCreatedChan: make(chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated), + operatorSetUpdateChan: make(chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock), + checkpointTaskResponseCreatedChan: make(chan taskmanager.CheckpointTaskResponse), + operatorSetUpdateMessageChan: make(chan registryrollup.OperatorSetUpdateMessage), + logger: logger, + }, nil +} + +func (avsManager *AvsManager) GetCheckpointTaskResponseChan() <-chan taskmanager.CheckpointTaskResponse { + return avsManager.checkpointTaskResponseCreatedChan +} + +func (avsManager *AvsManager) GetOperatorSetUpdateChan() <-chan registryrollup.OperatorSetUpdateMessage { + return avsManager.operatorSetUpdateMessageChan +} + +func (avsManager *AvsManager) Start(ctx context.Context, operatorAddr common.Address) error { + operatorIsRegistered, err := avsManager.IsOperatorRegistered(&bind.CallOpts{}, operatorAddr) + if err != nil { + avsManager.logger.Error("Error checking if operator is registered", "err", err) + return err + } + + if !operatorIsRegistered { + // We bubble the error all the way up instead of using logger.Fatal because logger.Fatal prints a huge stack trace + // that hides the actual error message. This error msg is more explicit and doesn't require showing a stack trace to the user. + return fmt.Errorf("operator is not registered. Registering operator using the operator-cli before starting operator") + } + + newTasksSub, err := avsManager.avsSubscriber.SubscribeToNewTasks(avsManager.checkpointTaskCreatedChan) + if err != nil { + avsManager.logger.Error("Error subscribing to new tasks", "err", err) + return err + } + + operatorSetUpdateSub, err := avsManager.avsSubscriber.SubscribeToOperatorSetUpdates(avsManager.operatorSetUpdateChan) + if err != nil { + avsManager.logger.Error("Error subscribing to operator set updates", "err", err) + return err + } + + go func() { + for { + select { + case <-ctx.Done(): + return + + case err := <-newTasksSub.Err(): + avsManager.logger.Error("Error in websocket subscription", "err", err) + // TODO(samlaf): write unit tests to check if this fixed the issues we were seeing + newTasksSub.Unsubscribe() + // TODO(samlaf): wrap this call with increase in avs-node-spec metric + newTasksSub, err = avsManager.avsSubscriber.SubscribeToNewTasks(avsManager.checkpointTaskCreatedChan) + if err != nil { + avsManager.logger.Error("Error re-subscribing to new tasks", "err", err) + close(avsManager.checkpointTaskResponseCreatedChan) + return + } + + continue + + case checkpointTaskCreatedLog := <-avsManager.checkpointTaskCreatedChan: + taskResponse := avsManager.ProcessCheckpointTaskCreatedLog(checkpointTaskCreatedLog) + avsManager.checkpointTaskResponseCreatedChan <- taskResponse + + case err := <-operatorSetUpdateSub.Err(): + avsManager.logger.Error("Error in websocket subscription", "err", err) + operatorSetUpdateSub.Unsubscribe() + operatorSetUpdateSub, err = avsManager.avsSubscriber.SubscribeToOperatorSetUpdates(avsManager.operatorSetUpdateChan) + if err != nil { + avsManager.logger.Error("Error re-subscribing to operator set updates", "err", err) + close(avsManager.checkpointTaskResponseCreatedChan) + return + } + + continue + + case operatorSetUpdate := <-avsManager.operatorSetUpdateChan: + go avsManager.handleOperatorSetUpdate(ctx, operatorSetUpdate) + continue + } + } + }() + + return nil +} + +// Takes a CheckpointTaskCreatedLog struct as input and returns a TaskResponseHeader struct. +// The TaskResponseHeader struct is the struct that is signed and sent to the contract as a task response. +func (avsManager *AvsManager) ProcessCheckpointTaskCreatedLog(checkpointTaskCreatedLog *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated) taskmanager.CheckpointTaskResponse { + avsManager.logger.Debug("Received new task", "task", checkpointTaskCreatedLog) + avsManager.logger.Info("Received new task", + "fromTimestamp", checkpointTaskCreatedLog.Task.FromTimestamp, + "toTimestamp", checkpointTaskCreatedLog.Task.ToTimestamp, + "taskIndex", checkpointTaskCreatedLog.TaskIndex, + "taskCreatedBlock", checkpointTaskCreatedLog.Task.TaskCreatedBlock, + "quorumNumbers", checkpointTaskCreatedLog.Task.QuorumNumbers, + "quorumThreshold", checkpointTaskCreatedLog.Task.QuorumThreshold, + ) + + // TODO: build SMT based on stored message agreements and update the test + + taskResponse := taskmanager.CheckpointTaskResponse{ + ReferenceTaskIndex: checkpointTaskCreatedLog.TaskIndex, + StateRootUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + OperatorSetUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + } + return taskResponse +} + +func (avsManager *AvsManager) handleOperatorSetUpdate(ctx context.Context, data *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock) error { + operatorSetDelta, err := avsManager.avsReader.GetOperatorSetUpdateDelta(ctx, data.Id) + if err != nil { + avsManager.logger.Errorf("Couldn't get Operator set update delta: %v for block: %v", err, data.Id) + return err + } + + operators := make([]registryrollup.OperatorsOperator, len(operatorSetDelta)) + for i := 0; i < len(operatorSetDelta); i++ { + operators[i] = registryrollup.OperatorsOperator{ + Pubkey: registryrollup.BN254G1Point{ + X: operatorSetDelta[i].Pubkey.X, + Y: operatorSetDelta[i].Pubkey.Y, + }, + Weight: operatorSetDelta[i].Weight, + } + } + + message := registryrollup.OperatorSetUpdateMessage{ + Id: data.Id, + Timestamp: data.Timestamp, + Operators: operators, + } + + avsManager.operatorSetUpdateMessageChan <- message + return nil +} + +func (avsManager *AvsManager) DepositIntoStrategy(operatorAddr common.Address, strategyAddr common.Address, amount *big.Int) error { + _, tokenAddr, err := avsManager.eigenlayerReader.GetStrategyAndUnderlyingToken(&bind.CallOpts{}, strategyAddr) + if err != nil { + avsManager.logger.Error("Failed to fetch strategy contract", "err", err) + return err + } + contractErc20Mock, err := avsManager.avsReader.GetErc20Mock(context.Background(), tokenAddr) + if err != nil { + avsManager.logger.Error("Failed to fetch ERC20Mock contract", "err", err) + return err + } + txOpts, err := avsManager.avsWriter.TxMgr.GetNoSendTxOpts() + tx, err := contractErc20Mock.Mint(txOpts, operatorAddr, amount) + if err != nil { + avsManager.logger.Errorf("Error assembling Mint tx") + return err + } + _, err = avsManager.avsWriter.TxMgr.Send(context.Background(), tx) + if err != nil { + avsManager.logger.Errorf("Error submitting Mint tx") + return err + } + + _, err = avsManager.eigenlayerWriter.DepositERC20IntoStrategy(context.Background(), strategyAddr, amount) + if err != nil { + avsManager.logger.Errorf("Error depositing into strategy", "err", err) + return err + } + return nil +} + +func (avsManager *AvsManager) RegisterOperatorWithEigenlayer(operatorAddr common.Address) error { + operator := eigenSdkTypes.Operator{ + Address: operatorAddr.String(), + EarningsReceiverAddress: operatorAddr.String(), + } + _, err := avsManager.eigenlayerWriter.RegisterAsOperator(context.Background(), operator) + if err != nil { + avsManager.logger.Errorf("Error registering operator with eigenlayer") + return err + } + + return nil +} + +// RegisterOperatorWithAvs Registration specific functions +func (avsManager *AvsManager) RegisterOperatorWithAvs( + client eth.EthClient, + operatorEcdsaKeyPair *ecdsa.PrivateKey, + blsKeyPair *bls.KeyPair, +) error { + // hardcode these things for now + quorumNumbers := []byte{0} + socket := "Not Needed" + operatorToAvsRegistrationSigSalt := [32]byte{123} + curBlockNum, err := client.BlockNumber(context.Background()) + if err != nil { + avsManager.logger.Errorf("Unable to get current block number") + return err + } + + curBlock, err := client.BlockByNumber(context.Background(), big.NewInt(int64(curBlockNum))) + if err != nil { + avsManager.logger.Errorf("Unable to get current block") + return err + } + + sigValidForSeconds := int64(1_000_000) + operatorToAvsRegistrationSigExpiry := big.NewInt(int64(curBlock.Time()) + sigValidForSeconds) + _, err = avsManager.avsWriter.RegisterOperatorInQuorumWithAVSRegistryCoordinator( + context.Background(), + operatorEcdsaKeyPair, operatorToAvsRegistrationSigSalt, operatorToAvsRegistrationSigExpiry, + blsKeyPair, quorumNumbers, socket, + ) + + if err != nil { + avsManager.logger.Errorf("Unable to register operator with avs registry coordinator") + return err + } + avsManager.logger.Infof("Registered operator with avs registry coordinator.") + + return nil +} + +func (avsManager *AvsManager) IsOperatorRegistered(options *bind.CallOpts, address common.Address) (bool, error) { + return avsManager.avsReader.IsOperatorRegistered(options, address) +} + +func (avsManager *AvsManager) GetOperatorId(options *bind.CallOpts, address common.Address) ([32]byte, error) { + return avsManager.avsReader.GetOperatorId(options, address) +} diff --git a/operator/operator.go b/operator/operator.go index 1372d5e8..608f1e46 100644 --- a/operator/operator.go +++ b/operator/operator.go @@ -2,37 +2,36 @@ package operator import ( "context" + "crypto/ecdsa" + "encoding/hex" + "encoding/json" "fmt" + "math/big" "os" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/prometheus/client_golang/prometheus" - - opsetupdatereg "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLOperatorSetUpdateRegistry" - registryrollup "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLRegistryRollup" - taskmanager "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLTaskManager" - "github.com/NethermindEth/near-sffl/core" - "github.com/NethermindEth/near-sffl/core/chainio" - coretypes "github.com/NethermindEth/near-sffl/core/types" - "github.com/NethermindEth/near-sffl/metrics" - "github.com/NethermindEth/near-sffl/operator/attestor" - "github.com/NethermindEth/near-sffl/types" - "github.com/Layr-Labs/eigensdk-go/chainio/clients" - sdkelcontracts "github.com/Layr-Labs/eigensdk-go/chainio/clients/elcontracts" "github.com/Layr-Labs/eigensdk-go/chainio/clients/eth" "github.com/Layr-Labs/eigensdk-go/chainio/txmgr" "github.com/Layr-Labs/eigensdk-go/crypto/bls" sdkecdsa "github.com/Layr-Labs/eigensdk-go/crypto/ecdsa" "github.com/Layr-Labs/eigensdk-go/logging" sdklogging "github.com/Layr-Labs/eigensdk-go/logging" - sdkmetrics "github.com/Layr-Labs/eigensdk-go/metrics" "github.com/Layr-Labs/eigensdk-go/metrics/collectors/economic" rpccalls "github.com/Layr-Labs/eigensdk-go/metrics/collectors/rpc_calls" "github.com/Layr-Labs/eigensdk-go/nodeapi" "github.com/Layr-Labs/eigensdk-go/signerv2" sdktypes "github.com/Layr-Labs/eigensdk-go/types" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/prometheus/client_golang/prometheus" + + registryrollup "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLRegistryRollup" + taskmanager "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLTaskManager" + "github.com/NethermindEth/near-sffl/core" + coretypes "github.com/NethermindEth/near-sffl/core/types" + "github.com/NethermindEth/near-sffl/metrics" + "github.com/NethermindEth/near-sffl/operator/attestor" + "github.com/NethermindEth/near-sffl/types" ) const AVS_NAME = "super-fast-finality-layer" @@ -47,23 +46,12 @@ type Operator struct { // this way, auditing this operator code makes it obvious that operators don't need to // write to the chain during the course of their normal operations // writing to the chain should be done via the cli only - metricsReg *prometheus.Registry - metrics metrics.Metrics - nodeApi *nodeapi.NodeApi - avsWriter *chainio.AvsWriter - avsReader chainio.AvsReaderer - avsSubscriber chainio.AvsSubscriberer - eigenlayerReader sdkelcontracts.ELReader - eigenlayerWriter sdkelcontracts.ELWriter - blsKeypair *bls.KeyPair - operatorId bls.OperatorId - operatorAddr common.Address - - // receive new tasks in this chan (typically from listening to onchain event) - checkpointTaskCreatedChan chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated - // receive operator set updates in this chan - // TODO: agree on operatorSetUpdateC vs operatorSetUpdateChan - operatorSetUpdateChan chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock + metricsReg *prometheus.Registry + metrics metrics.Metrics + nodeApi *nodeapi.NodeApi + blsKeypair *bls.KeyPair + operatorId bls.OperatorId + operatorAddr common.Address // ip address of aggregator aggregatorServerIpPortAddr string @@ -73,9 +61,11 @@ type Operator struct { sfflServiceManagerAddr common.Address // NEAR DA indexer consumer attestor attestor.Attestorer + // Avs Manager + avsManager *AvsManager } -func createEthClients(config types.NodeConfig, registry *prometheus.Registry, logger sdklogging.Logger) (eth.EthClient, eth.EthClient, error) { +func createEthClients(config *types.NodeConfig, registry *prometheus.Registry, logger sdklogging.Logger) (eth.EthClient, eth.EthClient, error) { if config.EnableMetrics { rpcCallsCollector := rpccalls.NewCollector(AVS_NAME, registry) ethRpcClient, err := eth.NewInstrumentedClient(config.EthRpcUrl, rpcCallsCollector) @@ -126,28 +116,30 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { return nil, err } - reg := prometheus.NewRegistry() - eigenMetrics := sdkmetrics.NewEigenMetrics(AVS_NAME, c.EigenMetricsIpPortAddress, reg, logger) - avsAndEigenMetrics := metrics.NewAvsAndEigenMetrics(AVS_NAME, eigenMetrics, reg) - // Setup Node Api nodeApi := nodeapi.NewNodeApi(AVS_NAME, SEM_VER, c.NodeApiIpPortAddress, logger) + blsKeyPassword, ok := os.LookupEnv("OPERATOR_BLS_KEY_PASSWORD") + if !ok { + logger.Warnf("OPERATOR_BLS_KEY_PASSWORD env var not set. using empty string") + } - ethRpcClient, ethWsClient, err := createEthClients(c, reg, logger) + blsKeyPair, err := bls.ReadPrivateKeyFromFile(c.BlsPrivateKeyStorePath, blsKeyPassword) if err != nil { + logger.Errorf("Cannot parse bls private key", "err", err) return nil, err } - blsKeyPassword, ok := os.LookupEnv("OPERATOR_BLS_KEY_PASSWORD") + ecdsaKeyPassword, ok := os.LookupEnv("OPERATOR_ECDSA_KEY_PASSWORD") if !ok { - logger.Warnf("OPERATOR_BLS_KEY_PASSWORD env var not set. using empty string") + logger.Warnf("OPERATOR_ECDSA_KEY_PASSWORD env var not set. using empty string") } - blsKeyPair, err := bls.ReadPrivateKeyFromFile(c.BlsPrivateKeyStorePath, blsKeyPassword) + reg := prometheus.NewRegistry() + ethRpcClient, ethWsClient, err := createEthClients(&c, reg, logger) if err != nil { - logger.Errorf("Cannot parse bls private key", "err", err) return nil, err } + // TODO(samlaf): should we add the chainId to the config instead? // this way we can prevent creating a signer that signs on mainnet by mistake // if the config says chainId=5, then we can only create a goerli signer @@ -157,11 +149,6 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { return nil, err } - ecdsaKeyPassword, ok := os.LookupEnv("OPERATOR_ECDSA_KEY_PASSWORD") - if !ok { - logger.Warnf("OPERATOR_ECDSA_KEY_PASSWORD env var not set. using empty string") - } - signerV2, _, err := signerv2.SignerFromConfig(signerv2.Config{ KeystorePath: c.EcdsaPrivateKeyStorePath, Password: ecdsaKeyPassword, @@ -170,6 +157,7 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { panic(err) } + txMgr := txmgr.NewSimpleTxManager(ethRpcClient, logger, signerV2, common.HexToAddress(c.OperatorAddress)) chainioConfig := clients.BuildAllConfig{ EthHttpUrl: c.EthRpcUrl, EthWsUrl: c.EthWsUrl, @@ -183,30 +171,16 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { panic(err) } - txMgr := txmgr.NewSimpleTxManager(ethRpcClient, logger, signerV2, common.HexToAddress(c.OperatorAddress)) - avsWriter, err := chainio.BuildAvsWriter( - txMgr, common.HexToAddress(c.AVSRegistryCoordinatorAddress), - common.HexToAddress(c.OperatorStateRetrieverAddress), ethRpcClient, logger, - ) - if err != nil { - logger.Error("Cannot create AvsWriter", "err", err) - return nil, err - } - - avsReader, err := chainio.BuildAvsReader( - common.HexToAddress(c.AVSRegistryCoordinatorAddress), - common.HexToAddress(c.OperatorStateRetrieverAddress), - ethRpcClient, logger) + avsAndEigenMetrics := metrics.NewAvsAndEigenMetrics(AVS_NAME, sdkClients.Metrics, reg) + aggregatorRpcClient, err := NewAggregatorRpcClient(c.AggregatorServerIpPortAddress, logger, avsAndEigenMetrics) if err != nil { - logger.Error("Cannot create AvsReader", "err", err) + logger.Error("Cannot create AggregatorRpcClient. Is aggregator running?", "err", err) return nil, err } - avsSubscriber, err := chainio.BuildAvsSubscriber(common.HexToAddress(c.AVSRegistryCoordinatorAddress), - common.HexToAddress(c.OperatorStateRetrieverAddress), ethWsClient, logger, - ) + avsManager, err := NewAvsManager(&c, ethRpcClient, ethWsClient, sdkClients, txMgr, logger) if err != nil { - logger.Error("Cannot create AvsSubscriber", "err", err) + logger.Error("Cannot create AvsManager", "err", err) return nil, err } @@ -220,30 +194,18 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { AVS_NAME, logger, common.HexToAddress(c.OperatorAddress), quorumNames) reg.MustRegister(economicMetricsCollector) - aggregatorRpcClient, err := NewAggregatorRpcClient(c.AggregatorServerIpPortAddress, logger, avsAndEigenMetrics) - if err != nil { - logger.Error("Cannot create AggregatorRpcClient. Is aggregator running?", "err", err) - return nil, err - } - operator := &Operator{ config: c, logger: logger, + ethClient: ethRpcClient, metricsReg: reg, metrics: avsAndEigenMetrics, nodeApi: nodeApi, - ethClient: ethRpcClient, - avsWriter: avsWriter, - avsReader: avsReader, - avsSubscriber: avsSubscriber, - eigenlayerReader: sdkClients.ElChainReader, - eigenlayerWriter: sdkClients.ElChainWriter, + avsManager: avsManager, blsKeypair: blsKeyPair, operatorAddr: common.HexToAddress(c.OperatorAddress), aggregatorServerIpPortAddr: c.AggregatorServerIpPortAddress, aggregatorRpcClient: aggregatorRpcClient, - checkpointTaskCreatedChan: make(chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated), - operatorSetUpdateChan: make(chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock), sfflServiceManagerAddr: common.HexToAddress(c.AVSRegistryCoordinatorAddress), operatorId: [32]byte{0}, // this is set below } @@ -256,15 +218,17 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { if err != nil { return nil, err } + operator.registerOperatorOnStartup(operatorEcdsaPrivateKey, common.HexToAddress(c.TokenStrategyAddr)) } // OperatorId is set in contract during registration so we get it after registering operator. - operatorId, err := sdkClients.AvsRegistryChainReader.GetOperatorId(&bind.CallOpts{}, operator.operatorAddr) + operatorId, err := avsManager.GetOperatorId(&bind.CallOpts{}, operator.operatorAddr) if err != nil { logger.Error("Cannot get operator id", "err", err) return nil, err } + operator.operatorId = operatorId logger.Info("Operator info", "operatorId", operatorId, @@ -283,16 +247,9 @@ func NewOperatorFromConfig(c types.NodeConfig) (*Operator, error) { } func (o *Operator) Start(ctx context.Context) error { - operatorIsRegistered, err := o.avsReader.IsOperatorRegistered(&bind.CallOpts{}, o.operatorAddr) - if err != nil { - o.logger.Error("Error checking if operator is registered", "err", err) + if err := o.avsManager.Start(ctx, o.operatorAddr); err != nil { return err } - if !operatorIsRegistered { - // We bubble the error all the way up instead of using logger.Fatal because logger.Fatal prints a huge stack trace - // that hides the actual error message. This error msg is more explicit and doesn't require showing a stack trace to the user. - return fmt.Errorf("operator is not registered. Registering operator using the operator-cli before starting operator") - } // TODO: hmm maybe remove start from attestor? if err := o.attestor.Start(ctx); err != nil { @@ -312,65 +269,55 @@ func (o *Operator) Start(ctx context.Context) error { metricsErrChan = make(chan error, 1) } - // TODO(samlaf): wrap this call with increase in avs-node-spec metric - newTasksSub, err := o.avsSubscriber.SubscribeToNewTasks(o.checkpointTaskCreatedChan) - if err != nil { - o.logger.Error("Error subscribing to new tasks", "err", err) - return err - } - - operatorSetUpdateSub, err := o.avsSubscriber.SubscribeToOperatorSetUpdates(o.operatorSetUpdateChan) - if err != nil { - o.logger.Error("Error subscribing to operator set updates", "err", err) - return err - } - + // TODO: decide who have a right to sign signedRootsC := o.attestor.GetSignedRootC() + checkpointTaskResponseChan := o.avsManager.GetCheckpointTaskResponseChan() + operatorSetUpdateChan := o.avsManager.GetOperatorSetUpdateChan() + for { select { case <-ctx.Done(): return o.Close() + case err := <-metricsErrChan: // TODO(samlaf); we should also register the service as unhealthy in the node api // https://eigen.nethermind.io/docs/spec/api/ o.logger.Fatal("Error in metrics server", "err", err) - case err := <-newTasksSub.Err(): - o.logger.Error("Error in websocket subscription", "err", err) - // TODO(samlaf): write unit tests to check if this fixed the issues we were seeing - newTasksSub.Unsubscribe() - // TODO(samlaf): wrap this call with increase in avs-node-spec metric - newTasksSub, err = o.avsSubscriber.SubscribeToNewTasks(o.checkpointTaskCreatedChan) - - // TODO: retry flow - if err != nil { - o.logger.Error("Error re-subscribing to new tasks", "err", err) + continue + + case signedStateRootUpdateMessage := <-signedRootsC: + go o.aggregatorRpcClient.SendSignedStateRootUpdateToAggregator(&signedStateRootUpdateMessage) + continue + + case checkpointTaskResponse, ok := <-checkpointTaskResponseChan: + if !ok { + o.logger.Info("Closing Operator") return o.Close() } - case checkpointTaskCreatedLog := <-o.checkpointTaskCreatedChan: + o.metrics.IncNumTasksReceived() - taskResponse := o.ProcessCheckpointTaskCreatedLog(checkpointTaskCreatedLog) - signedCheckpointTaskResponse, err := o.SignTaskResponse(taskResponse) + signedCheckpointTaskResponse, err := o.SignTaskResponse(&checkpointTaskResponse) if err != nil { + o.logger.Error("Failed to sign checkpoint task response", "checkpointTaskResponse", checkpointTaskResponse) continue } go o.aggregatorRpcClient.SendSignedCheckpointTaskResponseToAggregator(signedCheckpointTaskResponse) - case err := <-operatorSetUpdateSub.Err(): - o.logger.Error("Error in websocket subscription", "err", err) - operatorSetUpdateSub.Unsubscribe() - operatorSetUpdateSub, err = o.avsSubscriber.SubscribeToOperatorSetUpdates(o.operatorSetUpdateChan) + continue - // TODO: retry flow - if err != nil { - o.logger.Error("Error re-subscribing to operator set updates", "err", err) + case operatorSetUpdate, ok := <-operatorSetUpdateChan: + if !ok { + o.logger.Info("Closing Operator") return o.Close() } - case operatorSetUpdate := <-o.operatorSetUpdateChan: - go o.handleOperatorSetUpdate(ctx, operatorSetUpdate) - case signedStateRootUpdateMessage := <-signedRootsC: - go o.aggregatorRpcClient.SendSignedStateRootUpdateToAggregator(&signedStateRootUpdateMessage) + signedOperatorSetUpdate, err := SignOperatorSetUpdate(operatorSetUpdate, o.blsKeypair, o.operatorId) + if err != nil { + o.logger.Error("Failed to sign operator set update", "signedOperatorSetUpdate", signedOperatorSetUpdate) + continue + } + go o.aggregatorRpcClient.SendSignedOperatorSetUpdateToAggregator(signedOperatorSetUpdate) continue } } @@ -384,95 +331,117 @@ func (o *Operator) Close() error { return nil } -func (o *Operator) BlsPubkeyG1() *bls.G1Point { - return o.blsKeypair.GetPubKeyG1() -} - -// Takes a CheckpointTaskCreatedLog struct as input and returns a TaskResponseHeader struct. -// The TaskResponseHeader struct is the struct that is signed and sent to the contract as a task response. -func (o *Operator) ProcessCheckpointTaskCreatedLog(checkpointTaskCreatedLog *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated) *taskmanager.CheckpointTaskResponse { - o.logger.Debug("Received new task", "task", checkpointTaskCreatedLog) - o.logger.Info("Received new task", - "fromTimestamp", checkpointTaskCreatedLog.Task.FromTimestamp, - "toTimestamp", checkpointTaskCreatedLog.Task.ToTimestamp, - "taskIndex", checkpointTaskCreatedLog.TaskIndex, - "taskCreatedBlock", checkpointTaskCreatedLog.Task.TaskCreatedBlock, - "quorumNumbers", checkpointTaskCreatedLog.Task.QuorumNumbers, - "quorumThreshold", checkpointTaskCreatedLog.Task.QuorumThreshold, - ) - - // TODO: build SMT based on stored message agreements and update the test - - taskResponse := &taskmanager.CheckpointTaskResponse{ - ReferenceTaskIndex: checkpointTaskCreatedLog.TaskIndex, - StateRootUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - OperatorSetUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - } - return taskResponse -} - func (o *Operator) SignTaskResponse(taskResponse *taskmanager.CheckpointTaskResponse) (*coretypes.SignedCheckpointTaskResponse, error) { taskResponseHash, err := core.GetCheckpointTaskResponseDigest(taskResponse) if err != nil { o.logger.Error("Error getting task response header hash. skipping task (this is not expected and should be investigated)", "err", err) return nil, err } + blsSignature := o.blsKeypair.SignMessage(taskResponseHash) signedCheckpointTaskResponse := &coretypes.SignedCheckpointTaskResponse{ TaskResponse: *taskResponse, BlsSignature: *blsSignature, OperatorId: o.operatorId, } + o.logger.Debug("Signed task response", "signedCheckpointTaskResponse", signedCheckpointTaskResponse) return signedCheckpointTaskResponse, nil } -func (o *Operator) handleOperatorSetUpdate(ctx context.Context, data *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock) error { - operatorSetDelta, err := o.avsReader.GetOperatorSetUpdateDelta(ctx, data.Id) +func SignOperatorSetUpdate(message registryrollup.OperatorSetUpdateMessage, blsKeyPair *bls.KeyPair, operatorId bls.OperatorId) (*coretypes.SignedOperatorSetUpdateMessage, error) { + messageHash, err := core.GetOperatorSetUpdateMessageDigest(&message) if err != nil { - o.logger.Errorf("Couldn't get Operator set update delta: %v for block: %v", err, data.Id) - return err + return nil, err } - - operators := make([]registryrollup.OperatorsOperator, len(operatorSetDelta)) - for i := 0; i < len(operatorSetDelta); i++ { - operators[i] = registryrollup.OperatorsOperator{ - Pubkey: registryrollup.BN254G1Point{ - X: operatorSetDelta[i].Pubkey.X, - Y: operatorSetDelta[i].Pubkey.Y, - }, - Weight: operatorSetDelta[i].Weight, - } + signature := blsKeyPair.SignMessage(messageHash) + signedOperatorSetUpdate := coretypes.SignedOperatorSetUpdateMessage{ + Message: message, + OperatorId: operatorId, + BlsSignature: *signature, } - message := registryrollup.OperatorSetUpdateMessage{ - Id: data.Id, - Timestamp: data.Timestamp, - Operators: operators, + return &signedOperatorSetUpdate, nil +} + +func (o *Operator) RegisterOperatorWithAvs( + operatorEcdsaKeyPair *ecdsa.PrivateKey, +) error { + return o.avsManager.RegisterOperatorWithAvs(o.ethClient, operatorEcdsaKeyPair, o.blsKeypair) +} + +func (o *Operator) DepositIntoStrategy(strategyAddr common.Address, amount *big.Int) error { + return o.avsManager.DepositIntoStrategy(o.operatorAddr, strategyAddr, amount) +} + +func (o *Operator) RegisterOperatorWithEigenlayer() error { + return o.avsManager.RegisterOperatorWithEigenlayer(o.operatorAddr) +} + +type OperatorStatus struct { + EcdsaAddress string + // pubkey compendium related + PubkeysRegistered bool + G1Pubkey string + G2Pubkey string + // avs related + RegisteredWithAvs bool + OperatorId string +} + +func (o *Operator) PrintOperatorStatus() error { + fmt.Println("Printing operator status") + operatorId, err := o.avsManager.GetOperatorId(&bind.CallOpts{}, o.operatorAddr) + if err != nil { + return err } - signedMessage, err := SignOperatorSetUpdate(message, o.blsKeypair, o.operatorId) + pubkeysRegistered := operatorId != [32]byte{} + registeredWithAvs := o.operatorId != [32]byte{} + operatorStatus := OperatorStatus{ + EcdsaAddress: o.operatorAddr.String(), + PubkeysRegistered: pubkeysRegistered, + G1Pubkey: o.blsKeypair.GetPubKeyG1().String(), + G2Pubkey: o.blsKeypair.GetPubKeyG2().String(), + RegisteredWithAvs: registeredWithAvs, + OperatorId: hex.EncodeToString(o.operatorId[:]), + } + operatorStatusJson, err := json.MarshalIndent(operatorStatus, "", " ") if err != nil { - o.logger.Error("Couldn't sign operator set update message", "err", err) return err } - o.logger.Debug("Signed operator set update response", "signedMessage", signedMessage) - o.aggregatorRpcClient.SendSignedOperatorSetUpdateToAggregator(signedMessage) + fmt.Println(string(operatorStatusJson)) return nil } -func SignOperatorSetUpdate(message registryrollup.OperatorSetUpdateMessage, blsKeyPair *bls.KeyPair, operatorId bls.OperatorId) (*coretypes.SignedOperatorSetUpdateMessage, error) { - messageHash, err := core.GetOperatorSetUpdateMessageDigest(&message) +func (o *Operator) registerOperatorOnStartup( + operatorEcdsaPrivateKey *ecdsa.PrivateKey, + mockTokenStrategyAddr common.Address, +) { + err := o.RegisterOperatorWithEigenlayer() if err != nil { - return nil, err + // This error might only be that the operator was already registered with eigenlayer, so we don't want to fatal + o.logger.Error("Error registering operator with eigenlayer", "err", err) + } else { + o.logger.Infof("Registered operator with eigenlayer") } - signature := blsKeyPair.SignMessage(messageHash) - signedOperatorSetUpdate := coretypes.SignedOperatorSetUpdateMessage{ - Message: message, - OperatorId: operatorId, - BlsSignature: *signature, + + // TODO(samlaf): shouldn't hardcode number here + amount := big.NewInt(1000) + err = o.DepositIntoStrategy(mockTokenStrategyAddr, amount) + if err != nil { + o.logger.Fatal("Error depositing into strategy", "err", err) } + o.logger.Infof("Deposited %s into strategy %s", amount, mockTokenStrategyAddr) - return &signedOperatorSetUpdate, nil + err = o.avsManager.RegisterOperatorWithAvs(o.ethClient, operatorEcdsaPrivateKey, o.blsKeypair) + if err != nil { + o.logger.Fatal("Error registering operator with avs", "err", err) + } + o.logger.Infof("Registered operator with avs") +} + +func (o *Operator) BlsPubkeyG1() *bls.G1Point { + return o.blsKeypair.GetPubKeyG1() } diff --git a/operator/operator_test.go b/operator/operator_test.go index ce77bf49..fadabeb0 100644 --- a/operator/operator_test.go +++ b/operator/operator_test.go @@ -28,7 +28,7 @@ import ( ) func TestOperator(t *testing.T) { - operator, mockConsumer, err := createMockOperator() + operator, avsManager, mockConsumer, err := createMockOperator() assert.Nil(t, err) const taskIndex = 1 @@ -47,8 +47,8 @@ func TestOperator(t *testing.T) { }, Raw: types.Log{}, } - got := operator.ProcessCheckpointTaskCreatedLog(newTaskCreatedLog) - want := &taskmanager.CheckpointTaskResponse{ + got := avsManager.ProcessCheckpointTaskCreatedLog(newTaskCreatedLog) + want := taskmanager.CheckpointTaskResponse{ ReferenceTaskIndex: taskIndex, StateRootUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, OperatorSetUpdatesRoot: [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, @@ -138,23 +138,23 @@ func TestOperator(t *testing.T) { operator.aggregatorRpcClient = mockAggregatorRpcClient mockSubscriber := chainiomocks.NewMockAvsSubscriberer(mockCtrl) - mockSubscriber.EXPECT().SubscribeToNewTasks(operator.checkpointTaskCreatedChan).Return(event.NewSubscription(func(quit <-chan struct{}) error { + mockSubscriber.EXPECT().SubscribeToNewTasks(avsManager.checkpointTaskCreatedChan).Return(event.NewSubscription(func(quit <-chan struct{}) error { // loop forever <-quit return nil }), nil) - mockSubscriber.EXPECT().SubscribeToOperatorSetUpdates(operator.operatorSetUpdateChan).Return(event.NewSubscription(func(quit <-chan struct{}) error { + mockSubscriber.EXPECT().SubscribeToOperatorSetUpdates(avsManager.operatorSetUpdateChan).Return(event.NewSubscription(func(quit <-chan struct{}) error { // loop forever <-quit return nil }), nil) - operator.avsSubscriber = mockSubscriber + avsManager.avsSubscriber = mockSubscriber mockReader := chainiomocks.NewMockAvsReaderer(mockCtrl) mockReader.EXPECT().IsOperatorRegistered(gomock.Any(), operator.operatorAddr).Return(true, nil) mockReader.EXPECT().GetOperatorSetUpdateDelta(gomock.Any(), operatorSetUpdate.Id).Return(make([]opsetupdatereg.OperatorsOperator, 0), nil) - operator.avsReader = mockReader + avsManager.avsReader = mockReader ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -164,8 +164,8 @@ func TestOperator(t *testing.T) { assert.Nil(t, err) }() - operator.checkpointTaskCreatedChan <- newTaskCreatedEvent - operator.operatorSetUpdateChan <- operatorSetUpdate + avsManager.checkpointTaskCreatedChan <- newTaskCreatedEvent + avsManager.operatorSetUpdateChan <- operatorSetUpdate mockConsumer.MockReceiveBlockData(consumer.BlockData{ RollupId: signedStateRootUpdateMessage.Message.RollupId, Block: *block, diff --git a/operator/registration.go b/operator/registration.go deleted file mode 100644 index 6b401fcc..00000000 --- a/operator/registration.go +++ /dev/null @@ -1,183 +0,0 @@ -package operator - -// OUTDATED -// This file contains cli functions for registering an operator with the AVS and printing status -// However, all of this functionality has been moved to the plugin/ package -// we are just waiting for eigenlayer-cli to be open sourced so we can completely get rid of this registration functionality in the operator - -import ( - "context" - "crypto/ecdsa" - "encoding/hex" - "encoding/json" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - - "github.com/Layr-Labs/eigensdk-go/crypto/bls" - eigenSdkTypes "github.com/Layr-Labs/eigensdk-go/types" - - regcoord "github.com/Layr-Labs/eigensdk-go/contracts/bindings/RegistryCoordinator" -) - -func (o *Operator) registerOperatorOnStartup( - operatorEcdsaPrivateKey *ecdsa.PrivateKey, - mockTokenStrategyAddr common.Address, -) { - err := o.RegisterOperatorWithEigenlayer() - if err != nil { - // This error might only be that the operator was already registered with eigenlayer, so we don't want to fatal - o.logger.Error("Error registering operator with eigenlayer", "err", err) - } else { - o.logger.Infof("Registered operator with eigenlayer") - } - - // TODO(samlaf): shouldn't hardcode number here - amount := big.NewInt(1000) - err = o.DepositIntoStrategy(mockTokenStrategyAddr, amount) - if err != nil { - o.logger.Fatal("Error depositing into strategy", "err", err) - } - o.logger.Infof("Deposited %s into strategy %s", amount, mockTokenStrategyAddr) - - err = o.RegisterOperatorWithAvs(operatorEcdsaPrivateKey) - if err != nil { - o.logger.Fatal("Error registering operator with avs", "err", err) - } - o.logger.Infof("Registered operator with avs") -} - -func (o *Operator) RegisterOperatorWithEigenlayer() error { - op := eigenSdkTypes.Operator{ - Address: o.operatorAddr.String(), - EarningsReceiverAddress: o.operatorAddr.String(), - } - _, err := o.eigenlayerWriter.RegisterAsOperator(context.Background(), op) - if err != nil { - o.logger.Errorf("Error registering operator with eigenlayer") - return err - } - return nil -} - -func (o *Operator) DepositIntoStrategy(strategyAddr common.Address, amount *big.Int) error { - _, tokenAddr, err := o.eigenlayerReader.GetStrategyAndUnderlyingToken(&bind.CallOpts{}, strategyAddr) - if err != nil { - o.logger.Error("Failed to fetch strategy contract", "err", err) - return err - } - contractErc20Mock, err := o.avsReader.GetErc20Mock(context.Background(), tokenAddr) - if err != nil { - o.logger.Error("Failed to fetch ERC20Mock contract", "err", err) - return err - } - txOpts, err := o.avsWriter.TxMgr.GetNoSendTxOpts() - tx, err := contractErc20Mock.Mint(txOpts, o.operatorAddr, amount) - if err != nil { - o.logger.Errorf("Error assembling Mint tx") - return err - } - _, err = o.avsWriter.TxMgr.Send(context.Background(), tx) - if err != nil { - o.logger.Errorf("Error submitting Mint tx") - return err - } - - _, err = o.eigenlayerWriter.DepositERC20IntoStrategy(context.Background(), strategyAddr, amount) - if err != nil { - o.logger.Errorf("Error depositing into strategy", "err", err) - return err - } - return nil -} - -// Registration specific functions -func (o *Operator) RegisterOperatorWithAvs( - operatorEcdsaKeyPair *ecdsa.PrivateKey, -) error { - // hardcode these things for now - quorumNumbers := []byte{0} - socket := "Not Needed" - operatorToAvsRegistrationSigSalt := [32]byte{123} - curBlockNum, err := o.ethClient.BlockNumber(context.Background()) - if err != nil { - o.logger.Errorf("Unable to get current block number") - return err - } - curBlock, err := o.ethClient.BlockByNumber(context.Background(), big.NewInt(int64(curBlockNum))) - if err != nil { - o.logger.Errorf("Unable to get current block") - return err - } - sigValidForSeconds := int64(1_000_000) - operatorToAvsRegistrationSigExpiry := big.NewInt(int64(curBlock.Time()) + sigValidForSeconds) - _, err = o.avsWriter.RegisterOperatorInQuorumWithAVSRegistryCoordinator( - context.Background(), - operatorEcdsaKeyPair, operatorToAvsRegistrationSigSalt, operatorToAvsRegistrationSigExpiry, - o.blsKeypair, quorumNumbers, socket, - ) - if err != nil { - o.logger.Errorf("Unable to register operator with avs registry coordinator") - return err - } - o.logger.Infof("Registered operator with avs registry coordinator.") - - return nil -} - -// PRINTING STATUS OF OPERATOR: 1 -// operator address: 0xa0ee7a142d267c1f36714e4a8f75612f20a79720 -// dummy token balance: 0 -// delegated shares in dummyTokenStrat: 200 -// operator pubkey hash in AVS pubkey compendium (0 if not registered): 0x4b7b8243d970ff1c90a7c775c008baad825893ec6e806dfa5d3663dc093ed17f -// operator is opted in to eigenlayer: true -// operator is opted in to playgroundAVS (aka can be slashed): true -// operator status in AVS registry: REGISTERED -// -// operatorId: 0x4b7b8243d970ff1c90a7c775c008baad825893ec6e806dfa5d3663dc093ed17f -// middlewareTimesLen (# of stake updates): 0 -// -// operator is frozen: false -type OperatorStatus struct { - EcdsaAddress string - // pubkey compendium related - PubkeysRegistered bool - G1Pubkey string - G2Pubkey string - // avs related - RegisteredWithAvs bool - OperatorId string -} - -func (o *Operator) PrintOperatorStatus() error { - fmt.Println("Printing operator status") - operatorId, err := o.avsReader.GetOperatorId(&bind.CallOpts{}, o.operatorAddr) - if err != nil { - return err - } - pubkeysRegistered := operatorId != [32]byte{} - registeredWithAvs := o.operatorId != [32]byte{} - operatorStatus := OperatorStatus{ - EcdsaAddress: o.operatorAddr.String(), - PubkeysRegistered: pubkeysRegistered, - G1Pubkey: o.blsKeypair.GetPubKeyG1().String(), - G2Pubkey: o.blsKeypair.GetPubKeyG2().String(), - RegisteredWithAvs: registeredWithAvs, - OperatorId: hex.EncodeToString(o.operatorId[:]), - } - operatorStatusJson, err := json.MarshalIndent(operatorStatus, "", " ") - if err != nil { - return err - } - fmt.Println(string(operatorStatusJson)) - return nil -} - -func pubKeyG1ToBN254G1Point(p *bls.G1Point) regcoord.BN254G1Point { - return regcoord.BN254G1Point{ - X: p.X.BigInt(new(big.Int)), - Y: p.Y.BigInt(new(big.Int)), - } -} diff --git a/operator/registration_test.go b/operator/registration_test.go index f4c1e306..fbb71afd 100644 --- a/operator/registration_test.go +++ b/operator/registration_test.go @@ -1,7 +1,6 @@ package operator import ( - "github.com/NethermindEth/near-sffl/operator/mocks" "testing" "github.com/Layr-Labs/eigensdk-go/crypto/bls" @@ -10,8 +9,10 @@ import ( "github.com/stretchr/testify/assert" opsetupdatereg "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLOperatorSetUpdateRegistry" + registryrollup "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLRegistryRollup" taskmanager "github.com/NethermindEth/near-sffl/contracts/bindings/SFFLTaskManager" "github.com/NethermindEth/near-sffl/metrics" + "github.com/NethermindEth/near-sffl/operator/mocks" "github.com/NethermindEth/near-sffl/tests" ) @@ -26,35 +27,41 @@ var MOCK_OPERATOR_ID = [32]byte{207, 73, 226, 221, 104, 100, 123, 41, 192, 3, 9, func IntegrationTestOperatorRegistration(t *testing.T) { anvilCmd := tests.StartAnvilChainAndDeployContracts() defer anvilCmd.Process.Kill() - operator, _, err := createMockOperator() + operator, _, _, err := createMockOperator() assert.Nil(t, err) err = operator.RegisterOperatorWithEigenlayer() assert.Nil(t, err) } -func createMockOperator() (*Operator, *mocks.MockConsumer, error) { +func createMockOperator() (*Operator, *AvsManager, *mocks.MockConsumer, error) { logger := sdklogging.NewNoopLogger() reg := prometheus.NewRegistry() noopMetrics := metrics.NewNoopMetrics() blsPrivateKey, err := bls.NewPrivateKey(MOCK_OPERATOR_BLS_PRIVATE_KEY) if err != nil { - return nil, nil, err + return nil, nil, nil, err } operatorKeypair := bls.NewKeyPair(blsPrivateKey) mockAttestor := mocks.NewMockAttestor(operatorKeypair, MOCK_OPERATOR_ID) + avsManager := &AvsManager{ + logger: logger, + checkpointTaskCreatedChan: make(chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated), + operatorSetUpdateChan: make(chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock), + checkpointTaskResponseCreatedChan: make(chan taskmanager.CheckpointTaskResponse), + operatorSetUpdateMessageChan: make(chan registryrollup.OperatorSetUpdateMessage), + } operator := &Operator{ - logger: logger, - blsKeypair: operatorKeypair, - metricsReg: reg, - metrics: noopMetrics, - checkpointTaskCreatedChan: make(chan *taskmanager.ContractSFFLTaskManagerCheckpointTaskCreated), - operatorSetUpdateChan: make(chan *opsetupdatereg.ContractSFFLOperatorSetUpdateRegistryOperatorSetUpdatedAtBlock), - operatorId: MOCK_OPERATOR_ID, - attestor: mockAttestor, + logger: logger, + blsKeypair: operatorKeypair, + metricsReg: reg, + metrics: noopMetrics, + operatorId: MOCK_OPERATOR_ID, + attestor: mockAttestor, + avsManager: avsManager, } - return operator, mockAttestor.MockGetConsumer(), nil + return operator, avsManager, mockAttestor.MockGetConsumer(), nil } diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 66dac31d..88bf5a17 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -117,7 +117,7 @@ func TestIntegration(t *testing.T) { <-ctx.Done() } -type TestEnv struct { +type testEnv struct { mainnetAnvil *AnvilInstance rollupAnvils []*AnvilInstance rabbitMq *rabbitmq.RabbitMQContainer @@ -130,7 +130,7 @@ type TestEnv struct { registryRollupAuths []*bind.TransactOpts } -func setupTestEnv(t *testing.T, ctx context.Context) *TestEnv { +func setupTestEnv(t *testing.T, ctx context.Context) *testEnv { containersCtx, cancelContainersCtx := context.WithCancel(context.Background()) mainnetAnvil := startAnvilTestContainer(t, containersCtx, "8545", "1", true) @@ -180,7 +180,7 @@ func setupTestEnv(t *testing.T, ctx context.Context) *TestEnv { cancelContainersCtx() }) - return &TestEnv{ + return &testEnv{ mainnetAnvil: mainnetAnvil, rollupAnvils: rollupAnvils, rabbitMq: rabbitMq,