diff --git a/avssync.go b/avssync.go index adda2cc..63adf8b 100644 --- a/avssync.go +++ b/avssync.go @@ -59,10 +59,6 @@ func (a *AvsSync) Start() { a.logger.Infof("Starting avs sync with sleepBeforeFirstSyncDuration=%s, syncInterval=%s, operators=%v, quorums=%v, fetchQuorumsDynamically=%v, readerTimeoutDuration=%s, writerTimeoutDuration=%s", a.sleepBeforeFirstSyncDuration, a.syncInterval, a.operators, a.quorums, a.fetchQuorumsDynamically, a.readerTimeoutDuration, a.writerTimeoutDuration) - // run something every syncInterval - ticker := time.NewTicker(a.syncInterval) - defer ticker.Stop() - // ticker doesn't tick immediately, so we send a first updateStakes here // see https://github.com/golang/go/issues/17601 // we first sleep some amount of time before the first sync, which allows the syncs to happen at some preferred time @@ -73,6 +69,15 @@ func (a *AvsSync) Start() { a.logger.Error("Error updating stakes", err) } + if a.syncInterval == 0 { + a.logger.Infof("Sync interval is 0, running updateStakes once and exiting") + return // only run once + } + + // update stakes every syncInterval + ticker := time.NewTicker(a.syncInterval) + defer ticker.Stop() + for range ticker.C { err := a.updateStakes() if err != nil { diff --git a/flags.go b/flags.go index 68b8d42..e208ec3 100644 --- a/flags.go +++ b/flags.go @@ -37,7 +37,7 @@ var ( SyncIntervalFlag = cli.DurationFlag{ Name: "sync-interval", Required: true, - Usage: "call updateStakes function at every `TIME` interval", + Usage: "Interval at which to sync with the chain (e.g. 24h). If set to 0, will only sync once and then exit.", Value: 24 * time.Hour, EnvVar: envVarPrefix + "SYNC_INTERVAL", } diff --git a/integration_test.go b/integration_test.go index 00824ed..64c09dc 100644 --- a/integration_test.go +++ b/integration_test.go @@ -61,7 +61,7 @@ func TestIntegrationUpdateSingleOperatorPath(t *testing.T) { } operatorAddr := crypto.PubkeyToAddress(operatorEcdsaPrivKey.PublicKey) operatorBlsPrivKey := "0x1" - avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{operatorAddr}) + avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{operatorAddr}, 30*time.Second) // first register operator into avs. at this point, the operator will have whatever stake it had registered in eigenlayer in the avs registerOperatorWithAvs(anvilHttpEndpoint, contractAddresses, operatorEcdsaPrivKeyHex, operatorBlsPrivKey) @@ -117,7 +117,7 @@ func TestIntegrationFullOperatorSet(t *testing.T) { } operatorAddr := crypto.PubkeyToAddress(operatorEcdsaPrivKey.PublicKey) operatorBlsPrivKey := "0x1" - avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{}) + avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{}, 30*time.Second) // first register operator into avs. at this point, the operator will have whatever stake it had registered in eigenlayer in the avs registerOperatorWithAvs(anvilHttpEndpoint, contractAddresses, operatorEcdsaPrivKeyHex, operatorBlsPrivKey) @@ -179,7 +179,7 @@ func TestIntegrationFullOperatorSetWithRetry(t *testing.T) { // we create avs sync and replace its avsWriter with a mock that will fail the first 2 times we call UpdateStakesOfEntireOperatorSetForQuorums // and succeed on the third time - avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{}) + avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{}, 30*time.Second) mockCtrl := gomock.NewController(t) mockAvsRegistryWriter := chainiomocks.NewMockAvsRegistryWriter(mockCtrl) // this is the test. we just make sure this is called 3 times @@ -194,7 +194,59 @@ func TestIntegrationFullOperatorSetWithRetry(t *testing.T) { } -func NewTestAvsSync(anvilHttpEndpoint string, contractAddresses ContractAddresses, operators []common.Address) *AvsSync { +func TestSingleRun(t *testing.T) { + /* Start the anvil chain */ + anvilC := startAnvilTestContainer() + // Not sure why but deferring anvilC.Terminate() causes a panic when the test finishes... + // so letting it terminate silently for now + anvilHttpEndpoint, err := anvilC.Endpoint(context.Background(), "http") + if err != nil { + t.Fatal(err) + } + + contractAddresses := getContractAddressesFromContractRegistry(anvilHttpEndpoint) + operatorEcdsaPrivKeyHex := "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + operatorEcdsaPrivKey, err := crypto.HexToECDSA(operatorEcdsaPrivKeyHex) + if err != nil { + t.Fatal(err) + } + operatorAddr := crypto.PubkeyToAddress(operatorEcdsaPrivKey.PublicKey) + operatorBlsPrivKey := "0x1" + // set sync interval to 0 so that we only run once + avsSync := NewTestAvsSync(anvilHttpEndpoint, contractAddresses, []common.Address{}, 0) + + // first register operator into avs. at this point, the operator will have whatever stake it had registered in eigenlayer in the avs + registerOperatorWithAvs(anvilHttpEndpoint, contractAddresses, operatorEcdsaPrivKeyHex, operatorBlsPrivKey) + + // get stake of operator before sync + operatorsPerQuorumBeforeSync, err := avsSync.avsReader.GetOperatorsStakeInQuorumsAtCurrentBlock(&bind.CallOpts{}, []byte{0}) + if err != nil { + t.Fatal(err) + } + // TODO: should be checking all operators, not just the first one + operatorStakeBeforeSync := operatorsPerQuorumBeforeSync[0][0].Stake + + // deposit into strategy to create a diff between eigenlayer and avs stakes + depositAmount := big.NewInt(100) + depositErc20IntoStrategyForOperator(anvilHttpEndpoint, contractAddresses.DelegationManager, contractAddresses.Erc20MockStrategy, operatorEcdsaPrivKeyHex, operatorAddr.Hex(), depositAmount) + + avsSync.Start() + + // get stake of operator after sync + operatorsPerQuorumAfterSync, err := avsSync.avsReader.GetOperatorsStakeInQuorumsAtCurrentBlock(&bind.CallOpts{}, []byte{0}) + if err != nil { + t.Fatal(err) + } + operatorStakeAfterSync := operatorsPerQuorumAfterSync[0][0].Stake + operatorStakeDiff := new(big.Int).Sub(operatorStakeAfterSync, operatorStakeBeforeSync) + + // we just check that the diff is equal to the deposited amount + if operatorStakeDiff.Cmp(depositAmount) != 0 { + t.Errorf("expected operator stake diff to be equal to deposit amount, got %v", operatorStakeDiff) + } +} + +func NewTestAvsSync(anvilHttpEndpoint string, contractAddresses ContractAddresses, operators []common.Address, syncInterval time.Duration) *AvsSync { logger, err := logging.NewZapLogger(logging.Development) if err != nil { panic(err) @@ -252,7 +304,7 @@ func NewTestAvsSync(anvilHttpEndpoint string, contractAddresses ContractAddresse avsReader, avsWriter, 0*time.Second, - 30*time.Second, + syncInterval, operators, // we only test with one quorum []byte{0}, diff --git a/main.go b/main.go index 7ef4d16..3605222 100644 --- a/main.go +++ b/main.go @@ -111,7 +111,7 @@ func avsSyncMain(cliCtx *cli.Context) error { cliCtx.Duration(ReaderTimeoutDurationFlag.Name), cliCtx.Duration(WriterTimeoutDurationFlag.Name), ) - avsSync.Start() + avsSync.Start() return nil }