From fbaf9eb431538cfeacf34301cd99303d9799f9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gianguido=20Sor=C3=A0?= Date: Tue, 19 Mar 2024 12:53:58 +0100 Subject: [PATCH] app: run flow only on slot modulo (#61) * app: run flow only on slot modulo Define a random slot modulo based on the SLOTS_PER_EPOCH value, and run Obol API interactions only when slot % slotModulo == 0. This should lower the load on Obol API by staggering access across all lido-dv-exit instances. * cleanup --- app/app.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/app/app.go b/app/app.go index 9b99836..c484e11 100644 --- a/app/app.go +++ b/app/app.go @@ -4,10 +4,12 @@ package app import ( "context" + "crypto/rand" "encoding/hex" "encoding/json" "fmt" "math" + "math/big" "os" "path/filepath" "time" @@ -108,6 +110,28 @@ func Run(ctx context.Context, config Config) error { return errors.Wrap(err, "can't subscribe to slot") } + specResp, err := bnClient.Spec(ctx, ð2api.SpecOpts{}) + if err != nil { + return errors.Wrap(err, "cannot fetch genesis spec") + } + + rawSlotsPerEpoch, ok := specResp.Data["SLOTS_PER_EPOCH"] + if !ok { + return errors.Wrap(err, "spec field SLOTS_PER_EPOCH not found in spec") + } + + slotPerEpoch, ok := rawSlotsPerEpoch.(uint64) + if !ok { + return errors.Wrap(err, "spec field SLOTS_PER_EPOCH is not uint64") + } + + // define slot modulo + // when slot % slotModulo == 0, execution of the main loop will run + slotModulo, err := safeRand(slotPerEpoch + 1) + if err != nil { + return errors.Wrap(err, "can't get random number") + } + oAPI := obolapi.Client{ObolAPIUrl: config.ObolAPIURL} var signedExits []obolapi.ExitBlob @@ -136,8 +160,15 @@ func Run(ctx context.Context, config Config) error { break // we finished signing everything we had to sign } - if !slot.FirstInEpoch() { - log.Debug(ctx, "Slot not first in epoch, not doing anything", z.U64("epoch", slot.Epoch()), z.U64("slot", slot.Slot)) + if !(slot.Slot%slotModulo == 0) { + log.Debug( + ctx, + "Slot not in modulo", + z.U64("epoch", slot.Epoch()), + z.U64("modulo", slotModulo), + z.U64("slot", slot.Slot), + ) + continue } @@ -444,3 +475,24 @@ func loadExistingValidatorExits(ejectorPath string) (map[eth2p0.ValidatorIndex]s return ret, nil } + +// safeRand returns a random uint64 from 1 to max, using crypto/rand as a source. +func safeRand(max uint64) (uint64, error) { + bigMax := big.NewInt(int64(max)) + zero := big.NewInt(0) + + for { + candidate, err := rand.Int(rand.Reader, bigMax) + if err != nil { + //nolint:wrapcheck // will wrap in outer field + return 0, err + } + + // -1 if x < y + // 0 if x == y + // +1 if x > y + if candidate.Cmp(zero) == 1 { + return candidate.Uint64(), nil + } + } +}