Skip to content

Commit

Permalink
app/eth2wrap: deep validator cache (#3114)
Browse files Browse the repository at this point in the history
Implement a validator cache that caches the complete Validators endpoint request.

Short-cut the duty selection for the DV validator set to the known-active, cached list instead: this will reduce the load on the beacon node drastically, since before this change we were calling the BN once every slot.

Try to reduce at a minimum upstream BN requests when the VC calls for validator state.

All Validators() calls to the upstream BN, if greater than 200 validators, will have an exponential timeout calculated as `(50ms * validator amount)`.

category: feature
ticket: none
  • Loading branch information
gsora authored and KaloyanTanev committed Jun 5, 2024
1 parent af7a3e0 commit 6ce6550
Show file tree
Hide file tree
Showing 18 changed files with 342 additions and 93 deletions.
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func wireCoreWorkflow(ctx context.Context, life *lifecycle.Manager, conf Config,
return nil
}
valCache.Trim()
_, err := valCache.Get(ctx)
_, _, err := valCache.Get(ctx)

Check warning on line 430 in app/app.go

View check run for this annotation

Codecov / codecov/patch

app/app.go#L430

Added line #L430 was not covered by tests

return err
})
Expand Down
6 changes: 3 additions & 3 deletions app/eth2wrap/eth2wrap_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/eth2wrap/genwrap/genwrap.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 46 additions & 3 deletions app/eth2wrap/httpwrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import (
"testing"
"time"

"github.com/attestantio/go-eth2-client/api"
apiv1 "github.com/attestantio/go-eth2-client/api/v1"
eth2http "github.com/attestantio/go-eth2-client/http"
eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0"

Expand Down Expand Up @@ -67,15 +69,15 @@ type httpAdapter struct {
address string
timeout time.Duration
valCacheMu sync.RWMutex
valCache func(context.Context) (ActiveValidators, error)
valCache func(context.Context) (ActiveValidators, CompleteValidators, error)
forkVersion [4]byte
}

func (h *httpAdapter) SetForkVersion(forkVersion [4]byte) {
h.forkVersion = forkVersion
}

func (h *httpAdapter) SetValidatorCache(valCache func(context.Context) (ActiveValidators, error)) {
func (h *httpAdapter) SetValidatorCache(valCache func(context.Context) (ActiveValidators, CompleteValidators, error)) {

Check warning on line 80 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L80

Added line #L80 was not covered by tests
h.valCacheMu.Lock()
h.valCache = valCache
h.valCacheMu.Unlock()
Expand All @@ -89,7 +91,48 @@ func (h *httpAdapter) ActiveValidators(ctx context.Context) (ActiveValidators, e
return nil, errors.New("no active validator cache")
}

return h.valCache(ctx)
active, _, err := h.valCache(ctx)

return active, err

Check warning on line 96 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L94-L96

Added lines #L94 - L96 were not covered by tests
}

func (h *httpAdapter) CompleteValidators(ctx context.Context) (CompleteValidators, error) {
h.valCacheMu.RLock()
defer h.valCacheMu.RUnlock()

if h.valCache == nil {
return nil, errors.New("no active validator cache")
}

Check warning on line 105 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L99-L105

Added lines #L99 - L105 were not covered by tests

_, complete, err := h.valCache(ctx)

return complete, err

Check warning on line 109 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L107-L109

Added lines #L107 - L109 were not covered by tests
}

// Validators returns the validators as requested in opts.
// If the amount of validators requested is greater than 200, exponentially increase the timeout: on crowded testnets
// this HTTP call takes a long time.
func (h *httpAdapter) Validators(ctx context.Context, opts *api.ValidatorsOpts) (
*api.Response[map[eth2p0.ValidatorIndex]*apiv1.Validator],
error,
) {
var cancel func()
reqCtx := ctx

maxValAmt := max(len(opts.PubKeys), len(opts.Indices))

if maxValAmt > 200 {
reqTimeout := time.Duration(50*maxValAmt) * time.Millisecond
reqCtx, cancel = context.WithTimeout(reqCtx, reqTimeout)
}

Check warning on line 127 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L118-L127

Added lines #L118 - L127 were not covered by tests

defer func() {
if cancel != nil {
cancel()
}

Check warning on line 132 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L129-L132

Added lines #L129 - L132 were not covered by tests
}()

return h.Service.Validators(reqCtx, opts)

Check warning on line 135 in app/eth2wrap/httpwrap.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/httpwrap.go#L135

Added line #L135 was not covered by tests
}

// AggregateBeaconCommitteeSelections implements eth2exp.BeaconCommitteeSelectionAggregator.
Expand Down
13 changes: 11 additions & 2 deletions app/eth2wrap/lazy.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ type lazy struct {

clientMu sync.RWMutex
client Client
valCache func(context.Context) (ActiveValidators, error)
valCache func(context.Context) (ActiveValidators, CompleteValidators, error)
}

// getClient returns the client and true if it is available.
Expand Down Expand Up @@ -146,7 +146,16 @@ func (l *lazy) ActiveValidators(ctx context.Context) (ActiveValidators, error) {
return cl.ActiveValidators(ctx)
}

func (l *lazy) SetValidatorCache(valCache func(context.Context) (ActiveValidators, error)) {
func (l *lazy) CompleteValidators(ctx context.Context) (CompleteValidators, error) {
cl, err := l.getOrCreateClient(ctx)
if err != nil {
return nil, err
}

Check warning on line 153 in app/eth2wrap/lazy.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/lazy.go#L149-L153

Added lines #L149 - L153 were not covered by tests

return cl.CompleteValidators(ctx)

Check warning on line 155 in app/eth2wrap/lazy.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/lazy.go#L155

Added line #L155 was not covered by tests
}

func (l *lazy) SetValidatorCache(valCache func(context.Context) (ActiveValidators, CompleteValidators, error)) {
l.clientMu.Lock()
l.valCache = valCache
l.clientMu.Unlock()
Expand Down
4 changes: 2 additions & 2 deletions app/eth2wrap/lazy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ func TestLazy_ActiveValidators(t *testing.T) {
}

func TestLazy_SetValidatorCache(t *testing.T) {
valCache := func(context.Context) (eth2wrap.ActiveValidators, error) {
return nil, nil
valCache := func(context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error) {
return nil, nil, nil
}

client := mocks.NewClient(t)
Expand Down
34 changes: 32 additions & 2 deletions app/eth2wrap/mocks/client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 19 additions & 1 deletion app/eth2wrap/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (m multi) IsSynced() bool {
return false
}

func (m multi) SetValidatorCache(valCache func(context.Context) (ActiveValidators, error)) {
func (m multi) SetValidatorCache(valCache func(context.Context) (ActiveValidators, CompleteValidators, error)) {
for _, cl := range m.clients {
cl.SetValidatorCache(valCache)
}
Expand All @@ -97,6 +97,24 @@ func (m multi) ActiveValidators(ctx context.Context) (ActiveValidators, error) {
return res0, err
}

func (m multi) CompleteValidators(ctx context.Context) (CompleteValidators, error) {
const label = "complete_validators"
// No latency since this is a cached endpoint.

res0, err := provide(ctx, m.clients,
func(ctx context.Context, cl Client) (CompleteValidators, error) {
return cl.CompleteValidators(ctx)
},

Check warning on line 107 in app/eth2wrap/multi.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/multi.go#L100-L107

Added lines #L100 - L107 were not covered by tests
nil, nil,
)
if err != nil {
incError(label)
err = wrapError(ctx, err, label)
}

Check warning on line 113 in app/eth2wrap/multi.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/multi.go#L110-L113

Added lines #L110 - L113 were not covered by tests

return res0, err

Check warning on line 115 in app/eth2wrap/multi.go

View check run for this annotation

Codecov / codecov/patch

app/eth2wrap/multi.go#L115

Added line #L115 was not covered by tests
}

func (m multi) ProposerConfig(ctx context.Context) (*eth2exp.ProposerConfigResponse, error) {
const label = "proposer_config"
defer latency(label)()
Expand Down
4 changes: 2 additions & 2 deletions app/eth2wrap/multi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,8 @@ func TestMulti_ActiveValidators(t *testing.T) {
}

func TestMulti_SetValidatorCache(t *testing.T) {
valCache := func(context.Context) (eth2wrap.ActiveValidators, error) {
return nil, nil
valCache := func(context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error) {
return nil, nil, nil
}

client := mocks.NewClient(t)
Expand Down
2 changes: 1 addition & 1 deletion app/eth2wrap/synthproposer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const (
)

type synthProposerEth2Provider interface {
ActiveValidatorsProvider
CachedValidatorsProvider
eth2client.SlotsPerEpochProvider
eth2client.ProposerDutiesProvider
}
Expand Down
8 changes: 4 additions & 4 deletions app/eth2wrap/synthproposer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ func TestSynthProposer(t *testing.T) {
},
}, nil
}
cached := bmock.ActiveValidatorsFunc
bmock.ActiveValidatorsFunc = func(ctx context.Context) (eth2wrap.ActiveValidators, error) {
cached := bmock.CachedValidatorsFunc
bmock.CachedValidatorsFunc = func(ctx context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error) {
activeVals++
return cached(ctx)
}
Expand Down Expand Up @@ -195,8 +195,8 @@ func TestSynthProposerBlockNotFound(t *testing.T) {
},
}, nil
}
cached := bmock.ActiveValidatorsFunc
bmock.ActiveValidatorsFunc = func(ctx context.Context) (eth2wrap.ActiveValidators, error) {
cached := bmock.CachedValidatorsFunc
bmock.CachedValidatorsFunc = func(ctx context.Context) (eth2wrap.ActiveValidators, eth2wrap.CompleteValidators, error) {
activeVals++
return cached(ctx)
}
Expand Down
Loading

0 comments on commit 6ce6550

Please sign in to comment.