Skip to content

Commit

Permalink
app: run flow only on slot modulo (#61)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
gsora authored Mar 19, 2024
1 parent 307cd93 commit fbaf9eb
Showing 1 changed file with 54 additions and 2 deletions.
56 changes: 54 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ package app

import (
"context"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"math"
"math/big"
"os"
"path/filepath"
"time"
Expand Down Expand Up @@ -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, &eth2api.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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
}
}
}

0 comments on commit fbaf9eb

Please sign in to comment.