diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b52a7da --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# Mandatory +AVS_SYNC_PRIVATE_KEY= +AVS_SYNC_REGISTRY_COORDINATOR_ADDR=0xd19d750531c5e95CcC5C9610bF119dDe3008A16D +AVS_SYNC_OPERATOR_STATE_RETRIEVER_ADDR=0x1b41CA79b86295e77Dd49f28DbB000286c022dfd +AVS_SYNC_ETH_HTTP_URL=http://localhost:8545 +AVS_SYNC_SYNC_INTERVAL=24h + +# Optional +AVS_SYNC_FIRST_SYNC_TIME=00:00:00 # this will make it run at midnight diff --git a/Makefile b/Makefile index 3570cb0..452505b 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,18 @@ help: @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +# Currently exporting the .env vars directly in the commands that need them +# see for eg. the run-avs-sync command +# Uncommenting the two lines below will export the .env vars for all commands +# include .env +# export + start-anvil-goerli-fork: ## anvil --fork-url https://goerli.gateway.tenderly.co run-avs-sync: ## - ./start.sh + @# we export the env vars from .env file and then run the go program + @set -a; . .env; set +a; go run . test: ## go test ./... diff --git a/avssync.go b/avssync.go index 7477a2e..32f081b 100644 --- a/avssync.go +++ b/avssync.go @@ -56,8 +56,17 @@ func NewAvsSync( } 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, convertQuorumsBytesToInts(a.quorums), a.fetchQuorumsDynamically, a.readerTimeoutDuration, a.writerTimeoutDuration) + // TODO: should prob put all of these in a config struct, to make sure we don't forget to print any of them + // when we add new ones. + a.logger.Info("Avssync config", + "sleepBeforeFirstSyncDuration", a.sleepBeforeFirstSyncDuration, + "syncInterval", a.syncInterval, + "operators", a.operators, + "quorums", a.quorums, + "fetchQuorumsDynamically", a.fetchQuorumsDynamically, + "readerTimeoutDuration", a.readerTimeoutDuration, + "writerTimeoutDuration", a.writerTimeoutDuration, + ) // ticker doesn't tick immediately, so we send a first updateStakes here // see https://github.com/golang/go/issues/17601 @@ -83,7 +92,7 @@ func (a *AvsSync) Start() { if err != nil { a.logger.Error("Error updating stakes", err) } - a.logger.Infof("Sleeping for %s\n", a.syncInterval) + a.logger.Infof("Sleeping for %s", a.syncInterval) } } @@ -98,7 +107,7 @@ func (a *AvsSync) updateStakes() error { for _, quorum := range a.quorums { a.tryNTimesUpdateStakesOfEntireOperatorSetForQuorum(quorum, a.retrySyncNTimes) } - a.logger.Info("Completed stake update successfully") + a.logger.Info("Completed stake update. Check logs to make sure every quorum update succeeded successfully.") return nil } else { a.logger.Infof("Updating stakes of operators: %v", a.operators) @@ -139,9 +148,11 @@ func (a *AvsSync) tryNTimesUpdateStakesOfEntireOperatorSetForQuorum(quorum byte, timeoutCtx, cancel := context.WithTimeout(context.Background(), a.readerTimeoutDuration) defer cancel() + // we need to refetch the operator set because one reason for update stakes failing is that the operator set has changed + // in between us fetching it and trying to update it (the contract makes sure the entire operator set is updated and reverts if not) operatorAddrsPerQuorum, err := a.avsReader.GetOperatorAddrsInQuorumsAtCurrentBlock(&bind.CallOpts{Context: timeoutCtx}, []byte{quorum}) if err != nil { - a.logger.Error("Error fetching operator addresses in quorums", "err", err) + a.logger.Error("Error fetching operator addresses in quorums", "err", err, "quorum", quorum, "retryNTimes", retryNTimes, "try", i+1) continue } var operators []common.Address diff --git a/flags.go b/flags.go index e208ec3..464a08a 100644 --- a/flags.go +++ b/flags.go @@ -42,12 +42,11 @@ var ( EnvVar: envVarPrefix + "SYNC_INTERVAL", } /* Optional Flags */ - SleepBeforeFirstSyncDurationFlag = cli.DurationFlag{ - Name: "sleep-before-first-sync-duration", + FirstSyncTimeFlag = cli.StringFlag{ + Name: "first-sync-time", Required: false, - Value: 0, - Usage: "sleep for `SECONDS` before first sync (default=0)", - EnvVar: envVarPrefix + "SLEEP_BEFORE_FIRST_SYNC_DURATION", + Usage: "Set the HH:MI:SS time at which to run the first sync update", + EnvVar: envVarPrefix + "FIRST_SYNC_TIME", } OperatorListFlag = cli.StringSliceFlag{ Name: "operators", @@ -59,9 +58,9 @@ var ( Usage: "List of quorums to update stakes for (only needs to be present if operators list not present and fetch-quorums-dynamically is false)", EnvVar: envVarPrefix + "QUORUMS", } - FetchQuorumDynamicallyFlag = cli.BoolFlag{ + FetchQuorumDynamicallyFlag = cli.BoolTFlag{ Name: "fetch-quorums-dynamically", - Usage: "If set to true, will fetch the list of quorums registered in the contract and update all of them", + Usage: "If set to true (default), will fetch the list of quorums registered in the contract and update all of them", EnvVar: envVarPrefix + "FETCH_QUORUMS_DYNAMICALLY", } ReaderTimeoutDurationFlag = cli.DurationFlag{ @@ -93,7 +92,7 @@ var RequiredFlags = []cli.Flag{ } var OptionalFlags = []cli.Flag{ - SleepBeforeFirstSyncDurationFlag, + FirstSyncTimeFlag, OperatorListFlag, QuorumListFlag, FetchQuorumDynamicallyFlag, diff --git a/main.go b/main.go index 3605222..d207996 100644 --- a/main.go +++ b/main.go @@ -98,11 +98,30 @@ func avsSyncMain(cliCtx *cli.Context) error { for _, quorum := range cliCtx.IntSlice(QuorumListFlag.Name) { quorums = append(quorums, byte(quorum)) } + + firstSyncTimeStr := cliCtx.String(FirstSyncTimeFlag.Name) + var sleepBeforeFirstSyncDuration time.Duration + if firstSyncTimeStr == "" { + sleepBeforeFirstSyncDuration = 0 * time.Second + } else { + now := time.Now() + firstSyncTime, err := time.Parse(time.TimeOnly, firstSyncTimeStr) + firstSyncTime = time.Date(now.Year(), now.Month(), now.Day(), firstSyncTime.Hour(), firstSyncTime.Minute(), firstSyncTime.Second(), 0, now.Location()) + if err != nil { + return err + } + if now.After(firstSyncTime) { + // If the set time is before the current time, add a day to the set time + firstSyncTime = firstSyncTime.Add(24 * time.Hour) + } + sleepBeforeFirstSyncDuration = firstSyncTime.Sub(now) + } + logger.Infof("Sleeping for %v before first sync, so that it happens at %v", sleepBeforeFirstSyncDuration, time.Now().Add(sleepBeforeFirstSyncDuration)) avsSync := NewAvsSync( logger, avsReader, avsWriter, - cliCtx.Duration(SleepBeforeFirstSyncDurationFlag.Name), + sleepBeforeFirstSyncDuration, cliCtx.Duration(SyncIntervalFlag.Name), operators, quorums, diff --git a/start.sh b/start.sh deleted file mode 100755 index e79dfb0..0000000 --- a/start.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -source .env - -go run . \ - --ecdsa-private-key $PRIVATE_KEY \ - --eth-http-url $ETH_HTTP_URL \ - --registry-coordinator-addr $REGISTRY_COORDINATOR_ADDR \ - --operator-state-retriever-addr $OPERATOR_STATE_RETRIEVER_ADDR \ - --sync-interval $SYNC_INTERVAL