Work for Altair.

This commit is contained in:
Jim McDonald 2021-07-18 07:49:38 +01:00
parent 2eba0ed622
commit 34d6df9312
No known key found for this signature in database
GPG Key ID: 89CEB61B2AD2A5E7
59 changed files with 4325 additions and 936 deletions

View File

@ -4,6 +4,12 @@
- fetch wallet accounts from Dirk in parallel
- fetch process-concurrency configuration value from most specific point in hierarchy
- add metrics to track strategy operation results
- support Altair:
- support updated `go-eth2-client` for versioned data
- manage sync committee operations:
- generate sync committee messages
- act as sync committee aggregator as required
- added metrics to track strategy operation results
- provide release metric in `vouch_release`
- provide ready metric in `vouch_ready`
- handle chain reorganisations, updating duties as appropriate

5
go.mod
View File

@ -9,12 +9,13 @@ require (
github.com/aws/aws-sdk-go v1.38.30
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/opentracing/opentracing-go v1.2.0
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/procfs v0.6.0 // indirect
github.com/prysmaticlabs/go-bitfield v0.0.0-20210202205921-7fcea7c45dc8
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d
github.com/rs/zerolog v1.21.0
github.com/sasha-s/go-deadlock v0.2.0
github.com/sirupsen/logrus v1.6.0
@ -39,3 +40,5 @@ require (
google.golang.org/grpc v1.38.0
gotest.tools v2.2.0+incompatible
)
replace github.com/attestantio/go-eth2-client => ../go-eth2-client

7
go.sum
View File

@ -226,6 +226,8 @@ github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8l
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -403,6 +405,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@ -482,6 +486,8 @@ github.com/prysmaticlabs/go-bitfield v0.0.0-20200322041314-62c2aee71669/go.mod h
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210202205921-7fcea7c45dc8 h1:18+Qqobq3HAUY0hgIhPGSqmLFnaLLocemmU7+Sj2aYQ=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210202205921-7fcea7c45dc8/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d h1:46gKr69IlRpv/ENdlzG0SWo5nMLKJxS3tI5NOSdZndQ=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/r3labs/sse/v2 v2.3.0 h1:R/UMa0ML6AYKQ8irQNHhY+204lz1LytDIdKhCxSVAd8=
github.com/r3labs/sse/v2 v2.3.0/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@ -542,6 +548,7 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

98
main.go
View File

@ -56,6 +56,12 @@ import (
"github.com/attestantio/vouch/services/submitter"
immediatesubmitter "github.com/attestantio/vouch/services/submitter/immediate"
multinodesubmitter "github.com/attestantio/vouch/services/submitter/multinode"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
standardsynccommitteeaggregator "github.com/attestantio/vouch/services/synccommitteeaggregator/standard"
"github.com/attestantio/vouch/services/synccommitteemessenger"
standardsynccommitteemessenger "github.com/attestantio/vouch/services/synccommitteemessenger/standard"
"github.com/attestantio/vouch/services/synccommitteesubscriber"
standardsynccommitteesubscriber "github.com/attestantio/vouch/services/synccommitteesubscriber/standard"
"github.com/attestantio/vouch/services/validatorsmanager"
standardvalidatorsmanager "github.com/attestantio/vouch/services/validatorsmanager/standard"
bestaggregateattestationstrategy "github.com/attestantio/vouch/strategies/aggregateattestation/best"
@ -191,7 +197,7 @@ func fetchConfig() error {
viper.AutomaticEnv()
// Defaults.
viper.SetDefault("process-concurrency", 16)
viper.SetDefault("process-concurrency", int64(runtime.GOMAXPROCS(-1)))
viper.SetDefault("eth2client.timeout", 2*time.Minute)
viper.SetDefault("controller.max-attestation-delay", 4*time.Second)
@ -390,24 +396,91 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
return errors.Wrap(err, "failed to start beacon committee subscriber service")
}
// Decide if the ETH2 client is capable of Altair.
altairCapable := false
spec, err := eth2Client.(eth2client.SpecProvider).Spec(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain spec")
}
if _, exists := spec["INACTIVITY_PENALTY_QUOTIENT_ALTAIR"]; exists {
altairCapable = true
log.Info().Msg("Client is Altair-capable")
} else {
log.Info().Msg("Client is not Altair-capable")
}
// The following items are for Altair. These are optional.
var syncCommitteeSubscriber synccommitteesubscriber.Service
var syncCommitteeMessenger synccommitteemessenger.Service
var syncCommitteeAggregator synccommitteeaggregator.Service
if altairCapable {
log.Trace().Msg("Starting sync committee subscriber service")
syncCommitteeSubscriber, err = standardsynccommitteesubscriber.New(ctx,
standardsynccommitteesubscriber.WithLogLevel(logLevel(viper.GetString("synccommiteesubscriber.log-level"))),
standardsynccommitteesubscriber.WithMonitor(monitor.(metrics.SyncCommitteeSubscriptionMonitor)),
standardsynccommitteesubscriber.WithSyncCommitteeSubmitter(submitterStrategy.(submitter.SyncCommitteeSubscriptionsSubmitter)),
)
if err != nil {
return errors.Wrap(err, "failed to start beacon committee subscriber service")
}
log.Trace().Msg("Starting sync committee aggregator")
syncCommitteeAggregator, err = standardsynccommitteeaggregator.New(ctx,
standardsynccommitteeaggregator.WithLogLevel(logLevel(viper.GetString("synccommitteeaggregator.log-level"))),
standardsynccommitteeaggregator.WithMonitor(monitor.(metrics.SyncCommitteeAggregationMonitor)),
standardsynccommitteeaggregator.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardsynccommitteeaggregator.WithBeaconBlockRootProvider(eth2Client.(eth2client.BeaconBlockRootProvider)),
standardsynccommitteeaggregator.WithContributionAndProofSigner(signerSvc.(signer.ContributionAndProofSigner)),
standardsynccommitteeaggregator.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardsynccommitteeaggregator.WithSyncCommitteeContributionProvider(eth2Client.(eth2client.SyncCommitteeContributionProvider)),
standardsynccommitteeaggregator.WithSyncCommitteeContributionsSubmitter(submitterStrategy.(submitter.SyncCommitteeContributionsSubmitter)),
)
if err != nil {
return errors.Wrap(err, "failed to start sync committee aggregator service")
}
log.Trace().Msg("Starting sync committee messenger")
syncCommitteeMessenger, err = standardsynccommitteemessenger.New(ctx,
standardsynccommitteemessenger.WithLogLevel(logLevel(viper.GetString("synccommitteemessenger.log-level"))),
standardsynccommitteemessenger.WithProcessConcurrency(viper.GetInt64("process-concurrency")),
standardsynccommitteemessenger.WithMonitor(monitor.(metrics.SyncCommitteeMessageMonitor)),
standardsynccommitteemessenger.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardsynccommitteemessenger.WithChainTimeService(chainTime),
standardsynccommitteemessenger.WithSyncCommitteeAggregator(syncCommitteeAggregator),
standardsynccommitteemessenger.WithBeaconBlockRootProvider(eth2Client.(eth2client.BeaconBlockRootProvider)),
standardsynccommitteemessenger.WithSyncCommitteeMessagesSubmitter(submitterStrategy.(submitter.SyncCommitteeMessagesSubmitter)),
standardsynccommitteemessenger.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardsynccommitteemessenger.WithSyncCommitteeRootSigner(signerSvc.(signer.SyncCommitteeRootSigner)),
standardsynccommitteemessenger.WithSyncCommitteeSelectionSigner(signerSvc.(signer.SyncCommitteeSelectionSigner)),
standardsynccommitteemessenger.WithSyncCommitteeSubscriptionsSubmitter(submitterStrategy.(submitter.SyncCommitteeSubscriptionsSubmitter)),
)
if err != nil {
return errors.Wrap(err, "failed to start sync committee messenger service")
}
}
log.Trace().Msg("Starting controller")
_, err = standardcontroller.New(ctx,
standardcontroller.WithLogLevel(logLevel(viper.GetString("controller.log-level"))),
standardcontroller.WithMonitor(monitor.(metrics.ControllerMonitor)),
standardcontroller.WithSlotDurationProvider(eth2Client.(eth2client.SlotDurationProvider)),
standardcontroller.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
standardcontroller.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardcontroller.WithChainTimeService(chainTime),
standardcontroller.WithProposerDutiesProvider(eth2Client.(eth2client.ProposerDutiesProvider)),
standardcontroller.WithAttesterDutiesProvider(eth2Client.(eth2client.AttesterDutiesProvider)),
standardcontroller.WithSyncCommitteeDutiesProvider(eth2Client.(eth2client.SyncCommitteeDutiesProvider)),
standardcontroller.WithEventsProvider(eth2Client.(eth2client.EventsProvider)),
standardcontroller.WithScheduler(scheduler),
standardcontroller.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardcontroller.WithAttester(attester),
standardcontroller.WithSyncCommitteeMessenger(syncCommitteeMessenger),
standardcontroller.WithSyncCommitteeAggregator(syncCommitteeAggregator),
standardcontroller.WithBeaconBlockProposer(beaconBlockProposer),
standardcontroller.WithAttestationAggregator(attestationAggregator),
standardcontroller.WithBeaconCommitteeSubscriber(beaconCommitteeSubscriber),
standardcontroller.WithSyncCommitteeSubscriber(syncCommitteeSubscriber),
standardcontroller.WithAccountsRefresher(accountManager.(accountmanager.Refresher)),
standardcontroller.WithMaxAttestationDelay(viper.GetDuration("controller.max-attestation-delay")),
standardcontroller.WithMaxSyncCommitteeMessageDelay(viper.GetDuration("controller.max-sync-committee-message-delay")),
standardcontroller.WithReorgs(viper.GetBool("controller.reorgs")),
)
if err != nil {
@ -597,12 +670,7 @@ func startSigner(ctx context.Context, monitor metrics.Service, eth2Client eth2cl
standardsigner.WithLogLevel(logLevel(viper.GetString("signer.log-level"))),
standardsigner.WithMonitor(monitor.(metrics.SignerMonitor)),
standardsigner.WithClientMonitor(monitor.(metrics.ClientMonitor)),
standardsigner.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
standardsigner.WithBeaconProposerDomainTypeProvider(eth2Client.(eth2client.BeaconProposerDomainProvider)),
standardsigner.WithBeaconAttesterDomainTypeProvider(eth2Client.(eth2client.BeaconAttesterDomainProvider)),
standardsigner.WithRANDAODomainTypeProvider(eth2Client.(eth2client.RANDAODomainProvider)),
standardsigner.WithSelectionProofDomainTypeProvider(eth2Client.(eth2client.SelectionProofDomainProvider)),
standardsigner.WithAggregateAndProofDomainTypeProvider(eth2Client.(eth2client.AggregateAndProofDomainProvider)),
standardsigner.WithSpecProvider(eth2Client.(eth2client.SpecProvider)),
standardsigner.WithDomainProvider(eth2Client.(eth2client.DomainProvider)),
)
@ -873,6 +941,9 @@ func selectSubmitterStrategy(ctx context.Context, monitor metrics.Service, eth2C
attestationsSubmitters := make(map[string]eth2client.AttestationsSubmitter)
aggregateAttestationSubmitters := make(map[string]eth2client.AggregateAttestationsSubmitter)
beaconCommitteeSubscriptionsSubmitters := make(map[string]eth2client.BeaconCommitteeSubscriptionsSubmitter)
syncCommitteeMessagesSubmitters := make(map[string]eth2client.SyncCommitteeMessagesSubmitter)
syncCommitteeContributionsSubmitters := make(map[string]eth2client.SyncCommitteeContributionsSubmitter)
syncCommitteeSubscriptionsSubmitters := make(map[string]eth2client.SyncCommitteeSubscriptionsSubmitter)
for _, address := range viper.GetStringSlice("submitter.beacon-node-addresses") {
client, err := fetchClient(ctx, address)
if err != nil {
@ -882,6 +953,9 @@ func selectSubmitterStrategy(ctx context.Context, monitor metrics.Service, eth2C
attestationsSubmitters[address] = client.(eth2client.AttestationsSubmitter)
aggregateAttestationSubmitters[address] = client.(eth2client.AggregateAttestationsSubmitter)
beaconCommitteeSubscriptionsSubmitters[address] = client.(eth2client.BeaconCommitteeSubscriptionsSubmitter)
syncCommitteeMessagesSubmitters[address] = client.(eth2client.SyncCommitteeMessagesSubmitter)
syncCommitteeContributionsSubmitters[address] = client.(eth2client.SyncCommitteeContributionsSubmitter)
syncCommitteeSubscriptionsSubmitters[address] = client.(eth2client.SyncCommitteeSubscriptionsSubmitter)
}
submitter, err = multinodesubmitter.New(ctx,
multinodesubmitter.WithClientMonitor(monitor.(metrics.ClientMonitor)),
@ -889,6 +963,9 @@ func selectSubmitterStrategy(ctx context.Context, monitor metrics.Service, eth2C
multinodesubmitter.WithLogLevel(logLevel(viper.GetString("submitter.log-level"))),
multinodesubmitter.WithBeaconBlockSubmitters(beaconBlockSubmitters),
multinodesubmitter.WithAttestationsSubmitters(attestationsSubmitters),
multinodesubmitter.WithSyncCommitteeMessagesSubmitters(syncCommitteeMessagesSubmitters),
multinodesubmitter.WithSyncCommitteeContributionsSubmitters(syncCommitteeContributionsSubmitters),
multinodesubmitter.WithSyncCommitteeSubscriptionsSubmitters(syncCommitteeSubscriptionsSubmitters),
multinodesubmitter.WithAggregateAttestationsSubmitters(aggregateAttestationSubmitters),
multinodesubmitter.WithBeaconCommitteeSubscriptionsSubmitters(beaconCommitteeSubscriptionsSubmitters),
)
@ -899,6 +976,9 @@ func selectSubmitterStrategy(ctx context.Context, monitor metrics.Service, eth2C
immediatesubmitter.WithClientMonitor(monitor.(metrics.ClientMonitor)),
immediatesubmitter.WithBeaconBlockSubmitter(eth2Client.(eth2client.BeaconBlockSubmitter)),
immediatesubmitter.WithAttestationsSubmitter(eth2Client.(eth2client.AttestationsSubmitter)),
immediatesubmitter.WithSyncCommitteeMessagesSubmitter(eth2Client.(eth2client.SyncCommitteeMessagesSubmitter)),
immediatesubmitter.WithSyncCommitteeContributionsSubmitter(eth2Client.(eth2client.SyncCommitteeContributionsSubmitter)),
immediatesubmitter.WithSyncCommitteeSubscriptionsSubmitter(eth2Client.(eth2client.SyncCommitteeSubscriptionsSubmitter)),
immediatesubmitter.WithBeaconCommitteeSubscriptionsSubmitter(eth2Client.(eth2client.BeaconCommitteeSubscriptionsSubmitter)),
immediatesubmitter.WithAggregateAttestationsSubmitter(eth2Client.(eth2client.AggregateAttestationsSubmitter)),
)

View File

@ -22,6 +22,8 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/prysmaticlabs/go-bitfield"
)
@ -146,6 +148,97 @@ func (m *AttesterDutiesProvider) AttesterDuties(ctx context.Context, epoch phase
return make([]*api.AttesterDuty, 0), nil
}
// SyncCommitteeDutiesProvider is a mock for eth2client.SyncCommitteeDutiesProvider.
type SyncCommitteeDutiesProvider struct{}
// NewSyncCommitteeDutiesProvider returns a mock attester duties provider.
func NewSyncCommitteeDutiesProvider() eth2client.SyncCommitteeDutiesProvider {
return &SyncCommitteeDutiesProvider{}
}
// SyncCommitteeDuties is a mock.
func (m *SyncCommitteeDutiesProvider) SyncCommitteeDuties(ctx context.Context, epoch phase0.Epoch, validatorIndices []phase0.ValidatorIndex) ([]*api.SyncCommitteeDuty, error) {
return make([]*api.SyncCommitteeDuty, 0), nil
}
// SyncCommitteeSubscriptionsSubmitter is a mock for eth2client.SyncCommitteeSubscriptionsSubmitter.
type SyncCommitteeSubscriptionsSubmitter struct{}
// NewSyncCommitteeSubscriptionsSubmitter returns a mock attester duties submitter.
func NewSyncCommitteeSubscriptionsSubmitter() eth2client.SyncCommitteeSubscriptionsSubmitter {
return &SyncCommitteeSubscriptionsSubmitter{}
}
// SubmitSyncCommitteeSubscriptions is a mock
func (m *SyncCommitteeSubscriptionsSubmitter) SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error {
return nil
}
// ErroringSyncCommitteeSubscriptionsSubmitter is a mock for eth2client.SyncCommitteeSubscriptionsSubmitter.
type ErroringSyncCommitteeSubscriptionsSubmitter struct{}
// NewErroringSyncCommitteeSubscriptionsSubmitter returns a mock attester duties submitter.
func NewErroringSyncCommitteeSubscriptionsSubmitter() eth2client.SyncCommitteeSubscriptionsSubmitter {
return &ErroringSyncCommitteeSubscriptionsSubmitter{}
}
// SubmitSyncCommitteeSubscriptions is a mock
func (m *ErroringSyncCommitteeSubscriptionsSubmitter) SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error {
return errors.New("error")
}
// SyncCommitteeMessagesSubmitter is a mock for eth2client.SyncCommitteeMessagesSubmitter.
type SyncCommitteeMessagesSubmitter struct{}
// NewSyncCommitteeMessagesSubmitter returns a mock attester duties submitter.
func NewSyncCommitteeMessagesSubmitter() eth2client.SyncCommitteeMessagesSubmitter {
return &SyncCommitteeMessagesSubmitter{}
}
// SubmitSyncCommitteeMessages submits sync committee messages.
func (m *SyncCommitteeMessagesSubmitter) SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error {
return nil
}
// ErroringSyncCommitteeMessagesSubmitter is a mock for eth2client.SyncCommitteeMessagesSubmitter.
type ErroringSyncCommitteeMessagesSubmitter struct{}
// NewErroringSyncCommitteeMessagesSubmitter returns a mock attester duties submitter.
func NewErroringSyncCommitteeMessagesSubmitter() eth2client.SyncCommitteeMessagesSubmitter {
return &ErroringSyncCommitteeMessagesSubmitter{}
}
// SubmitSyncCommitteeMessages submits sync committee messages.
func (m *ErroringSyncCommitteeMessagesSubmitter) SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error {
return errors.New("error")
}
// SyncCommitteeContributionsSubmitter is a mock for eth2client.SyncCommitteeContributionsSubmitter.
type SyncCommitteeContributionsSubmitter struct{}
// NewSyncCommitteeContributionsSubmitter returns a mock attester duties submitter.
func NewSyncCommitteeContributionsSubmitter() eth2client.SyncCommitteeContributionsSubmitter {
return &SyncCommitteeContributionsSubmitter{}
}
// SubmitSyncCommitteeContributions submits sync committee contributions.
func (m *SyncCommitteeContributionsSubmitter) SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error {
return nil
}
// ErroringSyncCommitteeContributionsSubmitter is a mock for eth2client.SyncCommitteeContributionsSubmitter.
type ErroringSyncCommitteeContributionsSubmitter struct{}
// NewErroringSyncCommitteeContributionsSubmitter returns a mock attester duties submitter.
func NewErroringSyncCommitteeContributionsSubmitter() eth2client.SyncCommitteeContributionsSubmitter {
return &ErroringSyncCommitteeContributionsSubmitter{}
}
// SubmitSyncCommitteeContributions submits sync committee contributions.
func (m *ErroringSyncCommitteeContributionsSubmitter) SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error {
return errors.New("error")
}
// EventsProvider is a mock for eth2client.EventsProvider.
type EventsProvider struct{}
@ -194,7 +287,7 @@ func NewBeaconBlockSubmitter() eth2client.BeaconBlockSubmitter {
}
// SubmitBeaconBlock is a mock.
func (m *BeaconBlockSubmitter) SubmitBeaconBlock(ctx context.Context, bloc *phase0.SignedBeaconBlock) error {
func (m *BeaconBlockSubmitter) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
return nil
}
@ -207,7 +300,7 @@ func NewErroringBeaconBlockSubmitter() eth2client.BeaconBlockSubmitter {
}
// SubmitBeaconBlock is a mock.
func (m *ErroringBeaconBlockSubmitter) SubmitBeaconBlock(ctx context.Context, bloc *phase0.SignedBeaconBlock) error {
func (m *ErroringBeaconBlockSubmitter) SubmitBeaconBlock(ctx context.Context, bloc *spec.VersionedSignedBeaconBlock) error {
return errors.New("error")
}
@ -272,7 +365,7 @@ func NewBeaconBlockProposalProvider() eth2client.BeaconBlockProposalProvider {
}
// BeaconBlockProposal is a mock.
func (m *BeaconBlockProposalProvider) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*phase0.BeaconBlock, error) {
func (m *BeaconBlockProposalProvider) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*spec.VersionedBeaconBlock, error) {
// Graffiti should be 32 bytes.
fixedGraffiti := make([]byte, 32)
copy(fixedGraffiti, graffiti)
@ -319,36 +412,39 @@ func (m *BeaconBlockProposalProvider) BeaconBlockProposal(ctx context.Context, s
}
}
block := &phase0.BeaconBlock{
Slot: slot,
ProposerIndex: 1,
ParentRoot: phase0.Root([32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
}),
StateRoot: phase0.Root([32]byte{
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
}),
Body: &phase0.BeaconBlockBody{
RANDAOReveal: randaoReveal,
ETH1Data: &phase0.ETH1Data{
DepositRoot: phase0.Root([32]byte{
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
}),
DepositCount: 16384,
BlockHash: []byte{
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
block := &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: slot,
ProposerIndex: 1,
ParentRoot: phase0.Root([32]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
}),
StateRoot: phase0.Root([32]byte{
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
}),
Body: &phase0.BeaconBlockBody{
RANDAOReveal: randaoReveal,
ETH1Data: &phase0.ETH1Data{
DepositRoot: phase0.Root([32]byte{
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
}),
DepositCount: 16384,
BlockHash: []byte{
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
},
},
Graffiti: fixedGraffiti,
ProposerSlashings: []*phase0.ProposerSlashing{},
AttesterSlashings: []*phase0.AttesterSlashing{},
Attestations: attestations,
Deposits: []*phase0.Deposit{},
VoluntaryExits: []*phase0.SignedVoluntaryExit{},
},
Graffiti: fixedGraffiti,
ProposerSlashings: []*phase0.ProposerSlashing{},
AttesterSlashings: []*phase0.AttesterSlashing{},
Attestations: attestations,
Deposits: []*phase0.Deposit{},
VoluntaryExits: []*phase0.SignedVoluntaryExit{},
},
}
@ -364,10 +460,13 @@ func NewSignedBeaconBlockProvider() eth2client.SignedBeaconBlockProvider {
}
// SignedBeaconBlock is a mock.
func (m *SignedBeaconBlockProvider) SignedBeaconBlock(ctx context.Context, stateID string) (*phase0.SignedBeaconBlock, error) {
return &phase0.SignedBeaconBlock{
Message: &phase0.BeaconBlock{
Slot: 123,
func (m *SignedBeaconBlockProvider) SignedBeaconBlock(ctx context.Context, stateID string) (*spec.VersionedSignedBeaconBlock, error) {
return &spec.VersionedSignedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.SignedBeaconBlock{
Message: &phase0.BeaconBlock{
Slot: 123,
},
},
}, nil
}
@ -550,186 +649,48 @@ func (m *SleepyAggregateAttestationProvider) AggregateAttestation(ctx context.Co
return m.next.AggregateAttestation(ctx, slot, attestationDataRoot)
}
// BeaconProposerDomainProvider is a mock for eth2client.BeaconProposerDomainProvider.
type BeaconProposerDomainProvider struct{}
// ErroringSpecProvider is a mock for eth2client.SpecProvider.
type ErroringSpecProvider struct{}
// NewBeaconProposerDomainProvider returns a mock beacon proposer domain provider.
func NewBeaconProposerDomainProvider() eth2client.BeaconProposerDomainProvider {
return &BeaconProposerDomainProvider{}
// NewErroringSpecProvider returns a mock spec provider.
func NewErroringSpecProvider() eth2client.SpecProvider {
return &ErroringSpecProvider{}
}
// BeaconProposerDomain is a mock.
func (m *BeaconProposerDomainProvider) BeaconProposerDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x00, 0x00, 0x00, 0x00}, nil
// Spec is a mock.
func (m *ErroringSpecProvider) Spec(ctx context.Context) (map[string]interface{}, error) {
return nil, errors.New("error")
}
// ErroringBeaconProposerDomainProvider is a mock for eth2client.BeaconProposerDomainProvider.
type ErroringBeaconProposerDomainProvider struct{}
// SpecProvider is a mock for eth2client.SpecProvider.
type SpecProvider struct{}
// NewErroringBeaconProposerDomainProvider returns a mock beacon proposer domain provider that errors.
func NewErroringBeaconProposerDomainProvider() eth2client.BeaconProposerDomainProvider {
return &ErroringBeaconProposerDomainProvider{}
// NewSpecProvider returns a mock spec provider.
func NewSpecProvider() eth2client.SpecProvider {
return &SpecProvider{}
}
// BeaconProposerDomain is a mock.
func (m *ErroringBeaconProposerDomainProvider) BeaconProposerDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// BeaconAttesterDomainProvider is a mock for eth2client.BeaconAttesterDomainProvider.
type BeaconAttesterDomainProvider struct{}
// NewBeaconAttesterDomainProvider returns a mock beacon attester domain provider.
func NewBeaconAttesterDomainProvider() eth2client.BeaconAttesterDomainProvider {
return &BeaconAttesterDomainProvider{}
}
// BeaconAttesterDomain is a mock.
func (m *BeaconAttesterDomainProvider) BeaconAttesterDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x01, 0x00, 0x00, 0x00}, nil
}
// ErroringBeaconAttesterDomainProvider is a mock for eth2client.BeaconAttesterDomainProvider.
type ErroringBeaconAttesterDomainProvider struct{}
// NewErroringBeaconAttesterDomainProvider returns a mock beacon attester domain provider that errors.
func NewErroringBeaconAttesterDomainProvider() eth2client.BeaconAttesterDomainProvider {
return &ErroringBeaconAttesterDomainProvider{}
}
// BeaconAttesterDomain is a mock.
func (m *ErroringBeaconAttesterDomainProvider) BeaconAttesterDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// RANDAODomainProvider is a mock for eth2client.RANDAODomainProvider.
type RANDAODomainProvider struct{}
// NewRANDAODomainProvider returns a mock RANDAO domain provider.
func NewRANDAODomainProvider() eth2client.RANDAODomainProvider {
return &RANDAODomainProvider{}
}
// RANDAODomain is a mock.
func (m *RANDAODomainProvider) RANDAODomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x02, 0x00, 0x00, 0x00}, nil
}
// ErroringRANDAODomainProvider is a mock for eth2client.RANDAODomainProvider.
type ErroringRANDAODomainProvider struct{}
// NewErroringRANDAODomainProvider returns a mock RANDAO domain provider that errors.
func NewErroringRANDAODomainProvider() eth2client.RANDAODomainProvider {
return &ErroringRANDAODomainProvider{}
}
// RANDAODomain is a mock.
func (m *ErroringRANDAODomainProvider) RANDAODomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// DepositDomainProvider is a mock for eth2client.DepositDomainProvider.
type DepositDomainProvider struct{}
// NewDepositDomainProvider returns a mock deposit domain provider.
func NewDepositDomainProvider() eth2client.DepositDomainProvider {
return &DepositDomainProvider{}
}
// DepositDomain is a mock.
func (m *DepositDomainProvider) DepositDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x03, 0x00, 0x00, 0x00}, nil
}
// ErroringDepositDomainProvider is a mock for eth2client.DepositDomainProvider.
type ErroringDepositDomainProvider struct{}
// NewErroringDepositDomainProvider returns a mock deposit domain provider that errors.
func NewErroringDepositDomainProvider() eth2client.DepositDomainProvider {
return &DepositDomainProvider{}
}
// DepositDomain is a mock.
func (m *ErroringDepositDomainProvider) DepositDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// VoluntaryExitDomainProvider is a mock for eth2client.VoluntaryExitDomainProvider.
type VoluntaryExitDomainProvider struct{}
// NewVoluntaryExitDomainProvider returns a mock voluntary exit domain provider.
func NewVoluntaryExitDomainProvider() eth2client.VoluntaryExitDomainProvider {
return &VoluntaryExitDomainProvider{}
}
// VoluntaryExitDomain is a mock.
func (m *VoluntaryExitDomainProvider) VoluntaryExitDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x04, 0x00, 0x00, 0x00}, nil
}
// ErroringVoluntaryExitDomainProvider is a mock for eth2client.VoluntaryExitDomainProvider.
type ErroringVoluntaryExitDomainProvider struct{}
// NewErroringVoluntaryExitDomainProvider returns a mock voluntary exit domain provider that errors.
func NewErroringVoluntaryExitDomainProvider() eth2client.VoluntaryExitDomainProvider {
return &VoluntaryExitDomainProvider{}
}
// VoluntaryExitDomain is a mock.
func (m *ErroringVoluntaryExitDomainProvider) VoluntaryExitDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// SelectionProofDomainProvider is a mock for eth2client.SelectionProofDomainProvider.
type SelectionProofDomainProvider struct{}
// NewSelectionProofDomainProvider returns a mock selection proof domain provider.
func NewSelectionProofDomainProvider() eth2client.SelectionProofDomainProvider {
return &SelectionProofDomainProvider{}
}
// SelectionProofDomain is a mock.
func (m *SelectionProofDomainProvider) SelectionProofDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x05, 0x00, 0x00, 0x00}, nil
}
// ErroringSelectionProofDomainProvider is a mock for eth2client.SelectionProofDomainProvider.
type ErroringSelectionProofDomainProvider struct{}
// NewErroringSelectionProofDomainProvider returns a mock selection proof domain provider that errors.
func NewErroringSelectionProofDomainProvider() eth2client.SelectionProofDomainProvider {
return &ErroringSelectionProofDomainProvider{}
}
// SelectionProofDomain is a mock.
func (m *ErroringSelectionProofDomainProvider) SelectionProofDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
}
// AggregateAndProofDomainProvider is a mock for eth2client.AggregateAndProofDomainProvider.
type AggregateAndProofDomainProvider struct{}
// NewAggregateAndProofDomainProvider returns a mock aggregate and proof domain provider.
func NewAggregateAndProofDomainProvider() eth2client.AggregateAndProofDomainProvider {
return &AggregateAndProofDomainProvider{}
}
// AggregateAndProofDomain is a mock.
func (m *AggregateAndProofDomainProvider) AggregateAndProofDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{0x06, 0x00, 0x00, 0x00}, nil
}
// ErroringAggregateAndProofDomainProvider is a mock for eth2client.AggregateAndProofDomainProvider.
type ErroringAggregateAndProofDomainProvider struct{}
// NewErroringAggregateAndProofDomainProvider returns a mock aggregate and proof domain provider that errors.
func NewErroringAggregateAndProofDomainProvider() eth2client.AggregateAndProofDomainProvider {
return &ErroringAggregateAndProofDomainProvider{}
}
// AggregateAndProofDomain is a mock.
func (m *ErroringAggregateAndProofDomainProvider) AggregateAndProofDomain(ctx context.Context) (phase0.DomainType, error) {
return phase0.DomainType{}, errors.New("error")
// Spec is a mock.
func (m *SpecProvider) Spec(ctx context.Context) (map[string]interface{}, error) {
return map[string]interface{}{
// Mainnet params (give or take).
"DOMAIN_AGGREGATE_AND_PROOF": phase0.DomainType{0x06, 0x00, 0x00, 0x00},
"DOMAIN_BEACON_ATTESTER": phase0.DomainType{0x00, 0x00, 0x00, 0x00},
"DOMAIN_BEACON_PROPOSER": phase0.DomainType{0x01, 0x00, 0x00, 0x00},
"DOMAIN_CONTRIBUTION_AND_PROOF": phase0.DomainType{0x09, 0x00, 0x00, 0x00},
"DOMAIN_DEPOSIT": phase0.DomainType{0x03, 0x00, 0x00, 0x00},
"DOMAIN_RANDAO": phase0.DomainType{0x02, 0x00, 0x00, 0x00},
"DOMAIN_SELECTION_PROOF": phase0.DomainType{0x05, 0x00, 0x00, 0x00},
"DOMAIN_SYNC_COMMITTEE": phase0.DomainType{0x07, 0x00, 0x00, 0x00},
"DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF": phase0.DomainType{0x08, 0x00, 0x00, 0x00},
"DOMAIN_VOLUNTARY_EXIT": phase0.DomainType{0x04, 0x00, 0x00, 0x00},
"EPOCHS_PER_SYNC_COMMITTEE_PERIOD": uint64(256),
"SECONDS_PER_SLOT": 12 * time.Second,
"SLOTS_PER_EPOCH": uint64(32),
"SYNC_COMMITTEE_SIZE": uint64(512),
"SYNC_COMMITTEE_SUBNET_COUNT": uint64(4),
"TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE": uint64(16),
}, nil
}
// DomainProvider is a mock for eth2client.DomainProvider.

View File

@ -156,6 +156,9 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
}
}
if parameters.processConcurrency == 0 {
return nil, errors.New("no process concurrency specified")
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2020, 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

View File

@ -19,6 +19,8 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/beaconblockproposer"
@ -156,25 +158,46 @@ func (s *Service) Propose(ctx context.Context, data interface{}) {
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained proposal")
if proposal.Slot != duty.Slot() {
log.Error().Uint64("proposal_slot", uint64(proposal.Slot)).Msg("Proposal data for incorrect slot; not proceeding")
proposalSlot, err := proposal.Slot()
if err != nil {
log.Error().Str("version", proposal.Version.String()).Err(err).Msg("Unknown proposal version")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
bodyRoot, err := proposal.Body.HashTreeRoot()
if proposalSlot != duty.Slot() {
log.Error().Uint64("proposal_slot", uint64(proposalSlot)).Msg("Proposal data for incorrect slot; not proceeding")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
bodyRoot, err := proposal.BodyRoot()
if err != nil {
log.Error().Err(err).Msg("Failed to calculate hash tree root of block")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
parentRoot, err := proposal.ParentRoot()
if err != nil {
log.Error().Err(err).Msg("Failed to obtain parent root of block")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
stateRoot, err := proposal.StateRoot()
if err != nil {
log.Error().Err(err).Msg("Failed to obtain state root of block")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
sig, err := s.beaconBlockSigner.SignBeaconBlockProposal(ctx,
duty.Account(),
proposal.Slot,
proposalSlot,
duty.ValidatorIndex(),
proposal.ParentRoot,
proposal.StateRoot,
parentRoot,
stateRoot,
bodyRoot)
if err != nil {
log.Error().Err(err).Msg("Failed to sign beacon block proposal")
@ -183,9 +206,24 @@ func (s *Service) Propose(ctx context.Context, data interface{}) {
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Signed proposal")
signedBlock := &phase0.SignedBeaconBlock{
Message: proposal,
Signature: sig,
signedBlock := &spec.VersionedSignedBeaconBlock{
Version: proposal.Version,
}
switch signedBlock.Version {
case spec.DataVersionPhase0:
signedBlock.Phase0 = &phase0.SignedBeaconBlock{
Message: proposal.Phase0,
Signature: sig,
}
case spec.DataVersionAltair:
signedBlock.Altair = &altair.SignedBeaconBlock{
Message: proposal.Altair,
Signature: sig,
}
default:
log.Error().Str("version", proposal.Version.String()).Msg("Unknown proposal version")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
// Submit the block.

View File

@ -96,7 +96,7 @@ func (s *Service) scheduleAttestations(ctx context.Context,
}
go func(duty *attester.Duty) {
// Adding 200 ms to ensure that head is up to date before we fetch attester duties.
jobTime := s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.maxAttestationDelay).Add(200 * time.Millisecond)
jobTime := s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.maxSyncCommitteeMessageDelay).Add(200 * time.Millisecond)
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Attestations for slot %d", duty.Slot()),
jobTime,

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2020, 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -105,6 +105,11 @@ func (s *Service) HandleHeadEvent(event *api.Event) {
log.Trace().Msg("Kicking off attestations for slot early due to receiving relevant block")
s.scheduler.RunJobIfExists(ctx, jobName)
}
jobName = fmt.Sprintf("Sync committee contributions for slot %d", data.Slot)
if s.scheduler.JobExists(ctx, jobName) {
log.Trace().Msg("Kicking off sync committee contributions for slot early due to receiving relevant block")
s.scheduler.RunJobIfExists(ctx, jobName)
}
// Remove old subscriptions if present.
delete(s.subscriptionInfos, s.chainTimeService.SlotToEpoch(data.Slot)-2)
@ -113,17 +118,29 @@ func (s *Service) HandleHeadEvent(event *api.Event) {
// handlePreviousDependentRootChanged handles the situation where the previous
// dependent root changed.
func (s *Service) handlePreviousDependentRootChanged(ctx context.Context) {
// Refreshes run in parallel.
// We need to refresh the attester duties for this epoch.
s.refreshAttesterDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch())
go s.refreshAttesterDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch())
}
// handlePreviousDependentRootChanged handles the situation where the current
// dependent root changed.
func (s *Service) handleCurrentDependentRootChanged(ctx context.Context) {
// Refreshes run in parallel.
// We need to refresh the proposer duties for this epoch.
s.refreshProposerDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch())
go s.refreshProposerDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch())
// We need to refresh the sync committee duties for this epoch if we are
// at the appropriate boundary.
if uint64(s.chainTimeService.CurrentEpoch())%s.epochsPerSyncCommitteePeriod == 0 {
// TODO is this correct?
// Check if this is the correct sync committee period (should it be the next one?)
// Check if this should only be recalculated on the sync committee period boundary.
go s.refreshSyncCommitteeDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch())
}
// We need to refresh the attester duties for the next epoch.
s.refreshAttesterDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch()+1)
go s.refreshAttesterDutiesForEpoch(ctx, s.chainTimeService.CurrentEpoch()+1)
}
func (s *Service) refreshProposerDutiesForEpoch(ctx context.Context, epoch phase0.Epoch) {
@ -181,3 +198,36 @@ func (s *Service) refreshAttesterDutiesForEpoch(ctx context.Context, epoch phase
s.subscriptionInfos[epoch] = subscriptionInfo
s.subscriptionInfosMutex.Unlock()
}
// TODO this should refresh for the entire period.
func (s *Service) refreshSyncCommitteeDutiesForEpoch(ctx context.Context, epoch phase0.Epoch) {
if !s.handlingAltair {
// Not handling Altair, nothing to do.
return
}
// First thing we do is cancel all scheduled sync committee message jobs.
firstSlot := s.chainTimeService.FirstSlotOfEpoch(epoch)
syncCommitteePeriod := uint64(s.chainTimeService.SlotToEpoch(firstSlot)) / s.epochsPerSyncCommitteePeriod
lastSlot := s.chainTimeService.FirstSlotOfEpoch(phase0.Epoch((syncCommitteePeriod+1)*s.epochsPerSyncCommitteePeriod)) - 1
for slot := firstSlot; slot <= lastSlot; slot++ {
if err := s.scheduler.CancelJob(ctx, fmt.Sprintf("Sync committee messages for slot %d", slot)); err != nil {
log.Debug().Err(err).Msg("Failed to cancel sync committee message job")
}
}
_, validatorIndices, err := s.accountsAndIndicesForEpoch(ctx, epoch)
if err != nil {
log.Error().Err(err).Uint64("epoch", uint64(epoch)).Msg("Failed to obtain active validators for epoch")
return
}
// Expect at least one validator.
if len(validatorIndices) == 0 {
log.Warn().Msg("No active validators; not validating")
return
}
// Reschedule sync committee messages.
go s.scheduleSyncCommitteeMessages(ctx, epoch, validatorIndices)
}

View File

@ -14,6 +14,7 @@
package standard
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
@ -25,28 +26,35 @@ import (
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/scheduler"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/attestantio/vouch/services/synccommitteemessenger"
"github.com/attestantio/vouch/services/synccommitteesubscriber"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.ControllerMonitor
slotDurationProvider eth2client.SlotDurationProvider
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
chainTimeService chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
attesterDutiesProvider eth2client.AttesterDutiesProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
scheduler scheduler.Service
eventsProvider eth2client.EventsProvider
attester attester.Service
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
accountsRefresher accountmanager.Refresher
maxAttestationDelay time.Duration
reorgs bool
logLevel zerolog.Level
monitor metrics.ControllerMonitor
specProvider eth2client.SpecProvider
chainTimeService chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
attesterDutiesProvider eth2client.AttesterDutiesProvider
syncCommitteeDutiesProvider eth2client.SyncCommitteeDutiesProvider
syncCommitteesSubscriber synccommitteesubscriber.Service
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
scheduler scheduler.Service
eventsProvider eth2client.EventsProvider
attester attester.Service
syncCommitteeMessenger synccommitteemessenger.Service
syncCommitteeAggregator synccommitteeaggregator.Service
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
accountsRefresher accountmanager.Refresher
maxAttestationDelay time.Duration
maxSyncCommitteeMessageDelay time.Duration
reorgs bool
}
// Parameter is the interface for service parameters.
@ -74,17 +82,10 @@ func WithMonitor(monitor metrics.ControllerMonitor) Parameter {
})
}
// WithSlotDurationProvider sets the slot duration provider.
func WithSlotDurationProvider(provider eth2client.SlotDurationProvider) Parameter {
// WithSpecProvider sets the spec provider.
func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.slotDurationProvider = provider
})
}
// WithSlotsPerEpochProvider sets the slots per epoch provider.
func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.slotsPerEpochProvider = provider
p.specProvider = provider
})
}
@ -109,6 +110,20 @@ func WithAttesterDutiesProvider(provider eth2client.AttesterDutiesProvider) Para
})
}
// WithSyncCommitteeDutiesProvider sets the sync committee duties provider.
func WithSyncCommitteeDutiesProvider(provider eth2client.SyncCommitteeDutiesProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeDutiesProvider = provider
})
}
// WithSyncCommitteeSubscriber sets the sync committee subscriber.
func WithSyncCommitteeSubscriber(subscriber synccommitteesubscriber.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteesSubscriber = subscriber
})
}
// WithEventsProvider sets the events provider.
func WithEventsProvider(provider eth2client.EventsProvider) Parameter {
return parameterFunc(func(p *parameters) {
@ -137,6 +152,20 @@ func WithAttester(attester attester.Service) Parameter {
})
}
// WithSyncCommitteeMessenger sets the sync committee messenger.
func WithSyncCommitteeMessenger(messenger synccommitteemessenger.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeMessenger = messenger
})
}
// WithSyncCommitteeAggregator sets the sync committee aggregator.
func WithSyncCommitteeAggregator(aggregator synccommitteeaggregator.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeAggregator = aggregator
})
}
// WithBeaconBlockProposer sets the beacon block propser.
func WithBeaconBlockProposer(proposer beaconblockproposer.Service) Parameter {
return parameterFunc(func(p *parameters) {
@ -172,6 +201,13 @@ func WithMaxAttestationDelay(delay time.Duration) Parameter {
})
}
// WithMaxSyncCommitteeMessageDelay sets the maximum delay before generating sync committee messages.
func WithMaxSyncCommitteeMessageDelay(delay time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.maxSyncCommitteeMessageDelay = delay
})
}
// WithReorgs sets or unsets reorgs.
func WithReorgs(reorgs bool) Parameter {
return parameterFunc(func(p *parameters) {
@ -182,8 +218,7 @@ func WithReorgs(reorgs bool) Parameter {
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
maxAttestationDelay: 4 * time.Second,
logLevel: zerolog.GlobalLevel(),
}
for _, p := range params {
if params != nil {
@ -194,11 +229,8 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
if parameters.slotDurationProvider == nil {
return nil, errors.New("no slot duration provider specified")
}
if parameters.slotsPerEpochProvider == nil {
return nil, errors.New("no slots per epoch provider specified")
if parameters.specProvider == nil {
return nil, errors.New("no spec provider specified")
}
if parameters.chainTimeService == nil {
return nil, errors.New("no chain time service specified")
@ -233,9 +265,41 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.accountsRefresher == nil {
return nil, errors.New("no accounts refresher specified")
}
var spec map[string]interface{}
var err error
if parameters.maxAttestationDelay == 0 {
return nil, errors.New("no maximum attestation delay specified")
spec, err = parameters.specProvider.Spec(context.Background())
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := spec["SECONDS_PER_SLOT"]
if !exists {
return nil, errors.New("SECONDS_PER_SLOT not found in spec")
}
slotDuration, ok := tmp.(time.Duration)
if !ok {
return nil, errors.New("SECONDS_PER_SLOT of unexpected type")
}
parameters.maxAttestationDelay = slotDuration / 3
}
if parameters.maxSyncCommitteeMessageDelay == 0 {
if spec == nil {
spec, err = parameters.specProvider.Spec(context.Background())
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
}
tmp, exists := spec["SECONDS_PER_SLOT"]
if !exists {
return nil, errors.New("SECONDS_PER_SLOT not found in spec")
}
slotDuration, ok := tmp.(time.Duration)
if !ok {
return nil, errors.New("SECONDS_PER_SLOT of unexpected type")
}
parameters.maxSyncCommitteeMessageDelay = slotDuration / 3
}
// Sync committee duties provider/messenger/aggregator/subscriber are optional so no checks here.
return &parameters, nil
}

View File

@ -29,6 +29,9 @@ import (
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/scheduler"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/attestantio/vouch/services/synccommitteemessenger"
"github.com/attestantio/vouch/services/synccommitteesubscriber"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
@ -39,24 +42,32 @@ import (
// It runs purely against clock events, setting up jobs for the validator's processes of block proposal, attestation
// creation and attestation aggregation.
type Service struct {
monitor metrics.ControllerMonitor
slotDuration time.Duration
slotsPerEpoch uint64
chainTimeService chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
attesterDutiesProvider eth2client.AttesterDutiesProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
scheduler scheduler.Service
attester attester.Service
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
activeValidators int
subscriptionInfos map[phase0.Epoch]map[phase0.Slot]map[phase0.CommitteeIndex]*beaconcommitteesubscriber.Subscription
subscriptionInfosMutex sync.Mutex
accountsRefresher accountmanager.Refresher
maxAttestationDelay time.Duration
reorgs bool
monitor metrics.ControllerMonitor
slotDuration time.Duration
slotsPerEpoch uint64
epochsPerSyncCommitteePeriod uint64
chainTimeService chaintime.Service
proposerDutiesProvider eth2client.ProposerDutiesProvider
attesterDutiesProvider eth2client.AttesterDutiesProvider
syncCommitteeDutiesProvider eth2client.SyncCommitteeDutiesProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
scheduler scheduler.Service
attester attester.Service
syncCommitteeMessenger synccommitteemessenger.Service
syncCommitteeAggregator synccommitteeaggregator.Service
syncCommitteesSubscriber synccommitteesubscriber.Service
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
activeValidators int
subscriptionInfos map[phase0.Epoch]map[phase0.Slot]map[phase0.CommitteeIndex]*beaconcommitteesubscriber.Subscription
subscriptionInfosMutex sync.Mutex
accountsRefresher accountmanager.Refresher
maxSyncCommitteeMessageDelay time.Duration
reorgs bool
// Hard fork control
handlingAltair bool
// Tracking for reorgs.
lastBlockRoot phase0.Root
@ -81,33 +92,64 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
log = log.Level(parameters.logLevel)
}
slotDuration, err := parameters.slotDurationProvider.SlotDuration(ctx)
spec, err := parameters.specProvider.Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slot duration")
return nil, errors.Wrap(err, "failed to obtain spec")
}
slotsPerEpoch, err := parameters.slotsPerEpochProvider.SlotsPerEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
tmp, exists := spec["SECONDS_PER_SLOT"]
if !exists {
return nil, errors.New("SECONDS_PER_SLOT not found in spec")
}
slotDuration, ok := tmp.(time.Duration)
if !ok {
return nil, errors.New("SECONDS_PER_SLOT of unexpected type")
}
tmp, exists = spec["SLOTS_PER_EPOCH"]
if !exists {
return nil, errors.New("SLOTS_PER_EPOCH not found in spec")
}
slotsPerEpoch, ok := tmp.(uint64)
if !ok {
return nil, errors.New("SLOTS_PER_EPOCH of unexpected type")
}
var epochsPerSyncCommitteePeriod uint64
if tmp, exists := spec["EPOCHS_PER_SYNC_COMMITTEE_PERIOD"]; exists {
tmp2, ok := tmp.(uint64)
if !ok {
return nil, errors.New("EPOCHS_PER_SYNC_COMMITTEE_PERIOD of unexpected type")
}
epochsPerSyncCommitteePeriod = tmp2
}
// Handling altair if we have the service and spec to do so.
handlingAltair := parameters.syncCommitteeAggregator != nil && epochsPerSyncCommitteePeriod != 0
s := &Service{
monitor: parameters.monitor,
slotDuration: slotDuration,
slotsPerEpoch: slotsPerEpoch,
chainTimeService: parameters.chainTimeService,
proposerDutiesProvider: parameters.proposerDutiesProvider,
attesterDutiesProvider: parameters.attesterDutiesProvider,
validatingAccountsProvider: parameters.validatingAccountsProvider,
scheduler: parameters.scheduler,
attester: parameters.attester,
beaconBlockProposer: parameters.beaconBlockProposer,
attestationAggregator: parameters.attestationAggregator,
beaconCommitteeSubscriber: parameters.beaconCommitteeSubscriber,
accountsRefresher: parameters.accountsRefresher,
maxAttestationDelay: parameters.maxAttestationDelay,
reorgs: parameters.reorgs,
subscriptionInfos: make(map[phase0.Epoch]map[phase0.Slot]map[phase0.CommitteeIndex]*beaconcommitteesubscriber.Subscription),
monitor: parameters.monitor,
slotDuration: slotDuration,
slotsPerEpoch: slotsPerEpoch,
epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod,
chainTimeService: parameters.chainTimeService,
proposerDutiesProvider: parameters.proposerDutiesProvider,
attesterDutiesProvider: parameters.attesterDutiesProvider,
syncCommitteeDutiesProvider: parameters.syncCommitteeDutiesProvider,
syncCommitteesSubscriber: parameters.syncCommitteesSubscriber,
validatingAccountsProvider: parameters.validatingAccountsProvider,
scheduler: parameters.scheduler,
attester: parameters.attester,
syncCommitteeMessenger: parameters.syncCommitteeMessenger,
syncCommitteeAggregator: parameters.syncCommitteeAggregator,
beaconBlockProposer: parameters.beaconBlockProposer,
attestationAggregator: parameters.attestationAggregator,
beaconCommitteeSubscriber: parameters.beaconCommitteeSubscriber,
accountsRefresher: parameters.accountsRefresher,
maxSyncCommitteeMessageDelay: parameters.maxSyncCommitteeMessageDelay,
reorgs: parameters.reorgs,
subscriptionInfos: make(map[phase0.Epoch]map[phase0.Slot]map[phase0.CommitteeIndex]*beaconcommitteesubscriber.Subscription),
handlingAltair: handlingAltair,
}
// Subscribe to head events. This allows us to go early for attestations if a block arrives, as well as
@ -137,6 +179,11 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
}
go s.scheduleProposals(ctx, epoch, validatorIndices, true /* notCurrentSlot */)
go s.scheduleAttestations(ctx, epoch, validatorIndices, true /* notCurrentSlot */)
if handlingAltair {
go s.scheduleSyncCommitteeMessages(ctx, epoch, validatorIndices)
nextSyncCommitteePeriodStartEpoch := phase0.Epoch((uint64(epoch)/s.epochsPerSyncCommitteePeriod + 1) * s.epochsPerSyncCommitteePeriod)
go s.scheduleSyncCommitteeMessages(ctx, nextSyncCommitteePeriodStartEpoch, validatorIndices)
}
go s.scheduleAttestations(ctx, epoch+1, nextEpochValidatorIndices, true /* notCurrentSlot */)
// Update beacon committee subscriptions this and the next epoch.
go func() {
@ -264,6 +311,12 @@ func (s *Service) epochTicker(ctx context.Context, data interface{}) {
go s.scheduleProposals(ctx, currentEpoch, validatorIndices, false /* notCurrentSlot */)
go s.scheduleAttestations(ctx, currentEpoch+1, nextEpochValidatorIndices, false /* notCurrentSlot */)
if s.handlingAltair {
// Only update if we are on an EPOCHS_PER_SYNC_COMMITTEE_PERIOD boundary.
if uint64(currentEpoch)%s.epochsPerSyncCommitteePeriod == 0 {
go s.scheduleSyncCommitteeMessages(ctx, currentEpoch, validatorIndices)
}
}
go func() {
// Update beacon committee subscriptions for the next epoch.
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, currentEpoch+1, nextEpochAccounts)

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2020, 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -28,6 +28,8 @@ import (
"github.com/attestantio/vouch/services/controller/standard"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
mockscheduler "github.com/attestantio/vouch/services/scheduler/mock"
mocksynccommitteemessenger "github.com/attestantio/vouch/services/synccommitteemessenger/mock"
mocksynccommitteesubscriber "github.com/attestantio/vouch/services/synccommitteesubscriber/mock"
"github.com/attestantio/vouch/testing/logger"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
@ -39,14 +41,18 @@ func TestService(t *testing.T) {
genesisTime := time.Now()
slotDuration := 12 * time.Second
slotsPerEpoch := uint64(32)
mockGenesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
mockSlotDurationProvider := mock.NewSlotDurationProvider(slotDuration)
mockSlotsPerEpochProvider := mock.NewSlotsPerEpochProvider(slotsPerEpoch)
genesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
slotDurationProvider := mock.NewSlotDurationProvider(slotDuration)
slotsPerEpochProvider := mock.NewSlotsPerEpochProvider(slotsPerEpoch)
specProvider := mock.NewSpecProvider()
proposerDutiesProvider := mock.NewProposerDutiesProvider()
attesterDutiesProvider := mock.NewAttesterDutiesProvider()
syncCommitteeDutiesProvider := mock.NewSyncCommitteeDutiesProvider()
mockScheduler := mockscheduler.New()
mockAttester := mockattester.New()
mockSyncCommitteeMessenger := mocksynccommitteemessenger.New()
mockSyncCommitteeSubscriber := mocksynccommitteesubscriber.New()
mockAttestationAggregator := mockattestationaggregator.New()
mockValidatingAccountsProvider := mockaccountmanager.NewValidatingAccountsProvider()
mockAccountsRefresher := mockaccountmanager.NewRefresher()
@ -55,9 +61,9 @@ func TestService(t *testing.T) {
mockBeaconCommitteeSubscriber := mockbeaconcommitteesubscriber.New()
chainTime, err := standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(mockGenesisTimeProvider),
standardchaintime.WithSlotDurationProvider(mockSlotDurationProvider),
standardchaintime.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standardchaintime.WithGenesisTimeProvider(genesisTimeProvider),
standardchaintime.WithSlotDurationProvider(slotDurationProvider),
standardchaintime.WithSlotsPerEpochProvider(slotsPerEpochProvider),
)
require.NoError(t, err)
@ -71,15 +77,17 @@ func TestService(t *testing.T) {
name: "MonitorNil",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -89,104 +97,67 @@ func TestService(t *testing.T) {
err: "problem with parameters: no monitor specified",
},
{
name: "SlotDurationProviderNotSpecified",
name: "SpecProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(4 * time.Second),
},
err: "problem with parameters: no slot duration provider specified",
err: "problem with parameters: no spec provider specified",
},
{
name: "SlotDurationProviderErrors",
name: "SpecProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mock.NewErroringSlotDurationProvider()),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(mock.NewErroringSpecProvider()),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(4 * time.Second),
},
err: "failed to obtain slot duration: mock",
},
{
name: "SlotsPerEpochProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(4 * time.Second),
},
err: "problem with parameters: no slots per epoch provider specified",
},
{
name: "SlotsPerEpochProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mock.NewErroringSlotsPerEpochProvider()),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(4 * time.Second),
},
err: "failed to obtain slots per epoch: error",
err: "problem with parameters: failed to obtain spec: error",
},
{
name: "ChainTimeServiceMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -200,14 +171,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -221,14 +194,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -242,14 +217,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -263,14 +240,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -284,14 +263,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -305,14 +286,16 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -326,15 +309,17 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
@ -347,15 +332,17 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
@ -368,15 +355,17 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAccountsRefresher(mockAccountsRefresher),
@ -389,15 +378,17 @@ func TestService(t *testing.T) {
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
@ -405,47 +396,51 @@ func TestService(t *testing.T) {
},
err: "problem with parameters: no accounts refresher specified",
},
{
name: "MaxAttestationDelayZero",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(0),
},
err: "problem with parameters: no maximum attestation delay specified",
},
{
name: "Good",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSlotDurationProvider(mockSlotDurationProvider),
standard.WithSlotsPerEpochProvider(mockSlotsPerEpochProvider),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithMaxAttestationDelay(4 * time.Second),
standard.WithReorgs(false),
},
},
{
name: "GoodDefaultMaxAttestationDelay",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithChainTimeService(chainTime),
standard.WithProposerDutiesProvider(proposerDutiesProvider),
standard.WithAttesterDutiesProvider(attesterDutiesProvider),
standard.WithSyncCommitteeDutiesProvider(syncCommitteeDutiesProvider),
standard.WithEventsProvider(mockEventsProvider),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithScheduler(mockScheduler),
standard.WithAttester(mockAttester),
standard.WithSyncCommitteeMessenger(mockSyncCommitteeMessenger),
standard.WithSyncCommitteeSubscriber(mockSyncCommitteeSubscriber),
standard.WithBeaconBlockProposer(mockBeaconBlockProposer),
standard.WithBeaconCommitteeSubscriber(mockBeaconCommitteeSubscriber),
standard.WithAttestationAggregator(mockAttestationAggregator),
standard.WithAccountsRefresher(mockAccountsRefresher),
standard.WithReorgs(true),
},
},
}

View File

@ -0,0 +1,175 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"fmt"
"time"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/attestantio/vouch/services/synccommitteemessenger"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// scheduleSyncCommitteeMessages schedules sync committee messages for the given epoch and validator indices.
func (s *Service) scheduleSyncCommitteeMessages(ctx context.Context,
epoch phase0.Epoch,
validatorIndices []phase0.ValidatorIndex,
) {
if len(validatorIndices) == 0 {
// Nothing to do.
return
}
started := time.Now()
log.Trace().Uint64("epoch", uint64(epoch)).Msg("Scheduling sync committee messages")
resp, err := s.syncCommitteeDutiesProvider.SyncCommitteeDuties(ctx, epoch, validatorIndices)
if err != nil {
log.Error().Err(err).Msg("Failed to fetch sync committee message duties")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(resp)).Msg("Fetched sync committee message duties")
// We combine the duties for the epoch.
messageIndices := make(map[phase0.ValidatorIndex][]phase0.CommitteeIndex, len(resp))
for _, duty := range resp {
messageIndices[duty.ValidatorIndex] = duty.ValidatorSyncCommitteeIndices
}
// log.Trace().Dur("elapsed", time.Since(started)).Str("duties", duty.String()).Msg("Generated sync committee message indices")
// Obtain the accounts for the validator indices.
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx, epoch, validatorIndices)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain validating accounts for epoch")
return
}
// Now we have the messages we can subscribe to the relevant subnets.
syncCommitteePeriodFirstSlot := s.chainTimeService.FirstSlotOfEpoch(phase0.Epoch((uint64(epoch) / s.epochsPerSyncCommitteePeriod) * s.epochsPerSyncCommitteePeriod))
syncCommitteePeriodLastSlot := syncCommitteePeriodFirstSlot + phase0.Slot(s.slotsPerEpoch*s.epochsPerSyncCommitteePeriod) - 1
if syncCommitteePeriodFirstSlot < s.chainTimeService.CurrentSlot() {
syncCommitteePeriodFirstSlot = s.chainTimeService.CurrentSlot()
}
log.Trace().
Uint64("first_slot", uint64(syncCommitteePeriodFirstSlot)).
Uint64("last_slot", uint64(syncCommitteePeriodLastSlot)).
Msg("Setting sync committee duties for period")
for slot := syncCommitteePeriodFirstSlot; slot <= syncCommitteePeriodLastSlot; slot++ {
go func(duty *synccommitteemessenger.Duty, accounts map[phase0.ValidatorIndex]e2wtypes.Account) {
for _, validatorIndex := range duty.ValidatorIndices() {
account, exists := accounts[validatorIndex]
if !exists {
log.Error().Uint64("validator_index", uint64(validatorIndex)).Msg("No validating account; cannot continue")
// Continue regardless of error, to attempt to schedule as many valid jobs as possible.
} else {
duty.SetAccount(validatorIndex, account)
}
}
prepareJobTime := s.chainTimeService.StartOfSlot(duty.Slot()).Add(-1 * time.Minute)
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Prepare sync committee messages for slot %d", duty.Slot()),
prepareJobTime,
s.prepareMessageSyncCommittee,
duty,
); err != nil {
log.Error().Err(err).Msg("Failed to schedule prepare sync committee messages")
return
}
jobTime := s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.maxSyncCommitteeMessageDelay)
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Sync committee messages for slot %d", duty.Slot()),
jobTime,
s.messageSyncCommittee,
duty,
); err != nil {
// Don't return here; we want to try to set up as many sync committee message jobs as possible.
log.Error().Err(err).Msg("Failed to schedule sync committee messages")
}
}(synccommitteemessenger.NewDuty(slot, messageIndices), accounts)
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Scheduled sync committee messages")
// TODO Obi-wan?
if err := s.syncCommitteesSubscriber.Subscribe(ctx, s.chainTimeService.SlotToEpoch(syncCommitteePeriodLastSlot), resp); err != nil {
log.Error().Err(err).Msg("Failed to submit sync committee subscribers")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Submitted sync committee subscribers")
}
func (s *Service) prepareMessageSyncCommittee(ctx context.Context, data interface{}) {
started := time.Now()
duty, ok := data.(*synccommitteemessenger.Duty)
if !ok {
log.Error().Msg("Passed invalid data")
return
}
log := log.With().Uint64("slot", uint64(s.chainTimeService.CurrentSlot())).Logger()
if err := s.syncCommitteeMessenger.Prepare(ctx, duty); err != nil {
log.Error().Uint64("sync_committee_slot", uint64(duty.Slot())).Err(err).Msg("Failed to prepare sync committee message")
return
}
// At this point we can schedule an aggregation job if reqiured.
aggregateValidatorIndices := make([]phase0.ValidatorIndex, 0)
selectionProofs := make(map[phase0.ValidatorIndex]map[uint64]phase0.BLSSignature)
for _, validatorIndex := range duty.ValidatorIndices() {
aggregationIndices := duty.AggregatorSubcommittees(validatorIndex)
if len(aggregationIndices) > 0 {
aggregateValidatorIndices = append(aggregateValidatorIndices, validatorIndex)
selectionProofs[validatorIndex] = aggregationIndices
}
}
if len(aggregateValidatorIndices) > 0 {
aggregatorDuty := &synccommitteeaggregator.Duty{
Slot: duty.Slot(),
ValidatorIndices: aggregateValidatorIndices,
SelectionProofs: selectionProofs,
Accounts: duty.Accounts(),
}
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Sync committee aggregation for slot %d", duty.Slot()),
s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.slotDuration*2/3),
s.syncCommitteeAggregator.Aggregate,
aggregatorDuty,
); err != nil {
log.Error().Err(err).Msg("Failed to schedule sync committee attestation aggregation job")
}
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Prepared")
}
func (s *Service) messageSyncCommittee(ctx context.Context, data interface{}) {
started := time.Now()
duty, ok := data.(*synccommitteemessenger.Duty)
if !ok {
log.Error().Msg("Passed invalid data")
return
}
log := log.With().Uint64("slot", uint64(s.chainTimeService.CurrentSlot())).Logger()
_, err := s.syncCommitteeMessenger.Message(ctx, duty)
if err != nil {
log.Warn().Err(err).Msg("Failed to submit sync committee message")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Messaged")
}

View File

@ -81,3 +81,11 @@ func (s *Service) ClientOperation(provider string, name string, succeeded bool,
// StrategyOperation provides a generic monitor for strategy operations.
func (s *Service) StrategyOperation(strategy string, provider string, operation string, duration time.Duration) {
}
// SyncCommitteeAggregationsCompleted is called when a sync committee aggregation process has completed.
func (s *Service) SyncCommitteeAggregationsCompleted(started time.Time, count int, result string) {
}
// SyncCommitteeMessagesCompleted is called when a sync committee message process has completed.
func (s *Service) SyncCommitteeMessagesCompleted(started time.Time, count int, result string) {
}

View File

@ -43,11 +43,21 @@ type Service struct {
attestationAggregationProcessRequests *prometheus.CounterVec
attestationAggregationCoverageRatio prometheus.Histogram
syncCommitteeMessageProcessTimer prometheus.Histogram
syncCommitteeMessageProcessRequests *prometheus.CounterVec
syncCommitteeAggregationProcessTimer prometheus.Histogram
syncCommitteeAggregationProcessRequests *prometheus.CounterVec
beaconCommitteeSubscriptionProcessTimer prometheus.Histogram
beaconCommitteeSubscriptionProcessRequests *prometheus.CounterVec
beaconCommitteeSubscribers prometheus.Gauge
beaconCommitteeAggregators prometheus.Gauge
syncCommitteeSubscriptionProcessTimer prometheus.Histogram
syncCommitteeSubscriptionProcessRequests *prometheus.CounterVec
syncCommitteeSubscribers prometheus.Gauge
accountManagerAccounts *prometheus.GaugeVec
clientOperationCounter *prometheus.CounterVec
@ -89,9 +99,18 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
if err := s.setupAttestationAggregationMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up attestation aggregation metrics")
}
if err := s.setupSyncCommitteeMessageMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up sync committee message metrics")
}
if err := s.setupSyncCommitteeAggregationMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up sync committee aggregation metrics")
}
if err := s.setupBeaconCommitteeSubscriptionMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up beacon committee subscription metrics")
}
if err := s.setupSyncCommitteeSubscriptionMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up sync committee subscription metrics")
}
if err := s.setupAccountManagerMetrics(); err != nil {
return nil, errors.Wrap(err, "failed to set up account manager metrics")
}

View File

@ -0,0 +1,54 @@
// Copyright © 2020 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus
import (
"time"
"github.com/prometheus/client_golang/prometheus"
)
func (s *Service) setupSyncCommitteeAggregationMetrics() error {
s.syncCommitteeAggregationProcessTimer =
prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: "vouch",
Subsystem: "sync_committee_aggregation_process",
Name: "duration_seconds",
Help: "The time vouch spends from starting the sync committee aggregation process to submitting the sync committee aggregations.",
Buckets: []float64{
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0,
},
})
if err := prometheus.Register(s.syncCommitteeAggregationProcessTimer); err != nil {
return err
}
s.syncCommitteeAggregationProcessRequests = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "vouch",
Subsystem: "sync_committee_aggregation_process",
Name: "requests_total",
Help: "The number of sync committee aggregation processes.",
}, []string{"result"})
return prometheus.Register(s.syncCommitteeAggregationProcessRequests)
}
// SyncCommitteeAggregationsCompleted is called when a sync committee aggregation process has completed.
func (s *Service) SyncCommitteeAggregationsCompleted(started time.Time, count int, result string) {
duration := time.Since(started).Seconds()
for i := 0; i < count; i++ {
s.syncCommitteeAggregationProcessTimer.Observe(duration)
}
s.syncCommitteeAggregationProcessRequests.WithLabelValues(result).Add(float64(count))
}

View File

@ -0,0 +1,54 @@
// Copyright © 2020 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus
import (
"time"
"github.com/prometheus/client_golang/prometheus"
)
func (s *Service) setupSyncCommitteeMessageMetrics() error {
s.syncCommitteeMessageProcessTimer =
prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: "vouch",
Subsystem: "sync_committee_message_process",
Name: "duration_seconds",
Help: "The time vouch spends from starting the sync committee message process to submitting the sync committee messages.",
Buckets: []float64{
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0,
},
})
if err := prometheus.Register(s.syncCommitteeMessageProcessTimer); err != nil {
return err
}
s.syncCommitteeMessageProcessRequests = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "vouch",
Subsystem: "sync_committee_message_process",
Name: "requests_total",
Help: "The number of sync committee message processes.",
}, []string{"result"})
return prometheus.Register(s.syncCommitteeMessageProcessRequests)
}
// SyncCommitteeMessagesCompleted is called when a sync committee message process has completed.
func (s *Service) SyncCommitteeMessagesCompleted(started time.Time, count int, result string) {
duration := time.Since(started).Seconds()
for i := 0; i < count; i++ {
s.syncCommitteeMessageProcessTimer.Observe(duration)
}
s.syncCommitteeMessageProcessRequests.WithLabelValues(result).Add(float64(count))
}

View File

@ -0,0 +1,66 @@
// Copyright © 2020 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prometheus
import (
"time"
"github.com/prometheus/client_golang/prometheus"
)
func (s *Service) setupSyncCommitteeSubscriptionMetrics() error {
s.syncCommitteeSubscriptionProcessTimer =
prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: "vouch",
Subsystem: "synccommitteesubscription_process",
Name: "duration_seconds",
Help: "The time vouch spends from starting the sync committee subscription process to submitting the subscription request.",
Buckets: []float64{
0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0,
},
})
if err := prometheus.Register(s.syncCommitteeSubscriptionProcessTimer); err != nil {
return err
}
s.syncCommitteeSubscriptionProcessRequests = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "vouch",
Subsystem: "synccommitteesubscription_process",
Name: "requests_total",
Help: "The number of sync committee subscription processes.",
}, []string{"result"})
if err := prometheus.Register(s.syncCommitteeSubscriptionProcessRequests); err != nil {
return err
}
s.syncCommitteeSubscribers = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "vouch",
Subsystem: "synccommitteesubscription",
Name: "subscribers_total",
Help: "The number of sync committee subscribed.",
})
return prometheus.Register(s.syncCommitteeSubscribers)
}
// SyncCommitteeSubscriptionCompleted is called when an sync committee subscription process has completed.
func (s *Service) SyncCommitteeSubscriptionCompleted(started time.Time, result string) {
s.syncCommitteeSubscriptionProcessTimer.Observe(time.Since(started).Seconds())
s.syncCommitteeSubscriptionProcessRequests.WithLabelValues(result).Inc()
}
// SyncCommitteeSubscribers sets the number of sync committees to which our validators are subscribed.
func (s *Service) SyncCommitteeSubscribers(subscribers int) {
s.syncCommitteeSubscribers.Set(float64(subscribers))
}

View File

@ -63,9 +63,21 @@ type AttestationAggregationMonitor interface {
AttestationAggregationCoverage(frac float64)
}
// SyncCommitteeMessageMonitor provides methods to monitor the sync committee message process.
type SyncCommitteeMessageMonitor interface {
// SyncCommitteeMessagesCompleted is called when a sync committee message process has completed.
SyncCommitteeMessagesCompleted(started time.Time, count int, result string)
}
// SyncCommitteeAggregationMonitor provides methods to monitor the sync committee aggregation process.
type SyncCommitteeAggregationMonitor interface {
// SyncCommitteeAggregationsCompleted is called when a sync committee aggregation process has completed.
SyncCommitteeAggregationsCompleted(started time.Time, count int, result string)
}
// BeaconCommitteeSubscriptionMonitor provides methods to monitor the outcome of beacon committee subscriptions.
type BeaconCommitteeSubscriptionMonitor interface {
// BeaconCommitteeSubscriptionCompleted is called when an beacon committee subscription process has completed.
// BeaconCommitteeSubscriptionCompleted is called when a beacon committee subscription process has completed.
BeaconCommitteeSubscriptionCompleted(started time.Time, result string)
// BeaconCommitteeSubscribers sets the number of beacon committees to which our validators are subscribed.
BeaconCommitteeSubscribers(subscribers int)
@ -73,6 +85,14 @@ type BeaconCommitteeSubscriptionMonitor interface {
BeaconCommitteeAggregators(aggregators int)
}
// SyncCommitteeSubscriptionMonitor provides methods to monitor the outcome of sync committee subscriptions.
type SyncCommitteeSubscriptionMonitor interface {
// SyncCommitteeSubscriptionCompleted is called when a sync committee subscription process has completed.
SyncCommitteeSubscriptionCompleted(started time.Time, result string)
// SyncCommitteeSubscribers sets the number of sync committees to which our validators are subscribed.
SyncCommitteeSubscribers(subscribers int)
}
// AccountManagerMonitor provides methods to monitor the account manager.
type AccountManagerMonitor interface {
// Accounts sets the number of accounts in a given state.

View File

@ -16,6 +16,7 @@ package mock
import (
"context"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
@ -112,3 +113,40 @@ func (s *Service) SignSlotSelection(ctx context.Context,
) {
return phase0.BLSSignature{}, nil
}
// SignContributionAndProof signs a sync committee contribution for given slot and root.
func (s *Service) SignContributionAndProof(ctx context.Context,
account e2wtypes.Account,
contributionAndProof *altair.ContributionAndProof,
) (
phase0.BLSSignature,
error,
) {
return phase0.BLSSignature{}, nil
}
// SignSyncCommitteeRoot returns a root signature.
// This signs a beacon block root with the "sync committee" domain.
func (s *Service) SignSyncCommitteeRoot(ctx context.Context,
account e2wtypes.Account,
epoch phase0.Epoch,
root phase0.Root,
) (
phase0.BLSSignature,
error,
) {
return phase0.BLSSignature{}, nil
}
// SignSyncCommitteeSelection returns a sync committee selection signature.
// This signs a slot and subcommittee with the "sync committee selection proof" domain.
func (s *Service) SignSyncCommitteeSelection(ctx context.Context,
account e2wtypes.Account,
slot phase0.Slot,
subcommitteeIndex uint64,
) (
phase0.BLSSignature,
error,
) {
return phase0.BLSSignature{}, nil
}

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -17,6 +17,7 @@ package signer
import (
"context"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
@ -114,3 +115,43 @@ type SlotSelectionSigner interface {
error,
)
}
// SyncCommitteeRootSigner provides methods to sign a sync committee root.
type SyncCommitteeRootSigner interface {
// SignSyncCommittee returns a root signature.
// This signs a beacon block root with the "sync committee" domain.
SignSyncCommitteeRoot(ctx context.Context,
account e2wtypes.Account,
epoch phase0.Epoch,
root phase0.Root,
) (
phase0.BLSSignature,
error,
)
}
// SyncCommitteeSelectionSigner provides methods to sign sync committee selections.
type SyncCommitteeSelectionSigner interface {
// SignSyncCommitteeSelection returns a sync committee selection signature.
// This signs a slot and subcommittee with the "sync committee selection proof" domain.
SignSyncCommitteeSelection(ctx context.Context,
account e2wtypes.Account,
slot phase0.Slot,
subcommitteeIndex uint64,
) (
phase0.BLSSignature,
error,
)
}
// ContributionAndProofSigner provides methods to sign contribution and proofs.
type ContributionAndProofSigner interface {
// SignContributionAndProof signs a sync committee contribution for given slot and root.
SignContributionAndProof(ctx context.Context,
account e2wtypes.Account,
contributionAndProof *altair.ContributionAndProof,
) (
phase0.BLSSignature,
error,
)
}

View File

@ -24,16 +24,11 @@ import (
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.SignerMonitor
clientMonitor metrics.ClientMonitor
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
beaconProposerDomainTypeProvider eth2client.BeaconProposerDomainProvider
beaconAttesterDomainTypeProvider eth2client.BeaconAttesterDomainProvider
randaoDomainTypeProvider eth2client.RANDAODomainProvider
selectionProofDomainTypeProvider eth2client.SelectionProofDomainProvider
aggregateAndProofDomainTypeProvider eth2client.AggregateAndProofDomainProvider
domainProvider eth2client.DomainProvider
logLevel zerolog.Level
monitor metrics.SignerMonitor
clientMonitor metrics.ClientMonitor
specProvider eth2client.SpecProvider
domainProvider eth2client.DomainProvider
}
// Parameter is the interface for service parameters.
@ -68,45 +63,10 @@ func WithClientMonitor(clientMonitor metrics.ClientMonitor) Parameter {
})
}
// WithSlotsPerEpochProvider sets the slots per epoch provider.
func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) Parameter {
// WithSpecProvider sets the spec provider.
func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.slotsPerEpochProvider = provider
})
}
// WithBeaconProposerDomainTypeProvider sets the beacon proposer domain provider.
func WithBeaconProposerDomainTypeProvider(provider eth2client.BeaconProposerDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconProposerDomainTypeProvider = provider
})
}
// WithBeaconAttesterDomainTypeProvider sets the beacon attester domain provider.
func WithBeaconAttesterDomainTypeProvider(provider eth2client.BeaconAttesterDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconAttesterDomainTypeProvider = provider
})
}
// WithRANDAODomainTypeProvider sets the RANDAO domain provider.
func WithRANDAODomainTypeProvider(provider eth2client.RANDAODomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.randaoDomainTypeProvider = provider
})
}
// WithSelectionProofDomainTypeProvider sets the RANDAO domain provider.
func WithSelectionProofDomainTypeProvider(provider eth2client.SelectionProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.selectionProofDomainTypeProvider = provider
})
}
// WithAggregateAndProofDomainTypeProvider sets the aggregate and proof domain provider.
func WithAggregateAndProofDomainTypeProvider(provider eth2client.AggregateAndProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.aggregateAndProofDomainTypeProvider = provider
p.specProvider = provider
})
}
@ -136,23 +96,8 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.clientMonitor == nil {
return nil, errors.New("no client monitor specified")
}
if parameters.slotsPerEpochProvider == nil {
return nil, errors.New("no slots per epoch provider specified")
}
if parameters.beaconProposerDomainTypeProvider == nil {
return nil, errors.New("no beacon proposer domain type provider specified")
}
if parameters.beaconAttesterDomainTypeProvider == nil {
return nil, errors.New("no beacon attester domain type provider specified")
}
if parameters.randaoDomainTypeProvider == nil {
return nil, errors.New("no RANDAO domain type provider specified")
}
if parameters.selectionProofDomainTypeProvider == nil {
return nil, errors.New("no selection proof domain type provider specified")
}
if parameters.aggregateAndProofDomainTypeProvider == nil {
return nil, errors.New("no aggregate and proof domain type provider specified")
if parameters.specProvider == nil {
return nil, errors.New("no spec provider specified")
}
if parameters.domainProvider == nil {
return nil, errors.New("no domain provider specified")

View File

@ -15,6 +15,7 @@ package standard
import (
"context"
"fmt"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
@ -26,15 +27,18 @@ import (
// Service is the manager for signers.
type Service struct {
monitor metrics.SignerMonitor
clientMonitor metrics.ClientMonitor
slotsPerEpoch phase0.Slot
beaconProposerDomainType phase0.DomainType
beaconAttesterDomainType phase0.DomainType
randaoDomainType phase0.DomainType
selectionProofDomainType phase0.DomainType
aggregateAndProofDomainType phase0.DomainType
domainProvider eth2client.DomainProvider
monitor metrics.SignerMonitor
clientMonitor metrics.ClientMonitor
slotsPerEpoch phase0.Slot
beaconProposerDomainType phase0.DomainType
beaconAttesterDomainType phase0.DomainType
randaoDomainType phase0.DomainType
selectionProofDomainType phase0.DomainType
aggregateAndProofDomainType phase0.DomainType
syncCommitteeDomainType *phase0.DomainType
syncCommitteeSelectionProofDomainType *phase0.DomainType
contributionAndProofDomainType *phase0.DomainType
domainProvider eth2client.DomainProvider
}
// module-wide log.
@ -53,42 +57,87 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
log = log.Level(parameters.logLevel)
}
slotsPerEpoch, err := parameters.slotsPerEpochProvider.SlotsPerEpoch(ctx)
spec, err := parameters.specProvider.Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
return nil, errors.Wrap(err, "failed to obtain spec")
}
beaconAttesterDomainType, err := parameters.beaconAttesterDomainTypeProvider.BeaconAttesterDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon attester domain type")
tmp, exists := spec["SLOTS_PER_EPOCH"]
if !exists {
return nil, errors.New("SLOTS_PER_EPOCH not found in spec")
}
beaconProposerDomainType, err := parameters.beaconProposerDomainTypeProvider.BeaconProposerDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon proposer domain type")
slotsPerEpoch, ok := tmp.(uint64)
if !ok {
return nil, errors.New("SLOTS_PER_EPOCH of unexpected type")
}
randaoDomainType, err := parameters.randaoDomainTypeProvider.RANDAODomain(ctx)
beaconAttesterDomainType, err := domainType(spec, "DOMAIN_BEACON_ATTESTER")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain RANDAO domain type")
return nil, err
}
selectionProofDomainType, err := parameters.selectionProofDomainTypeProvider.SelectionProofDomain(ctx)
beaconProposerDomainType, err := domainType(spec, "DOMAIN_BEACON_PROPOSER")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain selection proof domain type")
return nil, err
}
aggregateAndProofDomainType, err := parameters.aggregateAndProofDomainTypeProvider.AggregateAndProofDomain(ctx)
randaoDomainType, err := domainType(spec, "DOMAIN_RANDAO")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain aggregate and proof domain type")
return nil, err
}
selectionProofDomainType, err := domainType(spec, "DOMAIN_SELECTION_PROOF")
if err != nil {
return nil, err
}
aggregateAndProofDomainType, err := domainType(spec, "DOMAIN_AGGREGATE_AND_PROOF")
if err != nil {
return nil, err
}
// The following are optional.
var syncCommitteeDomainType *phase0.DomainType
if tmp, err := domainType(spec, "DOMAIN_SYNC_COMMITTEE"); err == nil {
syncCommitteeDomainType = &tmp
}
var syncCommitteeSelectionProofDomainType *phase0.DomainType
if tmp, err := domainType(spec, "DOMAIN_SYNC_COMMITTEE_SELECTION_PROOF"); err == nil {
syncCommitteeSelectionProofDomainType = &tmp
}
var contributionAndProofDomainType *phase0.DomainType
if tmp, err := domainType(spec, "DOMAIN_CONTRIBUTION_AND_PROOF"); err == nil {
contributionAndProofDomainType = &tmp
}
s := &Service{
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
slotsPerEpoch: phase0.Slot(slotsPerEpoch),
beaconAttesterDomainType: beaconAttesterDomainType,
beaconProposerDomainType: beaconProposerDomainType,
randaoDomainType: randaoDomainType,
selectionProofDomainType: selectionProofDomainType,
aggregateAndProofDomainType: aggregateAndProofDomainType,
domainProvider: parameters.domainProvider,
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
slotsPerEpoch: phase0.Slot(slotsPerEpoch),
beaconAttesterDomainType: beaconAttesterDomainType,
beaconProposerDomainType: beaconProposerDomainType,
randaoDomainType: randaoDomainType,
selectionProofDomainType: selectionProofDomainType,
aggregateAndProofDomainType: aggregateAndProofDomainType,
syncCommitteeDomainType: syncCommitteeDomainType,
syncCommitteeSelectionProofDomainType: syncCommitteeSelectionProofDomainType,
contributionAndProofDomainType: contributionAndProofDomainType,
domainProvider: parameters.domainProvider,
}
return s, nil
}
func domainType(spec map[string]interface{}, input string) (phase0.DomainType, error) {
tmp, exists := spec[input]
if !exists {
return phase0.DomainType{}, fmt.Errorf("%v not found in spec", input)
}
domainType, ok := tmp.(phase0.DomainType)
if !ok {
return phase0.DomainType{}, fmt.Errorf("%v of unexpected type", input)
}
return domainType, nil
}

View File

@ -26,12 +26,7 @@ import (
)
func TestService(t *testing.T) {
slotsPerEpochProvider := mock.NewSlotsPerEpochProvider(32)
beaconProposerDomainTypeProvider := mock.NewBeaconProposerDomainProvider()
beaconAttesterDomainTypeProvider := mock.NewBeaconAttesterDomainProvider()
randaoDomainTypeProvider := mock.NewRANDAODomainProvider()
selectionProofDomainTypeProvider := mock.NewSelectionProofDomainProvider()
aggregateAndProofDomainTypeProvider := mock.NewAggregateAndProofDomainProvider()
specProvider := mock.NewSpecProvider()
domainProvider := mock.NewDomainProvider()
tests := []struct {
@ -46,12 +41,7 @@ func TestService(t *testing.T) {
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nil),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithSpecProvider(specProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no monitor specified",
@ -62,216 +52,31 @@ func TestService(t *testing.T) {
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nil),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithSpecProvider(specProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no client monitor specified",
},
{
name: "SlotsPerEpochProviderMissing",
name: "SpecProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no slots per epoch provider specified",
err: "problem with parameters: no spec provider specified",
},
{
name: "SlotsPerEpochProviderErrors",
name: "SpecProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(mock.NewErroringSlotsPerEpochProvider()),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithSpecProvider(mock.NewErroringSpecProvider()),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain slots per epoch: error",
},
{
name: "BeaonProposerDomainTypeProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no beacon proposer domain type provider specified",
},
{
name: "BeaonProposerDomainTypeProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(mock.NewErroringBeaconProposerDomainProvider()),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain beacon proposer domain type: error",
},
{
name: "BeaonAttesterDomainTypeMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no beacon attester domain type provider specified",
},
{
name: "BeaonAttesterDomainTypeErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(mock.NewErroringBeaconAttesterDomainProvider()),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain beacon attester domain type: error",
},
{
name: "RANDAODomainTypeProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no RANDAO domain type provider specified",
},
{
name: "RANDAODomainTypeProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(mock.NewErroringRANDAODomainProvider()),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain RANDAO domain type: error",
},
{
name: "SelectionProofDomianTypeProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no selection proof domain type provider specified",
},
{
name: "SelectionProofDomianTypeProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(mock.NewErroringSelectionProofDomainProvider()),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain selection proof domain type: error",
},
{
name: "AggregateAndProofDomianTypeProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no aggregate and proof domain type provider specified",
},
{
name: "AggregateAndProofDomianTypeProviderErrors",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(mock.NewErroringAggregateAndProofDomainProvider()),
standard.WithDomainProvider(domainProvider),
},
err: "failed to obtain aggregate and proof domain type: error",
},
{
name: "DomainProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
},
err: "problem with parameters: no domain provider specified",
err: "failed to obtain spec: error",
},
{
name: "Good",
@ -279,12 +84,7 @@ func TestService(t *testing.T) {
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithSlotsPerEpochProvider(slotsPerEpochProvider),
standard.WithBeaconProposerDomainTypeProvider(beaconProposerDomainTypeProvider),
standard.WithBeaconAttesterDomainTypeProvider(beaconAttesterDomainTypeProvider),
standard.WithRANDAODomainTypeProvider(randaoDomainTypeProvider),
standard.WithSelectionProofDomainTypeProvider(selectionProofDomainTypeProvider),
standard.WithAggregateAndProofDomainTypeProvider(aggregateAndProofDomainTypeProvider),
standard.WithSpecProvider(specProvider),
standard.WithDomainProvider(domainProvider),
},
},

View File

@ -0,0 +1,55 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignContributionAndProof signs a sync committee contribution for given slot and root.
func (s *Service) SignContributionAndProof(ctx context.Context,
account e2wtypes.Account,
contributionAndProof *altair.ContributionAndProof,
) (
phase0.BLSSignature,
error,
) {
if s.contributionAndProofDomainType == nil {
return phase0.BLSSignature{}, errors.New("no contribution and proof domain type available; cannot sign")
}
root, err := contributionAndProof.HashTreeRoot()
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to calculate hash tree root")
}
// Calculate the domain.
epoch := phase0.Epoch(contributionAndProof.Contribution.Slot / s.slotsPerEpoch)
domain, err := s.domainProvider.Domain(ctx, *s.contributionAndProofDomainType, epoch)
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for contribution and proof")
}
sig, err := s.sign(ctx, account, root, domain)
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to sign contribution and proof")
}
return sig, nil
}

View File

@ -0,0 +1,50 @@
// Copyright © 2020 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignSyncCommitteeRoot returns a root signature.
// This signs a beacon block root with the "sync committee" domain.
func (s *Service) SignSyncCommitteeRoot(ctx context.Context,
account e2wtypes.Account,
epoch phase0.Epoch,
root phase0.Root,
) (
phase0.BLSSignature,
error,
) {
if s.syncCommitteeDomainType == nil {
return phase0.BLSSignature{}, errors.New("no sync committee domain type available; cannot sign")
}
// Calculate the domain.
domain, err := s.domainProvider.Domain(ctx, *s.syncCommitteeDomainType, epoch)
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for sync committee")
}
sig, err := s.sign(ctx, account, root, domain)
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to sign sync committee root")
}
return sig, nil
}

View File

@ -0,0 +1,62 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignSyncCommitteeSelection returns a sync committee selection signature.
// This signs a slot and subcommittee with the "sync committee selection proof" domain.
func (s *Service) SignSyncCommitteeSelection(ctx context.Context,
account e2wtypes.Account,
slot phase0.Slot,
subcommitteeIndex uint64,
) (
phase0.BLSSignature,
error,
) {
if s.syncCommitteeSelectionProofDomainType == nil {
return phase0.BLSSignature{}, errors.New("no sync committee selection proof domain type, cannot sign")
}
// Calculate the domain.
domain, err := s.domainProvider.Domain(ctx,
*s.syncCommitteeSelectionProofDomainType,
phase0.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for sync committee selection proof")
}
selectionData := &altair.SyncAggregatorSelectionData{
Slot: slot,
SubcommitteeIndex: subcommitteeIndex,
}
root, err := selectionData.HashTreeRoot()
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to obtain hash tree root of sync aggregator selection data")
}
sig, err := s.sign(ctx, account, root, domain)
if err != nil {
return phase0.BLSSignature{}, errors.Wrap(err, "failed to sign sync committee selection proof")
}
return sig, nil
}

View File

@ -31,6 +31,9 @@ type parameters struct {
attestationsSubmitter eth2client.AttestationsSubmitter
beaconCommitteeSubscriptionsSubmitter eth2client.BeaconCommitteeSubscriptionsSubmitter
aggregateAttestationsSubmitter eth2client.AggregateAttestationsSubmitter
syncCommitteeMessagesSubmitter eth2client.SyncCommitteeMessagesSubmitter
syncCommitteeSubscriptionsSubmitter eth2client.SyncCommitteeSubscriptionsSubmitter
syncCommitteeContributionsSubmitter eth2client.SyncCommitteeContributionsSubmitter
}
// Parameter is the interface for service parameters.
@ -72,6 +75,27 @@ func WithAttestationsSubmitter(submitter eth2client.AttestationsSubmitter) Param
})
}
// WithSyncCommitteeMessagesSubmitter sets the sync committee messages submitter.
func WithSyncCommitteeMessagesSubmitter(submitter eth2client.SyncCommitteeMessagesSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeMessagesSubmitter = submitter
})
}
// WithSyncCommitteeSubscriptionsSubmitter sets the sync committee subscriptions submitter
func WithSyncCommitteeSubscriptionsSubmitter(submitter eth2client.SyncCommitteeSubscriptionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeSubscriptionsSubmitter = submitter
})
}
// WithSyncCommitteeContributionsSubmitter sets the sync committee contributions submitter
func WithSyncCommitteeContributionsSubmitter(submitter eth2client.SyncCommitteeContributionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionsSubmitter = submitter
})
}
// WithBeaconCommitteeSubscriptionsSubmitter sets the attestation subnet subscriptions submitter
func WithBeaconCommitteeSubscriptionsSubmitter(submitter eth2client.BeaconCommitteeSubscriptionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
@ -107,6 +131,15 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.attestationsSubmitter == nil {
return nil, errors.New("no attestations submitter specified")
}
if parameters.syncCommitteeMessagesSubmitter == nil {
return nil, errors.New("no sync committee messages submitter specified")
}
if parameters.syncCommitteeSubscriptionsSubmitter == nil {
return nil, errors.New("no sync committee subscriptions submitter specified")
}
if parameters.syncCommitteeContributionsSubmitter == nil {
return nil, errors.New("no sync committee contributions submitter specified")
}
if parameters.beaconCommitteeSubscriptionsSubmitter == nil {
return nil, errors.New("no beacon committee subscriptions submitter specified")
}

View File

@ -20,6 +20,8 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/metrics"
"github.com/pkg/errors"
@ -34,6 +36,9 @@ type Service struct {
beaconBlockSubmitter eth2client.BeaconBlockSubmitter
beaconCommitteeSubscriptionsSubmitter eth2client.BeaconCommitteeSubscriptionsSubmitter
aggregateAttestationsSubmitter eth2client.AggregateAttestationsSubmitter
syncCommitteeMessagesSubmitter eth2client.SyncCommitteeMessagesSubmitter
syncCommitteeSubscriptionsSubmitter eth2client.SyncCommitteeSubscriptionsSubmitter
syncCommitteeContributionsSubmitter eth2client.SyncCommitteeContributionsSubmitter
}
// module-wide log.
@ -58,13 +63,16 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
beaconBlockSubmitter: parameters.beaconBlockSubmitter,
beaconCommitteeSubscriptionsSubmitter: parameters.beaconCommitteeSubscriptionsSubmitter,
aggregateAttestationsSubmitter: parameters.aggregateAttestationsSubmitter,
syncCommitteeMessagesSubmitter: parameters.syncCommitteeMessagesSubmitter,
syncCommitteeSubscriptionsSubmitter: parameters.syncCommitteeSubscriptionsSubmitter,
syncCommitteeContributionsSubmitter: parameters.syncCommitteeContributionsSubmitter,
}
return s, nil
}
// SubmitBeaconBlock submits a block.
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *phase0.SignedBeaconBlock) error {
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
if block == nil {
return errors.New("no beacon block supplied")
}
@ -187,3 +195,84 @@ func (s *Service) SubmitAggregateAttestations(ctx context.Context, aggregates []
return nil
}
// SubmitSyncCommitteeMessages submits sync committee messages.
func (s *Service) SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error {
if len(messages) == 0 {
return errors.New("no sync committee messages supplied")
}
started := time.Now()
err := s.syncCommitteeMessagesSubmitter.SubmitSyncCommitteeMessages(ctx, messages)
if service, isService := s.aggregateAttestationsSubmitter.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "submit sync committee messages", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "submit sync committee messages", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to submit sync committee messages")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(messages)
if err == nil {
e.Str("messages", string(data)).Msg("Submitted sync committee messages")
}
}
return nil
}
// SubmitSyncCommitteeSubscriptions submits a batch of beacon committee subscriptions.
func (s *Service) SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error {
if len(subscriptions) == 0 {
return errors.New("no sync committee subscriptions supplied")
}
started := time.Now()
err := s.syncCommitteeSubscriptionsSubmitter.SubmitSyncCommitteeSubscriptions(ctx, subscriptions)
if service, isService := s.syncCommitteeSubscriptionsSubmitter.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "submit sync committee subscription", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "submit sync committee subscription", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to submit sync committee subscriptions")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(subscriptions)
if err == nil {
e.Str("subscriptions", string(data)).Int("subscribing", len(subscriptions)).Msg("Submitted subscriptions")
}
}
return nil
}
// SubmitSyncCommitteeContributions submits sync committee contributions.
func (s *Service) SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error {
if len(contributionAndProofs) == 0 {
return errors.New("no sync committee contribution and proofs supplied")
}
started := time.Now()
err := s.syncCommitteeContributionsSubmitter.SubmitSyncCommitteeContributions(ctx, contributionAndProofs)
if service, isService := s.syncCommitteeContributionsSubmitter.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "submit sync committee contribution and proofs", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "submit sync committee contribution and proofs", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to submit sync committee contribution and proofs")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(contributionAndProofs)
if err == nil {
e.Str("contributionAndProofs", string(data)).Msg("Submitted contribution and proofs")
}
}
return nil
}

View File

@ -18,6 +18,8 @@ import (
"testing"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/services/submitter"
@ -31,12 +33,30 @@ func TestService(t *testing.T) {
beaconBlockSubmitter := mock.NewBeaconBlockSubmitter()
beaconCommitteeSubscriptionSubmitter := mock.NewBeaconCommitteeSubscriptionsSubmitter()
aggregateAttestationSubmitter := mock.NewAggregateAttestationsSubmitter()
syncCommitteeMessagesSubmitter := mock.NewSyncCommitteeMessagesSubmitter()
syncCommitteeSubscriptionsSubmitter := mock.NewSyncCommitteeSubscriptionsSubmitter()
syncCommitteeContributionsSubmitter := mock.NewSyncCommitteeContributionsSubmitter()
tests := []struct {
name string
params []immediate.Parameter
err string
}{
{
name: "ClientMonitorMisssing",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithClientMonitor(nil),
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no client monitor specified",
},
{
name: "AttestationsSubmitterMissing",
params: []immediate.Parameter{
@ -44,6 +64,9 @@ func TestService(t *testing.T) {
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no attestations submitter specified",
},
@ -54,6 +77,9 @@ func TestService(t *testing.T) {
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no beacon block submitter specified",
},
@ -64,6 +90,9 @@ func TestService(t *testing.T) {
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no beacon committee subscriptions submitter specified",
},
@ -74,9 +103,50 @@ func TestService(t *testing.T) {
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no aggregate attestations submitter specified",
},
{
name: "SyncCommitteeSubscriptionsSubmitterMissing",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no sync committee subscriptions submitter specified",
},
{
name: "SyncCommitteeMessagesSubmitterMissing",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
err: "problem with parameters: no sync committee messages submitter specified",
},
{
name: "SyncCommitteeContributionsSubmitterMissing",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(attestationsSubmitter),
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
},
err: "problem with parameters: no sync committee contributions submitter specified",
},
{
name: "Good",
params: []immediate.Parameter{
@ -85,6 +155,9 @@ func TestService(t *testing.T) {
immediate.WithBeaconBlockSubmitter(beaconBlockSubmitter),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(beaconCommitteeSubscriptionSubmitter),
immediate.WithAggregateAttestationsSubmitter(aggregateAttestationSubmitter),
immediate.WithSyncCommitteeSubscriptionsSubmitter(syncCommitteeSubscriptionsSubmitter),
immediate.WithSyncCommitteeMessagesSubmitter(syncCommitteeMessagesSubmitter),
immediate.WithSyncCommitteeContributionsSubmitter(syncCommitteeContributionsSubmitter),
},
},
}
@ -107,6 +180,9 @@ func TestInterfaces(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
)
require.NoError(t, err)
require.Implements(t, (*submitter.BeaconBlockSubmitter)(nil), s)
@ -119,7 +195,7 @@ func TestSubmitBeaconBlock(t *testing.T) {
tests := []struct {
name string
params []immediate.Parameter
block *phase0.SignedBeaconBlock
block *spec.VersionedSignedBeaconBlock
err string
}{
{
@ -130,6 +206,9 @@ func TestSubmitBeaconBlock(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no beacon block supplied",
},
@ -141,8 +220,11 @@ func TestSubmitBeaconBlock(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
block: &phase0.SignedBeaconBlock{},
block: &spec.VersionedSignedBeaconBlock{},
},
{
name: "Erroring",
@ -152,8 +234,11 @@ func TestSubmitBeaconBlock(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewErroringBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
block: &phase0.SignedBeaconBlock{},
block: &spec.VersionedSignedBeaconBlock{},
err: "failed to submit beacon block: error",
},
{
@ -164,8 +249,11 @@ func TestSubmitBeaconBlock(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
block: &phase0.SignedBeaconBlock{},
block: &spec.VersionedSignedBeaconBlock{},
},
}
@ -199,6 +287,9 @@ func TestSubmitAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no attestations supplied",
},
@ -210,6 +301,9 @@ func TestSubmitAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
attestations: []*phase0.Attestation{},
err: "no attestations supplied",
@ -222,6 +316,9 @@ func TestSubmitAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
attestations: []*phase0.Attestation{{}},
err: "failed to submit attestations: error",
@ -234,6 +331,9 @@ func TestSubmitAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
attestations: []*phase0.Attestation{{}},
},
@ -269,6 +369,9 @@ func TestSubmitAggregateAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no aggregate attestations supplied",
},
@ -280,6 +383,9 @@ func TestSubmitAggregateAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
aggregates: []*phase0.SignedAggregateAndProof{},
err: "no aggregate attestations supplied",
@ -292,6 +398,9 @@ func TestSubmitAggregateAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewErroringAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
aggregates: []*phase0.SignedAggregateAndProof{
{},
@ -306,6 +415,9 @@ func TestSubmitAggregateAttestations(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
aggregates: []*phase0.SignedAggregateAndProof{
{},
@ -343,6 +455,9 @@ func TestSubmitBeaconCommitteeSubscriptions(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no beacon committee subscriptions supplied",
},
@ -354,6 +469,9 @@ func TestSubmitBeaconCommitteeSubscriptions(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.BeaconCommitteeSubscription{},
err: "no beacon committee subscriptions supplied",
@ -366,6 +484,9 @@ func TestSubmitBeaconCommitteeSubscriptions(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewErroringBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.BeaconCommitteeSubscription{
{},
@ -380,6 +501,9 @@ func TestSubmitBeaconCommitteeSubscriptions(t *testing.T) {
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.BeaconCommitteeSubscription{
{},
@ -401,3 +525,261 @@ func TestSubmitBeaconCommitteeSubscriptions(t *testing.T) {
})
}
}
func TestSubmitSyncCommitteeSubscriptions(t *testing.T) {
tests := []struct {
name string
params []immediate.Parameter
subscriptions []*api.SyncCommitteeSubscription
err string
}{
{
name: "Nil",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no sync committee subscriptions supplied",
},
{
name: "Empty",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.SyncCommitteeSubscription{},
err: "no sync committee subscriptions supplied",
},
{
name: "Erroring",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewErroringSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.SyncCommitteeSubscription{
{},
},
err: "failed to submit sync committee subscriptions: error",
},
{
name: "Good",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
subscriptions: []*api.SyncCommitteeSubscription{
{},
},
},
}
for _, test := range tests {
s, err := immediate.New(context.Background(), test.params...)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
err := s.SubmitSyncCommitteeSubscriptions(context.Background(), test.subscriptions)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestSubmitSyncCommitteeMessages(t *testing.T) {
tests := []struct {
name string
params []immediate.Parameter
messages []*altair.SyncCommitteeMessage
err string
}{
{
name: "Nil",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no sync committee messages supplied",
},
{
name: "Empty",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
messages: []*altair.SyncCommitteeMessage{},
err: "no sync committee messages supplied",
},
{
name: "Erroring",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewErroringSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
messages: []*altair.SyncCommitteeMessage{
{},
},
err: "failed to submit sync committee messages: error",
},
{
name: "Good",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
messages: []*altair.SyncCommitteeMessage{
{},
},
},
}
for _, test := range tests {
s, err := immediate.New(context.Background(), test.params...)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
err := s.SubmitSyncCommitteeMessages(context.Background(), test.messages)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestSubmitSyncCommitteeContributions(t *testing.T) {
tests := []struct {
name string
params []immediate.Parameter
contributions []*altair.SignedContributionAndProof
err string
}{
{
name: "Nil",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
err: "no sync committee contribution and proofs supplied",
},
{
name: "Empty",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
contributions: []*altair.SignedContributionAndProof{},
err: "no sync committee contribution and proofs supplied",
},
{
name: "Erroring",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.Disabled),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewErroringSyncCommitteeContributionsSubmitter()),
},
contributions: []*altair.SignedContributionAndProof{
{},
},
err: "failed to submit sync committee contribution and proofs: error",
},
{
name: "Good",
params: []immediate.Parameter{
immediate.WithLogLevel(zerolog.TraceLevel),
immediate.WithAttestationsSubmitter(mock.NewAttestationsSubmitter()),
immediate.WithBeaconBlockSubmitter(mock.NewBeaconBlockSubmitter()),
immediate.WithBeaconCommitteeSubscriptionsSubmitter(mock.NewBeaconCommitteeSubscriptionsSubmitter()),
immediate.WithAggregateAttestationsSubmitter(mock.NewAggregateAttestationsSubmitter()),
immediate.WithSyncCommitteeSubscriptionsSubmitter(mock.NewSyncCommitteeSubscriptionsSubmitter()),
immediate.WithSyncCommitteeMessagesSubmitter(mock.NewSyncCommitteeMessagesSubmitter()),
immediate.WithSyncCommitteeContributionsSubmitter(mock.NewSyncCommitteeContributionsSubmitter()),
},
contributions: []*altair.SignedContributionAndProof{
{},
},
},
}
for _, test := range tests {
s, err := immediate.New(context.Background(), test.params...)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
err := s.SubmitSyncCommitteeContributions(context.Background(), test.contributions)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}

View File

@ -33,6 +33,9 @@ type parameters struct {
attestationsSubmitters map[string]eth2client.AttestationsSubmitter
aggregateAttestationsSubmitters map[string]eth2client.AggregateAttestationsSubmitter
beaconCommitteeSubscriptionsSubmitters map[string]eth2client.BeaconCommitteeSubscriptionsSubmitter
syncCommitteeMessagesSubmitter map[string]eth2client.SyncCommitteeMessagesSubmitter
syncCommitteeSubscriptionsSubmitters map[string]eth2client.SyncCommitteeSubscriptionsSubmitter
syncCommitteeContributionsSubmitters map[string]eth2client.SyncCommitteeContributionsSubmitter
}
// Parameter is the interface for service parameters.
@ -95,6 +98,27 @@ func WithBeaconCommitteeSubscriptionsSubmitters(submitters map[string]eth2client
})
}
// WithSyncCommitteeMessagesSubmitters sets the sync committee messages submitters.
func WithSyncCommitteeMessagesSubmitters(submitters map[string]eth2client.SyncCommitteeMessagesSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeMessagesSubmitter = submitters
})
}
// WithSyncCommitteeSubscriptionsSubmitters sets the sync committee subscriptions submitters.
func WithSyncCommitteeSubscriptionsSubmitters(submitters map[string]eth2client.SyncCommitteeSubscriptionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeSubscriptionsSubmitters = submitters
})
}
// WithSyncCommitteeContributionsSubmitters sets the sync committee contributions submitters.
func WithSyncCommitteeContributionsSubmitters(submitters map[string]eth2client.SyncCommitteeContributionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionsSubmitters = submitters
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -125,6 +149,15 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if len(parameters.beaconCommitteeSubscriptionsSubmitters) == 0 {
return nil, errors.New("no beacon committee subscription submitters specified")
}
if len(parameters.syncCommitteeMessagesSubmitter) == 0 {
return nil, errors.New("no sync committee messages submitters specified")
}
if len(parameters.syncCommitteeSubscriptionsSubmitters) == 0 {
return nil, errors.New("no sync committee subscription submitters specified")
}
if len(parameters.syncCommitteeContributionsSubmitters) == 0 {
return nil, errors.New("no sync committee subscription contributions specified")
}
return &parameters, nil
}

View File

@ -31,6 +31,9 @@ type Service struct {
attestationsSubmitters map[string]eth2client.AttestationsSubmitter
aggregateAttestationsSubmitters map[string]eth2client.AggregateAttestationsSubmitter
beaconCommitteeSubscriptionSubmitters map[string]eth2client.BeaconCommitteeSubscriptionsSubmitter
syncCommitteeMessagesSubmitter map[string]eth2client.SyncCommitteeMessagesSubmitter
syncCommitteeSubscriptionSubmitters map[string]eth2client.SyncCommitteeSubscriptionsSubmitter
syncCommitteeContributionsSubmitters map[string]eth2client.SyncCommitteeContributionsSubmitter
}
// module-wide log.
@ -56,6 +59,9 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
attestationsSubmitters: parameters.attestationsSubmitters,
aggregateAttestationsSubmitters: parameters.aggregateAttestationsSubmitters,
beaconCommitteeSubscriptionSubmitters: parameters.beaconCommitteeSubscriptionsSubmitters,
syncCommitteeMessagesSubmitter: parameters.syncCommitteeMessagesSubmitter,
syncCommitteeSubscriptionSubmitters: parameters.syncCommitteeSubscriptionsSubmitters,
syncCommitteeContributionsSubmitters: parameters.syncCommitteeContributionsSubmitters,
}
log.Trace().Int64("process_concurrency", s.processConcurrency).Msg("Set process concurrency")

View File

@ -19,18 +19,23 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/go-eth2-client/spec"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
)
// SubmitBeaconBlock submits a beacon block.
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *phase0.SignedBeaconBlock) error {
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
if block == nil {
return errors.New("no beacon block supplied")
}
log := log.With().Uint64("slot", uint64(block.Message.Slot)).Logger()
blockSlot, err := block.Slot()
if err != nil {
return err
}
log := log.With().Uint64("slot", uint64(blockSlot)).Logger()
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for name, submitter := range s.beaconBlockSubmitters {

View File

@ -0,0 +1,71 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package multinode
import (
"context"
"encoding/json"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
)
// SubmitSyncCommitteeContributions submits sync committee contributions.
func (s *Service) SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error {
if len(contributionAndProofs) == 0 {
return errors.New("no contribution and proofs supplied")
}
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for name, submitter := range s.syncCommitteeContributionsSubmitters {
wg.Add(1)
go func(ctx context.Context,
sem *semaphore.Weighted,
wg *sync.WaitGroup,
name string,
submitter eth2client.SyncCommitteeContributionsSubmitter,
) {
defer wg.Done()
log := log.With().Str("beacon_node_address", name).Uint64("slot", uint64(contributionAndProofs[0].Message.Contribution.Slot)).Logger()
if err := sem.Acquire(ctx, 1); err != nil {
log.Error().Err(err).Msg("Failed to acquire semaphore")
return
}
defer sem.Release(1)
_, address := s.serviceInfo(ctx, submitter)
started := time.Now()
err := submitter.SubmitSyncCommitteeContributions(ctx, contributionAndProofs)
s.clientMonitor.ClientOperation(address, "submit contribution and proofs", err == nil, time.Since(started))
if err != nil {
log.Warn().Err(err).Msg("Failed to submit contribution and proofs")
} else {
data, err := json.Marshal(contributionAndProofs)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal JSON")
} else {
log.Trace().Str("data", string(data)).Msg("Submitted contribution and proofs")
}
}
}(ctx, sem, &wg, name, submitter)
}
wg.Wait()
return nil
}

View File

@ -0,0 +1,71 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package multinode
import (
"context"
"encoding/json"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
)
// SubmitSyncCommitteeMessages submits sync committee messages.
func (s *Service) SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error {
if len(messages) == 0 {
return errors.New("no messages supplied")
}
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for name, submitter := range s.syncCommitteeMessagesSubmitter {
wg.Add(1)
go func(ctx context.Context,
sem *semaphore.Weighted,
wg *sync.WaitGroup,
name string,
submitter eth2client.SyncCommitteeMessagesSubmitter,
) {
defer wg.Done()
log := log.With().Str("beacon_node_address", name).Uint64("slot", uint64(messages[0].Slot)).Logger()
if err := sem.Acquire(ctx, 1); err != nil {
log.Error().Err(err).Msg("Failed to acquire semaphore")
return
}
defer sem.Release(1)
_, address := s.serviceInfo(ctx, submitter)
started := time.Now()
err := submitter.SubmitSyncCommitteeMessages(ctx, messages)
s.clientMonitor.ClientOperation(address, "submit messages", err == nil, time.Since(started))
if err != nil {
log.Warn().Err(err).Msg("Failed to submit messages")
} else {
data, err := json.Marshal(messages)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal JSON")
} else {
log.Trace().Str("data", string(data)).Msg("Submitted messages")
}
}
}(ctx, sem, &wg, name, submitter)
}
wg.Wait()
return nil
}

View File

@ -0,0 +1,71 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package multinode
import (
"context"
"encoding/json"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/pkg/errors"
"golang.org/x/sync/semaphore"
)
// SubmitSyncCommitteeSubscriptions submits a batch of sync committee subscriptions.
func (s *Service) SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error {
if len(subscriptions) == 0 {
return errors.New("no subscriptions supplied")
}
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for name, submitter := range s.syncCommitteeSubscriptionSubmitters {
wg.Add(1)
go func(ctx context.Context,
sem *semaphore.Weighted,
wg *sync.WaitGroup,
name string,
submitter eth2client.SyncCommitteeSubscriptionsSubmitter,
) {
defer wg.Done()
log := log.With().Str("beacon_node_address", name).Int("subscriptions", len(subscriptions)).Logger()
if err := sem.Acquire(ctx, 1); err != nil {
log.Error().Err(err).Msg("Failed to acquire semaphore")
return
}
defer sem.Release(1)
_, address := s.serviceInfo(ctx, submitter)
started := time.Now()
err := submitter.SubmitSyncCommitteeSubscriptions(ctx, subscriptions)
s.clientMonitor.ClientOperation(address, "submit sync committee subscriptions", err == nil, time.Since(started))
if err != nil {
log.Warn().Err(err).Msg("Failed to submit subscriptions")
} else {
data, err := json.Marshal(submitter)
if err != nil {
log.Error().Err(err).Msg("Failed to marshal JSON")
} else {
log.Trace().Str("data", string(data)).Msg("Submitted subscriptions")
}
}
}(ctx, sem, &wg, name, submitter)
}
wg.Wait()
return nil
}

View File

@ -18,6 +18,8 @@ import (
"encoding/json"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
"github.com/rs/zerolog"
@ -49,7 +51,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
}
// SubmitBeaconBlock submits a block.
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *phase0.SignedBeaconBlock) error {
func (s *Service) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error {
if block == nil {
return errors.New("no beacon block supplied")
}
@ -119,3 +121,51 @@ func (s *Service) SubmitAggregateAttestations(ctx context.Context, aggregates []
return nil
}
// SubmitSyncCommitteeMessages submits sync committee messages.
func (s *Service) SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error {
if len(messages) == 0 {
return errors.New("no sync committee messages supplied")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(messages)
if err == nil {
e.Str("messages", string(data)).Msg("Not submitting sync committee messages")
}
}
return nil
}
// SubmitSyncCommitteeSubscriptions submits a batch of sync committee subscriptions.
func (s *Service) SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error {
if len(subscriptions) == 0 {
return errors.New("no sync committee subscriptions supplied")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(subscriptions)
if err == nil {
e.Str("subscriptions", string(data)).Msg("Not submitting sync committee subscriptions")
}
}
return nil
}
// SubmitSyncCommitteeContributions submits sync committee contributions.
func (s *Service) SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error {
if len(contributionAndProofs) == 0 {
return errors.New("no sync committee contribution and proofs supplied")
}
if e := log.Trace(); e.Enabled() {
data, err := json.Marshal(contributionAndProofs)
if err == nil {
e.Str("contribution_and_proofs", string(data)).Msg("Not submitting sync committee contribution and proofs")
}
}
return nil
}

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2020, 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -17,6 +17,8 @@ import (
"context"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
)
@ -32,7 +34,7 @@ type AttestationsSubmitter interface {
// BeaconBlockSubmitter is the interface for a submitter of beacon blocks.
type BeaconBlockSubmitter interface {
// SubmitBeaconBlock submits a block.
SubmitBeaconBlock(ctx context.Context, block *phase0.SignedBeaconBlock) error
SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSignedBeaconBlock) error
}
// BeaconCommitteeSubscriptionsSubmitter is the interface for a submitter of beacon committee subscriptions.
@ -46,3 +48,21 @@ type AggregateAttestationsSubmitter interface {
// SubmitAggregateAttestations submits aggregate attestations.
SubmitAggregateAttestations(ctx context.Context, aggregateAttestations []*phase0.SignedAggregateAndProof) error
}
// SyncCommitteeMessagesSubmitter is the interface for a submitter of sync committee messages.
type SyncCommitteeMessagesSubmitter interface {
// SubmitSyncCommitteeMessages submits sync committee messages.
SubmitSyncCommitteeMessages(ctx context.Context, messages []*altair.SyncCommitteeMessage) error
}
// SyncCommitteeSubscriptionsSubmitter is the interface for a submitter of sync committee subscriptions.
type SyncCommitteeSubscriptionsSubmitter interface {
// SubmitSyncCommitteeSubscription submits a batch of sync committee subscriptions.
SubmitSyncCommitteeSubscriptions(ctx context.Context, subscriptions []*api.SyncCommitteeSubscription) error
}
// SyncCommitteeContributionsSubmitter is the interface for a submitter of sync committee contributions.
type SyncCommitteeContributionsSubmitter interface {
// SubmitSyncCommitteeContributions submits sync committee contributions.
SubmitSyncCommitteeContributions(ctx context.Context, contributionAndProofs []*altair.SignedContributionAndProof) error
}

View File

@ -0,0 +1,36 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/attestantio/go-eth2-client/spec/phase0"
)
// Service is a mock sync committee aggregator.
type Service struct{}
// New creates a new mock sync committee aggregator.
func New() *Service {
return &Service{}
}
// SetBeaconBlockRoot sets the beacon block root used for a given slot.
func (s *Service) SetBeaconBlockRoot(slot phase0.Slot, root phase0.Root) {
}
// Aggregate carries out aggregation for a slot and committee.
func (s *Service) Aggregate(ctx context.Context, details interface{}) {
}

View File

@ -0,0 +1,45 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package synccommitteeaggregator
import (
"context"
"github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Duty contains information about a sync committee aggregation duty.
type Duty struct {
// Slot is the slot of the sync committee aggregation; required for obtaining the aggregate.
Slot phase0.Slot
// ValidatorIndices are the validators that aggregate for this slot.
ValidatorIndices []phase0.ValidatorIndex
// SelectionProofs are the selection proofs of the subcommittees for which each validator aggregates.
SelectionProofs map[phase0.ValidatorIndex]map[uint64]phase0.BLSSignature
// Accounts is used to sign the sync committee contribution and proof.
Accounts map[phase0.ValidatorIndex]e2wtypes.Account
}
// Service is the sync committee aggregation service.
type Service interface {
// SetBeaconBlockRoot sets the beacon block root used for a given slot.
SetBeaconBlockRoot(slot phase0.Slot, root phase0.Root)
// Aggregate carries out aggregation for a slot and committee.
Aggregate(ctx context.Context, details interface{})
}

View File

@ -0,0 +1,138 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/submitter"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.SyncCommitteeAggregationMonitor
specProvider eth2client.SpecProvider
beaconBlockRootProvider eth2client.BeaconBlockRootProvider
contributionAndProofSigner signer.ContributionAndProofSigner
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
syncCommitteeContributionProvider eth2client.SyncCommitteeContributionProvider
syncCommitteeContributionsSubmitter submitter.SyncCommitteeContributionsSubmitter
}
// Parameter is the interface for service parameters.
type Parameter interface {
apply(*parameters)
}
type parameterFunc func(*parameters)
func (f parameterFunc) apply(p *parameters) {
f(p)
}
// WithLogLevel sets the log level for the module.
func WithLogLevel(logLevel zerolog.Level) Parameter {
return parameterFunc(func(p *parameters) {
p.logLevel = logLevel
})
}
// WithMonitor sets the monitor for this module.
func WithMonitor(monitor metrics.SyncCommitteeAggregationMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.monitor = monitor
})
}
// WithSpecProvider sets the spec provider.
func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.specProvider = provider
})
}
// WithBeaconBlockRootProvider sets the beacon block root provider.
func WithBeaconBlockRootProvider(provider eth2client.BeaconBlockRootProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconBlockRootProvider = provider
})
}
// WithContributionAndProofSigner sets the contribution and proof submitter.
func WithContributionAndProofSigner(signer signer.ContributionAndProofSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.contributionAndProofSigner = signer
})
}
// WithValidatingAccountsProvider sets the account manager.
func WithValidatingAccountsProvider(provider accountmanager.ValidatingAccountsProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.validatingAccountsProvider = provider
})
}
// WithSyncCommitteeContributionProvider sets the sync committee contribution provider.
func WithSyncCommitteeContributionProvider(provider eth2client.SyncCommitteeContributionProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionProvider = provider
})
}
// WithSyncCommitteeContributionsSubmitter sets the sync committee contributions submitter.
func WithSyncCommitteeContributionsSubmitter(submitter submitter.SyncCommitteeContributionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionsSubmitter = submitter
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
if parameters.specProvider == nil {
return nil, errors.New("no spec provider specified")
}
if parameters.beaconBlockRootProvider == nil {
return nil, errors.New("no beacon block root provider specified")
}
if parameters.contributionAndProofSigner == nil {
return nil, errors.New("no contribution and proof signer specified")
}
if parameters.validatingAccountsProvider == nil {
return nil, errors.New("no validating accounts provider specified")
}
if parameters.syncCommitteeContributionProvider == nil {
return nil, errors.New("no sync committee contribution provider specified")
}
if parameters.syncCommitteeContributionsSubmitter == nil {
return nil, errors.New("no sync committee contributions submitter specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,211 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"fmt"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is a sync committee aggregator.
type Service struct {
monitor metrics.SyncCommitteeAggregationMonitor
slotsPerEpoch uint64
syncCommitteeSize uint64
syncCommitteeSubnetCount uint64
targetAggregatorsPerSyncSubcommittee uint64
beaconBlockRootProvider eth2client.BeaconBlockRootProvider
contributionAndProofSigner signer.ContributionAndProofSigner
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
syncCommitteeContributionProvider eth2client.SyncCommitteeContributionProvider
syncCommitteeContributionsSubmitter eth2client.SyncCommitteeContributionsSubmitter
beaconBlockRoots map[phase0.Slot]phase0.Root
beaconBlockRootsMu sync.Mutex
}
// module-wide log.
var log zerolog.Logger
// New creates a new sync committee aggregator.
func New(ctx context.Context, params ...Parameter) (*Service, error) {
parameters, err := parseAndCheckParameters(params...)
if err != nil {
return nil, errors.Wrap(err, "problem with parameters")
}
// Set logging.
log = zerologger.With().Str("service", "synccommitteeaggregator").Str("impl", "standard").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
spec, err := parameters.specProvider.Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
tmp, exists := spec["SLOTS_PER_EPOCH"]
if !exists {
return nil, errors.New("SLOTS_PER_EPOCH not found in spec")
}
slotsPerEpoch, ok := tmp.(uint64)
if !ok {
return nil, errors.New("SLOTS_PER_EPOCH of unexpected type")
}
tmp, exists = spec["SYNC_COMMITTEE_SIZE"]
if !exists {
return nil, errors.New("SYNC_COMMITTEE_SIZE not found in spec")
}
syncCommitteeSize, ok := tmp.(uint64)
if !ok {
return nil, errors.New("SYNC_COMMITTEE_SIZE of unexpected type")
}
tmp, exists = spec["SYNC_COMMITTEE_SUBNET_COUNT"]
if !exists {
return nil, errors.New("SYNC_COMMITTEE_SUBNET_COUNT not found in spec")
}
syncCommitteeSubnetCount, ok := tmp.(uint64)
if !ok {
return nil, errors.New("SYNC_COMMITTEE_SUBNET_COUNT of unexpected type")
}
tmp, exists = spec["TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE"]
if !exists {
return nil, errors.New("TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE not found in spec")
}
targetAggregatorsPerSyncSubcommittee, ok := tmp.(uint64)
if !ok {
return nil, errors.New("TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE of unexpected type")
}
s := &Service{
monitor: parameters.monitor,
slotsPerEpoch: slotsPerEpoch,
syncCommitteeSize: syncCommitteeSize,
syncCommitteeSubnetCount: syncCommitteeSubnetCount,
targetAggregatorsPerSyncSubcommittee: targetAggregatorsPerSyncSubcommittee,
beaconBlockRootProvider: parameters.beaconBlockRootProvider,
contributionAndProofSigner: parameters.contributionAndProofSigner,
validatingAccountsProvider: parameters.validatingAccountsProvider,
syncCommitteeContributionProvider: parameters.syncCommitteeContributionProvider,
syncCommitteeContributionsSubmitter: parameters.syncCommitteeContributionsSubmitter,
beaconBlockRoots: map[phase0.Slot]phase0.Root{},
}
return s, nil
}
// SetBeaconBlockRoot sets the beacon block root used for a given slot.
// Set by the sync committee messenger when it is creating the messages for the slot.
func (s *Service) SetBeaconBlockRoot(slot phase0.Slot, root phase0.Root) {
s.beaconBlockRootsMu.Lock()
s.beaconBlockRoots[slot] = root
s.beaconBlockRootsMu.Unlock()
}
// Aggregate aggregates the attestations for a given slot/committee combination.
func (s *Service) Aggregate(ctx context.Context, data interface{}) {
started := time.Now()
duty, ok := data.(*synccommitteeaggregator.Duty)
if !ok {
log.Error().Msg("Passed invalid data structure")
return
}
log := log.With().Uint64("slot", uint64(duty.Slot)).Int("validators", len(duty.ValidatorIndices)).Logger()
log.Trace().Msg("Aggregating")
var beaconBlockRoot *phase0.Root
var err error
s.beaconBlockRootsMu.Lock()
if tmp, exists := s.beaconBlockRoots[duty.Slot]; exists {
beaconBlockRoot = &tmp
delete(s.beaconBlockRoots, duty.Slot)
s.beaconBlockRootsMu.Unlock()
log.Trace().Msg("Obtained beacon block root from cache")
} else {
s.beaconBlockRootsMu.Unlock()
log.Debug().Msg("Failed to obtain beacon block root from cache; using head")
beaconBlockRoot, err = s.beaconBlockRootProvider.BeaconBlockRoot(ctx, "head")
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain beacon block root")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
if beaconBlockRoot == nil {
log.Warn().Msg("Returned empty beacon block root")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
}
log.Trace().Dur("elapsed", time.Since(started)).Str("beacon_block_root", fmt.Sprintf("%#x", *beaconBlockRoot)).Msg("Obtained beacon block root")
for _, validatorIndex := range duty.ValidatorIndices {
for subcommitteeIndex := range duty.SelectionProofs[validatorIndex] {
log.Warn().Uint64("validator_index", uint64(validatorIndex)).Uint64("subcommittee_index", subcommitteeIndex).Str("beacon_block_root", fmt.Sprintf("%#x", *beaconBlockRoot)).Msg("jgm Aggregating")
contribution, err := s.syncCommitteeContributionProvider.SyncCommitteeContribution(ctx, duty.Slot, subcommitteeIndex, *beaconBlockRoot)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain sync committee contribution")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
if contribution == nil {
log.Warn().Msg("Returned empty contribution")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
contributionAndProof := &altair.ContributionAndProof{
AggregatorIndex: validatorIndex,
Contribution: contribution,
SelectionProof: duty.SelectionProofs[validatorIndex][subcommitteeIndex],
}
sig, err := s.contributionAndProofSigner.SignContributionAndProof(ctx, duty.Accounts[validatorIndex], contributionAndProof)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain signature of contribution and proof")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
signedContributionAndProof := &altair.SignedContributionAndProof{
Message: contributionAndProof,
Signature: sig,
}
if err := s.syncCommitteeContributionsSubmitter.SubmitSyncCommitteeContributions(ctx, []*altair.SignedContributionAndProof{signedContributionAndProof}); err != nil {
log.Warn().Err(err).Msg("Failed to submit signed contribution and proof")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "failed")
return
}
log.Trace().Msg("Submitted signed contribution and proof")
s.monitor.SyncCommitteeAggregationsCompleted(started, len(duty.ValidatorIndices), "succeeded")
}
}
}

View File

@ -0,0 +1,170 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard_test
import (
"context"
"testing"
mocketh2client "github.com/attestantio/go-eth2-client/mock"
"github.com/attestantio/vouch/mock"
mockaccountmanager "github.com/attestantio/vouch/services/accountmanager/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
mocksigner "github.com/attestantio/vouch/services/signer/mock"
nullsubmitter "github.com/attestantio/vouch/services/submitter/null"
"github.com/attestantio/vouch/services/synccommitteeaggregator/standard"
"github.com/attestantio/vouch/testing/logger"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
ctx := context.Background()
specProvider := mock.NewSpecProvider()
mockSigner := mocksigner.New()
nullSubmitter, err := nullsubmitter.New(ctx)
require.NoError(t, err)
mockETH2Client, err := mocketh2client.New(ctx)
require.NoError(t, err)
mockValidatingAccountsProvider := mockaccountmanager.NewValidatingAccountsProvider()
tests := []struct {
name string
params []standard.Parameter
err string
logEntry string
}{
{
name: "MonitorMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no monitor specified",
},
{
name: "SpecProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no spec provider specified",
},
{
name: "BeaconBlockRootProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no beacon block root provider specified",
},
{
name: "ContributionAndProofSignerMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no contribution and proof signer specified",
},
{
name: "ValidatingAccountsProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no validating accounts provider specified",
},
{
name: "SyncCommitteeContributionProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no sync committee contribution provider specified",
},
{
name: "SyncCommitteeContributionsSubmitterMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
},
err: "problem with parameters: no sync committee contributions submitter specified",
},
{
name: "Good",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithContributionAndProofSigner(mockSigner),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeContributionProvider(mockETH2Client),
standard.WithSyncCommitteeContributionsSubmitter(nullSubmitter),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
capture := logger.NewLogCapture()
_, err := standard.New(ctx, test.params...)
if test.err != "" {
require.EqualError(t, err, test.err)
if test.logEntry != "" {
capture.AssertHasEntry(t, test.logEntry)
}
} else {
require.NoError(t, err)
}
})
}
}

View File

@ -0,0 +1,39 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
"github.com/attestantio/go-eth2-client/spec/altair"
)
// Service is a mock sync committee contributor.
type Service struct{}
// New creates a new mock sync committee contributor.
func New() *Service {
return &Service{}
}
// Prepare prepares in advance of a sync committee message.
func (s *Service) Prepare(ctx context.Context, data interface{}) error {
return nil
}
// Message generates and broadcasts sync committee messages for a slot.
// It returns a list of messages made.
func (s *Service) Message(ctx context.Context, data interface{}) ([]*altair.SyncCommitteeMessage, error) {
return make([]*altair.SyncCommitteeMessage, 0), nil
}

View File

@ -0,0 +1,135 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package synccommitteemessenger
import (
"context"
"fmt"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Duty contains information about a sync committee contribution duty.
type Duty struct {
// Details for the duty.
slot phase0.Slot
validatorIndices []phase0.ValidatorIndex
contributionIndices map[phase0.ValidatorIndex][]phase0.CommitteeIndex
// account is used to sign the sync committee contribution; can be pre-fetched.
accounts map[phase0.ValidatorIndex]e2wtypes.Account
// aggregatorSubcommittees are the subcommittees for which the validator must aggregate.
aggregatorSubcommittees map[phase0.ValidatorIndex]map[uint64]phase0.BLSSignature
}
// NewDuty creates a new sync committee contribution duty.
func NewDuty(slot phase0.Slot, contributionIndices map[phase0.ValidatorIndex][]phase0.CommitteeIndex) *Duty {
validatorIndices := make([]phase0.ValidatorIndex, 0, len(contributionIndices))
for k := range contributionIndices {
validatorIndices = append(validatorIndices, k)
}
return &Duty{
slot: slot,
validatorIndices: validatorIndices,
contributionIndices: contributionIndices,
accounts: make(map[phase0.ValidatorIndex]e2wtypes.Account, len(contributionIndices)),
aggregatorSubcommittees: make(map[phase0.ValidatorIndex]map[uint64]phase0.BLSSignature),
}
}
// Slot provides the slot for the sync committee messenger.
func (d *Duty) Slot() phase0.Slot {
return d.slot
}
// ValidatorIndices provides the validator indices for the sync committee messenger.
func (d *Duty) ValidatorIndices() []phase0.ValidatorIndex {
return d.validatorIndices
}
// ContributionIndices provides the contribution indices for the sync committee messenger.
func (d *Duty) ContributionIndices() map[phase0.ValidatorIndex][]phase0.CommitteeIndex {
return d.contributionIndices
}
// String provides a friendly string for the struct.
func (d *Duty) String() string {
return fmt.Sprintf("sync committee contributions %v", d.Tuples())
}
// Tuples returns a slice of (validator index, committee indices) strings.
func (d *Duty) Tuples() []string {
res := make([]string, 0, len(d.contributionIndices))
for k, v := range d.contributionIndices {
res = append(res, fmt.Sprintf("(%d,%v)", k, v))
}
return res
}
// // SetRandaoReveal sets the RANDAO reveal.
// func (d *Duty) SetRandaoReveal(randaoReveal spec.BLSSignature) {
// d.randaoReveal = randaoReveal
// }
//
// // RANDAOReveal provides the RANDAO reveal.
// func (d *Duty) RANDAOReveal() spec.BLSSignature {
// return d.randaoReveal
// }
// SetAccount sets the account.
func (d *Duty) SetAccount(index phase0.ValidatorIndex, account e2wtypes.Account) {
d.accounts[index] = account
}
// Accounts provides all accounts.
func (d *Duty) Accounts() map[phase0.ValidatorIndex]e2wtypes.Account {
return d.accounts
}
// Account provides a specific account.
func (d *Duty) Account(index phase0.ValidatorIndex) e2wtypes.Account {
return d.accounts[index]
}
// SetAggregatorSubcommittees sets the aggregator state for a validator.
func (d *Duty) SetAggregatorSubcommittees(index phase0.ValidatorIndex, subcommittee uint64, selectionProof phase0.BLSSignature) {
_, exists := d.aggregatorSubcommittees[index]
if !exists {
d.aggregatorSubcommittees[index] = make(map[uint64]phase0.BLSSignature)
}
d.aggregatorSubcommittees[index][subcommittee] = selectionProof
}
// AggregatorSubcommittees returns the map of subcommittees for which the supplied index is an aggregator.
func (d *Duty) AggregatorSubcommittees(index phase0.ValidatorIndex) map[uint64]phase0.BLSSignature {
aggregatorSubcommittees, exists := d.aggregatorSubcommittees[index]
if !exists {
return make(map[uint64]phase0.BLSSignature)
}
return aggregatorSubcommittees
}
// Service is the sync committee messenger service.
type Service interface {
// Prepare prepares in advance of a sync committee message.
Prepare(ctx context.Context, data interface{}) error
// Message generates and broadcasts sync committee messages for a slot.
// It returns a list of messages made.
Message(ctx context.Context, data interface{}) ([]*altair.SyncCommitteeMessage, error)
}

View File

@ -0,0 +1,188 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/metrics"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/submitter"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
processConcurrency int64
monitor metrics.SyncCommitteeMessageMonitor
chainTimeService chaintime.Service
syncCommitteeAggregator synccommitteeaggregator.Service
specProvider eth2client.SpecProvider
beaconBlockRootProvider eth2client.BeaconBlockRootProvider
syncCommitteeMessagesSubmitter submitter.SyncCommitteeMessagesSubmitter
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
syncCommitteeRootSigner signer.SyncCommitteeRootSigner
syncCommitteeSelectionSigner signer.SyncCommitteeSelectionSigner
syncCommitteeSubscriptionsSubmitter submitter.SyncCommitteeSubscriptionsSubmitter
}
// Parameter is the interface for service parameters.
type Parameter interface {
apply(*parameters)
}
type parameterFunc func(*parameters)
func (f parameterFunc) apply(p *parameters) {
f(p)
}
// WithLogLevel sets the log level for the module.
func WithLogLevel(logLevel zerolog.Level) Parameter {
return parameterFunc(func(p *parameters) {
p.logLevel = logLevel
})
}
// WithProcessConcurrency sets the concurrency for the service.
func WithProcessConcurrency(concurrency int64) Parameter {
return parameterFunc(func(p *parameters) {
p.processConcurrency = concurrency
})
}
// WithMonitor sets the monitor for this module.
func WithMonitor(monitor metrics.SyncCommitteeMessageMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.monitor = monitor
})
}
// WithChainTimeService sets the chaintime service.
func WithChainTimeService(service chaintime.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.chainTimeService = service
})
}
// WithSyncCommitteeAggregator sets the sync committee aggregator.
func WithSyncCommitteeAggregator(aggregator synccommitteeaggregator.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeAggregator = aggregator
})
}
// WithSpecProvider sets the spec provider.
func WithSpecProvider(provider eth2client.SpecProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.specProvider = provider
})
}
// WithBeaconBlockRootProvider sets the beacon block root provider.
func WithBeaconBlockRootProvider(provider eth2client.BeaconBlockRootProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconBlockRootProvider = provider
})
}
// WithSyncCommitteeMessagesSubmitter sets the sync committee messages submitter.
func WithSyncCommitteeMessagesSubmitter(submitter submitter.SyncCommitteeMessagesSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeMessagesSubmitter = submitter
})
}
// WithValidatingAccountsProvider sets the account manager.
func WithValidatingAccountsProvider(provider accountmanager.ValidatingAccountsProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.validatingAccountsProvider = provider
})
}
// WithSyncCommitteeRootSigner sets the sync committee root signer.
func WithSyncCommitteeRootSigner(signer signer.SyncCommitteeRootSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeRootSigner = signer
})
}
// WithSyncCommitteeSelectionSigner sets the sync committee selection signer.
func WithSyncCommitteeSelectionSigner(signer signer.SyncCommitteeSelectionSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeSelectionSigner = signer
})
}
// WithSyncCommitteeSubscriptionsSubmitter sets the sync committee subscriptions submitter.
func WithSyncCommitteeSubscriptionsSubmitter(submitter submitter.SyncCommitteeSubscriptionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeSubscriptionsSubmitter = submitter
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
monitor: nullmetrics.New(context.Background()),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.processConcurrency == 0 {
return nil, errors.New("no process concurrency specified")
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
if parameters.specProvider == nil {
return nil, errors.New("no spec provider specified")
}
if parameters.chainTimeService == nil {
return nil, errors.New("no chain time service specified")
}
if parameters.syncCommitteeAggregator == nil {
return nil, errors.New("no sync committee aggregator specified")
}
if parameters.beaconBlockRootProvider == nil {
return nil, errors.New("no beacon block root provider specified")
}
if parameters.syncCommitteeMessagesSubmitter == nil {
return nil, errors.New("no sync committee messages submitter specified")
}
if parameters.syncCommitteeSubscriptionsSubmitter == nil {
return nil, errors.New("no sync committee subscriptions submitter specified")
}
if parameters.validatingAccountsProvider == nil {
return nil, errors.New("no validating accounts provider specified")
}
if parameters.syncCommitteeSelectionSigner == nil {
return nil, errors.New("no sync committee selection signer specified")
}
if parameters.syncCommitteeRootSigner == nil {
return nil, errors.New("no sync committee root signer specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,296 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"crypto/sha256"
"encoding/binary"
"fmt"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/submitter"
"github.com/attestantio/vouch/services/synccommitteeaggregator"
"github.com/attestantio/vouch/services/synccommitteemessenger"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
"golang.org/x/sync/semaphore"
)
// Service is a beacon block attester.
type Service struct {
monitor metrics.SyncCommitteeMessageMonitor
processConcurrency int64
slotsPerEpoch uint64
syncCommitteeSize uint64
syncCommitteeSubnetCount uint64
targetAggregatorsPerSyncCommittee uint64
chainTimeService chaintime.Service
syncCommitteeAggregator synccommitteeaggregator.Service
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
beaconBlockRootProvider eth2client.BeaconBlockRootProvider
syncCommitteeMessagesSubmitter submitter.SyncCommitteeMessagesSubmitter
syncCommitteeSelectionSigner signer.SyncCommitteeSelectionSigner
syncCommitteeRootSigner signer.SyncCommitteeRootSigner
}
// module-wide log.
var log zerolog.Logger
// New creates a new sync committee messenger.
func New(ctx context.Context, params ...Parameter) (*Service, error) {
parameters, err := parseAndCheckParameters(params...)
if err != nil {
return nil, errors.Wrap(err, "problem with parameters")
}
// Set logging.
log = zerologger.With().Str("service", "synccommitteemessenger").Str("impl", "standard").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
spec, err := parameters.specProvider.Spec(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain spec")
}
slotsPerEpoch, err := specUint64(spec, "SLOTS_PER_EPOCH")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain SLOTS_PER_EPOCH from spec")
}
syncCommitteeSize, err := specUint64(spec, "SYNC_COMMITTEE_SIZE")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain SYNC_COMMITTEE_SIZE from spec")
}
syncCommitteeSubnetCount, err := specUint64(spec, "SYNC_COMMITTEE_SUBNET_COUNT")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain SYNC_COMMITTEE_SUBNET_COUNT from spec")
}
targetAggregatorsPerSyncCommittee, err := specUint64(spec, "TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE")
if err != nil {
return nil, errors.Wrap(err, "failed to obtain TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE from spec")
}
s := &Service{
monitor: parameters.monitor,
processConcurrency: parameters.processConcurrency,
slotsPerEpoch: slotsPerEpoch,
syncCommitteeSize: syncCommitteeSize,
syncCommitteeSubnetCount: syncCommitteeSubnetCount,
targetAggregatorsPerSyncCommittee: targetAggregatorsPerSyncCommittee,
chainTimeService: parameters.chainTimeService,
syncCommitteeAggregator: parameters.syncCommitteeAggregator,
validatingAccountsProvider: parameters.validatingAccountsProvider,
beaconBlockRootProvider: parameters.beaconBlockRootProvider,
syncCommitteeMessagesSubmitter: parameters.syncCommitteeMessagesSubmitter,
syncCommitteeSelectionSigner: parameters.syncCommitteeSelectionSigner,
syncCommitteeRootSigner: parameters.syncCommitteeRootSigner,
}
return s, nil
}
// Prepare prepares in advance of a sync committee message.
func (s *Service) Prepare(ctx context.Context, data interface{}) error {
started := time.Now()
duty, ok := data.(*synccommitteemessenger.Duty)
if !ok {
s.monitor.SyncCommitteeMessagesCompleted(started, len(duty.ValidatorIndices()), "failed")
return errors.New("passed invalid data structure")
}
// Decide if we are an aggregator.
for _, validatorIndex := range duty.ValidatorIndices() {
subcommittees := make(map[uint64]bool)
for _, contributionIndex := range duty.ContributionIndices()[validatorIndex] {
subcommittee := uint64(contributionIndex) / (s.syncCommitteeSize / s.syncCommitteeSubnetCount)
subcommittees[subcommittee] = true
}
for subcommittee := range subcommittees {
isAggregator, sig, err := s.isAggregator(ctx, duty.Account(validatorIndex), duty.Slot(), subcommittee)
if err != nil {
return errors.Wrap(err, "failed to calculate if this is an aggregator")
}
if isAggregator {
duty.SetAggregatorSubcommittees(validatorIndex, subcommittee, sig)
}
}
}
return nil
}
// Message generates and broadcasts sync committee messages for a slot.
// It returns a list of messages made.
func (s *Service) Message(ctx context.Context, data interface{}) ([]*altair.SyncCommitteeMessage, error) {
started := time.Now()
duty, ok := data.(*synccommitteemessenger.Duty)
if !ok {
s.monitor.SyncCommitteeMessagesCompleted(started, len(duty.ValidatorIndices()), "failed")
return nil, errors.New("passed invalid data structure")
}
// Fetch the beacon block root.
beaconBlockRoot, err := s.beaconBlockRootProvider.BeaconBlockRoot(ctx, "head")
if err != nil {
s.monitor.SyncCommitteeMessagesCompleted(started, len(duty.ValidatorIndices()), "failed")
return nil, errors.Wrap(err, "failed to obtain beacon block root")
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained beacon block root")
s.syncCommitteeAggregator.SetBeaconBlockRoot(duty.Slot(), *beaconBlockRoot)
// Sign in parallel.
msgs := make([]*altair.SyncCommitteeMessage, 0, len(duty.ContributionIndices()))
var msgsMu sync.Mutex
validatorIndices := make([]phase0.ValidatorIndex, 0, len(duty.ContributionIndices()))
for validatorIndex := range duty.ContributionIndices() {
validatorIndices = append(validatorIndices, validatorIndex)
}
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for i := range validatorIndices {
wg.Add(1)
go func(ctx context.Context,
sem *semaphore.Weighted,
wg *sync.WaitGroup,
i int,
) {
defer wg.Done()
sig, err := s.contribute(ctx, duty.Account(validatorIndices[i]), s.chainTimeService.SlotToEpoch(duty.Slot()), *beaconBlockRoot)
if err != nil {
log.Error().Err(err).Msg("Failed to sign sync committee message")
return
}
log.Trace().Str("signature", fmt.Sprintf("%#x", sig)).Msg("Signed sync committee message")
msg := &altair.SyncCommitteeMessage{
Slot: duty.Slot(),
BeaconBlockRoot: *beaconBlockRoot,
ValidatorIndex: validatorIndices[i],
Signature: sig,
}
msgsMu.Lock()
msgs = append(msgs, msg)
msgsMu.Unlock()
}(ctx, sem, &wg, i)
}
// msgs := make([]*altair.SyncCommitteeMessage, 0, len(duty.ContributionIndices()))
// validatorIndices := make([]phase0.ValidatorIndex, 0, len(duty.ContributionIndices()))
// for validatorIndex := range duty.ContributionIndices() {
// validatorIndices = append(validatorIndices, validatorIndex)
// }
// _, err = util.Scatter(len(duty.ContributionIndices()), func(offset int, entries int, mu *sync.RWMutex) (interface{}, error) {
// for i := offset; i < offset+entries; i++ {
// sig, err := s.contribute(ctx, duty.Account(validatorIndices[i]), s.chainTimeService.SlotToEpoch(duty.Slot()), *beaconBlockRoot)
// if err != nil {
// log.Error().Err(err).Msg("Failed to sign sync committee message")
// continue
// }
// log.Trace().Str("signature", fmt.Sprintf("%#x", sig)).Msg("Signed sync committee message")
//
// msg := &altair.SyncCommitteeMessage{
// Slot: duty.Slot(),
// BeaconBlockRoot: *beaconBlockRoot,
// ValidatorIndex: validatorIndices[i],
// Signature: sig,
// }
// mu.Lock()
// msgs = append(msgs, msg)
// mu.Unlock()
// }
// return nil, nil
// })
// if err != nil {
// s.monitor.SyncCommitteeMessagesCompleted(started, len(msgs), "failed")
// log.Error().Err(err).Str("result", "failed").Msg("Failed to obtain committee messages")
// }
if err := s.syncCommitteeMessagesSubmitter.SubmitSyncCommitteeMessages(ctx, msgs); err != nil {
s.monitor.SyncCommitteeMessagesCompleted(started, len(msgs), "failed")
return nil, errors.Wrap(err, "failed to submit sync committee messages")
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Submitted sync committee messages")
return msgs, nil
}
func (s *Service) contribute(ctx context.Context,
account e2wtypes.Account,
epoch phase0.Epoch,
root phase0.Root,
) (
phase0.BLSSignature,
error,
) {
sig, err := s.syncCommitteeRootSigner.SignSyncCommitteeRoot(ctx, account, epoch, root)
if err != nil {
return phase0.BLSSignature{}, err
}
return sig, err
}
func (s *Service) isAggregator(ctx context.Context, account e2wtypes.Account, slot phase0.Slot, subcommitteeIndex uint64) (bool, phase0.BLSSignature, error) {
modulo := s.syncCommitteeSize / s.syncCommitteeSubnetCount / s.targetAggregatorsPerSyncCommittee
if modulo < 1 {
modulo = 1
}
// Sign the slot.
signature, err := s.syncCommitteeSelectionSigner.SignSyncCommitteeSelection(ctx, account, slot, subcommitteeIndex)
if err != nil {
return false, phase0.BLSSignature{}, errors.Wrap(err, "failed to sign the slot")
}
// Hash the signature.
sigHash := sha256.New()
n, err := sigHash.Write(signature[:])
if err != nil {
return false, phase0.BLSSignature{}, errors.Wrap(err, "failed to hash the slot signature")
}
if n != len(signature) {
return false, phase0.BLSSignature{}, errors.New("failed to write all bytes of the slot signature to the hash")
}
hash := sigHash.Sum(nil)
return binary.LittleEndian.Uint64(hash[:8])%modulo == 0, signature, nil
}
func specUint64(spec map[string]interface{}, item string) (uint64, error) {
tmp, exists := spec[item]
if !exists {
return 0, fmt.Errorf("%s not found in spec", item)
}
val, ok := tmp.(uint64)
if !ok {
return 0, fmt.Errorf("%s of unexpected type", item)
}
return val, nil
}

View File

@ -0,0 +1,289 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard_test
import (
"context"
"testing"
"time"
mocketh2client "github.com/attestantio/go-eth2-client/mock"
"github.com/attestantio/vouch/mock"
mockaccountmanager "github.com/attestantio/vouch/services/accountmanager/mock"
standardchaintime "github.com/attestantio/vouch/services/chaintime/standard"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
mocksigner "github.com/attestantio/vouch/services/signer/mock"
nullsubmitter "github.com/attestantio/vouch/services/submitter/null"
mocksynccommitteeaggregator "github.com/attestantio/vouch/services/synccommitteeaggregator/mock"
"github.com/attestantio/vouch/services/synccommitteemessenger/standard"
"github.com/attestantio/vouch/testing/logger"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
ctx := context.Background()
genesisTime := time.Now()
slotDuration := 12 * time.Second
slotsPerEpoch := uint64(32)
genesisTimeProvider := mock.NewGenesisTimeProvider(genesisTime)
slotDurationProvider := mock.NewSlotDurationProvider(slotDuration)
slotsPerEpochProvider := mock.NewSlotsPerEpochProvider(slotsPerEpoch)
specProvider := mock.NewSpecProvider()
mockSyncCommitteeAggregator := mocksynccommitteeaggregator.New()
mockSigner := mocksigner.New()
nullSubmitter, err := nullsubmitter.New(ctx)
require.NoError(t, err)
mockETH2Client, err := mocketh2client.New(ctx)
require.NoError(t, err)
mockValidatingAccountsProvider := mockaccountmanager.NewValidatingAccountsProvider()
chainTime, err := standardchaintime.New(ctx,
standardchaintime.WithGenesisTimeProvider(genesisTimeProvider),
standardchaintime.WithSlotDurationProvider(slotDurationProvider),
standardchaintime.WithSlotsPerEpochProvider(slotsPerEpochProvider),
)
require.NoError(t, err)
tests := []struct {
name string
params []standard.Parameter
err string
logEntry string
}{
{
name: "ProcessConcurrencyBad",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(-1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no process concurrency specified",
},
{
name: "MonitorMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nil),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no monitor specified",
},
{
name: "ChainTimeMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no chain time service specified",
},
{
name: "SyncCommitteeAggregatorMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no sync committee aggregator specified",
},
{
name: "SpecProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no spec provider specified",
},
{
name: "BeaconBlockRootProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no beacon block root provider specified",
},
{
name: "SyncCommitteeMessagesSubmitterMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no sync committee messages submitter specified",
},
{
name: "ValidatingAccountsProviderMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no validating accounts provider specified",
},
{
name: "SyncCommitteeRootSignerMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no sync committee root signer specified",
},
{
name: "SyncCommitteeSelectionSignerMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
err: "problem with parameters: no sync committee selection signer specified",
},
{
name: "SynccommitteeSubscriptionsSubmitterMissing",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
},
err: "problem with parameters: no sync committee subscriptions submitter specified",
},
{
name: "Good",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithProcessConcurrency(1),
standard.WithMonitor(nullmetrics.New(ctx)),
standard.WithChainTimeService(chainTime),
standard.WithSyncCommitteeAggregator(mockSyncCommitteeAggregator),
standard.WithSpecProvider(specProvider),
standard.WithBeaconBlockRootProvider(mockETH2Client),
standard.WithSyncCommitteeMessagesSubmitter(nullSubmitter),
standard.WithValidatingAccountsProvider(mockValidatingAccountsProvider),
standard.WithSyncCommitteeRootSigner(mockSigner),
standard.WithSyncCommitteeSelectionSigner(mockSigner),
standard.WithSyncCommitteeSubscriptionsSubmitter(nullSubmitter),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
capture := logger.NewLogCapture()
_, err := standard.New(ctx, test.params...)
if test.err != "" {
require.EqualError(t, err, test.err)
if test.logEntry != "" {
capture.AssertHasEntry(t, test.logEntry)
}
} else {
require.NoError(t, err)
}
})
}
}

View File

@ -0,0 +1,37 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mock
import (
"context"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/synccommitteesubscriber"
)
type service struct{}
// New is a mock sync committee subscriber service.
func New() synccommitteesubscriber.Service {
return &service{}
}
// Subscribe is a mock.
func (s *service) Subscribe(ctx context.Context,
endEpoch spec.Epoch,
duties []*api.SyncCommitteeDuty,
) error {
return nil
}

View File

@ -0,0 +1,31 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package synccommitteesubscriber is a package that manages subscriptions for sync committees.
package synccommitteesubscriber
import (
"context"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
)
// Service is the sync committee subscriber service.
type Service interface {
// Subscribe subscribes to sync committees given a set of duties.
Subscribe(ctx context.Context,
endEpoch spec.Epoch,
duties []*api.SyncCommitteeDuty,
) error
}

View File

@ -0,0 +1,81 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/submitter"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.SyncCommitteeSubscriptionMonitor
syncCommitteeSubmitter submitter.SyncCommitteeSubscriptionsSubmitter
}
// Parameter is the interface for service parameters.
type Parameter interface {
apply(*parameters)
}
type parameterFunc func(*parameters)
func (f parameterFunc) apply(p *parameters) {
f(p)
}
// WithLogLevel sets the log level for the module.
func WithLogLevel(logLevel zerolog.Level) Parameter {
return parameterFunc(func(p *parameters) {
p.logLevel = logLevel
})
}
// WithMonitor sets the monitor for the module.
func WithMonitor(monitor metrics.SyncCommitteeSubscriptionMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.monitor = monitor
})
}
// WithSyncCommitteeSubmitter sets the sync committee subscriptions provider.
func WithSyncCommitteeSubmitter(provider eth2client.SyncCommitteeSubscriptionsSubmitter) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeSubmitter = provider
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
if parameters.syncCommitteeSubmitter == nil {
return nil, errors.New("no sync committee submitter specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,91 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
"context"
"time"
api "github.com/attestantio/go-eth2-client/api/v1"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/submitter"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is an beacon committee subscriber.
type Service struct {
monitor metrics.SyncCommitteeSubscriptionMonitor
submitter submitter.SyncCommitteeSubscriptionsSubmitter
}
// module-wide log.
var log zerolog.Logger
// New creates a new sync committee subscriber.
func New(ctx context.Context, params ...Parameter) (*Service, error) {
parameters, err := parseAndCheckParameters(params...)
if err != nil {
return nil, errors.Wrap(err, "problem with parameters")
}
// Set logging.
log = zerologger.With().Str("service", "synccommitteesubscriber").Str("impl", "standard").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
s := &Service{
monitor: parameters.monitor,
submitter: parameters.syncCommitteeSubmitter,
}
return s, nil
}
// Subscribe subscribes to sync committees given a set of duties.
func (s *Service) Subscribe(ctx context.Context,
endEpoch phase0.Epoch,
duties []*api.SyncCommitteeDuty,
) error {
if len(duties) == 0 {
// Nothing to do.
return nil
}
started := time.Now()
log := log.With().Uint64("end_epoch", uint64(endEpoch)).Logger()
log.Trace().Msg("Subscribing")
subscriptions := make([]*api.SyncCommitteeSubscription, 0, len(duties))
for _, duty := range duties {
subscriptions = append(subscriptions, &api.SyncCommitteeSubscription{
ValidatorIndex: duty.ValidatorIndex,
SyncCommitteeIndices: duty.ValidatorSyncCommitteeIndices,
UntilEpoch: endEpoch,
})
}
if err := s.submitter.SubmitSyncCommitteeSubscriptions(ctx, subscriptions); err != nil {
s.monitor.SyncCommitteeSubscriptionCompleted(started, "failed")
return errors.Wrap(err, "failed to subscribe to sync committees")
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Submitted subscription request")
s.monitor.SyncCommitteeSubscriptionCompleted(started, "succeeded")
return nil
}

View File

@ -50,11 +50,11 @@ func (s *Service) scoreAttestationData(ctx context.Context,
log.Warn().Str("block_root", fmt.Sprintf("%#x", attestationData.BeaconBlockRoot)).Msg("No block returned by provider")
return float64(attestationData.Source.Epoch + attestationData.Target.Epoch)
}
if block.Message == nil {
slot, err = block.Slot()
if err != nil {
log.Warn().Str("block_root", fmt.Sprintf("%#x", attestationData.BeaconBlockRoot)).Msg("Empty block returned by provider")
return float64(attestationData.Source.Epoch + attestationData.Target.Epoch)
}
slot = block.Message.Slot
} else {
log.Warn().Msg("Cannot score attestation")
return float64(attestationData.Source.Epoch + attestationData.Target.Epoch)

View File

@ -20,15 +20,16 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"golang.org/x/sync/semaphore"
)
// BeaconBlockProposal provides the best beacon block proposal from a number of beacon nodes.
func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*phase0.BeaconBlock, error) {
func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*spec.VersionedBeaconBlock, error) {
var mu sync.Mutex
bestScore := float64(0)
var bestProposal *phase0.BeaconBlock
var bestProposal *spec.VersionedBeaconBlock
bestProvider := ""
started := time.Now()
@ -63,17 +64,34 @@ func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, ran
// Obtain the slot of the block to which the proposal refers.
// We use this to allow the scorer to score blocks with earlier parents lower.
parentRoot, err := proposal.ParentRoot()
if err != nil {
log.Error().Str("version", proposal.Version.String()).Msg("Failed to obtain parent root")
return
}
var parentSlot phase0.Slot
parentBlock, err := s.signedBeaconBlockProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%#x", proposal.ParentRoot[:]))
parentBlock, err := s.signedBeaconBlockProvider.SignedBeaconBlock(ctx, fmt.Sprintf("%#x", parentRoot[:]))
switch {
case err != nil:
log.Warn().Err(err).Msg("Failed to obtain parent block")
parentSlot = proposal.Slot - 1
slot, err := proposal.Slot()
if err != nil {
log.Error().Str("version", proposal.Version.String()).Err(err).Msg("Failed to obtain proposal slot")
}
parentSlot = slot - 1
case parentBlock == nil:
log.Warn().Err(err).Msg("Failed to obtain parent block")
parentSlot = proposal.Slot - 1
log.Warn().Msg("Empty parent block")
slot, err := proposal.Slot()
if err != nil {
log.Error().Str("version", proposal.Version.String()).Err(err).Msg("Failed to obtain proposal slot")
}
parentSlot = slot - 1
default:
parentSlot = parentBlock.Message.Slot
slot, err := parentBlock.Slot()
if err != nil {
log.Error().Str("version", proposal.Version.String()).Err(err).Msg("Failed to obtain proposal slot for parent block")
}
parentSlot = slot - 1
}
mu.Lock()

View File

@ -17,7 +17,6 @@ package best
import (
"context"
"runtime"
"time"
eth2client "github.com/attestantio/go-eth2-client"
@ -92,10 +91,9 @@ func WithSignedBeaconBlockProvider(provider eth2client.SignedBeaconBlockProvider
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
timeout: 2 * time.Second,
clientMonitor: nullmetrics.New(context.Background()),
processConcurrency: int64(runtime.GOMAXPROCS(-1)),
logLevel: zerolog.GlobalLevel(),
timeout: 2 * time.Second,
clientMonitor: nullmetrics.New(context.Background()),
}
for _, p := range params {
if params != nil {

View File

@ -1,4 +1,4 @@
// Copyright © 2020 Attestant Limited.
// Copyright © 2020, 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@ -17,16 +17,17 @@ import (
"context"
"sort"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
)
// scoreBeaconBlockPropsal generates a score for a beacon block.
// The score is relative to the reward expected by proposing the block.
func scoreBeaconBlockProposal(ctx context.Context, name string, parentSlot phase0.Slot, blockProposal *phase0.BeaconBlock) float64 {
func scoreBeaconBlockProposal(ctx context.Context, name string, parentSlot phase0.Slot, blockProposal *spec.VersionedBeaconBlock) float64 {
if blockProposal == nil {
return 0
}
if blockProposal.Body == nil {
if blockProposal.IsEmpty() {
return 0
}
@ -36,7 +37,12 @@ func scoreBeaconBlockProposal(ctx context.Context, name string, parentSlot phase
// We need to avoid duplicates in attestations.
// Map is slot -> committee index -> validator committee index -> attested.
attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]map[uint64]bool)
for _, attestation := range blockProposal.Body.Attestations {
attestations, err := blockProposal.Attestations()
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain attestations")
return 0
}
for _, attestation := range attestations {
slotAttested, exists := attested[attestation.Data.Slot]
if !exists {
slotAttested = make(map[phase0.CommitteeIndex]map[uint64]bool)
@ -54,9 +60,15 @@ func scoreBeaconBlockProposal(ctx context.Context, name string, parentSlot phase
}
}
blockProposalSlot, err := blockProposal.Slot()
if err != nil {
log.Error().Str("version", blockProposal.Version.String()).Msg("Unknown proposal version")
return 0
}
// Calculate inclusion score.
for slot, slotAttested := range attested {
inclusionDistance := float64(blockProposal.Slot - slot)
inclusionDistance := float64(blockProposalSlot - slot)
for _, committeeAttested := range slotAttested {
attestationScore += float64(len(committeeAttested)) * (float64(0.75) + float64(0.25)/inclusionDistance)
if inclusionDistance == 1 {
@ -73,38 +85,56 @@ func scoreBeaconBlockProposal(ctx context.Context, name string, parentSlot phase
slashingWeight := float64(700)
// Add proposer slashing scores.
proposerSlashingScore := float64(len(blockProposal.Body.ProposerSlashings)) * slashingWeight
proposerSlashings, err := blockProposal.ProposerSlashings()
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain proposer slashings")
proposerSlashings = make([]*phase0.ProposerSlashing, 0)
}
proposerSlashingScore := float64(len(proposerSlashings)) * slashingWeight
// Add attester slashing scores.
attesterSlashings, err := blockProposal.AttesterSlashings()
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain attester slashings")
attesterSlashings = make([]*phase0.AttesterSlashing, 0)
}
indicesSlashed := 0
for i := range blockProposal.Body.AttesterSlashings {
slashing := blockProposal.Body.AttesterSlashings[i]
for i := range attesterSlashings {
slashing := attesterSlashings[i]
indicesSlashed += len(intersection(slashing.Attestation1.AttestingIndices, slashing.Attestation2.AttestingIndices))
}
attesterSlashingScore := slashingWeight * float64(indicesSlashed)
// Add sync committee score.
syncCommitteeScore := float64(0)
if blockProposal.Version == spec.DataVersionAltair {
// An individual sync proposal is worth roughly 0.3 of a fully correct attestation.
syncCommitteeScore = float64(blockProposal.Altair.Body.SyncAggregate.SyncCommitteeBits.Count()) * 0.3
}
// Scale scores by the distance between the proposal and parent slots.
var scale uint64
if blockProposal.Slot <= parentSlot {
log.Warn().Uint64("slot", uint64(blockProposal.Slot)).Uint64("parent_slot", uint64(parentSlot)).Msg("Invalid parent slot for proposal")
if blockProposalSlot <= parentSlot {
log.Warn().Uint64("slot", uint64(blockProposalSlot)).Uint64("parent_slot", uint64(parentSlot)).Msg("Invalid parent slot for proposal")
scale = 32
} else {
scale = uint64(blockProposal.Slot - parentSlot)
scale = uint64(blockProposalSlot - parentSlot)
}
log.Trace().
Uint64("slot", uint64(blockProposal.Slot)).
Uint64("slot", uint64(blockProposalSlot)).
Uint64("parent_slot", uint64(parentSlot)).
Str("provider", name).
Float64("immediate_attestations", immediateAttestationScore).
Float64("attestations", attestationScore).
Float64("proposer_slashings", proposerSlashingScore).
Float64("attester_slashings", attesterSlashingScore).
Float64("sync_committee", syncCommitteeScore).
Uint64("scale", scale).
Float64("total", (attestationScore+proposerSlashingScore+attesterSlashingScore)/float64(scale)).
Msg("Scored block")
return (attestationScore + proposerSlashingScore + attesterSlashingScore) / float64(scale)
return (attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore) / float64(scale)
}
// intersection returns a list of items common between the two sets.

View File

@ -17,6 +17,7 @@ import (
"context"
"testing"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/testutil"
"github.com/prysmaticlabs/go-bitfield"
@ -42,7 +43,7 @@ func specificAggregationBits(set []uint64, total uint64) bitfield.Bitlist {
func TestScore(t *testing.T) {
tests := []struct {
name string
block *phase0.BeaconBlock
block *spec.VersionedBeaconBlock
parentSlot phase0.Slot
score float64
err string
@ -54,20 +55,23 @@ func TestScore(t *testing.T) {
},
{
name: "Empty",
block: &phase0.BeaconBlock{},
block: &spec.VersionedBeaconBlock{},
parentSlot: 1,
score: 0,
},
{
name: "SingleAttestation",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
@ -78,14 +82,17 @@ func TestScore(t *testing.T) {
},
{
name: "SingleAttestationParentRootDistance2",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
@ -96,14 +103,17 @@ func TestScore(t *testing.T) {
},
{
name: "SingleAttestationDistance2",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12343,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12343,
},
},
},
},
@ -114,20 +124,23 @@ func TestScore(t *testing.T) {
},
{
name: "TwoAttestations",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(2, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(2, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12341,
{
AggregationBits: aggregationBits(1, 128),
Data: &phase0.AttestationData{
Slot: 12341,
},
},
},
},
@ -138,24 +151,27 @@ func TestScore(t *testing.T) {
},
{
name: "AttesterSlashing",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
},
},
},
},
@ -166,20 +182,23 @@ func TestScore(t *testing.T) {
},
{
name: "DuplicateAttestations",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: specificAggregationBits([]uint64{1, 2, 3}, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: specificAggregationBits([]uint64{1, 2, 3}, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
{
AggregationBits: specificAggregationBits([]uint64{2, 3, 4}, 128),
Data: &phase0.AttestationData{
Slot: 12344,
{
AggregationBits: specificAggregationBits([]uint64{2, 3, 4}, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
@ -190,48 +209,51 @@ func TestScore(t *testing.T) {
},
{
name: "Full",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
},
},
},
},
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
},
},
@ -242,48 +264,51 @@ func TestScore(t *testing.T) {
},
{
name: "FullParentRootDistance2",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
},
},
},
},
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
},
},
@ -294,48 +319,51 @@ func TestScore(t *testing.T) {
},
{
name: "FullParentRootDistance4",
block: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
block: &spec.VersionedBeaconBlock{
Version: spec.DataVersionPhase0,
Phase0: &phase0.BeaconBlock{
Slot: 12345,
Body: &phase0.BeaconBlockBody{
Attestations: []*phase0.Attestation{
{
AggregationBits: aggregationBits(50, 128),
Data: &phase0.AttestationData{
Slot: 12344,
},
},
},
},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
AttesterSlashings: []*phase0.AttesterSlashing{
{
Attestation1: &phase0.IndexedAttestation{
AttestingIndices: []uint64{1, 2, 3},
},
Attestation2: &phase0.IndexedAttestation{
AttestingIndices: []uint64{2, 3, 4},
},
},
},
},
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
ProposerSlashings: []*phase0.ProposerSlashing{
{
SignedHeader1: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
SignedHeader2: &phase0.SignedBeaconBlockHeader{
Message: &phase0.BeaconBlockHeader{
Slot: 10,
ProposerIndex: 1,
ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"),
StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"),
BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"),
},
},
},

View File

@ -18,6 +18,7 @@ import (
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/metrics"
"github.com/pkg/errors"
@ -58,14 +59,14 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
}
// BeaconBlockProposal provides the first beacon block proposal from a number of beacon nodes.
func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*phase0.BeaconBlock, error) {
func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, randaoReveal phase0.BLSSignature, graffiti []byte) (*spec.VersionedBeaconBlock, error) {
// We create a cancelable context with a timeout. As soon as the first provider has responded we
// cancel the context to cancel the other requests.
ctx, cancel := context.WithTimeout(ctx, s.timeout)
proposalCh := make(chan *phase0.BeaconBlock, 1)
proposalCh := make(chan *spec.VersionedBeaconBlock, 1)
for name, provider := range s.beaconBlockProposalProviders {
go func(ctx context.Context, name string, provider eth2client.BeaconBlockProposalProvider, ch chan *phase0.BeaconBlock) {
go func(ctx context.Context, name string, provider eth2client.BeaconBlockProposalProvider, ch chan *spec.VersionedBeaconBlock) {
log := log.With().Str("provider", name).Uint64("slot", uint64(slot)).Logger()
started := time.Now()