Separate accountmanager.

This commit is contained in:
Jim McDonald 2020-11-25 00:02:13 +00:00
parent b53a039da9
commit fd48090007
No known key found for this signature in database
GPG Key ID: 89CEB61B2AD2A5E7
56 changed files with 2969 additions and 3049 deletions

View File

@ -1,4 +1,7 @@
Development:
- rework controller to schedule jobs in separate functions, allowing future flexibility
- break accountmanager in to accountmanager, signer and validatorsmanager
- better for maintainability and additional features
- provide clearer log messages for submitter
- upgrade wallet account manager to be able to accept multiple attestations to sign in a single request

View File

@ -81,7 +81,9 @@ Modules levels are used for each module, overriding the global log level. The a
- **graffiti** provision of graffiti for proposed blocks
- **majordomo** accesss to secrets
- **scheduler** starting internal jobs such as proposing a block at the appropriate time
- **signer** carries out signing activities
- **strategies.beaconblockproposer** decisions on how to obtain information from multiple beacon nodes
- **submitter** decisions on how to submit information to multiple beacon nodes
- **validatorsmanager** obtaining validator state from beacon nodes and providing it to other modules
This can be configured using the environment variables `VOUCH_<MODULE>_LOG_LEVEL` or the configuration option `<module>.log-level`. For example, the controller module logging could be configured using the environment variable `VOUCH_CONTROLLER_LOG_LEVEL` or the configuration option `controller.log-level`.

10
go.mod
View File

@ -6,11 +6,11 @@ require (
cloud.google.com/go v0.72.0 // indirect
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/attestantio/dirk v0.9.6
github.com/attestantio/go-eth2-client v0.6.12
github.com/aws/aws-sdk-go v1.35.29
github.com/attestantio/go-eth2-client v0.6.14
github.com/aws/aws-sdk-go v1.35.35
github.com/ferranbt/fastssz v0.0.0-20201030134205-9b9624098321
github.com/goccy/go-yaml v1.8.4 // indirect
github.com/google/go-cmp v0.5.3 // indirect
github.com/google/go-cmp v0.5.4 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/herumi/bls-eth-go-binary v0.0.0-20201104034342-d782bdf735de
github.com/mitchellh/go-homedir v1.1.0
@ -19,7 +19,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.8.0
github.com/prometheus/common v0.15.0 // indirect
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba
github.com/prysmaticlabs/ethereumapis v0.0.0-20201117145913-073714f478fb
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65
github.com/rs/zerolog v1.20.0
github.com/sasha-s/go-deadlock v0.2.0
@ -38,8 +38,10 @@ require (
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.1
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.1
github.com/wealdtech/go-majordomo v1.0.1
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58 // indirect
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4 // indirect
google.golang.org/grpc v1.33.2
gotest.tools v2.2.0+incompatible
)

17
go.sum
View File

@ -79,6 +79,10 @@ github.com/attestantio/go-eth2-client v0.6.10 h1:PMNBMLk6xfMEUqhaUnsI0/HZRrstZF1
github.com/attestantio/go-eth2-client v0.6.10/go.mod h1:ODAZ4yS1YYYew/EsgGsVb/siNEoa505CrGsvlVFdkfo=
github.com/attestantio/go-eth2-client v0.6.12 h1:7yha5QtDg5xsdTPIc8GWba97+gBkOhmowLVFzRCjdoc=
github.com/attestantio/go-eth2-client v0.6.12/go.mod h1:ODAZ4yS1YYYew/EsgGsVb/siNEoa505CrGsvlVFdkfo=
github.com/attestantio/go-eth2-client v0.6.13 h1:n2mN6ZUJcKbNKh/rhE8la7IB5Z8V2Tgr8lIkW5mHGgo=
github.com/attestantio/go-eth2-client v0.6.13/go.mod h1:Hya4fp1ZLWAFI64qMhNbQgfY4StWiHulW4CFwu+vP3s=
github.com/attestantio/go-eth2-client v0.6.14 h1:zP/zP8CKygHVfbxV1ppwWBqbI8yfs3paBsv7Nccx34Q=
github.com/attestantio/go-eth2-client v0.6.14/go.mod h1:Hya4fp1ZLWAFI64qMhNbQgfY4StWiHulW4CFwu+vP3s=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.33.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
@ -92,6 +96,8 @@ github.com/aws/aws-sdk-go v1.35.28 h1:S2LuRnfC8X05zgZLC8gy/Sb82TGv2Cpytzbzz7tkeH
github.com/aws/aws-sdk-go v1.35.28/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
github.com/aws/aws-sdk-go v1.35.29 h1:1kYnwrWTp2e+lI9yYFaDo7OFaLug8yXC6Qdj+u8451Q=
github.com/aws/aws-sdk-go v1.35.29/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
github.com/aws/aws-sdk-go v1.35.35 h1:o/EbgEcIPWga7GWhJhb3tiaxqk4/goTdo5YEMdnVxgE=
github.com/aws/aws-sdk-go v1.35.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -243,6 +249,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
@ -487,6 +495,8 @@ github.com/protolambda/zssz v0.1.5/go.mod h1:a4iwOX5FE7/JkKA+J/PH0Mjo9oXftN6P8NZ
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba h1:ItW6tq3B45Gws8dO0cIuU1Srlgf4qomZnWkc0sDCln0=
github.com/prysmaticlabs/ethereumapis v0.0.0-20201020182719-7f66dae2bbba/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
github.com/prysmaticlabs/ethereumapis v0.0.0-20201117145913-073714f478fb h1:OUfQgEA6zB19I66EQ2nPtjdBbk+Vv7eCBf2+x3BTv5w=
github.com/prysmaticlabs/ethereumapis v0.0.0-20201117145913-073714f478fb/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
github.com/prysmaticlabs/go-bitfield v0.0.0-20191017011753-53b773adde52/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20200322041314-62c2aee71669/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20200618145306-2ae0807bef65 h1:hJfAWrlxx7SKpn4S/h2JGl2HHwA1a2wSS3HAzzZ0F+U=
@ -694,6 +704,8 @@ golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0a
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582 h1:0WDrJ1E7UolDk1KhTXxxw3Fc8qtk5x7dHP431KHEJls=
golang.org/x/crypto v0.0.0-20201116153603-4be66e5b6582/go.mod h1:tCqSYrHVcf3i63Co2FzBkTCo2gdF6Zak62921dSfraU=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc=
golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -854,7 +866,10 @@ golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cH
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48 h1:AYCWBZhgIw6XobZ5CibNJr0Rc4ZofGGKvWa1vcx2IGk=
golang.org/x/sys v0.0.0-20201116194326-cc9327a14d48/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1005,6 +1020,8 @@ google.golang.org/genproto v0.0.0-20201113130914-ce600e9a6f9e h1:jRAe+6EDD0LNrVz
google.golang.org/genproto v0.0.0-20201113130914-ce600e9a6f9e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe h1:4zuxUDBUB7MD0/Nb3BD2e2+YHXs2BQxZ9ivB5RJd7ng=
google.golang.org/genproto v0.0.0-20201116205149-79184cff4dfe/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4 h1:Rt0FRalMgdSlXAVJvX4pr65KfqaxHXSLkSJRD9pw6g0=
google.golang.org/genproto v0.0.0-20201119123407-9b1e624d6bc4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=

152
main.go
View File

@ -45,9 +45,15 @@ import (
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
prometheusmetrics "github.com/attestantio/vouch/services/metrics/prometheus"
basicscheduler "github.com/attestantio/vouch/services/scheduler/basic"
"github.com/attestantio/vouch/services/signer"
standardsigner "github.com/attestantio/vouch/services/signer/standard"
"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/validatorsmanager"
standardvalidatorsmanager "github.com/attestantio/vouch/services/validatorsmanager/standard"
bestattestationdatastrategy "github.com/attestantio/vouch/strategies/attestationdata/best"
firstattestationdatastrategy "github.com/attestantio/vouch/strategies/attestationdata/first"
bestbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/beaconblockproposal/best"
firstbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/beaconblockproposal/first"
"github.com/aws/aws-sdk-go/aws/credentials"
@ -258,8 +264,20 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
return errors.Wrap(err, "failed to start scheduler service")
}
log.Trace().Msg("Starting validators manager")
validatorsManager, err := startValidatorsManager(ctx, monitor, eth2Client)
if err != nil {
return errors.Wrap(err, "failed to start validators manager")
}
log.Trace().Msg("Starting signer")
signerSvc, err := startSigner(ctx, monitor, eth2Client)
if err != nil {
return errors.Wrap(err, "failed to start signer")
}
log.Trace().Msg("Starting account manager")
accountManager, err := startAccountManager(ctx, monitor, eth2Client, majordomo)
accountManager, err := startAccountManager(ctx, monitor, eth2Client, validatorsManager, majordomo)
if err != nil {
return errors.Wrap(err, "failed to start account manager")
}
@ -285,28 +303,38 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
log.Trace().Msg("Starting beacon block proposer")
beaconBlockProposer, err := standardbeaconblockproposer.New(ctx,
standardbeaconblockproposer.WithLogLevel(logLevel(viper.GetString("beaconblockproposer.log-level"))),
standardbeaconblockproposer.WithChainTimeService(chainTime),
standardbeaconblockproposer.WithProposalDataProvider(beaconBlockProposalProvider),
standardbeaconblockproposer.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardbeaconblockproposer.WithGraffitiProvider(graffitiProvider),
standardbeaconblockproposer.WithMonitor(monitor.(metrics.BeaconBlockProposalMonitor)),
standardbeaconblockproposer.WithBeaconBlockSubmitter(submitterStrategy.(submitter.BeaconBlockSubmitter)),
standardbeaconblockproposer.WithRANDAORevealSigner(signerSvc.(signer.RANDAORevealSigner)),
standardbeaconblockproposer.WithBeaconBlockSigner(signerSvc.(signer.BeaconBlockSigner)),
)
if err != nil {
return errors.Wrap(err, "failed to start beacon block proposer service")
}
log.Trace().Msg("Starting beacon block attester")
log.Trace().Msg("Selecting attestation data provider")
attestationDataProvider, err := selectAttestationDataProvider(ctx, monitor, eth2Client)
if err != nil {
return errors.Wrap(err, "failed to select attestation data provider")
}
log.Trace().Msg("Starting attester")
attester, err := standardattester.New(ctx,
standardattester.WithLogLevel(logLevel(viper.GetString("attester.log-level"))),
standardattester.WithProcessConcurrency(viper.GetInt64("process-concurrency")),
standardattester.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
standardattester.WithAttestationDataProvider(eth2Client.(eth2client.AttestationDataProvider)),
standardattester.WithAttestationDataProvider(attestationDataProvider),
standardattester.WithAttestationSubmitter(submitterStrategy.(submitter.AttestationSubmitter)),
standardattester.WithMonitor(monitor.(metrics.AttestationMonitor)),
standardattester.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardattester.WithBeaconAttestationsSigner(signerSvc.(signer.BeaconAttestationsSigner)),
)
if err != nil {
return errors.Wrap(err, "failed to start beacon block attester service")
return errors.Wrap(err, "failed to start attester service")
}
log.Trace().Msg("Starting beacon attestation aggregator")
@ -323,6 +351,9 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
standardattestationaggregator.WithAggregateAttestationsSubmitter(eth2Client.(eth2client.AggregateAttestationsSubmitter)),
standardattestationaggregator.WithMonitor(monitor.(metrics.AttestationAggregationMonitor)),
standardattestationaggregator.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardattestationaggregator.WithSlotSelectionSigner(signerSvc.(signer.SlotSelectionSigner)),
standardattestationaggregator.WithAggregateAndProofSigner(signerSvc.(signer.AggregateAndProofSigner)),
standardattestationaggregator.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
)
if err != nil {
return errors.Wrap(err, "failed to start beacon attestation aggregator service")
@ -357,6 +388,7 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
standardcontroller.WithBeaconBlockProposer(beaconBlockProposer),
standardcontroller.WithAttestationAggregator(attestationAggregator),
standardcontroller.WithBeaconCommitteeSubscriber(beaconCommitteeSubscriber),
standardcontroller.WithAccountsRefresher(accountManager.(accountmanager.Refresher)),
)
if err != nil {
return errors.Wrap(err, "failed to start controller service")
@ -498,7 +530,46 @@ func startGraffitiProvider(ctx context.Context, majordomo majordomo.Service) (gr
}
}
func startAccountManager(ctx context.Context, monitor metrics.Service, eth2Client eth2client.Service, majordomo majordomo.Service) (accountmanager.Service, error) {
func startValidatorsManager(ctx context.Context, monitor metrics.Service, eth2Client eth2client.Service) (validatorsmanager.Service, error) {
farFutureEpoch, err := eth2Client.(eth2client.FarFutureEpochProvider).FarFutureEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain far future epoch")
}
validatorsManager, err := standardvalidatorsmanager.New(ctx,
standardvalidatorsmanager.WithLogLevel(logLevel(viper.GetString("validatorsmanager.log-level"))),
standardvalidatorsmanager.WithMonitor(monitor.(metrics.ValidatorsManagerMonitor)),
standardvalidatorsmanager.WithClientMonitor(monitor.(metrics.ClientMonitor)),
standardvalidatorsmanager.WithValidatorsProvider(eth2Client.(eth2client.ValidatorsProvider)),
standardvalidatorsmanager.WithFarFutureEpoch(farFutureEpoch),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start standard validators manager service")
}
return validatorsManager, nil
}
func startSigner(ctx context.Context, monitor metrics.Service, eth2Client eth2client.Service) (signer.Service, error) {
signer, err := standardsigner.New(ctx,
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.WithDomainProvider(eth2Client.(eth2client.DomainProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start signer provider service")
}
return signer, nil
}
func startAccountManager(ctx context.Context, monitor metrics.Service, eth2Client eth2client.Service, validatorsManager validatorsmanager.Service, majordomo majordomo.Service) (accountmanager.Service, error) {
var accountManager accountmanager.Service
if viper.Get("accountmanager.dirk") != nil {
log.Info().Msg("Starting dirk account manager")
@ -521,19 +592,14 @@ func startAccountManager(ctx context.Context, monitor metrics.Service, eth2Clien
dirkaccountmanager.WithLogLevel(logLevel(viper.GetString("accountmanager.dirk.log-level"))),
dirkaccountmanager.WithMonitor(monitor.(metrics.AccountManagerMonitor)),
dirkaccountmanager.WithClientMonitor(monitor.(metrics.ClientMonitor)),
dirkaccountmanager.WithValidatorsProvider(eth2Client.(eth2client.ValidatorsProvider)),
dirkaccountmanager.WithValidatorsManager(validatorsManager),
dirkaccountmanager.WithEndpoints(viper.GetStringSlice("accountmanager.dirk.endpoints")),
dirkaccountmanager.WithAccountPaths(viper.GetStringSlice("accountmanager.dirk.accounts")),
dirkaccountmanager.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
dirkaccountmanager.WithBeaconProposerDomainProvider(eth2Client.(eth2client.BeaconProposerDomainProvider)),
dirkaccountmanager.WithBeaconAttesterDomainProvider(eth2Client.(eth2client.BeaconAttesterDomainProvider)),
dirkaccountmanager.WithRANDAODomainProvider(eth2Client.(eth2client.RANDAODomainProvider)),
dirkaccountmanager.WithSelectionProofDomainProvider(eth2Client.(eth2client.SelectionProofDomainProvider)),
dirkaccountmanager.WithAggregateAndProofDomainProvider(eth2Client.(eth2client.AggregateAndProofDomainProvider)),
dirkaccountmanager.WithDomainProvider(eth2Client.(eth2client.DomainProvider)),
dirkaccountmanager.WithClientCert(certPEMBlock),
dirkaccountmanager.WithClientKey(keyPEMBlock),
dirkaccountmanager.WithCACert(caPEMBlock),
dirkaccountmanager.WithDomainProvider(eth2Client.(eth2client.DomainProvider)),
dirkaccountmanager.WithFarFutureEpochProvider(eth2Client.(eth2client.FarFutureEpochProvider)),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start dirk account manager service")
@ -559,16 +625,12 @@ func startAccountManager(ctx context.Context, monitor metrics.Service, eth2Clien
accountManager, err = walletaccountmanager.New(ctx,
walletaccountmanager.WithLogLevel(logLevel(viper.GetString("accountmanager.wallet.log-level"))),
walletaccountmanager.WithMonitor(monitor.(metrics.AccountManagerMonitor)),
walletaccountmanager.WithValidatorsProvider(eth2Client.(eth2client.ValidatorsProvider)),
walletaccountmanager.WithValidatorsManager(validatorsManager),
walletaccountmanager.WithAccountPaths(viper.GetStringSlice("accountmanager.wallet.accounts")),
walletaccountmanager.WithPassphrases(passphrases),
walletaccountmanager.WithLocations(viper.GetStringSlice("accountmanager.wallet.locations")),
walletaccountmanager.WithSlotsPerEpochProvider(eth2Client.(eth2client.SlotsPerEpochProvider)),
walletaccountmanager.WithBeaconProposerDomainProvider(eth2Client.(eth2client.BeaconProposerDomainProvider)),
walletaccountmanager.WithBeaconAttesterDomainProvider(eth2Client.(eth2client.BeaconAttesterDomainProvider)),
walletaccountmanager.WithRANDAODomainProvider(eth2Client.(eth2client.RANDAODomainProvider)),
walletaccountmanager.WithSelectionProofDomainProvider(eth2Client.(eth2client.SelectionProofDomainProvider)),
walletaccountmanager.WithAggregateAndProofDomainProvider(eth2Client.(eth2client.AggregateAndProofDomainProvider)),
walletaccountmanager.WithFarFutureEpochProvider(eth2Client.(eth2client.FarFutureEpochProvider)),
walletaccountmanager.WithDomainProvider(eth2Client.(eth2client.DomainProvider)),
)
if err != nil {
@ -580,6 +642,58 @@ func startAccountManager(ctx context.Context, monitor metrics.Service, eth2Clien
return nil, errors.New("no account manager defined")
}
func selectAttestationDataProvider(ctx context.Context,
monitor metrics.Service,
eth2Client eth2client.Service,
) (eth2client.AttestationDataProvider, error) {
var attestationDataProvider eth2client.AttestationDataProvider
var err error
switch viper.GetString("strategies.attestationdata.style") {
case "best":
log.Info().Msg("Starting best attestation data strategy")
attestationDataProviders := make(map[string]eth2client.AttestationDataProvider)
for _, address := range viper.GetStringSlice("strategies.attestationdata.beacon-node-addresses") {
client, err := fetchClient(ctx, address)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for attestation data strategy", address))
}
attestationDataProviders[address] = client.(eth2client.AttestationDataProvider)
}
attestationDataProvider, err = bestattestationdatastrategy.New(ctx,
bestattestationdatastrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)),
bestattestationdatastrategy.WithProcessConcurrency(viper.GetInt64("process-concurrency")),
bestattestationdatastrategy.WithLogLevel(logLevel(viper.GetString("strategies.attestationdata.log-level"))),
bestattestationdatastrategy.WithAttestationDataProviders(attestationDataProviders),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start best attestation data strategy")
}
case "first":
log.Info().Msg("Starting first attestation data strategy")
attestationDataProviders := make(map[string]eth2client.AttestationDataProvider)
for _, address := range viper.GetStringSlice("strategies.attestationdata.beacon-node-addresses") {
client, err := fetchClient(ctx, address)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for attestation data strategy", address))
}
attestationDataProviders[address] = client.(eth2client.AttestationDataProvider)
}
attestationDataProvider, err = firstattestationdatastrategy.New(ctx,
firstattestationdatastrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)),
firstattestationdatastrategy.WithLogLevel(logLevel(viper.GetString("strategies.attestationdata.log-level"))),
firstattestationdatastrategy.WithAttestationDataProviders(attestationDataProviders),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start first attestation data strategy")
}
default:
log.Info().Msg("Starting simple attestation data strategy")
attestationDataProvider = eth2Client.(eth2client.AttestationDataProvider)
}
return attestationDataProvider, nil
}
func selectBeaconBlockProposalProvider(ctx context.Context,
monitor metrics.Service,
eth2Client eth2client.Service,

View File

@ -1,219 +0,0 @@
// 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 mock
import (
"context"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/herumi/bls-eth-go-binary/bls"
"github.com/pkg/errors"
)
type validatingAccount struct {
index uint64
key *bls.SecretKey
}
func (a *validatingAccount) PubKey(ctx context.Context) (spec.BLSPubKey, error) {
var res spec.BLSPubKey
copy(res[:], a.key.GetPublicKey().Serialize())
return res, nil
}
func (a *validatingAccount) Index(ctx context.Context) (spec.ValidatorIndex, error) {
return spec.ValidatorIndex(a.index), nil
}
func (a *validatingAccount) State() api.ValidatorState {
return api.ValidatorStateActiveOngoing
}
func (a *validatingAccount) SignSlotSelection(ctx context.Context, slot uint64, signatureDomain []byte) ([]byte, error) {
slotBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(slotBytes, slot)
hash := sha256.New()
n, err := hash.Write(slotBytes)
if err != nil {
return nil, errors.Wrap(err, "failed to write slot")
}
if n != 32 {
return nil, errors.New("failed to write all slot bytes")
}
n, err = hash.Write(signatureDomain)
if err != nil {
return nil, errors.Wrap(err, "failed to write signature domain")
}
if n != 32 {
return nil, errors.New("failed to write all signature domain bytes")
}
root := hash.Sum(nil)
sig := a.key.SignByte(root)
return sig.Serialize(), nil
}
// ValidatingAccountsProvider is a mock for accountmanager.ValidatingAccountsProvider.
type ValidatingAccountsProvider struct {
validatingAccounts []accountmanager.ValidatingAccount
}
func _secretKey(input string) *bls.SecretKey {
bytes, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
if err != nil {
panic(err)
}
var key bls.SecretKey
if err := key.Deserialize(bytes); err != nil {
panic(err)
}
return &key
}
// NewValidatingAccountsProvider returns a mock account manager with pre-configured keys.
func NewValidatingAccountsProvider() accountmanager.ValidatingAccountsProvider {
validatingAccounts := []accountmanager.ValidatingAccount{
&validatingAccount{
index: 5184,
key: _secretKey("0x01e748d098d3bcb477d636f19d510399ae18205fadf9814ee67052f88c1f77c0"),
},
&validatingAccount{
index: 5221,
key: _secretKey("0x376880b8079dca3bbd06c93958b5208929cbc169c9ce4caf8731be10e94f710e"),
},
&validatingAccount{
index: 14499,
key: _secretKey("0x3fb0a5e8ec5f9f421b682d8956e08e02af5ed921e7f82a78cc6258869c283500"),
},
&validatingAccount{
index: 14096,
key: _secretKey("0x1432f616d724ebe44ba92c603496627dc9a4899ffa7956948caa4a9cebaac171"),
},
&validatingAccount{
index: 14407,
key: _secretKey("0x5dc083116a71299cbd8582ab0da28c32f64359039d1f5f5f4a664d2c7deb258e"),
},
&validatingAccount{
index: 13885,
key: _secretKey("0x1d6b5e0a9c9c05b7a318602ac1f204c83b6fd2ff8e7b7a3de0aa5e9ff42df071"),
},
&validatingAccount{
index: 13743,
key: _secretKey("0x6f91850e101d59b80cc77faa3658c730653827413e653dbdaf9ecfd727cb72e7"),
},
&validatingAccount{
index: 13594,
key: _secretKey("0x63510d1383f4ab8285a5b47f6e36da895c2eabd892d580f9820d1c2cf65bc2a9"),
},
&validatingAccount{
index: 13796,
key: _secretKey("0x1f9a5ceb86e03e0e94154b4bb7774d8e0cb0dafbe52c8c82acec775723a1e288"),
},
&validatingAccount{
index: 14201,
key: _secretKey("0x11d9711e3d67e6b4ea5bf3485babcd365eef48bb9c69b1c89f689e31d5cf5fe2"),
},
&validatingAccount{
index: 13790,
key: _secretKey("0x10c0c8b5ca8fdfba14819373e13f4c980f125a075f4c4edce3b32ad037c93740"),
},
&validatingAccount{
index: 13981,
key: _secretKey("0x28939bb5986f4074172417273f4174e4cddf75a1f88595cd9d4b6082cbf476fa"),
},
&validatingAccount{
index: 13643,
key: _secretKey("0x3662b248e8cfe57e99e73a9e57e7fe0ee9244880b5c9284e8d878c64aca6b5fc"),
},
&validatingAccount{
index: 13536,
key: _secretKey("0x281c019804bf23792963095041d1db1f8b79df49d31b07c9cbed1994ff794974"),
},
&validatingAccount{
index: 13673,
key: _secretKey("0x15d98ae5d17b78b159dd7feee9aee7b3a7dbaf4777de92da004eb3b46101c5a1"),
},
&validatingAccount{
index: 14032,
key: _secretKey("0x6099d69ff55e3dfeba26a4c7db572b7d34792e090704f0eef9ae149260de909f"),
},
&validatingAccount{
index: 14370,
key: _secretKey("0x4e693b831328f20818df32fafd50be61daf7cb7de6b96a8767fc183a8e9bfa76"),
},
&validatingAccount{
index: 14368,
key: _secretKey("0x0e0c93d7fe17ef80ced6f431dd482abd02530f29294b2f47318da24d82fb54ef"),
},
}
return &ValidatingAccountsProvider{
validatingAccounts: validatingAccounts,
}
}
// Accounts returns accounts.
func (m *ValidatingAccountsProvider) Accounts(ctx context.Context) ([]accountmanager.ValidatingAccount, error) {
return m.validatingAccounts, nil
}
// AccountsByIndex returns accounts.
func (m *ValidatingAccountsProvider) AccountsByIndex(ctx context.Context, indices []spec.ValidatorIndex) ([]accountmanager.ValidatingAccount, error) {
indexMap := make(map[spec.ValidatorIndex]bool)
for _, index := range indices {
indexMap[index] = true
}
res := make([]accountmanager.ValidatingAccount, 0)
for _, validatingAccount := range m.validatingAccounts {
index, err := validatingAccount.Index(ctx)
if err != nil {
continue
}
if _, required := indexMap[index]; required {
res = append(res, validatingAccount)
}
}
return res, nil
}
// AccountsByPubKey returns accounts.
func (m *ValidatingAccountsProvider) AccountsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) ([]accountmanager.ValidatingAccount, error) {
keyMap := make(map[string]bool)
for _, pubKey := range pubKeys {
keyMap[fmt.Sprintf("%x", pubKey)] = true
}
res := make([]accountmanager.ValidatingAccount, 0)
for _, validatingAccount := range m.validatingAccounts {
publicKey, err := validatingAccount.PubKey(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain public key of account")
}
pubKey := fmt.Sprintf("%x", publicKey)
if _, required := keyMap[pubKey]; required {
res = append(res, validatingAccount)
}
}
return res, nil
}

View File

@ -60,6 +60,23 @@ func (m *SlotDurationProvider) SlotDuration(ctx context.Context) (time.Duration,
return m.slotDuration, nil
}
// FarFutureEpochProvider is a mock for eth2client.FarFutureEpochProvider.
type FarFutureEpochProvider struct {
farFutureEpoch spec.Epoch
}
// NewFarFutureEpochProvider returns a mock far future epoch provider with the provided value.
func NewFarFutureEpochProvider(farFutureEpoch spec.Epoch) eth2client.FarFutureEpochProvider {
return &FarFutureEpochProvider{
farFutureEpoch: farFutureEpoch,
}
}
// FarFutureEpoch is a mock.
func (m *FarFutureEpochProvider) FarFutureEpoch(ctx context.Context) (spec.Epoch, error) {
return m.farFutureEpoch, nil
}
// SlotsPerEpochProvider is a mock for eth2client.SlotsPerEpochProvider.
type SlotsPerEpochProvider struct {
slotsPerEpoch uint64
@ -595,14 +612,6 @@ func (m *ErroringDomainProvider) Domain(ctx context.Context, domainType spec.Dom
return spec.Domain{}, errors.New("error")
}
// ValidatorsProvider is a mock for eth2client.ValidatorsProvider.
type ValidatorsProvider struct{}
// NewValidatorsProvider returns a mock validators provider.
func NewValidatorsProvider() eth2client.ValidatorsProvider {
return &ValidatorsProvider{}
}
func _byte(input string) []byte {
res, _ := hex.DecodeString(strings.TrimPrefix(input, "0x"))
return res
@ -633,9 +642,17 @@ func _epochValidator(index spec.ValidatorIndex, pubKey string, withdrwalCredenti
}
}
// ValidatorsProvider is a mock.
type ValidatorsProvider struct{}
// NewValidatorsProvider returns a mock validators provider.
func NewValidatorsProvider() eth2client.ValidatorsProvider {
return &ValidatorsProvider{}
}
// Validators is a mock.
func (m *ValidatorsProvider) Validators(ctx context.Context, stateID string, validators []spec.ValidatorIndex) (map[spec.ValidatorIndex]*api.Validator, error) {
return map[spec.ValidatorIndex]*api.Validator{
base := map[spec.ValidatorIndex]*api.Validator{
0: _epochValidator(0,
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
@ -732,12 +749,27 @@ func (m *ValidatorsProvider) Validators(ctx context.Context, stateID string, val
31: _epochValidator(31,
"0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d",
"0x0077c6a139204cbdaae840e0beb43b384c35182aabbc1104207b6a5a626fe75b"),
}, nil
}
if len(validators) == 0 {
return base, nil
}
res := make(map[spec.ValidatorIndex]*api.Validator)
for k, v := range base {
for _, index := range validators {
if k == index {
res[k] = v
break
}
}
}
return res, nil
}
// ValidatorsByPubKey is a mock.
func (m *ValidatorsProvider) ValidatorsByPubKey(ctx context.Context, stateID string, validators []spec.BLSPubKey) (map[spec.ValidatorIndex]*api.Validator, error) {
return map[spec.ValidatorIndex]*api.Validator{
base := map[spec.ValidatorIndex]*api.Validator{
0: _epochValidator(0,
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
@ -834,7 +866,22 @@ func (m *ValidatorsProvider) ValidatorsByPubKey(ctx context.Context, stateID str
31: _epochValidator(31,
"0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d",
"0x0077c6a139204cbdaae840e0beb43b384c35182aabbc1104207b6a5a626fe75b"),
}, nil
}
if len(validators) == 0 {
return base, nil
}
res := make(map[spec.ValidatorIndex]*api.Validator)
for k, v := range base {
for _, pubKey := range validators {
if v.Validator.PublicKey == pubKey {
res[k] = v
break
}
}
}
return res, nil
}
// ValidatorsWithoutBalanceProvider is a mock for eth2client.ValidatorsProvider with eth2client.ValidatorsWithoutBalanceProvider.
@ -847,7 +894,7 @@ func NewValidatorsWithoutBalanceProvider() eth2client.ValidatorsProvider {
// Validators is a mock.
func (m *ValidatorsWithoutBalanceProvider) Validators(ctx context.Context, stateID string, validators []spec.ValidatorIndex) (map[spec.ValidatorIndex]*api.Validator, error) {
return map[spec.ValidatorIndex]*api.Validator{
base := map[spec.ValidatorIndex]*api.Validator{
0: _epochValidator(0,
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
@ -944,12 +991,27 @@ func (m *ValidatorsWithoutBalanceProvider) Validators(ctx context.Context, state
31: _epochValidator(31,
"0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d",
"0x0077c6a139204cbdaae840e0beb43b384c35182aabbc1104207b6a5a626fe75b"),
}, nil
}
if len(validators) == 0 {
return base, nil
}
res := make(map[spec.ValidatorIndex]*api.Validator)
for k, v := range base {
for _, index := range validators {
if k == index {
res[k] = v
break
}
}
}
return res, nil
}
// ValidatorsByPubKey is a mock.
func (m *ValidatorsWithoutBalanceProvider) ValidatorsByPubKey(ctx context.Context, stateID string, validators []spec.BLSPubKey) (map[spec.ValidatorIndex]*api.Validator, error) {
return map[spec.ValidatorIndex]*api.Validator{
base := map[spec.ValidatorIndex]*api.Validator{
0: _epochValidator(0,
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
@ -1046,12 +1108,27 @@ func (m *ValidatorsWithoutBalanceProvider) ValidatorsByPubKey(ctx context.Contex
31: _epochValidator(31,
"0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d",
"0x0077c6a139204cbdaae840e0beb43b384c35182aabbc1104207b6a5a626fe75b"),
}, nil
}
if len(validators) == 0 {
return base, nil
}
res := make(map[spec.ValidatorIndex]*api.Validator)
for k, v := range base {
for _, pubKey := range validators {
if v.Validator.PublicKey == pubKey {
res[k] = v
break
}
}
}
return res, nil
}
// ValidatorsWithoutBalance is a mock.
func (m *ValidatorsWithoutBalanceProvider) ValidatorsWithoutBalance(ctx context.Context, stateID string, validators []spec.ValidatorIndex) (map[spec.ValidatorIndex]*api.Validator, error) {
res := map[spec.ValidatorIndex]*api.Validator{
base := map[spec.ValidatorIndex]*api.Validator{
0: _epochValidator(0,
"0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c",
"0x00fad2a6bfb0e7f1f0f45460944fbd8dfa7f37da06a4d13b3983cc90bb46963b"),
@ -1150,9 +1227,22 @@ func (m *ValidatorsWithoutBalanceProvider) ValidatorsWithoutBalance(ctx context.
"0x0077c6a139204cbdaae840e0beb43b384c35182aabbc1104207b6a5a626fe75b"),
}
for _, validator := range res {
for _, validator := range base {
validator.Balance = 0
}
if len(validators) == 0 {
return base, nil
}
res := make(map[spec.ValidatorIndex]*api.Validator)
for k, v := range base {
for _, index := range validators {
if k == index {
res[k] = v
break
}
}
}
return res, nil
}

49
mock/validatorsmanager.go Normal file
View File

@ -0,0 +1,49 @@
// 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 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/validatorsmanager"
)
type validatorsManager struct{}
// NewValidatorsManager creates a mock validators manager.
func NewValidatorsManager() validatorsmanager.Service {
return &validatorsManager{}
}
// RefreshValidatorsFromBeaconNode is a mock.
func (v *validatorsManager) RefreshValidatorsFromBeaconNode(ctx context.Context, pubKeys []spec.BLSPubKey) error {
return nil
}
// ValidatorsByIndex is a mock.
func (v *validatorsManager) ValidatorsByIndex(ctx context.Context, indices []spec.ValidatorIndex) map[spec.ValidatorIndex]*spec.Validator {
return make(map[spec.ValidatorIndex]*spec.Validator)
}
// ValidatorsByIndex is a mock.
func (v *validatorsManager) ValidatorsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) map[spec.ValidatorIndex]*spec.Validator {
return make(map[spec.ValidatorIndex]*spec.Validator)
}
// ValidatorStateAtEpoch is a mock.
func (v *validatorsManager) ValidatorStateAtEpoch(ctx context.Context, index spec.ValidatorIndex, epoch spec.Epoch) (api.ValidatorState, error) {
return api.ValidatorStateUnknown, nil
}

View File

@ -19,27 +19,23 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.AccountManagerMonitor
clientMonitor metrics.ClientMonitor
endpoints []string
accountPaths []string
clientCert []byte
clientKey []byte
caCert []byte
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
beaconProposerDomainProvider eth2client.BeaconProposerDomainProvider
beaconAttesterDomainProvider eth2client.BeaconAttesterDomainProvider
randaoDomainProvider eth2client.RANDAODomainProvider
selectionProofDomainProvider eth2client.SelectionProofDomainProvider
aggregateAndProofDomainProvider eth2client.AggregateAndProofDomainProvider
domainProvider eth2client.DomainProvider
validatorsProvider eth2client.ValidatorsProvider
logLevel zerolog.Level
monitor metrics.AccountManagerMonitor
clientMonitor metrics.ClientMonitor
endpoints []string
accountPaths []string
clientCert []byte
clientKey []byte
caCert []byte
domainProvider eth2client.DomainProvider
validatorsManager validatorsmanager.Service
farFutureEpochProvider eth2client.FarFutureEpochProvider
}
// Parameter is the interface for service parameters.
@ -109,52 +105,10 @@ func WithCACert(cert []byte) Parameter {
})
}
// WithValidatorsProvider sets the validator status provider.
func WithValidatorsProvider(provider eth2client.ValidatorsProvider) Parameter {
// WithValidatorsManager sets the validators manager.
func WithValidatorsManager(provider validatorsmanager.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.validatorsProvider = provider
})
}
// WithSlotsPerEpochProvider sets the slots per epoch provider.
func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.slotsPerEpochProvider = provider
})
}
// WithBeaconProposerDomainProvider sets the beacon proposer domain provider.
func WithBeaconProposerDomainProvider(provider eth2client.BeaconProposerDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconProposerDomainProvider = provider
})
}
// WithBeaconAttesterDomainProvider sets the beacon attester domain provider.
func WithBeaconAttesterDomainProvider(provider eth2client.BeaconAttesterDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconAttesterDomainProvider = provider
})
}
// WithRANDAODomainProvider sets the RANDAO domain provider.
func WithRANDAODomainProvider(provider eth2client.RANDAODomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.randaoDomainProvider = provider
})
}
// WithSelectionProofDomainProvider sets the RANDAO domain provider.
func WithSelectionProofDomainProvider(provider eth2client.SelectionProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.selectionProofDomainProvider = provider
})
}
// WithAggregateAndProofDomainProvider sets the aggregate and proof domain provider.
func WithAggregateAndProofDomainProvider(provider eth2client.AggregateAndProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.aggregateAndProofDomainProvider = provider
p.validatorsManager = provider
})
}
@ -165,6 +119,13 @@ func WithDomainProvider(provider eth2client.DomainProvider) Parameter {
})
}
// WithFarFutureEpochProvider sets the far future epoch provider.
func WithFarFutureEpochProvider(provider eth2client.FarFutureEpochProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.farFutureEpochProvider = provider
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -196,30 +157,15 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.clientKey == nil {
return nil, errors.New("no client key specified")
}
if parameters.validatorsProvider == nil {
return nil, errors.New("no validators provider specified")
}
if parameters.slotsPerEpochProvider == nil {
return nil, errors.New("no slots per epoch provider specified")
}
if parameters.beaconProposerDomainProvider == nil {
return nil, errors.New("no beacon proposer domain provider specified")
}
if parameters.beaconAttesterDomainProvider == nil {
return nil, errors.New("no beacon attester domain provider specified")
}
if parameters.randaoDomainProvider == nil {
return nil, errors.New("no RANDAO domain provider specified")
}
if parameters.selectionProofDomainProvider == nil {
return nil, errors.New("no selection proof domain provider specified")
}
if parameters.aggregateAndProofDomainProvider == nil {
return nil, errors.New("no aggregate and proof domain provider specified")
if parameters.validatorsManager == nil {
return nil, errors.New("no validators manager specified")
}
if parameters.domainProvider == nil {
return nil, errors.New("no domain provider specified")
}
if parameters.farFutureEpochProvider == nil {
return nil, errors.New("no far future epoch provider specified")
}
return &parameters, nil
}

View File

@ -22,13 +22,12 @@ import (
"strconv"
"strings"
"sync"
"time"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "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/validatorsmanager"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
@ -40,21 +39,18 @@ import (
// Service is the manager for dirk accounts.
type Service struct {
mutex sync.RWMutex
monitor metrics.AccountManagerMonitor
clientMonitor metrics.ClientMonitor
endpoints []*dirk.Endpoint
accountPaths []string
credentials credentials.TransportCredentials
accounts map[spec.BLSPubKey]*ValidatingAccount
validatorsProvider eth2client.ValidatorsProvider
slotsPerEpoch spec.Slot
beaconProposerDomainType spec.DomainType
beaconAttesterDomainType spec.DomainType
randaoDomainType spec.DomainType
selectionProofDomainType spec.DomainType
aggregateAndProofDomainType spec.DomainType
domainProvider eth2client.DomainProvider
mutex sync.RWMutex
monitor metrics.AccountManagerMonitor
clientMonitor metrics.ClientMonitor
endpoints []*dirk.Endpoint
accountPaths []string
credentials credentials.TransportCredentials
accounts map[spec.BLSPubKey]e2wtypes.Account
validatorsManager validatorsmanager.Service
domainProvider eth2client.DomainProvider
farFutureEpoch spec.Epoch
wallets map[string]e2wtypes.Wallet
walletsMutex sync.RWMutex
}
// module-wide log.
@ -100,116 +96,47 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
return nil, errors.New("no valid endpoints specified")
}
slotsPerEpoch, err := parameters.slotsPerEpochProvider.SlotsPerEpoch(ctx)
farFutureEpoch, err := parameters.farFutureEpochProvider.FarFutureEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
}
beaconAttesterDomainType, err := parameters.beaconAttesterDomainProvider.BeaconAttesterDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon attester domain")
}
beaconProposerDomainType, err := parameters.beaconProposerDomainProvider.BeaconProposerDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon proposer domain")
}
randaoDomainType, err := parameters.randaoDomainProvider.RANDAODomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain RANDAO domain")
}
selectionProofDomainType, err := parameters.selectionProofDomainProvider.SelectionProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain selection proof domain")
}
aggregateAndProofDomainType, err := parameters.aggregateAndProofDomainProvider.AggregateAndProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain aggregate and proof domain")
return nil, errors.Wrap(err, "failed to obtain far future epoch")
}
s := &Service{
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
endpoints: endpoints,
accountPaths: parameters.accountPaths,
credentials: credentials,
slotsPerEpoch: spec.Slot(slotsPerEpoch),
beaconAttesterDomainType: beaconAttesterDomainType,
beaconProposerDomainType: beaconProposerDomainType,
randaoDomainType: randaoDomainType,
selectionProofDomainType: selectionProofDomainType,
aggregateAndProofDomainType: aggregateAndProofDomainType,
domainProvider: parameters.domainProvider,
validatorsProvider: parameters.validatorsProvider,
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
endpoints: endpoints,
accountPaths: parameters.accountPaths,
credentials: credentials,
domainProvider: parameters.domainProvider,
validatorsManager: parameters.validatorsManager,
farFutureEpoch: farFutureEpoch,
wallets: make(map[string]e2wtypes.Wallet),
}
if err := s.RefreshAccounts(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch validating keys")
if err := s.refreshAccounts(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch accounts")
}
if err := s.refreshValidators(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch validator states")
}
return s, nil
}
// UpdateAccountsState updates account state with the latest information from the beacon chain.
// This should be run at the beginning of each epoch to ensure that any newly-activated accounts are registered.
func (s *Service) UpdateAccountsState(ctx context.Context) error {
validatorIDProviders := make([]eth2client.ValidatorIDProvider, 0, len(s.accounts))
for _, account := range s.accounts {
if !account.state.HasActivated() {
validatorIDProviders = append(validatorIDProviders, account)
}
// Refresh refreshes the accounts from Dirk, and account validator state from
// the validators provider.
// This is a relatively expensive operation, so should not be run in the validating path.
func (s *Service) Refresh(ctx context.Context) {
if err := s.refreshAccounts(ctx); err != nil {
log.Error().Err(err).Msg("Failed to refresh accounts")
}
if len(validatorIDProviders) == 0 {
// Nothing to do.
log.Trace().Msg("No unactivated keys")
return nil
if err := s.refreshValidators(ctx); err != nil {
log.Error().Err(err).Msg("Failed to refresh validators")
}
// Unactivated validators can have an index of 0, so cannot send via an API call that is by index. Need to use bypubkeys.
log.Trace().Int("total", len(s.accounts)).Int("unactivated", len(validatorIDProviders)).Msg("Updating state of unactivated keys")
var validators map[spec.ValidatorIndex]*api.Validator
var err error
if validatorsWithoutBalanceProvider, isProvider := s.validatorsProvider.(eth2client.ValidatorsWithoutBalanceProvider); isProvider {
started := time.Now()
validators, err = validatorsWithoutBalanceProvider.ValidatorsWithoutBalance(ctx, "head", validatorIDProviders)
if service, isService := s.validatorsProvider.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "validators without balance", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "validators without balance", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to obtain validators without balances")
}
} else {
started := time.Now()
validatorIDs := make([]spec.ValidatorIndex, 0, len(s.accounts))
for _, account := range s.accounts {
if !account.state.HasActivated() {
index, err := account.Index(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain account index")
}
validatorIDs = append(validatorIDs, index)
}
}
validators, err = s.validatorsProvider.Validators(ctx, "head", validatorIDs)
if service, isService := s.validatorsProvider.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "validators", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "validators", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
}
log.Trace().Int("received", len(validators)).Msg("Received state of known unactivated keys")
s.mutex.Lock()
s.updateAccountStates(ctx, s.accounts, validators)
s.mutex.Unlock()
return nil
}
// RefreshAccounts refreshes the entire list of validating keys.
func (s *Service) RefreshAccounts(ctx context.Context) error {
// refreshAccounts refreshes the accounts from Dirk.
func (s *Service) refreshAccounts(ctx context.Context) error {
// Create the relevant wallets.
wallets := make(map[string]e2wtypes.Wallet)
pathsByWallet := make(map[string][]string)
@ -222,7 +149,7 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
paths = make([]string, 0)
}
pathsByWallet[pathBits[0]] = append(paths, path)
wallet, err := dirk.OpenWallet(ctx, pathBits[0], s.credentials, s.endpoints)
wallet, err := s.openWallet(ctx, pathBits[0])
if err != nil {
log.Warn().Err(err).Str("wallet", pathBits[0]).Msg("Failed to open wallet")
} else {
@ -232,56 +159,18 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
verificationRegexes := accountPathsToVerificationRegexes(s.accountPaths)
// Fetch accounts for each wallet.
accounts := make(map[spec.BLSPubKey]*ValidatingAccount)
accounts := make(map[spec.BLSPubKey]e2wtypes.Account)
for _, wallet := range wallets {
// if _, isProvider := wallet.(e2wtypes.WalletAccountsByPathProvider); isProvider {
// fmt.Printf("TODO: fetch accounts by path")
// } else {
s.fetchAccountsForWallet(ctx, wallet, accounts, verificationRegexes)
walletAccounts := s.fetchAccountsForWallet(ctx, wallet, verificationRegexes)
for k, v := range walletAccounts {
accounts[k] = v
}
//}
}
// Update indices for accounts.
pubKeys := make([]spec.BLSPubKey, 0, len(accounts))
for _, account := range accounts {
pubKey, err := account.PubKey(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain public key")
}
pubKeys = append(pubKeys, pubKey)
}
validators, err := s.validatorsProvider.ValidatorsByPubKey(ctx, "head", pubKeys)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
// log.Trace().Int("accounts", len(validatorIDProviders)).Msg("Obtaining validator state of accounts")
// var validators map[uint64]*api.Validator
// var err error
// if validatorsWithoutBalanceProvider, isProvider := s.validatorsProvider.(eth2client.ValidatorsWithoutBalanceProvider); isProvider {
// validators, err = validatorsWithoutBalanceProvider.ValidatorsWithoutBalance(ctx, "head", validatorIDProviders)
// if err != nil {
// return errors.Wrap(err, "failed to obtain validators without balances")
// }
// } else {
// validatorIDs := make([]uint64, 0, len(s.accounts))
// for _, account := range s.accounts {
// if !account.state.IsAttesting() {
// index, err := account.Index(ctx)
// if err != nil {
// return errors.Wrap(err, "failed to obtain account index")
// }
// validatorIDs = append(validatorIDs, index)
// }
// }
// validators, err = s.validatorsProvider.Validators(ctx, "head", validatorIDs)
// if err != nil {
// return errors.Wrap(err, "failed to obtain validators")
// }
// }
log.Trace().Int("received", len(validators)).Msg("Received state of accounts")
s.updateAccountStates(ctx, accounts, validators)
log.Trace().Int("accounts", len(accounts)).Msg("Obtained accounts")
s.mutex.Lock()
s.accounts = accounts
@ -290,6 +179,36 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
return nil
}
// openWallet opens a wallet, using an existing one if present.
func (s *Service) openWallet(ctx context.Context, name string) (e2wtypes.Wallet, error) {
s.walletsMutex.Lock()
defer s.walletsMutex.Unlock()
wallet, exists := s.wallets[name]
var err error
if !exists {
wallet, err = dirk.OpenWallet(ctx, name, s.credentials, s.endpoints)
if err != nil {
return nil, err
}
s.wallets[name] = wallet
}
return wallet, nil
}
// refreshValidators refreshes the validator information for our known accounts.
func (s *Service) refreshValidators(ctx context.Context) error {
accountPubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
accountPubKeys = append(accountPubKeys, pubKey)
}
if err := s.validatorsManager.RefreshValidatorsFromBeaconNode(ctx, accountPubKeys); err != nil {
return errors.Wrap(err, "failed to refresh validators")
}
return nil
}
func credentialsFromCerts(ctx context.Context, clientCert []byte, clientKey []byte, caCert []byte) (credentials.TransportCredentials, error) {
clientPair, err := tls.X509KeyPair(clientCert, clientKey)
if err != nil {
@ -312,71 +231,49 @@ func credentialsFromCerts(ctx context.Context, clientCert []byte, clientKey []by
return credentials.NewTLS(tlsCfg), nil
}
// Accounts returns all attesting accounts.
func (s *Service) Accounts(ctx context.Context) ([]accountmanager.ValidatingAccount, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
// ValidatingAccountsForEpoch obtains the validating accounts for a given epoch.
func (s *Service) ValidatingAccountsForEpoch(ctx context.Context, epoch spec.Epoch) (map[spec.ValidatorIndex]e2wtypes.Account, error) {
validatingAccounts := make(map[spec.ValidatorIndex]e2wtypes.Account)
pubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
pubKeys = append(pubKeys, pubKey)
}
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for _, account := range s.accounts {
if account.state.IsAttesting() {
accounts = append(accounts, account)
validators := s.validatorsManager.ValidatorsByPubKey(ctx, pubKeys)
for index, validator := range validators {
state := api.ValidatorToState(validator, epoch, s.farFutureEpoch)
if state == api.ValidatorStateActiveOngoing || state == api.ValidatorStateActiveExiting {
validatingAccounts[index] = s.accounts[validator.PublicKey]
}
}
return accounts, nil
return validatingAccounts, nil
}
// AccountsByIndex returns attesting accounts.
func (s *Service) AccountsByIndex(ctx context.Context, indices []spec.ValidatorIndex) ([]accountmanager.ValidatingAccount, error) {
indexMap := make(map[spec.ValidatorIndex]bool)
// ValidatingAccountsForEpochByIndex obtains the specified validating accounts for a given epoch.
func (s *Service) ValidatingAccountsForEpochByIndex(ctx context.Context, epoch spec.Epoch, indices []spec.ValidatorIndex) (map[spec.ValidatorIndex]e2wtypes.Account, error) {
validatingAccounts := make(map[spec.ValidatorIndex]e2wtypes.Account)
pubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
pubKeys = append(pubKeys, pubKey)
}
indexPresenceMap := make(map[spec.ValidatorIndex]bool)
for _, index := range indices {
indexMap[index] = true
indexPresenceMap[index] = true
}
s.mutex.RLock()
defer s.mutex.RUnlock()
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for _, account := range s.accounts {
if !account.state.IsAttesting() {
validators := s.validatorsManager.ValidatorsByPubKey(ctx, pubKeys)
for index, validator := range validators {
if _, present := indexPresenceMap[index]; !present {
continue
}
index, err := account.Index(ctx)
if err != nil {
log.Error().Err(err).Msg("No index for account")
continue
}
if _, exists := indexMap[index]; exists {
accounts = append(accounts, account)
state := api.ValidatorToState(validator, epoch, s.farFutureEpoch)
if state == api.ValidatorStateActiveOngoing || state == api.ValidatorStateActiveExiting {
validatingAccounts[index] = s.accounts[validator.PublicKey]
}
}
return accounts, nil
}
// AccountsByPubKey returns validating accounts.
func (s *Service) AccountsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) ([]accountmanager.ValidatingAccount, error) {
pubKeyMap := make(map[spec.BLSPubKey]bool)
for _, pubKey := range pubKeys {
pubKeyMap[pubKey] = true
}
s.mutex.RLock()
defer s.mutex.RUnlock()
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for pubKey, account := range s.accounts {
if !account.state.IsAttesting() {
continue
}
if _, exists := pubKeyMap[pubKey]; exists {
accounts = append(accounts, account)
}
}
return accounts, nil
return validatingAccounts, nil
}
// accountPathsToVerificationRegexes turns account paths in to regexes to allow verification.
@ -410,36 +307,8 @@ func accountPathsToVerificationRegexes(paths []string) []*regexp.Regexp {
return regexes
}
func (s *Service) updateAccountStates(ctx context.Context, accounts map[spec.BLSPubKey]*ValidatingAccount, validators map[spec.ValidatorIndex]*api.Validator) {
validatorsByPubKey := make(map[[48]byte]*api.Validator, len(validators))
for _, validator := range validators {
validatorsByPubKey[validator.Validator.PublicKey] = validator
}
validatorStateCounts := make(map[string]uint64)
for pubKey, account := range accounts {
if validator, exists := validatorsByPubKey[pubKey]; exists {
account.index = validator.Index
account.state = validator.Status
}
validatorStateCounts[strings.ToLower(account.state.String())]++
}
for state, count := range validatorStateCounts {
s.monitor.Accounts(state, count)
}
if e := log.Trace(); e.Enabled() {
for _, account := range accounts {
log.Trace().
Str("name", account.account.Name()).
Str("public_key", fmt.Sprintf("%x", account.account.PublicKey().Marshal())).
Str("state", account.state.String()).
Msg("Validating account")
}
}
}
func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wallet, accounts map[spec.BLSPubKey]*ValidatingAccount, verificationRegexes []*regexp.Regexp) {
func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wallet, verificationRegexes []*regexp.Regexp) map[spec.BLSPubKey]e2wtypes.Account {
res := make(map[spec.BLSPubKey]e2wtypes.Account)
for account := range wallet.Accounts(ctx) {
// Ensure the name matches one of our account paths.
name := fmt.Sprintf("%s/%s", wallet.Name(), account.Name())
@ -462,11 +331,7 @@ func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wa
pubKey = account.PublicKey().Marshal()
}
// Set up account as unknown to beacon chain.
accounts[bytesutil.ToBytes48(pubKey)] = &ValidatingAccount{
account: account,
accountManager: s,
domainProvider: s.domainProvider,
}
res[bytesutil.ToBytes48(pubKey)] = account
}
return res
}

View File

@ -15,14 +15,9 @@ package dirk
import (
"context"
"fmt"
"math/rand"
"regexp"
"testing"
"time"
"github.com/attestantio/dirk/testing/daemon"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
@ -58,43 +53,19 @@ func TestFetchAccountsForWallet(t *testing.T) {
// Test with wallet regex.
s, err := setupService(ctx, t, []string{"localhost:12345"}, []string{"wallet1", "wallet2"})
require.NoError(t, err)
validatingAccounts := make(map[spec.BLSPubKey]*ValidatingAccount)
for account := range wallets[0].Accounts(ctx) {
var key spec.BLSPubKey
copy(key[:], account.PublicKey().Marshal())
validatingAccounts[key] = &ValidatingAccount{
account: account,
}
}
verificationRegexes := make([]*regexp.Regexp, 0)
verificationRegexes = append(verificationRegexes, regexp.MustCompile("wallet1"))
s.fetchAccountsForWallet(ctx, wallets[0], validatingAccounts, verificationRegexes)
for _, validatingAccount := range validatingAccounts {
require.NotNil(t, validatingAccount.accountManager)
}
accounts := s.fetchAccountsForWallet(ctx, wallets[0], verificationRegexes)
require.Equal(t, 3, len(accounts))
// Test with single account regex.
capture := logger.NewLogCapture()
s, err = setupService(ctx, t, []string{"localhost:12345"}, []string{"wallet1", "wallet2"})
require.NoError(t, err)
validatingAccounts = make(map[spec.BLSPubKey]*ValidatingAccount)
for account := range wallets[0].Accounts(ctx) {
var key spec.BLSPubKey
copy(key[:], account.PublicKey().Marshal())
validatingAccounts[key] = &ValidatingAccount{
account: account,
}
}
verificationRegexes = make([]*regexp.Regexp, 0)
verificationRegexes = append(verificationRegexes, regexp.MustCompile("1$"))
s.fetchAccountsForWallet(ctx, wallets[0], validatingAccounts, verificationRegexes)
fetched := 0
for _, validatingAccount := range validatingAccounts {
if validatingAccount.accountManager != nil {
fetched++
}
}
require.Equal(t, 1, fetched)
accounts = s.fetchAccountsForWallet(ctx, wallets[0], verificationRegexes)
require.Equal(t, 1, len(accounts))
capture.AssertHasEntry(t, "Received unwanted account from server; ignoring")
}
@ -181,7 +152,7 @@ func TestAccountPathsToVerificationRegexes(t *testing.T) {
func TestAccounts(t *testing.T) {
tests := []struct {
name string
accounts map[spec.BLSPubKey]*ValidatingAccount
accounts map[spec.BLSPubKey]e2wtypes.Account
expected int
}{
{
@ -195,96 +166,10 @@ func TestAccounts(t *testing.T) {
require.NoError(t, err)
// Manually add accounts.
s.accounts = test.accounts
accounts, err := s.Accounts(context.Background())
require.NoError(t, err)
require.Equal(t, test.expected, len(accounts))
})
}
}
func TestUpdateAccountsState(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.NoError(t, err)
s, err := setupService(context.Background(), t, []string{fmt.Sprintf("signer-test01:%d", port)}, []string{"Wallet 1", "Wallet 2"})
require.NoError(t, err)
accounts, err := s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 16, len(accounts))
// Manually clear out the accounts state.
for _, account := range s.accounts {
account.state = api.ValidatorStateUnknown
}
// Confirm that validators are unknown (hence not returned).
accounts, err = s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 0, len(accounts))
// Update and re-check.
require.NoError(t, s.UpdateAccountsState(ctx))
accounts, err = s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 16, len(accounts))
}
func TestUpdateAccountsStateWithoutBalances(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.NoError(t, err)
s, err := New(ctx,
WithLogLevel(zerolog.TraceLevel),
WithMonitor(nullmetrics.New(context.Background())),
WithClientMonitor(nullmetrics.New(context.Background())),
WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
WithClientCert([]byte(resources.ClientTest01Crt)),
WithClientKey([]byte(resources.ClientTest01Key)),
WithCACert([]byte(resources.CACrt)),
WithValidatorsProvider(mock.NewValidatorsWithoutBalanceProvider()),
WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
WithDomainProvider(mock.NewDomainProvider()),
)
require.NoError(t, err)
accounts, err := s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 16, len(accounts))
// Manually clear out the accounts state.
for _, account := range s.accounts {
account.state = api.ValidatorStateUnknown
}
// Confirm that validators are unknown (hence not returned).
accounts, err = s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 0, len(accounts))
// Update and re-check.
require.NoError(t, s.UpdateAccountsState(ctx))
accounts, err = s.Accounts(ctx)
require.NoError(t, err)
require.Equal(t, 16, len(accounts))
}
func setupService(ctx context.Context, t *testing.T, endpoints []string, accountPaths []string) (*Service, error) {
return New(ctx,
WithLogLevel(zerolog.TraceLevel),
@ -295,14 +180,9 @@ func setupService(ctx context.Context, t *testing.T, endpoints []string, account
WithClientCert([]byte(resources.ClientTest01Crt)),
WithClientKey([]byte(resources.ClientTest01Key)),
WithCACert([]byte(resources.CACrt)),
WithValidatorsProvider(mock.NewValidatorsProvider()),
WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
WithValidatorsManager(mock.NewValidatorsManager()),
WithDomainProvider(mock.NewDomainProvider()),
WithFarFutureEpochProvider(mock.NewFarFutureEpochProvider(0xffffffffffffffff)),
)
}

View File

@ -15,64 +15,21 @@ package dirk_test
import (
"context"
"encoding/hex"
"fmt"
"math/rand"
"strings"
"testing"
"time"
"github.com/attestantio/dirk/testing/daemon"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/services/accountmanager/dirk"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/testing/logger"
"github.com/attestantio/vouch/testing/resources"
"github.com/attestantio/vouch/testutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func _root(input string) spec.Root {
res, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
if err != nil {
panic(err)
}
var root spec.Root
copy(root[:], res)
return root
}
func _sig(input string) spec.BLSSignature {
res, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
if err != nil {
panic(err)
}
var sig spec.BLSSignature
copy(sig[:], res)
return sig
}
func _pubKey(input string) spec.BLSPubKey {
res, err := hex.DecodeString(strings.TrimPrefix(input, "0x"))
if err != nil {
panic(err)
}
var pubKey spec.BLSPubKey
copy(pubKey[:], res)
return pubKey
}
func TestService(t *testing.T) {
slotsPerEpochProvider := mock.NewSlotsPerEpochProvider(32)
beaconProposerDomainProvider := mock.NewBeaconProposerDomainProvider()
beaconAttesterDomainProvider := mock.NewBeaconAttesterDomainProvider()
randaoDomainProvider := mock.NewRANDAODomainProvider()
selectionProofDomainProvider := mock.NewSelectionProofDomainProvider()
aggregateAndProofDomainProvider := mock.NewAggregateAndProofDomainProvider()
domainProvider := mock.NewDomainProvider()
validatorsProvider := mock.NewValidatorsProvider()
validatorsManager := mock.NewValidatorsManager()
farFutureEpochProvider := mock.NewFarFutureEpochProvider(0xffffffffffffffff)
tests := []struct {
name string
@ -91,14 +48,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no monitor specified",
},
@ -113,14 +65,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no client monitor specified",
},
@ -134,14 +81,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no endpoints specified",
},
@ -156,14 +98,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no endpoints specified",
},
@ -178,14 +115,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "no valid endpoints specified",
logEntry: "Malformed endpoint",
@ -201,14 +133,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "no valid endpoints specified",
logEntry: "Malformed port",
@ -224,14 +151,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "no valid endpoints specified",
logEntry: "Invalid port",
@ -246,14 +168,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no account paths specified",
},
@ -268,14 +185,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no account paths specified",
},
@ -289,14 +201,9 @@ func TestService(t *testing.T) {
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no client certificate specified",
},
@ -310,14 +217,9 @@ func TestService(t *testing.T) {
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no client key specified",
},
@ -332,19 +234,14 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest02Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "failed to build credentials: failed to load client keypair: tls: private key does not match public key",
},
{
name: "ValidatorsProviderMissing",
name: "ValidatorsManagerMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
@ -354,273 +251,10 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no validators provider specified",
},
{
name: "SlotsPerEpochProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no slots per epoch provider specified",
},
{
name: "SlotsPerEpochProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.Disabled),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(mock.NewErroringSlotsPerEpochProvider()),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain slots per epoch: error",
},
{
name: "BeaconProposerDomainProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no beacon proposer domain provider specified",
},
{
name: "BeaconProposerDomainProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(mock.NewErroringBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain beacon proposer domain: error",
},
{
name: "BeaconAttesterDomainProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no beacon attester domain provider specified",
},
{
name: "BeaconAttesterDomainProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(mock.NewErroringBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain beacon attester domain: error",
},
{
name: "RANDAODomainProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no RANDAO domain provider specified",
},
{
name: "RANDAODomainProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(mock.NewErroringRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain RANDAO domain: error",
},
{
name: "SelectionProofDomainProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no selection proof domain provider specified",
},
{
name: "SelectionProofDomainProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(mock.NewErroringSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain selection proof domain: error",
},
{
name: "AggregateAndProofDomainProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no aggregate and proof domain provider specified",
},
{
name: "AggregateAndProofDomainProviderErrors",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(mock.NewErroringAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(domainProvider),
},
err: "failed to obtain aggregate and proof domain: error",
err: "problem with parameters: no validators manager specified",
},
{
name: "DomainProviderMissing",
@ -633,16 +267,27 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
err: "problem with parameters: no domain provider specified",
},
{
name: "FarFutureEpochProviderMissing",
params: []dirk.Parameter{
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{"localhost:12345", "localhost:12346"}),
dirk.WithAccountPaths([]string{"wallet1", "wallet2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no far future epoch provider specified",
},
{
name: "Good",
params: []dirk.Parameter{
@ -654,14 +299,9 @@ func TestService(t *testing.T) {
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(validatorsProvider),
dirk.WithSlotsPerEpochProvider(slotsPerEpochProvider),
dirk.WithBeaconProposerDomainProvider(beaconProposerDomainProvider),
dirk.WithBeaconAttesterDomainProvider(beaconAttesterDomainProvider),
dirk.WithRANDAODomainProvider(randaoDomainProvider),
dirk.WithSelectionProofDomainProvider(selectionProofDomainProvider),
dirk.WithAggregateAndProofDomainProvider(aggregateAndProofDomainProvider),
dirk.WithValidatorsManager(validatorsManager),
dirk.WithDomainProvider(domainProvider),
dirk.WithFarFutureEpochProvider(farFutureEpochProvider),
},
},
}
@ -681,216 +321,3 @@ func TestService(t *testing.T) {
})
}
}
func TestAccounts(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
tests := []struct {
name string
accountPaths []string
accounts int
}{
{
name: "Empty",
accountPaths: []string{"None"},
accounts: 0,
},
{
name: "Wallet1",
accountPaths: []string{"Wallet 1"},
accounts: 16,
},
{
name: "Wallet1OddAccounts",
accountPaths: []string{"Wallet 1/Account .*[13579]"},
accounts: 8,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths(test.accountPaths),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
accounts, err := s.Accounts(ctx)
require.Nil(t, err)
require.Equal(t, test.accounts, len(accounts))
})
}
}
func TestAccountsByIndex(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
tests := []struct {
name string
indices []spec.ValidatorIndex
accounts int
}{
{
name: "Nil",
accounts: 0,
},
{
name: "Empty",
indices: []spec.ValidatorIndex{},
accounts: 0,
},
{
name: "All",
indices: []spec.ValidatorIndex{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
accounts: 16,
},
{
name: "Missing",
indices: []spec.ValidatorIndex{15, 16},
accounts: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
accounts, err := s.AccountsByIndex(ctx, test.indices)
require.Nil(t, err)
require.Equal(t, test.accounts, len(accounts))
})
}
}
func TestAccountsByPubKey(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
tests := []struct {
name string
pubKeys []spec.BLSPubKey
accounts int
}{
{
name: "Nil",
accounts: 0,
},
{
name: "Empty",
pubKeys: []spec.BLSPubKey{
{},
},
accounts: 0,
},
{
name: "All",
pubKeys: []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
testutil.HexToPubKey("0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e"),
testutil.HexToPubKey("0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e"),
testutil.HexToPubKey("0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34"),
testutil.HexToPubKey("0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373"),
testutil.HexToPubKey("0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac"),
testutil.HexToPubKey("0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7"),
testutil.HexToPubKey("0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a"),
testutil.HexToPubKey("0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268"),
testutil.HexToPubKey("0xaec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d"),
testutil.HexToPubKey("0x9314c6de0386635e2799af798884c2ea09c63b9f079e572acc00b06a7faccce501ea4dfc0b1a23b8603680a5e3481327"),
testutil.HexToPubKey("0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c"),
testutil.HexToPubKey("0x84398f539a64cbe01cfcd8c485ea51cd6657b94df93ee9b5dc61e1f18f69da6ca9d4dba63c956a81c68d5d4d4277a60f"),
testutil.HexToPubKey("0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f"),
},
accounts: 16,
},
{
name: "Missing",
pubKeys: []spec.BLSPubKey{
testutil.HexToPubKey("0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f"),
testutil.HexToPubKey("0x8f467e5723deac7659e1ca273e28410cbaa6d495ab66ae77014f4cd21c64b6b5ab9987c9b5537fe0279bd063fe609be7"),
},
accounts: 1,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
accounts, err := s.AccountsByPubKey(ctx, test.pubKeys)
require.Nil(t, err)
require.Equal(t, test.accounts, len(accounts))
})
}
}

View File

@ -1,251 +0,0 @@
// 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 dirk
import (
"context"
"encoding/binary"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// ValidatingAccount is a wrapper around the dirk account that implements ValidatingAccount.
type ValidatingAccount struct {
account e2wtypes.Account
index spec.ValidatorIndex
state api.ValidatorState
accountManager *Service
domainProvider eth2client.DomainProvider
}
// PubKey returns the public key of the validating account.
func (d *ValidatingAccount) PubKey(ctx context.Context) (spec.BLSPubKey, error) {
var pubKey spec.BLSPubKey
if provider, isProvider := d.account.(e2wtypes.AccountCompositePublicKeyProvider); isProvider {
copy(pubKey[:], provider.CompositePublicKey().Marshal())
} else {
copy(pubKey[:], d.account.PublicKey().Marshal())
}
return pubKey, nil
}
// Index returns the index of the validating account.
func (d *ValidatingAccount) Index(ctx context.Context) (spec.ValidatorIndex, error) {
return d.index, nil
}
// State returns the state of the validating account.
func (d *ValidatingAccount) State() api.ValidatorState {
return d.state
}
// SignSlotSelection returns a slot selection signature.
// This signs a slot with the "selection proof" domain.
func (d *ValidatingAccount) SignSlotSelection(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error) {
var messageRoot spec.Root
binary.LittleEndian.PutUint64(messageRoot[:], uint64(slot))
// Calculate the domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.selectionProofDomainType,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for selection proof")
}
slotBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(slotBytes, uint64(slot))
sig, err := d.account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, slotBytes, domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign slot")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}
// SignRANDAOReveal returns a RANDAO reveal signature.
// This signs an epoch with the "RANDAO reveal" domain.
func (d *ValidatingAccount) SignRANDAOReveal(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error) {
var messageRoot spec.Root
epoch := spec.Epoch(slot / d.accountManager.slotsPerEpoch)
binary.LittleEndian.PutUint64(messageRoot[:], uint64(epoch))
// Obtain the RANDAO reveal signature domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.randaoDomainType,
epoch)
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for RANDAO reveal")
}
epochBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(epochBytes, uint64(epoch))
sig, err := d.account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, epochBytes, domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign RANDO reveal")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}
// SignBeaconBlockProposal signs a beacon block proposal item.
func (d *ValidatingAccount) SignBeaconBlockProposal(ctx context.Context,
slot spec.Slot,
proposerIndex spec.ValidatorIndex,
parentRoot spec.Root,
stateRoot spec.Root,
bodyRoot spec.Root) (spec.BLSSignature, error) {
// Fetch the domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.beaconProposerDomainType,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon proposal")
}
sig, err := d.account.(e2wtypes.AccountProtectingSigner).SignBeaconProposal(ctx,
uint64(slot),
uint64(proposerIndex),
parentRoot[:],
stateRoot[:],
bodyRoot[:],
domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign beacon block proposal")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}
// SignBeaconAttestation signs a beacon attestation item.
func (d *ValidatingAccount) SignBeaconAttestation(ctx context.Context,
slot spec.Slot,
committeeIndex spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) (spec.BLSSignature, error) {
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.beaconAttesterDomainType,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon attestation")
}
sig, err := d.account.(e2wtypes.AccountProtectingSigner).SignBeaconAttestation(ctx,
uint64(slot),
uint64(committeeIndex),
blockRoot[:],
uint64(sourceEpoch),
sourceRoot[:],
uint64(targetEpoch),
targetRoot[:],
domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign beacon attestation")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}
// SignBeaconAttestations signs multiple beacon attestations.
func (d *ValidatingAccount) SignBeaconAttestations(ctx context.Context,
slot spec.Slot,
accounts []accountmanager.ValidatingAccount,
committeeIndices []spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) ([]spec.BLSSignature, error) {
span, ctx := opentracing.StartSpanFromContext(ctx, "dirk.SignBeaconAttestations")
defer span.Finish()
signatureDomain, err := d.domainProvider.Domain(ctx,
d.accountManager.beaconAttesterDomainType,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return nil, errors.Wrap(err, "failed to obtain signature domain for beacon attestation")
}
e2Accounts := make([]e2wtypes.Account, len(accounts))
for i := range accounts {
e2Accounts[i] = accounts[i].(*ValidatingAccount).account
}
uintCommitteeIndices := make([]uint64, len(committeeIndices))
for i := range committeeIndices {
uintCommitteeIndices[i] = uint64(committeeIndices[i])
}
sigs, err := d.account.(e2wtypes.AccountProtectingMultiSigner).SignBeaconAttestations(ctx,
uint64(slot),
e2Accounts,
uintCommitteeIndices,
blockRoot[:],
uint64(sourceEpoch),
sourceRoot[:],
uint64(targetEpoch),
targetRoot[:],
signatureDomain[:],
)
if err != nil {
return nil, errors.Wrap(err, "failed to sign beacon attestation")
}
res := make([]spec.BLSSignature, len(sigs))
for i := range sigs {
if sigs[i] != nil {
copy(res[i][:], sigs[i].Marshal())
}
}
return res, nil
}
// SignAggregateAndProof signs an aggregate and proof item.
func (d *ValidatingAccount) SignAggregateAndProof(ctx context.Context, slot spec.Slot, aggregateAndProofRoot spec.Root) (spec.BLSSignature, error) {
// Fetch the domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.aggregateAndProofDomainType,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon aggregate and proof")
}
sig, err := d.account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, aggregateAndProofRoot[:], domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to aggregate and proof")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -1,490 +0,0 @@
// 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 dirk_test
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/attestantio/dirk/testing/daemon"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/accountmanager/dirk"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/testing/resources"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestPubKey(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSPubKey
}{
{
name: "0",
index: 0,
expected: _pubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
},
{
name: "1",
index: 1,
expected: _pubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
provider, isProvider := account.(accountmanager.ValidatingAccountPubKeyProvider)
require.True(t, isProvider)
res, err := provider.PubKey(ctx)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
func TestState(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected api.ValidatorState
}{
{
name: "0",
index: 0,
expected: api.ValidatorStateActiveOngoing,
},
{
name: "1",
index: 1,
expected: api.ValidatorStateActiveOngoing,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
provider, isProvider := account.(accountmanager.ValidatingAccountStateProvider)
require.True(t, isProvider)
res := provider.State()
require.Equal(t, test.expected, res)
})
}
}
func TestSignSlotSelection(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSSignature
}{
{
name: "0",
index: 0,
expected: _sig("0xa207bbed7d1e43585e5d42e3f09a5179f21a12a33b66ac9af47132c20f9b7b7caaa162420e095664ca3318fe365776e80bd0f9ef1e3b07a2e5340d0c07152234bf18f7596c8d94d72e11961ad8455f08c4417b0019246e24f570a166f86de2c5"),
},
{
name: "1",
index: 1,
expected: _sig("0xa17b1a6decb1503b5b8eacb4ca48e2f1665e43c587088995bb7e2da0738268ad0e1f28519ed79fbb318b9b98c6e8b65805a005bca4c98656fefb9ec6753df29ad6ef92da5e3074c027caaaf738d37fd09e08d8d8f86531bd80cf4152709240f9"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
signer, isSigner := account.(accountmanager.SlotSelectionSigner)
require.True(t, isSigner)
res, err := signer.SignSlotSelection(ctx, 0)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
func TestSignRANDAOReveal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSSignature
}{
{
name: "0",
index: 0,
expected: _sig("0xb990eeca35dadda689f5561ac39bba9a27a0c41c40bd1ce584595ba2a44782a89e2630ced7b1fc348b2408d15fa6746c177c5227625e39031e6be1536387ae33e21ec3de6d1d0b46bada1b99b621f8d40eab81882400c028716b22d31999b177"),
},
{
name: "1",
index: 1,
expected: _sig("0x840c144974632f09084b91ec7be5044c3fe9dd0dc7ce1031c0c78911807e264f36cb4fee0ddccf0ca93bada5d102cfb7118e6b75b5ecc5e18648a29f21f70727f3587b8d34bc7474d926fab4fb30eae4436153c336e6eb290d3d1cdd88ee9a58"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
signer, isSigner := account.(accountmanager.RANDAORevealSigner)
require.True(t, isSigner)
res, err := signer.SignRANDAOReveal(ctx, 0)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
func TestSignBeaconBlockProposal(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSSignature
}{
{
name: "0",
index: 0,
expected: _sig("0x83d91f4fe6dd962493f212a94dc62c81358ffbdb6c3f8567d96fbe31c5ab505db1a3d37b8c3d653225621bb34918d30b069bbc02daeea9e1b2e62166014563f271fdfbe9aad37f0d155754f63eb368dd853c0bf723ad64af371a04b7778e891a"),
},
{
name: "1",
index: 1,
expected: _sig("0x9176ac06d426beb74bfb3969a619d364a5e214f0da511433b56f8dca47b93725c4ddd225c1cb81dd5a26fc321801a1d70b1c28c6cf705c010fadf9c4f139c044224680d8c60e425c9d6e6b8e8af21f12cffab38b393a9b7529527b92cf20e80a"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
signer, isSigner := account.(accountmanager.BeaconBlockSigner)
require.True(t, isSigner)
res, err := signer.SignBeaconBlockProposal(ctx,
1,
1,
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
func TestBeaconAttestationsSigner(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSSignature
}{
{
name: "0",
index: 0,
expected: _sig("0x974fb6782c196c8e523b74ea3dbdfe7b122c6a18590992f2cb789358dcea6c28498ffa6cac68629bf83175cb55bb0d3910919eab48d2934c3a81655dcc27c91a996ca4a9880ffde7864f6aba3cd7c40111e3317c817032776d077d8d6a348bb2"),
},
{
name: "1",
index: 1,
expected: _sig("0x84a1c3c50d093901ea1b027b18598e4b9cf0f848f6bd49f2807a3f6ea37c0cbf3794556705863de0ae8ea7dd2da4d4d91436e2ca96fa1eb045a22fb7704cedf8a842fa881a416eaa02444454d9f7f8040b84fc3cd3d42817f6992c3c29e16007"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
signer, isSigner := account.(accountmanager.BeaconAttestationSigner)
require.True(t, isSigner)
res, err := signer.SignBeaconAttestation(ctx,
1,
1,
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
0,
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
1,
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}
func TestAggregateAndProofsigner(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
rand.Seed(time.Now().UnixNano())
// #nosec G404
port := uint32(1024 + rand.Intn(15359))
_, _, err := daemon.New(ctx, "", 1, port, map[uint64]string{1: fmt.Sprintf("server-test01:%d", port)})
require.Nil(t, err)
s, err := dirk.New(context.Background(),
dirk.WithLogLevel(zerolog.TraceLevel),
dirk.WithMonitor(nullmetrics.New(context.Background())),
dirk.WithClientMonitor(nullmetrics.New(context.Background())),
dirk.WithEndpoints([]string{fmt.Sprintf("signer-test01:%d", port)}),
dirk.WithAccountPaths([]string{"Wallet 1", "Wallet 2"}),
dirk.WithClientCert([]byte(resources.ClientTest01Crt)),
dirk.WithClientKey([]byte(resources.ClientTest01Key)),
dirk.WithCACert([]byte(resources.CACrt)),
dirk.WithValidatorsProvider(mock.NewValidatorsProvider()),
dirk.WithSlotsPerEpochProvider(mock.NewSlotsPerEpochProvider(32)),
dirk.WithBeaconProposerDomainProvider(mock.NewBeaconProposerDomainProvider()),
dirk.WithBeaconAttesterDomainProvider(mock.NewBeaconAttesterDomainProvider()),
dirk.WithRANDAODomainProvider(mock.NewRANDAODomainProvider()),
dirk.WithSelectionProofDomainProvider(mock.NewSelectionProofDomainProvider()),
dirk.WithAggregateAndProofDomainProvider(mock.NewAggregateAndProofDomainProvider()),
dirk.WithDomainProvider(mock.NewDomainProvider()),
)
require.Nil(t, err)
tests := []struct {
name string
index spec.ValidatorIndex
expected spec.BLSSignature
}{
{
name: "0",
index: 0,
expected: _sig("0xafd6b2ed80506b63e964820aba6523735d6156d4f2e3d53c88075f22b2a48447d6f083952e7d6c315a96dfde35b6959616591ac9d8b2d7f1c423b7f257e6f5eb4406b97b55270a9101cca809e76c7759aaf1235b028aa23da118697dfb9c34c5"),
},
{
name: "1",
index: 1,
expected: _sig("0xa006085be92bb0ee2cf2bba3da0d5e21630efb7e32a5e4fa7ca742ea0bd273f76a6336ef3a9817200bddbc0b4a6dbecf1303d9804806fa38ee05b7d5cfbba6e851c37ca587d66df935c296e6210db69f8b4c96f5e590cb83c7f82c06aee1d4c3"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
accounts, err := s.AccountsByIndex(ctx, []spec.ValidatorIndex{test.index})
require.NoError(t, err)
require.Equal(t, 1, len(accounts))
account := accounts[0]
signer, isSigner := account.(accountmanager.AggregateAndProofSigner)
require.True(t, isSigner)
res, err := signer.SignAggregateAndProof(ctx,
1,
_root("0x0000000000000000000000000000000000000000000000000000000000000000"),
)
require.NoError(t, err)
require.Equal(t, test.expected, res)
})
}
}

View File

@ -17,8 +17,8 @@ package accountmanager
import (
"context"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Service is the generic accountmanager service.
@ -26,121 +26,23 @@ type Service interface{}
// ValidatingAccountsProvider provides methods for validating accounts.
type ValidatingAccountsProvider interface {
// Accounts provides information about all accounts that are configured to validate through this instance.
Accounts(ctx context.Context) ([]ValidatingAccount, error)
// ValidatingAccountsForEpoch obtains the validating accounts for a given epoch.
ValidatingAccountsForEpoch(ctx context.Context, epoch spec.Epoch) (map[spec.ValidatorIndex]e2wtypes.Account, error)
// AccountsByIndex provides information about the specific accounts that are configured to validate through this instance.
AccountsByIndex(ctx context.Context, indices []spec.ValidatorIndex) ([]ValidatingAccount, error)
// AccountsByPubKey provides information about the specific accounts that are configured to validate through this instance.
AccountsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) ([]ValidatingAccount, error)
// ValidatingAccountsForEpochByIndex obtains the specified validating accounts for a given epoch.
ValidatingAccountsForEpochByIndex(ctx context.Context,
epoch spec.Epoch,
indices []spec.ValidatorIndex,
) (
map[spec.ValidatorIndex]e2wtypes.Account,
error,
)
}
// ValidatingAccountPubKeyProvider provides methods for obtaining public keys from accounts.
type ValidatingAccountPubKeyProvider interface {
// PubKey() provides the public key for this account.
PubKey(ctx context.Context) (spec.BLSPubKey, error)
}
// ValidatingAccountIndexProvider provides methods for obtaining indices from accounts.
type ValidatingAccountIndexProvider interface {
// Index() provides the validator index for this account.
// Returns an error if there is no index for this validator.
Index(ctx context.Context) (spec.ValidatorIndex, error)
}
// ValidatingAccountStateProvider provides methods for obtaining state from accounts.
type ValidatingAccountStateProvider interface {
// State() provides the validator state for this account.
State() api.ValidatorState
}
// ValidatingAccount is a composite interface for common validating account features.
type ValidatingAccount interface {
ValidatingAccountPubKeyProvider
ValidatingAccountIndexProvider
ValidatingAccountStateProvider
}
// AccountsFetcher fetches accounts from the remote source.
type AccountsFetcher interface {
// RefreshAccounts refreshes the list of relevant accounts known by the account manager.
RefreshAccounts(ctx context.Context) error
}
// AccountsUpdater manages updates to accounts in line with internal and external changes.
type AccountsUpdater interface {
// UpdateAccountsState updates account state with the latest information from the beacon chain.
UpdateAccountsState(ctx context.Context) error
}
// IsAggregatorProvider provides methods for obtaining aggregation status from accounts.
type IsAggregatorProvider interface {
}
// RANDAORevealSigner provides methods to sign RANDAO reveals.
type RANDAORevealSigner interface {
// SignRANDAOReveal returns a RANDAO signature.
// This signs an epoch with the "RANDAO" domain.
SignRANDAOReveal(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error)
}
// SlotSelectionSigner provides methods to sign slot selections.
type SlotSelectionSigner interface {
// SignSlotSelection returns a slot selection signature.
// This signs a slot with the "selection proof" domain.
SignSlotSelection(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error)
}
// BeaconBlockSigner provides methods to sign beacon blocks.
type BeaconBlockSigner interface {
// SignBeaconBlockProposal signs a beacon block proposal.
SignBeaconBlockProposal(ctx context.Context,
slot spec.Slot,
proposerIndex spec.ValidatorIndex,
parentRoot spec.Root,
stateRoot spec.Root,
bodyRoot spec.Root) (spec.BLSSignature, error)
}
// BeaconAttestationSigner provides methods to sign beacon attestations.
type BeaconAttestationSigner interface {
// SignBeaconAttestation signs a beacon attestation.
SignBeaconAttestation(ctx context.Context,
slot spec.Slot,
committeeIndex spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) (spec.BLSSignature, error)
}
// BeaconAttestationsSigner provides methods to sign multiple beacon attestations.
type BeaconAttestationsSigner interface {
// SignBeaconAttestation signs multiple beacon attestations.
SignBeaconAttestations(ctx context.Context,
slot spec.Slot,
accounts []ValidatingAccount,
committeeIndices []spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) ([]spec.BLSSignature, error)
}
// AggregateAndProofSigner provides methods to sign aggregate and proofs.
type AggregateAndProofSigner interface {
// SignAggregateAndProof signs an aggregate attestation for given slot and root.
SignAggregateAndProof(ctx context.Context, slot spec.Slot, root spec.Root) (spec.BLSSignature, error)
}
// Signer is a composite interface for all signer operations.
type Signer interface {
RANDAORevealSigner
SlotSelectionSigner
BeaconBlockSigner
BeaconAttestationSigner
AggregateAndProofSigner
// Refresher refreshes account information from the remote source.
type Refresher interface {
// Refresh refreshes the accounts from the remote source, and account validator state from
// the validators provider.
// This is a relatively expensive operation, so should not be run in the validating path.
Refresh(ctx context.Context)
}

View File

@ -16,24 +16,21 @@ package wallet
import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/validatorsmanager"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.AccountManagerMonitor
locations []string
accountPaths []string
passphrases [][]byte
validatorsProvider eth2client.ValidatorsProvider
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
beaconProposerDomainProvider eth2client.BeaconProposerDomainProvider
beaconAttesterDomainProvider eth2client.BeaconAttesterDomainProvider
randaoDomainProvider eth2client.RANDAODomainProvider
selectionProofDomainProvider eth2client.SelectionProofDomainProvider
aggregateAndProofDomainProvider eth2client.AggregateAndProofDomainProvider
domainProvider eth2client.DomainProvider
logLevel zerolog.Level
monitor metrics.AccountManagerMonitor
locations []string
accountPaths []string
passphrases [][]byte
validatorsManager validatorsmanager.Service
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
domainProvider eth2client.DomainProvider
farFutureEpochProvider eth2client.FarFutureEpochProvider
}
// Parameter is the interface for service parameters.
@ -82,10 +79,10 @@ func WithPassphrases(passphrases [][]byte) Parameter {
})
}
// WithValidatorsProvider sets the validator status provider.
func WithValidatorsProvider(provider eth2client.ValidatorsProvider) Parameter {
// WithValidatorsManager sets the validator manager.
func WithValidatorsManager(manager validatorsmanager.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.validatorsProvider = provider
p.validatorsManager = manager
})
}
@ -96,38 +93,10 @@ func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) Parame
})
}
// WithBeaconProposerDomainProvider sets the beacon proposer domain provider.
func WithBeaconProposerDomainProvider(provider eth2client.BeaconProposerDomainProvider) Parameter {
// WithFarFutureEpochProvider sets the far future epoch provider.
func WithFarFutureEpochProvider(provider eth2client.FarFutureEpochProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconProposerDomainProvider = provider
})
}
// WithBeaconAttesterDomainProvider sets the beacon attester domain provider.
func WithBeaconAttesterDomainProvider(provider eth2client.BeaconAttesterDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconAttesterDomainProvider = provider
})
}
// WithRANDAODomainProvider sets the RANDAO domain provider.
func WithRANDAODomainProvider(provider eth2client.RANDAODomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.randaoDomainProvider = provider
})
}
// WithSelectionProofDomainProvider sets the RANDAO domain provider.
func WithSelectionProofDomainProvider(provider eth2client.SelectionProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.selectionProofDomainProvider = provider
})
}
// WithAggregateAndProofDomainProvider sets the aggregate and proof domain provider.
func WithAggregateAndProofDomainProvider(provider eth2client.AggregateAndProofDomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.aggregateAndProofDomainProvider = provider
p.farFutureEpochProvider = provider
})
}
@ -158,26 +127,14 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if len(parameters.passphrases) == 0 {
return nil, errors.New("no passphrases specified")
}
if parameters.validatorsProvider == nil {
return nil, errors.New("no validators provider specified")
if parameters.validatorsManager == nil {
return nil, errors.New("no validators manager specified")
}
if parameters.slotsPerEpochProvider == nil {
return nil, errors.New("no slots per epoch provider specified")
}
if parameters.beaconProposerDomainProvider == nil {
return nil, errors.New("no beacon proposer domain provider specified")
}
if parameters.beaconAttesterDomainProvider == nil {
return nil, errors.New("no beacon attester domain provider specified")
}
if parameters.randaoDomainProvider == nil {
return nil, errors.New("no RANDAO domain provider specified")
}
if parameters.selectionProofDomainProvider == nil {
return nil, errors.New("no selection proof domain provider specified")
}
if parameters.aggregateAndProofDomainProvider == nil {
return nil, errors.New("no aggregate and proof domain provider specified")
if parameters.farFutureEpochProvider == nil {
return nil, errors.New("no far future epoch provider specified")
}
if parameters.domainProvider == nil {
return nil, errors.New("no domain provider specified")

View File

@ -23,8 +23,8 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "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/validatorsmanager"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
@ -36,20 +36,16 @@ import (
// Service is the manager for wallet accounts.
type Service struct {
mutex sync.RWMutex
monitor metrics.AccountManagerMonitor
stores []e2wtypes.Store
accountPaths []string
passphrases [][]byte
accounts map[spec.BLSPubKey]*ValidatingAccount
validatorsProvider eth2client.ValidatorsProvider
slotsPerEpoch spec.Slot
beaconProposerDomain spec.DomainType
beaconAttesterDomain spec.DomainType
randaoDomain spec.DomainType
selectionProofDomain spec.DomainType
aggregateAndProofDomain spec.DomainType
domainProvider eth2client.DomainProvider
mutex sync.RWMutex
monitor metrics.AccountManagerMonitor
stores []e2wtypes.Store
accountPaths []string
passphrases [][]byte
accounts map[spec.BLSPubKey]e2wtypes.Account
validatorsManager validatorsmanager.Service
slotsPerEpoch spec.Slot
domainProvider eth2client.DomainProvider
farFutureEpoch spec.Epoch
}
// module-wide log.
@ -85,80 +81,46 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
}
beaconAttesterDomainType, err := parameters.beaconAttesterDomainProvider.BeaconAttesterDomain(ctx)
farFutureEpoch, err := parameters.farFutureEpochProvider.FarFutureEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon attester domain")
}
beaconProposerDomainType, err := parameters.beaconProposerDomainProvider.BeaconProposerDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon proposer domain")
}
randaoDomainType, err := parameters.randaoDomainProvider.RANDAODomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain RANDAO domain")
}
selectionProofDomainType, err := parameters.selectionProofDomainProvider.SelectionProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain selection proof domain")
}
aggregateAndProofDomainType, err := parameters.aggregateAndProofDomainProvider.AggregateAndProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain aggregate and proof domain")
}
s := &Service{
monitor: parameters.monitor,
stores: stores,
accountPaths: parameters.accountPaths,
passphrases: parameters.passphrases,
validatorsProvider: parameters.validatorsProvider,
slotsPerEpoch: spec.Slot(slotsPerEpoch),
beaconAttesterDomain: beaconAttesterDomainType,
beaconProposerDomain: beaconProposerDomainType,
randaoDomain: randaoDomainType,
selectionProofDomain: selectionProofDomainType,
aggregateAndProofDomain: aggregateAndProofDomainType,
domainProvider: parameters.domainProvider,
return nil, errors.Wrap(err, "failed to obtain far future epoch")
}
if err := s.RefreshAccounts(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch validating keys")
s := &Service{
monitor: parameters.monitor,
stores: stores,
accountPaths: parameters.accountPaths,
passphrases: parameters.passphrases,
validatorsManager: parameters.validatorsManager,
slotsPerEpoch: spec.Slot(slotsPerEpoch),
domainProvider: parameters.domainProvider,
farFutureEpoch: farFutureEpoch,
}
if err := s.refreshAccounts(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch accounts")
}
if err := s.refreshValidators(ctx); err != nil {
return nil, errors.Wrap(err, "failed to fetch validator states")
}
return s, nil
}
// UpdateAccountsState updates account state with the latest information from the beacon chain.
// This should be run at the beginning of each epoch to ensure that any newly-activated accounts are registered.
func (s *Service) UpdateAccountsState(ctx context.Context) error {
validatorIndices := make([]spec.ValidatorIndex, 0, len(s.accounts))
for _, account := range s.accounts {
if !account.state.IsAttesting() {
index, err := account.Index(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain account index")
}
validatorIndices = append(validatorIndices, index)
}
// Refresh refreshes the accounts from local store, and account validator state from
// the validators provider.
// This is a relatively expensive operation, so should not be run in the validating path.
func (s *Service) Refresh(ctx context.Context) {
if err := s.refreshAccounts(ctx); err != nil {
log.Error().Err(err).Msg("Failed to refresh accounts")
}
if len(validatorIndices) == 0 {
// Nothing to do.
log.Trace().Msg("No unactivated keys")
return nil
if err := s.refreshValidators(ctx); err != nil {
log.Error().Err(err).Msg("Failed to refresh validators")
}
validators, err := s.validatorsProvider.Validators(ctx, "head", validatorIndices)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
s.mutex.Lock()
s.updateAccountStates(ctx, s.accounts, validators)
s.mutex.Unlock()
return nil
}
// RefreshAccounts refreshes the entire list of validating keys.
func (s *Service) RefreshAccounts(ctx context.Context) error {
// refreshAccounts refreshes the accounts from local store.
func (s *Service) refreshAccounts(ctx context.Context) error {
// Find the relevant wallets.
wallets := make(map[string]e2wtypes.Wallet)
pathsByWallet := make(map[string][]string)
@ -188,7 +150,7 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
verificationRegexes := accountPathsToVerificationRegexes(s.accountPaths)
// Fetch accounts for each wallet.
accounts := make(map[spec.BLSPubKey]*ValidatingAccount)
accounts := make(map[spec.BLSPubKey]e2wtypes.Account)
for _, wallet := range wallets {
// if _, isProvider := wallet.(e2wtypes.WalletAccountsByPathProvider); isProvider {
// fmt.Printf("TODO: fetch accounts by path")
@ -196,28 +158,7 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
s.fetchAccountsForWallet(ctx, wallet, accounts, verificationRegexes)
//}
}
// Update indices for accounts.
pubKeys := make([]spec.BLSPubKey, 0, len(accounts))
for _, account := range accounts {
pubKey, err := account.PubKey(ctx)
if err != nil {
return errors.Wrap(err, "failed to obtain public key")
}
pubKeys = append(pubKeys, pubKey)
}
validators, err := s.validatorsProvider.ValidatorsByPubKey(ctx, "head", pubKeys)
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
log.Trace().Int("keys", len(accounts)).Msg("Keys obtained")
if len(pubKeys) == 0 {
log.Warn().Msg("No accounts obtained")
return nil
}
s.updateAccountStates(ctx, accounts, validators)
log.Trace().Int("accounts", len(accounts)).Msg("Obtained accounts")
s.mutex.Lock()
s.accounts = accounts
@ -226,71 +167,61 @@ func (s *Service) RefreshAccounts(ctx context.Context) error {
return nil
}
// Accounts returns all attesting accounts.
func (s *Service) Accounts(ctx context.Context) ([]accountmanager.ValidatingAccount, error) {
s.mutex.RLock()
defer s.mutex.RUnlock()
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for _, account := range s.accounts {
if account.state.IsAttesting() {
accounts = append(accounts, account)
}
// refreshValidators refreshes the validator information for our known accounts.
func (s *Service) refreshValidators(ctx context.Context) error {
accountPubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
accountPubKeys = append(accountPubKeys, pubKey)
}
return accounts, nil
if err := s.validatorsManager.RefreshValidatorsFromBeaconNode(ctx, accountPubKeys); err != nil {
return errors.Wrap(err, "failed to refresh validators")
}
return nil
}
// AccountsByIndex returns attesting accounts.
func (s *Service) AccountsByIndex(ctx context.Context, validatorIndices []spec.ValidatorIndex) ([]accountmanager.ValidatingAccount, error) {
indexMap := make(map[spec.ValidatorIndex]bool)
for _, index := range validatorIndices {
indexMap[index] = true
// ValidatingAccountsForEpoch obtains the validating accounts for a given epoch.
func (s *Service) ValidatingAccountsForEpoch(ctx context.Context, epoch spec.Epoch) (map[spec.ValidatorIndex]e2wtypes.Account, error) {
validatingAccounts := make(map[spec.ValidatorIndex]e2wtypes.Account)
pubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
pubKeys = append(pubKeys, pubKey)
}
s.mutex.RLock()
defer s.mutex.RUnlock()
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for _, account := range s.accounts {
if !account.state.IsAttesting() {
continue
}
index, err := account.Index(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain account index")
continue
}
if _, exists := indexMap[index]; exists {
accounts = append(accounts, account)
validators := s.validatorsManager.ValidatorsByPubKey(ctx, pubKeys)
for index, validator := range validators {
state := api.ValidatorToState(validator, epoch, s.farFutureEpoch)
if state == api.ValidatorStateActiveOngoing || state == api.ValidatorStateActiveExiting {
validatingAccounts[index] = s.accounts[validator.PublicKey]
}
}
return accounts, nil
return validatingAccounts, nil
}
// AccountsByPubKey returns validating accounts.
func (s *Service) AccountsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) ([]accountmanager.ValidatingAccount, error) {
pubKeyMap := make(map[spec.BLSPubKey]bool)
for _, pubKey := range pubKeys {
pubKeyMap[pubKey] = true
// ValidatingAccountsForEpochByIndex obtains the specified validating accounts for a given epoch.
func (s *Service) ValidatingAccountsForEpochByIndex(ctx context.Context, epoch spec.Epoch, indices []spec.ValidatorIndex) (map[spec.ValidatorIndex]e2wtypes.Account, error) {
validatingAccounts := make(map[spec.ValidatorIndex]e2wtypes.Account)
pubKeys := make([]spec.BLSPubKey, 0, len(s.accounts))
for pubKey := range s.accounts {
pubKeys = append(pubKeys, pubKey)
}
s.mutex.RLock()
defer s.mutex.RUnlock()
accounts := make([]accountmanager.ValidatingAccount, 0, len(s.accounts))
for pubKey, account := range s.accounts {
if !account.state.IsAttesting() {
indexPresenceMap := make(map[spec.ValidatorIndex]bool)
for _, index := range indices {
indexPresenceMap[index] = true
}
validators := s.validatorsManager.ValidatorsByPubKey(ctx, pubKeys)
for index, validator := range validators {
if _, present := indexPresenceMap[index]; !present {
continue
}
if _, exists := pubKeyMap[pubKey]; exists {
accounts = append(accounts, account)
state := api.ValidatorToState(validator, epoch, s.farFutureEpoch)
if state == api.ValidatorStateActiveOngoing || state == api.ValidatorStateActiveExiting {
validatingAccounts[index] = s.accounts[validator.PublicKey]
}
}
return accounts, nil
return validatingAccounts, nil
}
// accountPathsToVerificationRegexes turns account paths in to regexes to allow verification.
@ -323,37 +254,7 @@ func accountPathsToVerificationRegexes(paths []string) []*regexp.Regexp {
return regexes
}
func (s *Service) updateAccountStates(ctx context.Context, accounts map[spec.BLSPubKey]*ValidatingAccount, validators map[spec.ValidatorIndex]*api.Validator) {
validatorsByPubKey := make(map[spec.BLSPubKey]*api.Validator, len(validators))
for _, validator := range validators {
validatorsByPubKey[validator.Validator.PublicKey] = validator
}
validatorStateCounts := make(map[string]uint64)
for pubKey, account := range accounts {
validator, exists := validatorsByPubKey[pubKey]
if exists {
account.index = validator.Index
account.state = validator.Status
}
validatorStateCounts[strings.ToLower(account.state.String())]++
}
for state, count := range validatorStateCounts {
s.monitor.Accounts(state, count)
}
if e := log.Trace(); e.Enabled() {
for _, account := range accounts {
log.Trace().
Str("name", account.account.Name()).
Str("public_key", fmt.Sprintf("%x", account.account.PublicKey().Marshal())).
Str("state", account.state.String()).
Msg("Validating account")
}
}
}
func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wallet, accounts map[spec.BLSPubKey]*ValidatingAccount, verificationRegexes []*regexp.Regexp) {
func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wallet, accounts map[spec.BLSPubKey]e2wtypes.Account, verificationRegexes []*regexp.Regexp) {
for account := range wallet.Accounts(ctx) {
// Ensure the name matches one of our account paths.
name := fmt.Sprintf("%s/%s", wallet.Name(), account.Name())
@ -392,10 +293,6 @@ func (s *Service) fetchAccountsForWallet(ctx context.Context, wallet e2wtypes.Wa
}
// Set up account as unknown to beacon chain.
accounts[bytesutil.ToBytes48(pubKey)] = &ValidatingAccount{
account: account,
accountManager: s,
domainProvider: s.domainProvider,
}
accounts[bytesutil.ToBytes48(pubKey)] = account
}
}

View File

@ -1,235 +0,0 @@
// 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 wallet
import (
"context"
"encoding/binary"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/pkg/errors"
e2types "github.com/wealdtech/go-eth2-types/v2"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// ValidatingAccount is a wrapper around the wallet account that implements ValidatingAccount.
type ValidatingAccount struct {
account e2wtypes.Account
index spec.ValidatorIndex
state api.ValidatorState
accountManager *Service
domainProvider eth2client.DomainProvider
}
// PubKey returns the public key of the validating account.
func (d *ValidatingAccount) PubKey(ctx context.Context) (spec.BLSPubKey, error) {
var pubKey spec.BLSPubKey
if provider, isProvider := d.account.(e2wtypes.AccountCompositePublicKeyProvider); isProvider {
copy(pubKey[:], provider.CompositePublicKey().Marshal())
} else {
copy(pubKey[:], d.account.PublicKey().Marshal())
}
return pubKey, nil
}
// Index returns the index of the validating account.
func (d *ValidatingAccount) Index(ctx context.Context) (spec.ValidatorIndex, error) {
return d.index, nil
}
// State returns the state of the validating account.
func (d *ValidatingAccount) State() api.ValidatorState {
return d.state
}
// SignSlotSelection returns a slot selection signature.
// This signs a slot with the "selection proof" domain.
func (d *ValidatingAccount) SignSlotSelection(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error) {
var messageRoot spec.Root
binary.LittleEndian.PutUint64(messageRoot[:], uint64(slot))
// Calculate the domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.selectionProofDomain,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain domain for selection proof")
}
return d.sign(ctx, messageRoot, domain)
}
// SignRANDAOReveal returns a RANDAO reveal signature.
// This signs an epoch with the "RANDAO reveal" domain.
func (d *ValidatingAccount) SignRANDAOReveal(ctx context.Context, slot spec.Slot) (spec.BLSSignature, error) {
var messageRoot spec.Root
epoch := spec.Epoch(slot / d.accountManager.slotsPerEpoch)
binary.LittleEndian.PutUint64(messageRoot[:], uint64(epoch))
// Obtain the RANDAO reveal signature domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.randaoDomain,
epoch)
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for RANDAO reveal")
}
var epochBytes spec.Root
binary.LittleEndian.PutUint64(epochBytes[:], uint64(epoch))
return d.sign(ctx, epochBytes, domain)
}
// SignBeaconBlockProposal signs a beacon block proposal item.
func (d *ValidatingAccount) SignBeaconBlockProposal(ctx context.Context,
slot spec.Slot,
proposerIndex spec.ValidatorIndex,
parentRoot spec.Root,
stateRoot spec.Root,
bodyRoot spec.Root) (spec.BLSSignature, error) {
message := &spec.BeaconBlockHeader{
Slot: slot,
ProposerIndex: proposerIndex,
ParentRoot: parentRoot,
StateRoot: stateRoot,
BodyRoot: bodyRoot,
}
messageRoot, err := message.HashTreeRoot()
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain hash tree root of block")
}
// Fetch the domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.beaconProposerDomain,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon proposal")
}
return d.sign(ctx, messageRoot, domain)
}
// SignBeaconAttestations signs multiple beacon attestations.
func (d *ValidatingAccount) SignBeaconAttestations(ctx context.Context,
slot spec.Slot,
accounts []accountmanager.ValidatingAccount,
committeeIndices []spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) ([]spec.BLSSignature, error) {
signatures := make([]spec.BLSSignature, len(accounts))
var err error
for i, account := range accounts {
signatures[i], err = account.(*ValidatingAccount).SignBeaconAttestation(ctx,
slot,
committeeIndices[i],
blockRoot,
sourceEpoch,
sourceRoot,
targetEpoch,
targetRoot)
if err != nil {
return nil, err
}
}
return signatures, nil
}
// SignBeaconAttestation signs a beacon attestation item.
func (d *ValidatingAccount) SignBeaconAttestation(ctx context.Context,
slot spec.Slot,
committeeIndex spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root) (spec.BLSSignature, error) {
message := &spec.AttestationData{
Slot: slot,
Index: committeeIndex,
BeaconBlockRoot: blockRoot,
Source: &spec.Checkpoint{
Epoch: sourceEpoch,
Root: sourceRoot,
},
Target: &spec.Checkpoint{
Epoch: targetEpoch,
Root: targetRoot,
},
}
messageRoot, err := message.HashTreeRoot()
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain hash tree root of attestation data")
}
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.beaconAttesterDomain,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon attestation")
}
return d.sign(ctx, messageRoot, domain)
}
// SignAggregateAndProof signs an aggregate and proof item.
func (d *ValidatingAccount) SignAggregateAndProof(ctx context.Context, slot spec.Slot, aggregateAndProofRoot spec.Root) (spec.BLSSignature, error) {
// Fetch the signature domain.
domain, err := d.domainProvider.Domain(ctx,
d.accountManager.aggregateAndProofDomain,
spec.Epoch(slot/d.accountManager.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon aggregate and proof")
}
return d.sign(ctx, aggregateAndProofRoot, domain)
}
func (d *ValidatingAccount) sign(ctx context.Context, message spec.Root, domain spec.Domain) (spec.BLSSignature, error) {
var sig e2types.Signature
var err error
if protectingSigner, isProtectingSigner := d.account.(e2wtypes.AccountProtectingSigner); isProtectingSigner {
sig, err = protectingSigner.SignGeneric(ctx, message[:], domain[:])
} else {
// Create the root manually.
container := &spec.SigningData{
ObjectRoot: message,
Domain: domain,
}
var signingRoot spec.Root
signingRoot, err = container.HashTreeRoot()
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to generate hash tree root for signing container")
}
sig, err = d.account.(e2wtypes.AccountSigner).Sign(ctx, signingRoot[:])
}
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -17,7 +17,7 @@ import (
"context"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Duty contains information about an attestation aggregation duty.
@ -35,7 +35,7 @@ type Duty struct {
Attestation *spec.Attestation
// Account is the account carrying out the aggregation.
// Required for Prysm non-spec GRPC method.
Account accountmanager.ValidatingAccount
Account e2wtypes.Account
}
// IsAggregatorProvider provides information about if a validator is an aggregator.

View File

@ -17,6 +17,7 @@ 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"
@ -25,11 +26,14 @@ import (
type parameters struct {
logLevel zerolog.Level
monitor metrics.AttestationAggregationMonitor
slotsPerEpochProvider eth2client.SlotsPerEpochProvider
targetAggregatorsPerCommitteeProvider eth2client.TargetAggregatorsPerCommitteeProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
aggregateAttestationProvider eth2client.AggregateAttestationProvider
prysmAggregateAttestationProvider eth2client.PrysmAggregateAttestationProvider
aggregateAttestationsSubmitter submitter.AggregateAttestationsSubmitter
slotSelectionSigner signer.SlotSelectionSigner
aggregateAndProofSigner signer.AggregateAndProofSigner
}
// Parameter is the interface for service parameters.
@ -50,6 +54,13 @@ func WithLogLevel(logLevel zerolog.Level) Parameter {
})
}
// WithSlotsPerEpochProvider sets the slots per epoch provider.
func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.slotsPerEpochProvider = provider
})
}
// WithTargetAggregatorsPerCommitteeProvider sets the target aggregators per attestation provider.
func WithTargetAggregatorsPerCommitteeProvider(provider eth2client.TargetAggregatorsPerCommitteeProvider) Parameter {
return parameterFunc(func(p *parameters) {
@ -92,6 +103,20 @@ func WithAggregateAttestationsSubmitter(submitter submitter.AggregateAttestation
})
}
// WithSlotSelectionSigner sets the slot selection submitter.
func WithSlotSelectionSigner(signer signer.SlotSelectionSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.slotSelectionSigner = signer
})
}
// WithAggregateAndProofSigner sets the aggregate and proof submitter.
func WithAggregateAndProofSigner(signer signer.AggregateAndProofSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.aggregateAndProofSigner = signer
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -106,6 +131,9 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.targetAggregatorsPerCommitteeProvider == nil {
return nil, errors.New("no target aggregators per committee provider specified")
}
if parameters.slotsPerEpochProvider == nil {
return nil, errors.New("no slots per epoch provider specified")
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
@ -118,6 +146,12 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.aggregateAttestationsSubmitter == nil {
return nil, errors.New("no aggregate attestations submitter specified")
}
if parameters.slotSelectionSigner == nil {
return nil, errors.New("no slot selection signer specified")
}
if parameters.aggregateAndProofSigner == nil {
return nil, errors.New("no aggregate and proof signer specified")
}
return &parameters, nil
}

View File

@ -25,20 +25,25 @@ import (
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/attestationaggregator"
"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"
zerologger "github.com/rs/zerolog/log"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Service is an attestation aggregator.
type Service struct {
monitor metrics.AttestationAggregationMonitor
targetAggregatorsPerCommittee uint64
slotsPerEpoch uint64
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
aggregateAttestationProvider eth2client.AggregateAttestationProvider
prysmAggregateAttestationProvider eth2client.PrysmAggregateAttestationProvider
aggregateAttestationsSubmitter submitter.AggregateAttestationsSubmitter
slotSelectionSigner signer.SlotSelectionSigner
aggregateAndProofSigner signer.AggregateAndProofSigner
}
// module-wide log.
@ -61,14 +66,21 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
if err != nil {
return nil, errors.Wrap(err, "failed to obtain target aggregators per committee")
}
slotsPerEpoch, err := parameters.slotsPerEpochProvider.SlotsPerEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
}
s := &Service{
monitor: parameters.monitor,
targetAggregatorsPerCommittee: targetAggregatorsPerCommittee,
slotsPerEpoch: slotsPerEpoch,
validatingAccountsProvider: parameters.validatingAccountsProvider,
aggregateAttestationProvider: parameters.aggregateAttestationProvider,
prysmAggregateAttestationProvider: parameters.prysmAggregateAttestationProvider,
aggregateAttestationsSubmitter: parameters.aggregateAttestationsSubmitter,
slotSelectionSigner: parameters.slotSelectionSigner,
aggregateAndProofSigner: parameters.aggregateAndProofSigner,
}
return s, nil
@ -94,9 +106,10 @@ func (s *Service) Aggregate(ctx context.Context, data interface{}) {
aggregateAttestation, err = s.aggregateAttestationProvider.AggregateAttestation(ctx, duty.Slot, duty.AttestationDataRoot)
} else {
var validatorPubKey spec.BLSPubKey
validatorPubKey, err = duty.Account.PubKey(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain validator public key")
if provider, isProvider := duty.Account.(e2wtypes.AccountCompositePublicKeyProvider); isProvider {
copy(validatorPubKey[:], provider.CompositePublicKey().Marshal())
} else {
copy(validatorPubKey[:], duty.Account.PublicKey().Marshal())
}
aggregateAttestation, err = s.prysmAggregateAttestationProvider.PrysmAggregateAttestation(ctx, duty.Attestation, validatorPubKey, duty.SlotSignature)
}
@ -112,7 +125,8 @@ func (s *Service) Aggregate(ctx context.Context, data interface{}) {
}
// Fetch the validating account.
accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, []spec.ValidatorIndex{duty.ValidatorIndex})
epoch := spec.Epoch(uint64(aggregateAttestation.Data.Slot) / s.slotsPerEpoch)
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx, epoch, []spec.ValidatorIndex{duty.ValidatorIndex})
if err != nil {
log.Error().Err(err).Msg("Failed to obtain proposing validator account")
s.monitor.AttestationAggregationCompleted(started, "failed")
@ -123,27 +137,20 @@ func (s *Service) Aggregate(ctx context.Context, data interface{}) {
s.monitor.AttestationAggregationCompleted(started, "failed")
return
}
account := accounts[0]
account := accounts[duty.ValidatorIndex]
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained aggregating account")
// Sign the aggregate attestation.
signer, isSigner := account.(accountmanager.AggregateAndProofSigner)
if !isSigner {
log.Error().Msg("Account is not an aggregate and proof signer")
s.monitor.AttestationAggregationCompleted(started, "failed")
return
}
aggregateAndProof := &spec.AggregateAndProof{
AggregatorIndex: duty.ValidatorIndex,
Aggregate: aggregateAttestation,
SelectionProof: duty.SlotSignature,
}
aggregateAndProofRoot, err := aggregateAndProof.HashTreeRoot()
if err != nil {
log.Error().Err(err).Msg("Failed to generate hash tree root of aggregate and proof")
}
sig, err := signer.SignAggregateAndProof(ctx, duty.Slot, spec.Root(aggregateAndProofRoot))
sig, err := s.aggregateAndProofSigner.SignAggregateAndProof(ctx, account, duty.Slot, spec.Root(aggregateAndProofRoot))
if err != nil {
log.Error().Err(err).Msg("Failed to sign aggregate and proof")
s.monitor.AttestationAggregationCompleted(started, "failed")
@ -185,22 +192,18 @@ func (s *Service) IsAggregator(ctx context.Context,
}
// Fetch the validator from the account manager.
accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, []spec.ValidatorIndex{validatorIndex})
epoch := spec.Epoch(uint64(slot) / s.slotsPerEpoch)
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx, epoch, []spec.ValidatorIndex{validatorIndex})
if err != nil {
return false, spec.BLSSignature{}, errors.Wrap(err, "failed to obtain validator")
}
if len(accounts) == 0 {
return false, spec.BLSSignature{}, errors.New("validator unknown")
}
account := accounts[0]
slotSelectionSigner, isSlotSelectionSigner := account.(accountmanager.SlotSelectionSigner)
if !isSlotSelectionSigner {
return false, spec.BLSSignature{}, errors.New("validating account is not a slot selection signer")
}
account := accounts[validatorIndex]
// Sign the slot.
signature, err := slotSelectionSigner.SignSlotSelection(ctx, slot)
signature, err := s.slotSelectionSigner.SignSlotSelection(ctx, account, slot)
if err != nil {
return false, spec.BLSSignature{}, errors.Wrap(err, "failed to sign the slot")
}

View File

@ -17,6 +17,7 @@ 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"
@ -30,6 +31,7 @@ type parameters struct {
attestationDataProvider eth2client.AttestationDataProvider
attestationSubmitter submitter.AttestationSubmitter
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
beaconAttestationsSigner signer.BeaconAttestationsSigner
}
// Parameter is the interface for service parameters.
@ -92,6 +94,13 @@ func WithValidatingAccountsProvider(provider accountmanager.ValidatingAccountsPr
})
}
// WithBeaconAttestationsSigner sets the beacon attestations signer.
func WithBeaconAttestationsSigner(signer signer.BeaconAttestationsSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconAttestationsSigner = signer
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -121,6 +130,9 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.validatingAccountsProvider == nil {
return nil, errors.New("no validating accounts provider specified")
}
if parameters.beaconAttestationsSigner == nil {
return nil, errors.New("no beacon attestations signer specified")
}
return &parameters, nil
}

View File

@ -25,12 +25,14 @@ import (
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/attester"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/submitter"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Service is a beacon block attester.
@ -41,6 +43,7 @@ type Service struct {
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
attestationDataProvider eth2client.AttestationDataProvider
attestationSubmitter submitter.AttestationSubmitter
beaconAttestationsSigner signer.BeaconAttestationsSigner
}
// module-wide log.
@ -71,6 +74,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
validatingAccountsProvider: parameters.validatingAccountsProvider,
attestationDataProvider: parameters.attestationDataProvider,
attestationSubmitter: parameters.attestationSubmitter,
beaconAttestationsSigner: parameters.beaconAttestationsSigner,
}
return s, nil
@ -114,35 +118,40 @@ func (s *Service) Attest(ctx context.Context, data interface{}) ([]*spec.Attesta
}
// Fetch the validating accounts.
accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, duty.ValidatorIndices())
validatingAccounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx, spec.Epoch(uint64(duty.Slot())/s.slotsPerEpoch), duty.ValidatorIndices())
if err != nil {
s.monitor.AttestationCompleted(started, "failed")
return nil, errors.New("failed to obtain attesting validator accounts")
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained validating accounts")
// Break the map in to two arrays.
accountValidatorIndices := make([]spec.ValidatorIndex, 0, len(validatingAccounts))
accountsArray := make([]e2wtypes.Account, 0, len(validatingAccounts))
for index, account := range validatingAccounts {
accountValidatorIndices = append(accountValidatorIndices, index)
accountsArray = append(accountsArray, account)
}
// Set the per-validator information.
validatorIndexToArrayIndexMap := make(map[spec.ValidatorIndex]int)
for i := range duty.ValidatorIndices() {
validatorIndexToArrayIndexMap[duty.ValidatorIndices()[i]] = i
}
committeeIndices := make([]spec.CommitteeIndex, len(accounts))
validatorCommitteeIndices := make([]spec.ValidatorIndex, len(accounts))
committeeSizes := make([]uint64, len(accounts))
for i := range accounts {
validatorIndex, err := accounts[i].Index(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain validator index")
}
committeeIndices[i] = duty.CommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]]
validatorCommitteeIndices[i] = spec.ValidatorIndex(duty.ValidatorCommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]])
committeeIndices := make([]spec.CommitteeIndex, len(validatingAccounts))
validatorCommitteeIndices := make([]spec.ValidatorIndex, len(validatingAccounts))
committeeSizes := make([]uint64, len(validatingAccounts))
for i := range accountsArray {
committeeIndices[i] = duty.CommitteeIndices()[validatorIndexToArrayIndexMap[accountValidatorIndices[i]]]
validatorCommitteeIndices[i] = spec.ValidatorIndex(duty.ValidatorCommitteeIndices()[validatorIndexToArrayIndexMap[accountValidatorIndices[i]]])
committeeSizes[i] = duty.CommitteeSize(committeeIndices[i])
}
attestations, err := s.attest(ctx,
duty.Slot(),
duty,
accounts,
accountsArray,
accountValidatorIndices,
committeeIndices,
validatorCommitteeIndices,
committeeSizes,
@ -156,149 +165,30 @@ func (s *Service) Attest(ctx context.Context, data interface{}) ([]*spec.Attesta
return attestations, nil
}
// // Attest carries out attestations for a slot.
// // It returns a map of attestations made, keyed on the validator index.
// func (s *Service) Attest(ctx context.Context, data interface{}) ([]*spec.Attestation, error) {
// started := time.Now()
//
// duty, ok := data.(*attester.Duty)
// if !ok {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, errors.New("passed invalid data structure")
// }
// log := log.With().Uint64("slot", uint64(duty.Slot())).Logger()
// log.Trace().Strs("duties", duty.Tuples()).Msg("Attesting")
//
// // Fetch the attestation data.
// attestationData, err := s.attestationDataProvider.AttestationData(ctx, duty.Slot(), duty.CommitteeIndices()[0])
// if err != nil {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, errors.Wrap(err, "failed to obtain attestation data")
// }
// log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained attestation data")
//
// if attestationData.Slot != duty.Slot() {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, fmt.Errorf("attestation request for slot %d returned data for slot %d", duty.Slot(), attestationData.Slot)
// }
// if attestationData.Source.Epoch > attestationData.Target.Epoch {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, fmt.Errorf("attestation request for slot %d returned source epoch %d greater than target epoch %d", duty.Slot(), attestationData.Source.Epoch, attestationData.Target.Epoch)
// }
// if attestationData.Target.Epoch > duty.Slot()/s.slotsPerEpoch {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, fmt.Errorf("attestation request for slot %d returned target epoch %d greater than current epoch %d", duty.Slot(), attestationData.Target.Epoch, duty.Slot()/s.slotsPerEpoch)
// }
//
// // Fetch the validating accounts.
// accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, duty.ValidatorIndices())
// if err != nil {
// s.monitor.AttestationCompleted(started, "failed")
// return nil, errors.New("failed to obtain attesting validator accounts")
// }
// log.Trace().Dur("elapsed", time.Since(started)).Strs("tuples", duty.Tuples()).Msg("Obtained validating accounts")
//
// // Run the attestations in parallel, up to a concurrency limit.
// validatorIndexToArrayIndexMap := make(map[spec.ValidatorIndex]int)
// for i := range duty.ValidatorIndices() {
// validatorIndexToArrayIndexMap[duty.ValidatorIndices()[i]] = i
// }
// sem := semaphore.NewWeighted(s.processConcurrency)
// var wg sync.WaitGroup
// for _, account := range accounts {
// wg.Add(1)
// go func(sem *semaphore.Weighted, wg *sync.WaitGroup, account accountmanager.ValidatingAccount, attestations *[]*spec.Attestation, attestationsMutex *sync.Mutex) {
// // TODO update to common code format.
// defer wg.Done()
// if err := sem.Acquire(ctx, 1); err != nil {
// log.Error().Err(err).Msg("Failed to acquire semaphore")
// return
// }
// defer sem.Release(1)
//
// validatorIndex, err := account.Index(ctx)
// if err != nil {
// log.Warn().Err(err).Msg("Failed to obtain validator index")
// return
// }
// log := log.With().Uint64("validator_index", uint64(validatorIndex)).Logger()
// attestation, err := s.attest(ctx,
// duty.Slot(),
// duty.CommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]],
// duty.ValidatorCommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]],
// duty.CommitteeSize(duty.CommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]]),
// account,
// attestationData,
// )
// if err != nil {
// log.Warn().Err(err).Msg("Failed to attest")
// s.monitor.AttestationCompleted(started, "failed")
// return
// }
// log.Trace().Dur("elapsed", time.Since(started)).Msg("Attested")
// s.monitor.AttestationCompleted(started, "succeeded")
// attestationsMutex.Lock()
// *attestations = append(*attestations, attestation)
// attestationsMutex.Unlock()
// // // Set the per-validator information.
// // validatorIndexToArrayIndexMap := make(map[uint64]int)
// // for i := range duty.ValidatorIndices() {
// // validatorIndexToArrayIndexMap[duty.ValidatorIndices()[i]] = i
// // }
// // committeeIndices := make([]uint64, len(accounts))
// // validatorCommitteeIndices := make([]uint64, len(accounts))
// // committeeSizes := make([]uint64, len(accounts))
// // for i := range accounts {
// // validatorIndex, err := accounts[i].Index(ctx)
// // if err != nil {
// // return nil, errors.Wrap(err, "failed to obtain validator index")
// // }
// // committeeIndices[i] = duty.CommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]]
// // validatorCommitteeIndices[i] = duty.ValidatorCommitteeIndices()[validatorIndexToArrayIndexMap[validatorIndex]]
// // committeeSizes[i] = duty.CommitteeSize(committeeIndices[i])
// // }
//
// attestations, err := s.attest(ctx,
// slot,
// duty,
// accounts,
// committeeIndices,
// validatorCommitteeIndices,
// committeeSizes,
// attestationData,
// started,
// )
// if err != nil {
// log.Error().Err(err).Msg("Failed to attest")
// }
// }
//
// return attestations, nil
// }
func (s *Service) attest(
ctx context.Context,
slot spec.Slot,
duty *attester.Duty,
accounts []accountmanager.ValidatingAccount,
accounts []e2wtypes.Account,
validatorIndices []spec.ValidatorIndex,
committeeIndices []spec.CommitteeIndex,
validatorCommitteeIndices []spec.ValidatorIndex,
committeeSizes []uint64,
data *spec.AttestationData,
started time.Time,
) ([]*spec.Attestation, error) {
// Multisign the attestation for all validating accounts.
signer, isSigner := accounts[0].(accountmanager.BeaconAttestationsSigner)
if !isSigner {
return nil, errors.New("account is not a beacon attestations signer")
}
// Sign the attestation for all validating accounts.
uintCommitteeIndices := make([]uint64, len(committeeIndices))
for i := range committeeIndices {
uintCommitteeIndices[i] = uint64(committeeIndices[i])
}
sigs, err := signer.SignBeaconAttestations(ctx,
accountsArray := make([]e2wtypes.Account, 0, len(accounts))
accountsArray = append(accountsArray, accounts...)
sigs, err := s.beaconAttestationsSigner.SignBeaconAttestations(ctx,
accountsArray,
duty.Slot(),
accounts,
committeeIndices,
data.BeaconBlockRoot,
data.Source.Epoch,
@ -316,12 +206,7 @@ func (s *Service) attest(
attestations := make([]*spec.Attestation, len(sigs))
_, err = util.Scatter(len(sigs), func(offset int, entries int, _ *sync.RWMutex) (interface{}, error) {
for i := offset; i < offset+entries; i++ {
validatorIndex, err := accounts[i].Index(ctx)
if err != nil {
log.Warn().Err(err).Msg("failed to obtain validator index")
continue
}
log := log.With().Uint64("slot", uint64(duty.Slot())).Uint64("validator_index", uint64(validatorIndex)).Logger()
log := log.With().Uint64("slot", uint64(duty.Slot())).Uint64("validator_index", uint64(validatorIndices[i])).Logger()
if bytes.Equal(sigs[i][:], zeroSig[:]) {
log.Warn().Msg("No signature for validator; not creating attestation")
continue

View File

@ -18,7 +18,7 @@ import (
"fmt"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Duty contains information about a beacon block proposal duty.
@ -31,15 +31,15 @@ type Duty struct {
randaoReveal spec.BLSSignature
// account is used to sign the proposal; can be pre-fetched.
account accountmanager.ValidatingAccount
account e2wtypes.Account
}
// NewDuty creates a new beacon block proposer duty.
func NewDuty(ctx context.Context, slot spec.Slot, validatorIndex spec.ValidatorIndex) (*Duty, error) {
func NewDuty(slot spec.Slot, validatorIndex spec.ValidatorIndex) *Duty {
return &Duty{
slot: slot,
validatorIndex: validatorIndex,
}, nil
}
}
// Slot provides the slot for the beacon block proposer.
@ -68,12 +68,12 @@ func (d *Duty) RANDAOReveal() spec.BLSSignature {
}
// SetAccount sets the account.
func (d *Duty) SetAccount(account accountmanager.ValidatingAccount) {
func (d *Duty) SetAccount(account e2wtypes.Account) {
d.account = account
}
// Account provides the account.
func (d *Duty) Account() accountmanager.ValidatingAccount {
func (d *Duty) Account() e2wtypes.Account {
return d.account
}

View File

@ -18,8 +18,10 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/graffitiprovider"
"github.com/attestantio/vouch/services/metrics"
"github.com/attestantio/vouch/services/signer"
"github.com/attestantio/vouch/services/submitter"
"github.com/rs/zerolog"
)
@ -27,10 +29,13 @@ import (
type parameters struct {
logLevel zerolog.Level
monitor metrics.BeaconBlockProposalMonitor
chainTimeService chaintime.Service
proposalProvider eth2client.BeaconBlockProposalProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
graffitiProvider graffitiprovider.Service
beaconBlockSubmitter submitter.BeaconBlockSubmitter
randaoRevealSigner signer.RANDAORevealSigner
beaconBlockSigner signer.BeaconBlockSigner
}
// Parameter is the interface for service parameters.
@ -51,6 +56,13 @@ func WithLogLevel(logLevel zerolog.Level) Parameter {
})
}
// WithChainTimeService sets the chaintime service.
func WithChainTimeService(service chaintime.Service) Parameter {
return parameterFunc(func(p *parameters) {
p.chainTimeService = service
})
}
// WithProposalDataProvider sets the proposal data provider.
func WithProposalDataProvider(provider eth2client.BeaconBlockProposalProvider) Parameter {
return parameterFunc(func(p *parameters) {
@ -86,6 +98,20 @@ func WithBeaconBlockSubmitter(submitter submitter.BeaconBlockSubmitter) Paramete
})
}
// WithRANDAORevealSigner sets the RANDAO reveal signer.
func WithRANDAORevealSigner(signer signer.RANDAORevealSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.randaoRevealSigner = signer
})
}
// WithBeaconBlockSigner sets the beacon block signer.
func WithBeaconBlockSigner(signer signer.BeaconBlockSigner) Parameter {
return parameterFunc(func(p *parameters) {
p.beaconBlockSigner = signer
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -100,6 +126,9 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.proposalProvider == nil {
return nil, errors.New("no proposal data provider specified")
}
if parameters.chainTimeService == nil {
return nil, errors.New("no chain time service specified")
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
@ -109,6 +138,12 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.beaconBlockSubmitter == nil {
return nil, errors.New("no beacon block submitter specified")
}
if parameters.randaoRevealSigner == nil {
return nil, errors.New("no RANDAO reveal signer specified")
}
if parameters.beaconBlockSigner == nil {
return nil, errors.New("no beacon block signer specified")
}
return &parameters, nil
}

View File

@ -22,8 +22,10 @@ import (
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/beaconblockproposer"
"github.com/attestantio/vouch/services/chaintime"
"github.com/attestantio/vouch/services/graffitiprovider"
"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"
@ -33,10 +35,13 @@ import (
// Service is a beacon block proposer.
type Service struct {
monitor metrics.BeaconBlockProposalMonitor
chainTimeService chaintime.Service
proposalProvider eth2client.BeaconBlockProposalProvider
validatingAccountsProvider accountmanager.ValidatingAccountsProvider
graffitiProvider graffitiprovider.Service
beaconBlockSubmitter submitter.BeaconBlockSubmitter
randaoRevealSigner signer.RANDAORevealSigner
beaconBlockSigner signer.BeaconBlockSigner
}
// module-wide log.
@ -57,10 +62,13 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
s := &Service{
monitor: parameters.monitor,
chainTimeService: parameters.chainTimeService,
proposalProvider: parameters.proposalProvider,
validatingAccountsProvider: parameters.validatingAccountsProvider,
graffitiProvider: parameters.graffitiProvider,
beaconBlockSubmitter: parameters.beaconBlockSubmitter,
randaoRevealSigner: parameters.randaoRevealSigner,
beaconBlockSigner: parameters.beaconBlockSigner,
}
return s, nil
@ -78,23 +86,23 @@ func (s *Service) Prepare(ctx context.Context, data interface{}) error {
log := log.With().Uint64("proposing_slot", uint64(duty.Slot())).Uint64("validator_index", uint64(duty.ValidatorIndex())).Logger()
log.Trace().Msg("Preparing")
dutyEpoch := s.chainTimeService.SlotToEpoch(duty.Slot())
// Fetch the validating account.
accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, []spec.ValidatorIndex{duty.ValidatorIndex()})
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx,
dutyEpoch,
[]spec.ValidatorIndex{duty.ValidatorIndex()},
)
if err != nil {
return errors.Wrap(err, "failed to obtain proposing validator account")
}
if len(accounts) != 1 {
return fmt.Errorf("unknown proposing validator account %d", duty.ValidatorIndex())
}
account := accounts[0]
account := accounts[duty.ValidatorIndex()]
log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained proposing account")
duty.SetAccount(account)
revealSigner, isRevealSigner := account.(accountmanager.RANDAORevealSigner)
if !isRevealSigner {
return errors.New("account is not a RANDAO reveal signer")
}
randaoReveal, err := revealSigner.SignRANDAOReveal(ctx, duty.Slot())
randaoReveal, err := s.randaoRevealSigner.SignRANDAOReveal(ctx, account, duty.Slot())
if err != nil {
return errors.Wrap(err, "failed to sign RANDAO reveal")
}
@ -154,14 +162,8 @@ func (s *Service) Propose(ctx context.Context, data interface{}) {
return
}
// Sign the block.
signer, isSigner := duty.Account().(accountmanager.BeaconBlockSigner)
if !isSigner {
log.Error().Msg("Account is not a beacon block signer")
s.monitor.BeaconBlockProposalCompleted(started, "failed")
return
}
sig, err := signer.SignBeaconBlockProposal(ctx,
sig, err := s.beaconBlockSigner.SignBeaconBlockProposal(ctx,
duty.Account(),
proposal.Slot,
duty.ValidatorIndex(),
proposal.ParentRoot,

View File

@ -19,7 +19,7 @@ import (
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Subscription holds details of the committees to which we are subscribing.
@ -32,5 +32,8 @@ type Subscription struct {
// Service is the beacon committee subscriber service.
type Service interface {
// Subscribe subscribes to beacon committees for a given epoch.
Subscribe(ctx context.Context, epoch spec.Epoch, accounts []accountmanager.ValidatingAccount) (map[spec.Slot]map[spec.CommitteeIndex]*Subscription, error)
Subscribe(ctx context.Context,
epoch spec.Epoch,
accounts map[spec.ValidatorIndex]e2wtypes.Account,
) (map[spec.Slot]map[spec.CommitteeIndex]*Subscription, error)
}

View File

@ -21,7 +21,6 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/attestationaggregator"
"github.com/attestantio/vouch/services/attester"
"github.com/attestantio/vouch/services/beaconcommitteesubscriber"
@ -31,6 +30,7 @@ import (
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
"github.com/sasha-s/go-deadlock"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
"golang.org/x/sync/semaphore"
)
@ -74,27 +74,24 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
// This returns data about the subnets to which we are subscribing.
func (s *Service) Subscribe(ctx context.Context,
epoch spec.Epoch,
accounts []accountmanager.ValidatingAccount,
accounts map[spec.ValidatorIndex]e2wtypes.Account,
) (map[spec.Slot]map[spec.CommitteeIndex]*beaconcommitteesubscriber.Subscription, error) {
started := time.Now()
log := log.With().Uint64("epoch", uint64(epoch)).Logger()
log.Trace().Msg("Subscribing")
validatorIDs := make([]spec.ValidatorIndex, len(accounts))
var err error
for i, account := range accounts {
validatorIDs[i], err = account.Index(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain account index")
}
validatorIndices := make([]spec.ValidatorIndex, 0, len(accounts))
for index := range accounts {
validatorIndices = append(validatorIndices, index)
}
attesterDuties, err := s.attesterDutiesProvider.AttesterDuties(ctx, epoch, validatorIDs)
attesterDuties, err := s.attesterDutiesProvider.AttesterDuties(ctx, epoch, validatorIndices)
if err != nil {
s.monitor.BeaconCommitteeSubscriptionCompleted(started, "failed")
return nil, errors.Wrap(err, "failed to obtain attester duties")
}
log.Trace().Dur("elapsed", time.Since(started)).Int("accounts", len(validatorIDs)).Msg("Fetched attester duties")
log.Trace().Dur("elapsed", time.Since(started)).Int("accounts", len(validatorIndices)).Msg("Fetched attester duties")
duties, err := attester.MergeDuties(ctx, attesterDuties)
if err != nil {
s.monitor.BeaconCommitteeSubscriptionCompleted(started, "failed")
@ -154,7 +151,7 @@ func (s *Service) Subscribe(ctx context.Context,
// It returns a map of slot => committee => subscription information.
func (s *Service) calculateSubscriptionInfo(ctx context.Context,
epoch spec.Epoch,
accounts []accountmanager.ValidatingAccount,
accounts map[spec.ValidatorIndex]e2wtypes.Account,
duties []*attester.Duty,
) (map[spec.Slot]map[spec.CommitteeIndex]*beaconcommitteesubscriber.Subscription, error) {
@ -162,16 +159,16 @@ func (s *Service) calculateSubscriptionInfo(ctx context.Context,
subscriptionInfo := make(map[spec.Slot]map[spec.CommitteeIndex]*beaconcommitteesubscriber.Subscription)
subscriptionInfoMutex := deadlock.RWMutex{}
// Map is validator ID => account.
accountMap := make(map[spec.ValidatorIndex]accountmanager.ValidatingAccount, len(accounts))
for _, account := range accounts {
index, err := account.Index(ctx)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain account index for account map")
continue
}
accountMap[index] = account
}
// // Map is validator ID => account.
// accountMap := make(map[spec.ValidatorIndex]accountmanager.ValidatingAccount, len(accounts))
// for _, account := range accounts {
// index, err := account.Index(ctx)
// if err != nil {
// log.Warn().Err(err).Msg("Failed to obtain account index for account map")
// continue
// }
// accountMap[index] = account
// }
// Gather aggregators info in parallel.
// Note that it is possible for two validators to be aggregating for the same (slot,committee index) tuple, however
@ -205,13 +202,20 @@ func (s *Service) calculateSubscriptionInfo(ctx context.Context,
duty.Slot(),
duty.CommitteeSize(duty.CommitteeIndices()[i]))
if err != nil {
log.Error().Err(err).Msg("Failed to calculate if validator is an aggregator")
log.Error().
Uint64("slot", uint64(duty.Slot())).
Uint64("validator_index", uint64(duty.ValidatorIndices()[i])).
Err(err).
Msg("Failed to calculate if validator is an aggregator")
return
}
pubKey, err := accountMap[duty.ValidatorIndices()[i]].PubKey(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain validator's public key")
return
// Obtain composite public key if available, otherwise standard public key.
account := accounts[duty.ValidatorIndices()[i]]
var pubKey spec.BLSPubKey
if provider, isProvider := account.(e2wtypes.AccountCompositePublicKeyProvider); isProvider {
copy(pubKey[:], provider.CompositePublicKey().Marshal())
} else {
copy(pubKey[:], account.PublicKey().Marshal())
}
subscriptionInfoMutex.Lock()
if _, exists := subscriptionInfo[duty.Slot()]; !exists {

View File

@ -0,0 +1,49 @@
// 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"
"time"
"github.com/pkg/errors"
)
// startAccountsRefresher starts a period job that ticks approximately half-way through an epoch.
func (s *Service) startAccountsRefresher(ctx context.Context) error {
runtimeFunc := func(ctx context.Context, data interface{}) (time.Time, error) {
// Schedule for 65 seconds in to the of the next epoch.
// Doesn't matter too much when we run, but avoid the start of the epoch and
// also the start of a slot.
return s.chainTimeService.StartOfEpoch(s.chainTimeService.CurrentEpoch() + 1).Add(65 * time.Second), nil
}
if err := s.scheduler.SchedulePeriodicJob(ctx,
"Account refresh ticker",
runtimeFunc,
nil,
s.refreshAccounts,
nil,
); err != nil {
return errors.Wrap(err, "Failed to schedule accounts refresher")
}
return nil
}
// refreshAccounts refreshes accounts.
func (s *Service) refreshAccounts(ctx context.Context, data interface{}) {
started := time.Now()
s.accountsRefresher.Refresh(ctx)
log.Trace().Dur("elapsed", time.Since(started)).Msg("************************************Refreshed accounts")
}

View File

@ -20,50 +20,48 @@ import (
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/attestationaggregator"
"github.com/attestantio/vouch/services/attester"
)
// createAttesterJobs creates attestation jobs for the given epoch provided accounts.
func (s *Service) createAttesterJobs(ctx context.Context,
// scheduleAttestations schedules attestations for the given epoch and validator indices.
func (s *Service) scheduleAttestations(ctx context.Context,
epoch spec.Epoch,
accounts []accountmanager.ValidatingAccount,
firstRun bool) {
log.Trace().Msg("Creating attester jobs")
validatorIndices []spec.ValidatorIndex,
notCurrentSlot bool,
) {
started := time.Now()
log.Trace().Uint64("epoch", uint64(epoch)).Msg("Scheduling attestations")
validatorIDs := make([]spec.ValidatorIndex, len(accounts))
var err error
for i, account := range accounts {
validatorIDs[i], err = account.Index(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain account index")
return
}
}
resp, err := s.attesterDutiesProvider.AttesterDuties(ctx, epoch, validatorIDs)
resp, err := s.attesterDutiesProvider.AttesterDuties(ctx, epoch, validatorIndices)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain attester duties")
log.Error().Err(err).Msg("Failed to fetch attester duties")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(resp)).Msg("Fetched attester duties")
// Filter bad responses.
// Generate Vouch duties from the response.
filteredDuties := make([]*api.AttesterDuty, 0, len(resp))
firstSlot := spec.Slot(uint64(epoch) * s.slotsPerEpoch)
lastSlot := spec.Slot((uint64(epoch)+1)*s.slotsPerEpoch - 1)
firstSlot := s.chainTimeService.FirstSlotOfEpoch(epoch)
lastSlot := s.chainTimeService.FirstSlotOfEpoch(epoch+1) - 1
for _, duty := range resp {
if duty.Slot < firstSlot || duty.Slot > lastSlot {
log.Warn().Uint64("epoch", uint64(epoch)).Uint64("duty_slot", uint64(duty.Slot)).Msg("Attester duty has invalid slot for requested epoch; ignoring")
log.Warn().
Uint64("epoch", uint64(epoch)).
Uint64("duty_slot", uint64(duty.Slot)).
Msg("Attester duty has invalid slot for requested epoch; ignoring")
continue
}
filteredDuties = append(filteredDuties, duty)
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(filteredDuties)).Msg("Filtered attester duties")
duties, err := attester.MergeDuties(ctx, filteredDuties)
if err != nil {
log.Error().Err(err).Msg("Failed to merge attester duties")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(duties)).Msg("Merged attester duties")
if e := log.Trace(); e.Enabled() {
for _, duty := range duties {
@ -76,25 +74,35 @@ func (s *Service) createAttesterJobs(ctx context.Context,
currentSlot := s.chainTimeService.CurrentSlot()
for _, duty := range duties {
// Do not schedule attestations for past slots (or the current slot if we've just started).
// Do not schedule attestations for past slots (or the current slot if so instructed).
if duty.Slot() < currentSlot {
log.Debug().Uint64("attestation_slot", uint64(duty.Slot())).Uint64("current_slot", uint64(currentSlot)).Msg("Attestation for a past slot; not scheduling")
log.Debug().
Uint64("attestation_slot", uint64(duty.Slot())).
Uint64("current_slot", uint64(currentSlot)).
Msg("Attestation for a past slot; not scheduling")
continue
}
if firstRun && duty.Slot() == currentSlot {
log.Debug().Uint64("attestation_slot", uint64(duty.Slot())).Uint64("current_slot", uint64(currentSlot)).Msg("Attestation for the current slot and this is our first run; not scheduling")
if duty.Slot() == currentSlot && notCurrentSlot {
log.Debug().
Uint64("attestation_slot", uint64(duty.Slot())).
Uint64("current_slot", uint64(currentSlot)).
Msg("Attestation for the current slot and this is our first run; not scheduling")
continue
}
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Beacon block attestations for slot %d", duty.Slot()),
s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.slotDuration/3),
s.AttestAndScheduleAggregate,
duty,
); err != nil {
// Don't return here; we want to try to set up as many attester jobs as possible.
log.Error().Err(err).Msg("Failed to set attester job")
}
go func(duty *attester.Duty) {
if err := s.scheduler.ScheduleJob(ctx,
fmt.Sprintf("Beacon block attestations for slot %d", duty.Slot()),
// Adding 200 ms to ensure that head is up to date before we fetch attester duties.
s.chainTimeService.StartOfSlot(duty.Slot()).Add(s.slotDuration/3).Add(200*time.Millisecond),
s.AttestAndScheduleAggregate,
duty,
); err != nil {
// Don't return here; we want to try to set up as many attester jobs as possible.
log.Error().Err(err).Msg("Failed to schedule attestation")
}
}(duty)
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Scheduled attestations")
}
// AttestAndScheduleAggregate attests, then schedules aggregation jobs as required.
@ -118,12 +126,15 @@ func (s *Service) AttestAndScheduleAggregate(ctx context.Context, data interface
return
}
epoch := s.chainTimeService.SlotToEpoch(attestations[0].Data.Slot)
epoch := s.chainTimeService.SlotToEpoch(duty.Slot())
s.subscriptionInfosMutex.Lock()
subscriptionInfoMap, exists := s.subscriptionInfos[epoch]
s.subscriptionInfosMutex.Unlock()
if !exists {
log.Debug().Uint64("epoch", uint64(epoch)).Msg("No subscription info for this epoch; not aggregating")
log.Debug().
Uint64("slot", uint64(duty.Slot())).
Uint64("epoch", uint64(epoch)).
Msg("No subscription info for this epoch; not aggregating")
return
}
@ -145,7 +156,7 @@ func (s *Service) AttestAndScheduleAggregate(ctx context.Context, data interface
continue
}
if info.IsAggregator {
accounts, err := s.validatingAccountsProvider.AccountsByIndex(ctx, []spec.ValidatorIndex{info.Duty.ValidatorIndex})
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpochByIndex(ctx, epoch, []spec.ValidatorIndex{info.Duty.ValidatorIndex})
if err != nil {
// Don't return here; we want to try to set up as many aggregator jobs as possible.
log.Error().Err(err).Msg("Failed to obtain accounts")
@ -167,7 +178,7 @@ func (s *Service) AttestAndScheduleAggregate(ctx context.Context, data interface
AttestationDataRoot: attestationDataRoot,
ValidatorIndex: info.Duty.ValidatorIndex,
SlotSignature: info.Signature,
Account: accounts[0],
Account: accounts[info.Duty.ValidatorIndex],
Attestation: attestation,
}
if err := s.scheduler.ScheduleJob(ctx,

View File

@ -42,6 +42,7 @@ type parameters struct {
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
accountsRefresher accountmanager.Refresher
}
// Parameter is the interface for service parameters.
@ -153,6 +154,13 @@ func WithBeaconCommitteeSubscriber(subscriber beaconcommitteesubscriber.Service)
})
}
// WithAccountsRefresher sets the account refresher.
func WithAccountsRefresher(refresher accountmanager.Refresher) Parameter {
return parameterFunc(func(p *parameters) {
p.accountsRefresher = refresher
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
@ -200,6 +208,9 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
if parameters.beaconCommitteeSubscriber == nil {
return nil, errors.New("no beacon committee subscriber specified")
}
if parameters.accountsRefresher == nil {
return nil, errors.New("no accounts refresher specified")
}
return &parameters, nil
}

View File

@ -16,66 +16,64 @@ package standard
import (
"context"
"fmt"
"time"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/beaconblockproposer"
)
// createProposerJobs creates proposal jobs for the given epoch.
func (s *Service) createProposerJobs(ctx context.Context,
// scheduleProposals schedules proposals for the given epoch and validator indices.
func (s *Service) scheduleProposals(ctx context.Context,
epoch spec.Epoch,
accounts []accountmanager.ValidatingAccount,
firstRun bool) {
log.Trace().Msg("Creating proposer jobs")
validatorIndices []spec.ValidatorIndex,
notCurrentSlot bool,
) {
started := time.Now()
log.Trace().Uint64("epoch", uint64(epoch)).Msg("Scheduling proposals")
validatorIDs := make([]spec.ValidatorIndex, len(accounts))
var err error
for i, account := range accounts {
validatorIDs[i], err = account.Index(ctx)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain account index")
return
}
}
resp, err := s.proposerDutiesProvider.ProposerDuties(ctx, epoch, validatorIDs)
resp, err := s.proposerDutiesProvider.ProposerDuties(ctx, epoch, validatorIndices)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain proposer duties")
log.Error().Err(err).Msg("Failed to fetch proposer duties")
return
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(resp)).Msg("Fetched proposer duties")
// Filter bad responses.
// Generate Vouch duties from the response.
duties := make([]*beaconblockproposer.Duty, 0, len(resp))
firstSlot := s.chainTimeService.FirstSlotOfEpoch(epoch)
lastSlot := s.chainTimeService.FirstSlotOfEpoch(epoch+1) - 1
for _, respDuty := range resp {
if respDuty.Slot < firstSlot || respDuty.Slot > lastSlot {
log.Warn().Uint64("epoch", uint64(epoch)).Uint64("duty_slot", uint64(respDuty.Slot)).Msg("Proposer duty has invalid slot for requested epoch; ignoring")
log.Warn().
Uint64("epoch", uint64(epoch)).
Uint64("duty_slot", uint64(respDuty.Slot)).
Msg("Proposer duty has invalid slot for requested epoch; ignoring")
continue
}
duty, err := beaconblockproposer.NewDuty(ctx, respDuty.Slot, respDuty.ValidatorIndex)
if err != nil {
log.Error().Err(err).Msg("Failed to create proposer duty")
continue
}
duties = append(duties, duty)
duties = append(duties, beaconblockproposer.NewDuty(respDuty.Slot, respDuty.ValidatorIndex))
}
log.Trace().Dur("elapsed", time.Since(started)).Int("duties", len(duties)).Msg("Filtered proposer duties")
currentSlot := s.chainTimeService.CurrentSlot()
for _, duty := range duties {
// Do not schedule proposals for past slots (or the current slot if we've just started).
// Do not schedule proposals for past slots (or the current slot if so instructed).
if duty.Slot() < currentSlot {
log.Debug().Uint64("proposal_slot", uint64(duty.Slot())).Uint64("current_slot", uint64(currentSlot)).Msg("Proposal for a past slot; not scheduling")
log.Debug().
Uint64("proposal_slot", uint64(duty.Slot())).
Uint64("current_slot", uint64(currentSlot)).
Msg("Beacon block proposal for a past slot; not scheduling")
continue
}
if firstRun && duty.Slot() == currentSlot {
log.Debug().Uint64("proposal_slot", uint64(duty.Slot())).Uint64("current_slot", uint64(currentSlot)).Msg("Proposal for the current slot and this is our first run; not scheduling")
if duty.Slot() == currentSlot && notCurrentSlot {
log.Debug().
Uint64("proposal_slot", uint64(duty.Slot())).
Uint64("current_slot", uint64(currentSlot)).
Msg("Beacon block proposal for the current slot; not scheduling")
continue
}
go func(duty *beaconblockproposer.Duty) {
if err := s.beaconBlockProposer.Prepare(ctx, duty); err != nil {
log.Error().Uint64("proposal_slot", uint64(duty.Slot())).Err(err).Msg("Failed to prepare proposal")
log.Error().Uint64("proposal_slot", uint64(duty.Slot())).Err(err).Msg("Failed to prepare beacon block proposal")
return
}
if err := s.scheduler.ScheduleJob(ctx,
@ -85,8 +83,9 @@ func (s *Service) createProposerJobs(ctx context.Context,
duty,
); err != nil {
// Don't return here; we want to try to set up as many proposer jobs as possible.
log.Error().Err(err).Msg("Failed to set proposer job")
log.Error().Err(err).Msg("Failed to schedule beacon block proposal")
}
}(duty)
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Scheduled beacon block proposals")
}

View File

@ -32,6 +32,7 @@ import (
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Service is the co-ordination system for vouch.
@ -50,9 +51,10 @@ type Service struct {
beaconBlockProposer beaconblockproposer.Service
attestationAggregator attestationaggregator.Service
beaconCommitteeSubscriber beaconcommitteesubscriber.Service
activeAccounts int
activeValidators int
subscriptionInfos map[spec.Epoch]map[spec.Slot]map[spec.CommitteeIndex]*beaconcommitteesubscriber.Subscription
subscriptionInfosMutex sync.Mutex
accountsRefresher accountmanager.Refresher
}
// module-wide log.
@ -94,6 +96,7 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
beaconBlockProposer: parameters.beaconBlockProposer,
attestationAggregator: parameters.attestationAggregator,
beaconCommitteeSubscriber: parameters.beaconCommitteeSubscriber,
accountsRefresher: parameters.accountsRefresher,
subscriptionInfos: make(map[spec.Epoch]map[spec.Slot]map[spec.CommitteeIndex]*beaconcommitteesubscriber.Subscription),
}
@ -103,44 +106,49 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) {
return nil, errors.Wrap(err, "failed to add head event handler")
}
// Subscriptions are usually updated one epoch in advance, but as we're
// just starting we don't have subscriptions (or subscription information)
// for this or the next epoch; fetch them now.
go func() {
log.Trace().Msg("Fetching initial validator accounts")
accounts, err := s.validatingAccountsProvider.Accounts(ctx)
if err != nil {
log.Warn().Err(err).Msg("Failed to obtain accounts for initial validators")
return
}
log.Info().Int("accounts", len(accounts)).Msg("Initial validating accounts")
if len(accounts) == 0 {
log.Debug().Msg("No active validating accounts")
return
}
currentEpoch := s.chainTimeService.CurrentEpoch()
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, currentEpoch, accounts)
if err != nil {
log.Warn().Err(err).Msg("Failed to fetch initial beacon committees for current epoch")
return
}
s.subscriptionInfosMutex.Lock()
s.subscriptionInfos[currentEpoch] = subscriptionInfo
s.subscriptionInfosMutex.Unlock()
subscriptionInfo, err = s.beaconCommitteeSubscriber.Subscribe(ctx, currentEpoch+1, accounts)
if err != nil {
log.Warn().Err(err).Msg("Failed to fetch initial beacon committees for next epoch")
return
}
s.subscriptionInfosMutex.Lock()
s.subscriptionInfos[currentEpoch+1] = subscriptionInfo
s.subscriptionInfosMutex.Unlock()
}()
if err := s.startTickers(ctx); err != nil {
return nil, errors.Wrap(err, "failed to start controller tickers")
}
// Run specific actions now so we can carry out duties for the remainder of this epoch.
epoch := s.chainTimeService.CurrentEpoch()
accounts, validatorIndices, err := s.accountsAndIndicesForEpoch(ctx, epoch)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain active validator indices for the current epoch")
}
if len(validatorIndices) != s.activeValidators {
log.Info().Int("old_valdiators", s.activeValidators).Int("new_validators", len(validatorIndices)).Msg("Change in number of active validators")
s.activeValidators = len(validatorIndices)
}
nextEpochAccounts, nextEpochValidatorIndices, err := s.accountsAndIndicesForEpoch(ctx, epoch+1)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain active validator indices for the next epoch")
}
go s.scheduleProposals(ctx, epoch, validatorIndices, true /* notCurrentSlot */)
go s.scheduleAttestations(ctx, epoch, validatorIndices, true /* notCurrentSlot */)
go s.scheduleAttestations(ctx, epoch+1, nextEpochValidatorIndices, true /* notCurrentSlot */)
// Update beacon committee subscriptions this and the next epoch.
go func() {
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, epoch, accounts)
if err != nil {
log.Warn().Err(err).Msg("Failed to subscribe to beacon committees")
return
}
s.subscriptionInfosMutex.Lock()
s.subscriptionInfos[epoch] = subscriptionInfo
s.subscriptionInfosMutex.Unlock()
}()
go func() {
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, epoch+1, nextEpochAccounts)
if err != nil {
log.Warn().Err(err).Msg("Failed to subscribe to beacon committees")
return
}
s.subscriptionInfosMutex.Lock()
s.subscriptionInfos[epoch+1] = subscriptionInfo
s.subscriptionInfosMutex.Unlock()
}()
return s, nil
}
@ -158,12 +166,18 @@ func (s *Service) startTickers(ctx context.Context) error {
time.Sleep(500 * time.Millisecond)
}
// Start epoch ticker.
log.Trace().Msg("Starting epoch ticker")
// Start epoch tickers.
log.Trace().Msg("Starting epoch tickers")
if err := s.startEpochTicker(ctx, waitedForGenesis); err != nil {
return errors.Wrap(err, "failed to start epoch ticker")
}
// Start account refresher.
log.Trace().Msg("Starting accounts refresher")
if err := s.startAccountsRefresher(ctx); err != nil {
return errors.Wrap(err, "failed to start accounts refresher")
}
return nil
}
@ -193,11 +207,6 @@ func (s *Service) startEpochTicker(ctx context.Context, waitedForGenesis bool) e
return errors.Wrap(err, "Failed to schedule epoch ticker")
}
// Kick off the job immediately to fetch any duties for the current epoch.
if err := s.scheduler.RunJob(ctx, "Epoch ticker"); err != nil {
return errors.Wrap(err, "Failed to run epoch ticker")
}
return nil
}
@ -206,8 +215,7 @@ func (s *Service) epochTicker(ctx context.Context, data interface{}) {
// Ensure we don't run for the same epoch twice.
epochTickerData := data.(*epochTickerData)
currentEpoch := s.chainTimeService.CurrentEpoch()
firstRun := epochTickerData.latestEpochRan == -1
log.Trace().Uint64("epoch", uint64(currentEpoch)).Bool("first_run", firstRun).Msg("Starting per-epoch duties")
log.Trace().Uint64("epoch", uint64(currentEpoch)).Msg("Starting per-epoch job")
epochTickerData.mutex.Lock()
if epochTickerData.latestEpochRan >= int64(currentEpoch) {
log.Trace().Uint64("epoch", uint64(currentEpoch)).Msg("Already ran for this epoch; skipping")
@ -218,40 +226,38 @@ func (s *Service) epochTicker(ctx context.Context, data interface{}) {
epochTickerData.mutex.Unlock()
s.monitor.NewEpoch()
// Wait for half a second for the beacon node to update.
time.Sleep(500 * time.Millisecond)
// We wait for the beacon node to update, but keep ourselves busy in the meantime.
waitCtx, cancel := context.WithTimeout(ctx, 500*time.Millisecond)
// Do not update on first run because the service fetches accounts on startup.
if !firstRun {
log.Trace().Msg("Updating validating accounts")
err := s.validatingAccountsProvider.(accountmanager.AccountsUpdater).UpdateAccountsState(ctx)
if err != nil {
log.Warn().Err(err).Msg("Failed to update account state")
// Don't return even though we have an error here, as we can continue with the accounts we have from the previous run.
}
log.Trace().Msg("Updated validating accounts")
}
accounts, err := s.validatingAccountsProvider.Accounts(ctx)
_, validatorIndices, err := s.accountsAndIndicesForEpoch(ctx, currentEpoch)
if err != nil {
log.Error().Err(err).Msg("Failed to obtain accounts")
log.Error().Err(err).Uint64("epoch", uint64(currentEpoch)).Msg("Failed to obtain active validators for epoch")
cancel()
return
}
if len(accounts) != s.activeAccounts {
log.Info().Int("old_accounts", s.activeAccounts).Int("accounts", len(accounts)).Msg("Change in number of validating accounts")
s.activeAccounts = len(accounts)
}
if len(accounts) == 0 {
// Expect at least one account.
log.Warn().Msg("No active validating accounts; not validating")
nextEpochAccounts, nextEpochValidatorIndices, err := s.accountsAndIndicesForEpoch(ctx, currentEpoch+1)
if err != nil {
log.Error().Err(err).Uint64("epoch", uint64(currentEpoch)).Msg("Failed to obtain active validators for next epoch")
cancel()
return
}
// Create the jobs for our individual functions.
go s.createProposerJobs(ctx, currentEpoch, accounts, firstRun && !epochTickerData.atGenesis)
go s.createAttesterJobs(ctx, currentEpoch, accounts, firstRun && !epochTickerData.atGenesis)
// Expect at least one validator.
if len(validatorIndices) == 0 && len(nextEpochValidatorIndices) == 0 {
log.Warn().Msg("No active validators; not validating")
cancel()
return
}
// Done the preparation work available to us; wait for the end of the timer.
<-waitCtx.Done()
cancel()
go s.scheduleProposals(ctx, currentEpoch, validatorIndices, false /* notCurrentSlot */)
go s.scheduleAttestations(ctx, currentEpoch+1, nextEpochValidatorIndices, false /* notCurrentSlot */)
go func() {
// Update beacon committee subscriptions for the next epoch.
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, currentEpoch+1, accounts)
subscriptionInfo, err := s.beaconCommitteeSubscriber.Subscribe(ctx, currentEpoch+1, nextEpochAccounts)
if err != nil {
log.Warn().Err(err).Msg("Failed to subscribe to beacon committees")
return
@ -264,21 +270,23 @@ func (s *Service) epochTicker(ctx context.Context, data interface{}) {
epochTickerData.atGenesis = false
}
// // OnBeaconChainHeadUpdated runs attestations for a slot immediately, if the update is for the current slot.
// func (s *Service) OnBeaconChainHeadUpdated(ctx context.Context, slot uint64, stateRoot []byte, bodyRoot []byte, epochTransitioni bool) {
// if slot != s.chainTimeService.CurrentSlot() {
// return
// }
// s.monitor.BlockDelay(time.Since(s.chainTimeService.StartOfSlot(slot)))
//
// jobName := fmt.Sprintf("Beacon block attestations for slot %d", slot)
// if s.scheduler.JobExists(ctx, jobName) {
// log.Trace().Uint64("slot", slot).Msg("Kicking off attestations for slot early due to receiving relevant block")
// if err := s.scheduler.RunJobIfExists(ctx, jobName); err != nil {
// log.Error().Str("job", jobName).Err(err).Msg("Failed to run attester job")
// }
// }
//
// // Remove old subscriptions if present.
// delete(s.subscriptionInfos, s.chainTimeService.SlotToEpoch(slot)-2)
// }
// accountsAndIndicesForEpoch obtains the accounts and validator indices for the specified epoch.
func (s *Service) accountsAndIndicesForEpoch(ctx context.Context,
epoch spec.Epoch,
) (
map[spec.ValidatorIndex]e2wtypes.Account,
[]spec.ValidatorIndex,
error,
) {
accounts, err := s.validatingAccountsProvider.ValidatingAccountsForEpoch(ctx, epoch)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to obtain accounts")
}
validatorIndices := make([]spec.ValidatorIndex, 0, len(accounts))
for index := range accounts {
validatorIndices = append(validatorIndices, index)
}
return accounts, validatorIndices, nil
}

View File

@ -81,3 +81,11 @@ type ClientMonitor interface {
// ClientOperation provides a generic monitor for client operations.
ClientOperation(provider string, name string, succeeded bool, duration time.Duration)
}
// ValidatorsManagerMonitor provides methods to monitor the validators manager.
type ValidatorsManagerMonitor interface {
}
// SignerMonitor provides methods to monitor signers.
type SignerMonitor interface {
}

116
services/signer/service.go Normal file
View File

@ -0,0 +1,116 @@
// 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 signer is a package that provides application-level signing operations.
package signer
import (
"context"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// Service is the generic signer service.
type Service interface{}
// AggregateAndProofSigner provides methods to sign aggregate and proofs.
type AggregateAndProofSigner interface {
// SignAggregateAndProof signs an aggregate attestation for given slot and root.
SignAggregateAndProof(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
root spec.Root,
) (
spec.BLSSignature,
error,
)
}
// BeaconAttestationSigner provides methods to sign beacon attestations.
type BeaconAttestationSigner interface {
// SignBeaconAttestation signs a beacon attestation.
SignBeaconAttestation(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
committeeIndex spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root,
) (
spec.BLSSignature,
error,
)
}
// BeaconAttestationsSigner provides methods to sign multiple beacon attestations.
type BeaconAttestationsSigner interface {
// SignBeaconAttestation signs multiple beacon attestations.
SignBeaconAttestations(ctx context.Context,
accounts []e2wtypes.Account,
slot spec.Slot,
committeeIndices []spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root,
) (
[]spec.BLSSignature,
error,
)
}
// BeaconBlockSigner provides methods to sign beacon blocks.
type BeaconBlockSigner interface {
// SignBeaconBlockProposal signs a beacon block proposal.
SignBeaconBlockProposal(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
proposerIndex spec.ValidatorIndex,
parentRoot spec.Root,
stateRoot spec.Root,
bodyRoot spec.Root,
) (
spec.BLSSignature,
error,
)
}
// RANDAORevealSigner provides methods to sign RANDAO reveals.
type RANDAORevealSigner interface {
// SignRANDAOReveal returns a RANDAO signature.
// This signs an epoch with the "RANDAO" domain.
SignRANDAOReveal(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
) (
spec.BLSSignature,
error,
)
}
// SlotSelectionSigner provides methods to sign slot selections.
type SlotSelectionSigner interface {
// SignSlotSelection returns a slot selection signature.
// This signs a slot with the "selection proof" domain.
SignSlotSelection(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
) (
spec.BLSSignature,
error,
)
}

View File

@ -0,0 +1,162 @@
// 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"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
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
}
// 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.SignerMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.monitor = monitor
})
}
// WithClientMonitor sets the client monitor for the module.
func WithClientMonitor(clientMonitor metrics.ClientMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.clientMonitor = clientMonitor
})
}
// WithSlotsPerEpochProvider sets the slots per epoch provider.
func WithSlotsPerEpochProvider(provider eth2client.SlotsPerEpochProvider) 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
})
}
// WithDomainProvider sets the signature domain provider.
func WithDomainProvider(provider eth2client.DomainProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.domainProvider = 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(),
monitor: nullmetrics.New(context.Background()),
clientMonitor: nullmetrics.New(context.Background()),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
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.domainProvider == nil {
return nil, errors.New("no domain provider specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,94 @@
// 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"
eth2client "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"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is the manager for signers.
type Service struct {
monitor metrics.SignerMonitor
clientMonitor metrics.ClientMonitor
slotsPerEpoch spec.Slot
beaconProposerDomainType spec.DomainType
beaconAttesterDomainType spec.DomainType
randaoDomainType spec.DomainType
selectionProofDomainType spec.DomainType
aggregateAndProofDomainType spec.DomainType
domainProvider eth2client.DomainProvider
}
// module-wide log.
var log zerolog.Logger
// New creates a new dirk account manager.
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", "signer").Str("impl", "standard").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
slotsPerEpoch, err := parameters.slotsPerEpochProvider.SlotsPerEpoch(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain slots per epoch")
}
beaconAttesterDomainType, err := parameters.beaconAttesterDomainTypeProvider.BeaconAttesterDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon attester domain type")
}
beaconProposerDomainType, err := parameters.beaconProposerDomainTypeProvider.BeaconProposerDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain beacon proposer domain type")
}
randaoDomainType, err := parameters.randaoDomainTypeProvider.RANDAODomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain RANDAO domain type")
}
selectionProofDomainType, err := parameters.selectionProofDomainTypeProvider.SelectionProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain selection proof domain type")
}
aggregateAndProofDomainType, err := parameters.aggregateAndProofDomainTypeProvider.AggregateAndProofDomain(ctx)
if err != nil {
return nil, errors.Wrap(err, "failed to obtain aggregate and proof domain type")
}
s := &Service{
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
slotsPerEpoch: spec.Slot(slotsPerEpoch),
beaconAttesterDomainType: beaconAttesterDomainType,
beaconProposerDomainType: beaconProposerDomainType,
randaoDomainType: randaoDomainType,
selectionProofDomainType: selectionProofDomainType,
aggregateAndProofDomainType: aggregateAndProofDomainType,
domainProvider: parameters.domainProvider,
}
return s, nil
}

View File

@ -0,0 +1,307 @@
// 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_test
import (
"context"
"testing"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/signer/standard"
"github.com/attestantio/vouch/testing/logger"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
slotsPerEpochProvider := mock.NewSlotsPerEpochProvider(32)
beaconProposerDomainTypeProvider := mock.NewBeaconProposerDomainProvider()
beaconAttesterDomainTypeProvider := mock.NewBeaconAttesterDomainProvider()
randaoDomainTypeProvider := mock.NewRANDAODomainProvider()
selectionProofDomainTypeProvider := mock.NewSelectionProofDomainProvider()
aggregateAndProofDomainTypeProvider := mock.NewAggregateAndProofDomainProvider()
domainProvider := mock.NewDomainProvider()
tests := []struct {
name string
params []standard.Parameter
err string
logEntry string
}{
{
name: "MonitorMissing",
params: []standard.Parameter{
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.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no monitor specified",
},
{
name: "ClientMonitorMissing",
params: []standard.Parameter{
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.WithDomainProvider(domainProvider),
},
err: "problem with parameters: no client monitor specified",
},
{
name: "SlotsPerEpochProviderMissing",
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",
},
{
name: "SlotsPerEpochProviderErrors",
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.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",
},
{
name: "Good",
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),
standard.WithDomainProvider(domainProvider),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
capture := logger.NewLogCapture()
_, err := standard.New(context.Background(), 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,49 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignAggregateAndProof signs an aggregate and proof item.
func (s *Service) SignAggregateAndProof(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
aggregateAndProofRoot spec.Root,
) (
spec.BLSSignature,
error,
) {
// Fetch the domain.
domain, err := s.domainProvider.Domain(ctx,
s.aggregateAndProofDomainType,
spec.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon aggregate and proof")
}
sig, err := account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, aggregateAndProofRoot[:], domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to aggregate and proof")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -0,0 +1,62 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignBeaconAttestation signs a beacon attestation item.
func (s *Service) SignBeaconAttestation(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
committeeIndex spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root,
) (
spec.BLSSignature,
error,
) {
domain, err := s.domainProvider.Domain(ctx,
s.beaconAttesterDomainType,
spec.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon attestation")
}
sig, err := account.(e2wtypes.AccountProtectingSigner).SignBeaconAttestation(ctx,
uint64(slot),
uint64(committeeIndex),
blockRoot[:],
uint64(sourceEpoch),
sourceRoot[:],
uint64(targetEpoch),
targetRoot[:],
domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign beacon attestation")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -0,0 +1,79 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignBeaconAttestations signs multiple beacon attestations.
func (s *Service) SignBeaconAttestations(ctx context.Context,
accounts []e2wtypes.Account,
slot spec.Slot,
committeeIndices []spec.CommitteeIndex,
blockRoot spec.Root,
sourceEpoch spec.Epoch,
sourceRoot spec.Root,
targetEpoch spec.Epoch,
targetRoot spec.Root,
) (
[]spec.BLSSignature,
error,
) {
span, ctx := opentracing.StartSpanFromContext(ctx, "dirk.SignBeaconAttestations")
defer span.Finish()
if len(accounts) == 0 {
return nil, errors.New("no accounts supplied")
}
signatureDomain, err := s.domainProvider.Domain(ctx,
s.beaconAttesterDomainType,
spec.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return nil, errors.Wrap(err, "failed to obtain signature domain for beacon attestation")
}
uintCommitteeIndices := make([]uint64, len(committeeIndices))
for i := range committeeIndices {
uintCommitteeIndices[i] = uint64(committeeIndices[i])
}
sigs, err := accounts[0].(e2wtypes.AccountProtectingMultiSigner).SignBeaconAttestations(ctx,
uint64(slot),
accounts,
uintCommitteeIndices,
blockRoot[:],
uint64(sourceEpoch),
sourceRoot[:],
uint64(targetEpoch),
targetRoot[:],
signatureDomain[:],
)
if err != nil {
return nil, errors.Wrap(err, "failed to sign beacon attestation")
}
res := make([]spec.BLSSignature, len(sigs))
for i := range sigs {
if sigs[i] != nil {
copy(res[i][:], sigs[i].Marshal())
}
}
return res, nil
}

View File

@ -0,0 +1,59 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignBeaconBlockProposal signs a beacon block proposal item.
func (s *Service) SignBeaconBlockProposal(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
proposerIndex spec.ValidatorIndex,
parentRoot spec.Root,
stateRoot spec.Root,
bodyRoot spec.Root,
) (
spec.BLSSignature,
error,
) {
// Fetch the domain.
domain, err := s.domainProvider.Domain(ctx,
s.beaconProposerDomainType,
spec.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for beacon proposal")
}
sig, err := account.(e2wtypes.AccountProtectingSigner).SignBeaconProposal(ctx,
uint64(slot),
uint64(proposerIndex),
parentRoot[:],
stateRoot[:],
bodyRoot[:],
domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign beacon block proposal")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -0,0 +1,57 @@
// 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"
"encoding/binary"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignRANDAOReveal returns a RANDAO reveal signature.
// This signs an epoch with the "RANDAO reveal" domain.
func (s *Service) SignRANDAOReveal(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
) (
spec.BLSSignature,
error,
) {
var messageRoot spec.Root
epoch := spec.Epoch(slot / s.slotsPerEpoch)
binary.LittleEndian.PutUint64(messageRoot[:], uint64(epoch))
// Obtain the RANDAO reveal signature domain.
domain, err := s.domainProvider.Domain(ctx,
s.randaoDomainType,
epoch)
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for RANDAO reveal")
}
epochBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(epochBytes, uint64(epoch))
sig, err := account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, epochBytes, domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign RANDO reveal")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -0,0 +1,56 @@
// 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"
"encoding/binary"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
)
// SignSlotSelection returns a slot selection signature.
// This signs a slot with the "selection proof" domain.
func (s *Service) SignSlotSelection(ctx context.Context,
account e2wtypes.Account,
slot spec.Slot,
) (
spec.BLSSignature,
error,
) {
var messageRoot spec.Root
binary.LittleEndian.PutUint64(messageRoot[:], uint64(slot))
// Calculate the domain.
domain, err := s.domainProvider.Domain(ctx,
s.selectionProofDomainType,
spec.Epoch(slot/s.slotsPerEpoch))
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to obtain signature domain for selection proof")
}
slotBytes := make([]byte, 32)
binary.LittleEndian.PutUint64(slotBytes, uint64(slot))
sig, err := account.(e2wtypes.AccountProtectingSigner).SignGeneric(ctx, slotBytes, domain[:])
if err != nil {
return spec.BLSSignature{}, errors.Wrap(err, "failed to sign slot")
}
var signature spec.BLSSignature
copy(signature[:], sig.Marshal())
return signature, nil
}

View File

@ -0,0 +1,39 @@
// 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 validatorsmanager is a package that manages validator information,
// primarily from local information and backed by the data from a beacon node.
package validatorsmanager
import (
"context"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
)
// Service is the generic validators manager service.
type Service interface {
// RefreshValidatorsFromBeaconNode refreshes the local store from the beacon node.
// This is an expensive operation, and should not be called in the validating path.
RefreshValidatorsFromBeaconNode(ctx context.Context, pubKeys []spec.BLSPubKey) error
// ValidatorsByIndex fetches the requested validators from local store given their indices.
ValidatorsByIndex(ctx context.Context, indices []spec.ValidatorIndex) map[spec.ValidatorIndex]*spec.Validator
// ValidatorsByPubKey fetches the requested validators from local store given their public keys.
ValidatorsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) map[spec.ValidatorIndex]*spec.Validator
// ValidatorStateAtEpoch returns the given validator's state at the given epoch.
ValidatorStateAtEpoch(ctx context.Context, index spec.ValidatorIndex, epoch spec.Epoch) (api.ValidatorState, error)
}

View File

@ -0,0 +1,108 @@
// 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"
eth2client "github.com/attestantio/go-eth2-client"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/metrics"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/pkg/errors"
"github.com/rs/zerolog"
)
type parameters struct {
logLevel zerolog.Level
monitor metrics.ValidatorsManagerMonitor
clientMonitor metrics.ClientMonitor
validatorsProvider eth2client.ValidatorsProvider
farFutureEpoch spec.Epoch
}
// 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.ValidatorsManagerMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.monitor = monitor
})
}
// WithClientMonitor sets the client monitor for the module.
func WithClientMonitor(clientMonitor metrics.ClientMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.clientMonitor = clientMonitor
})
}
// WithValidatorsProvider sets the validator status provider.
func WithValidatorsProvider(provider eth2client.ValidatorsProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.validatorsProvider = provider
})
}
// WithFarFutureEpoch sets the far future epoch.
func WithFarFutureEpoch(farFutureEpoch spec.Epoch) Parameter {
return parameterFunc(func(p *parameters) {
p.farFutureEpoch = farFutureEpoch
})
}
// 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()),
clientMonitor: nullmetrics.New(context.Background()),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.monitor == nil {
return nil, errors.New("no monitor specified")
}
if parameters.clientMonitor == nil {
return nil, errors.New("no client monitor specified")
}
if parameters.validatorsProvider == nil {
return nil, errors.New("no validators provider specified")
}
if parameters.farFutureEpoch == 0 {
return nil, errors.New("no far future epoch specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,85 @@
// 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"
"time"
eth2client "github.com/attestantio/go-eth2-client"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
// RefreshValidatorsFromBeaconNode refreshes the local store from the beacon node.
// This is an expensive operation, and should not be called in the validating path.
func (s *Service) RefreshValidatorsFromBeaconNode(ctx context.Context, pubKeys []spec.BLSPubKey) error {
var validators map[spec.ValidatorIndex]*api.Validator
var err error
started := time.Now()
// Use ValidatorsWithoutBalance by preference (check relative timings to see if this has improved.)
if validatorsWithoutBalanceProvider, isProvider := s.validatorsProvider.(eth2client.ValidatorsWithoutBalanceProvider); isProvider {
log.Trace().Msg("Using ValidatorsWithoutBalanceByPubKey to refresh validator information")
validators, err = validatorsWithoutBalanceProvider.ValidatorsWithoutBalanceByPubKey(ctx, "head", pubKeys)
if service, isService := s.validatorsProvider.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "validators without balance", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "validators without balance", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to obtain validators without balances")
}
} else {
log.Trace().Msg("Using ValidatorsByPubKey to refresh validator information")
validators, err = s.validatorsProvider.ValidatorsByPubKey(ctx, "head", pubKeys)
if service, isService := s.validatorsProvider.(eth2client.Service); isService {
s.clientMonitor.ClientOperation(service.Address(), "validators", err == nil, time.Since(started))
} else {
s.clientMonitor.ClientOperation("<unknown>", "validators", err == nil, time.Since(started))
}
if err != nil {
return errors.Wrap(err, "failed to obtain validators")
}
}
log.Trace().Dur("elapsed", time.Since(started)).Int("received", len(validators)).Msg("Received validators from beacon node")
// If we have no validators at this point we leave early rather than possibly replace existing information.
if len(validators) == 0 {
log.Trace().Msg("No validators received; not replacing existing validators")
return nil
}
validatorsByIndex := make(map[spec.ValidatorIndex]*spec.Validator)
validatorsByPubKey := make(map[spec.BLSPubKey]*spec.Validator)
validatorPubKeyToIndex := make(map[spec.BLSPubKey]spec.ValidatorIndex)
for _, validator := range validators {
validatorsByIndex[validator.Index] = validator.Validator
validatorsByPubKey[validator.Validator.PublicKey] = validator.Validator
validatorPubKeyToIndex[validator.Validator.PublicKey] = validator.Index
}
log.Trace().
Int("validators_by_index", len(validatorsByIndex)).
Int("validators_by_pubkey", len(validatorsByPubKey)).
Int("validator_pubkey_to_index", len(validatorPubKeyToIndex)).
Msg("Updating validator cache")
s.validatorsMutex.Lock()
s.validatorsByIndex = validatorsByIndex
s.validatorsByPubKey = validatorsByPubKey
s.validatorPubKeyToIndex = validatorPubKeyToIndex
s.validatorsMutex.Unlock()
return nil
}

View File

@ -0,0 +1,72 @@
// 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_test
import (
"context"
"testing"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager/standard"
"github.com/attestantio/vouch/testutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestRefreshValidatorsFromBeaconNode(t *testing.T) {
ctx := context.Background()
s, err := standard.New(ctx,
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithFarFutureEpoch(spec.Epoch(0xffffffffffffffff)),
standard.WithValidatorsProvider(mock.NewValidatorsProvider()),
)
require.NoError(t, err)
// Keys we want to fetch.
fetchKeys := []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
}
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
}))
require.Len(t, s.ValidatorsByPubKey(ctx, fetchKeys), 2)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
}))
require.Len(t, s.ValidatorsByPubKey(ctx, fetchKeys), 3)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
}))
require.Len(t, s.ValidatorsByPubKey(ctx, fetchKeys), 2)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7"),
testutil.HexToPubKey("0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a"),
testutil.HexToPubKey("0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268"),
}))
require.Len(t, s.ValidatorsByPubKey(ctx, fetchKeys), 0)
}

View File

@ -0,0 +1,68 @@
// 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"
"sync"
eth2client "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"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is the manager for validators.
type Service struct {
monitor metrics.ValidatorsManagerMonitor
clientMonitor metrics.ClientMonitor
validatorsProvider eth2client.ValidatorsProvider
farFutureEpoch spec.Epoch
validatorsMutex sync.RWMutex
validatorsByIndex map[spec.ValidatorIndex]*spec.Validator
validatorsByPubKey map[spec.BLSPubKey]*spec.Validator
validatorPubKeyToIndex map[spec.BLSPubKey]spec.ValidatorIndex
}
// module-wide log.
var log zerolog.Logger
// New creates a new validator provider.
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", "validatorsmanager").Str("impl", "standard").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
s := &Service{
monitor: parameters.monitor,
clientMonitor: parameters.clientMonitor,
farFutureEpoch: parameters.farFutureEpoch,
validatorsProvider: parameters.validatorsProvider,
validatorsByIndex: make(map[spec.ValidatorIndex]*spec.Validator),
validatorsByPubKey: make(map[spec.BLSPubKey]*spec.Validator),
validatorPubKeyToIndex: make(map[spec.BLSPubKey]spec.ValidatorIndex),
}
return s, nil
}

View File

@ -0,0 +1,65 @@
// 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_test
import (
"context"
"testing"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager/standard"
"github.com/attestantio/vouch/testing/logger"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
farFutureEpoch := spec.Epoch(0xffffffffffffffff)
validatorsProvider := mock.NewValidatorsProvider()
tests := []struct {
name string
params []standard.Parameter
err string
logEntry string
}{
{
name: "Good",
params: []standard.Parameter{
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithFarFutureEpoch(farFutureEpoch),
standard.WithValidatorsProvider(validatorsProvider),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
capture := logger.NewLogCapture()
_, err := standard.New(context.Background(), 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,34 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
)
// ValidatorsByIndex fetches the requested validators from local store given their indices.
func (s *Service) ValidatorsByIndex(ctx context.Context, indices []spec.ValidatorIndex) map[spec.ValidatorIndex]*spec.Validator {
res := make(map[spec.ValidatorIndex]*spec.Validator)
s.validatorsMutex.RLock()
for _, index := range indices {
if validator, exists := s.validatorsByIndex[index]; exists {
res[index] = validator
}
}
s.validatorsMutex.RUnlock()
return res
}

View File

@ -0,0 +1,106 @@
// 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_test
import (
"context"
"testing"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager/standard"
"github.com/attestantio/vouch/testutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestValidatorsByIndex(t *testing.T) {
ctx := context.Background()
s, err := standard.New(ctx,
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithFarFutureEpoch(spec.Epoch(0xffffffffffffffff)),
standard.WithValidatorsProvider(mock.NewValidatorsProvider()),
)
require.NoError(t, err)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
testutil.HexToPubKey("0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e"),
testutil.HexToPubKey("0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e"),
testutil.HexToPubKey("0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34"),
testutil.HexToPubKey("0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373"),
testutil.HexToPubKey("0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac"),
testutil.HexToPubKey("0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7"),
testutil.HexToPubKey("0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a"),
testutil.HexToPubKey("0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268"),
testutil.HexToPubKey("0xaec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d"),
testutil.HexToPubKey("0x9314c6de0386635e2799af798884c2ea09c63b9f079e572acc00b06a7faccce501ea4dfc0b1a23b8603680a5e3481327"),
testutil.HexToPubKey("0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c"),
testutil.HexToPubKey("0x84398f539a64cbe01cfcd8c485ea51cd6657b94df93ee9b5dc61e1f18f69da6ca9d4dba63c956a81c68d5d4d4277a60f"),
testutil.HexToPubKey("0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f"),
testutil.HexToPubKey("0x8f467e5723deac7659e1ca273e28410cbaa6d495ab66ae77014f4cd21c64b6b5ab9987c9b5537fe0279bd063fe609be7"),
testutil.HexToPubKey("0x8dde8306920812b32def3b663f7c540b49180345d3bcb8d3770790b7dc80030ebc06497feebd1bcf017d918f00bfa88f"),
testutil.HexToPubKey("0xab8d3a9bcc160e518fac0756d3e192c74789588ed4a2b1debf0c78f78479ca8edb05b12ce21103076df6af4eb8756ff9"),
testutil.HexToPubKey("0x8d5d3672a233db513df7ad1e8beafeae99a9f0199ed4d949bbedbb6f394030c0416bd99b910e14f73c65b6a11fe6b62e"),
testutil.HexToPubKey("0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc"),
testutil.HexToPubKey("0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d"),
testutil.HexToPubKey("0x954eb88ed1207f891dc3c28fa6cfdf8f53bf0ed3d838f3476c0900a61314d22d4f0a300da3cd010444dd5183e35a593c"),
testutil.HexToPubKey("0xaf344fce60dbd5fb850070e6e76a065e1a32485245ef4f413135a86ae703da88407c5d01c71f6bb06a151ff96cca7191"),
testutil.HexToPubKey("0xae241af60691fda1cf8ca44d49573c55818c53b6141800cca2d488b9a3fba71c0f869179fff50c084657831fbeb42bf4"),
testutil.HexToPubKey("0x96746aaba64dc87835ba709332f4d5d7837ada092b439c49d251aecf92aab5dc132e917bf6f59799bc093f976a7bc021"),
testutil.HexToPubKey("0xb9d1d914df3d4565465c3fd52b5b96e637f9980570cabf5b5d4aadf5a329ac36ad672819d997e735f5052e28b1f0c104"),
testutil.HexToPubKey("0x963528adb5322c2e2c54dc296ffddd2861bb103cbf64646781dfa8a3c2d8a8eda7079d2b3e95600028c44365afbf8879"),
testutil.HexToPubKey("0xb245d63d3f9d8ea1807a629fcb1b328cb4d542f35a3d5bc478be0df389dddd712fc4c816ba3fede9a96320ae6b24a7d8"),
testutil.HexToPubKey("0xa98ed496c2f464226500a6ce04602ff9ef133ed6316f372f6c744aee165149f7e578b12780e0eacec307ae6907351d99"),
testutil.HexToPubKey("0xae00fc3de831b09661a0ac02873c45c84cb2b58cffb6430a3f607e4c3fa1e0932397f11307cd169cdc6f79c463527260"),
testutil.HexToPubKey("0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d"),
}))
tests := []struct {
name string
validatorIndices []spec.ValidatorIndex
expected int
}{
{
name: "Nil",
expected: 0,
},
{
name: "Empty",
validatorIndices: []spec.ValidatorIndex{},
expected: 0,
},
{
name: "Good",
validatorIndices: []spec.ValidatorIndex{1, 2, 3},
expected: 3,
},
{
name: "Missing",
validatorIndices: []spec.ValidatorIndex{1, 2, 12341234123412341234},
expected: 2,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
validators := s.ValidatorsByIndex(ctx, test.validatorIndices)
require.Len(t, validators, test.expected)
})
}
}

View File

@ -0,0 +1,34 @@
// 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"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
)
// ValidatorsByPubKey fetches the requested validators from local store given their public keys.
func (s *Service) ValidatorsByPubKey(ctx context.Context, pubKeys []spec.BLSPubKey) map[spec.ValidatorIndex]*spec.Validator {
res := make(map[spec.ValidatorIndex]*spec.Validator)
s.validatorsMutex.RLock()
for _, pubKey := range pubKeys {
if validator, exists := s.validatorsByPubKey[pubKey]; exists {
res[s.validatorPubKeyToIndex[pubKey]] = validator
}
}
s.validatorsMutex.RUnlock()
return res
}

View File

@ -0,0 +1,114 @@
// 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_test
import (
"context"
"testing"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager/standard"
"github.com/attestantio/vouch/testutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestValidatorsByPubKey(t *testing.T) {
ctx := context.Background()
s, err := standard.New(ctx,
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithFarFutureEpoch(spec.Epoch(0xffffffffffffffff)),
standard.WithValidatorsProvider(mock.NewValidatorsProvider()),
)
require.NoError(t, err)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
testutil.HexToPubKey("0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e"),
testutil.HexToPubKey("0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e"),
testutil.HexToPubKey("0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34"),
testutil.HexToPubKey("0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373"),
testutil.HexToPubKey("0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac"),
testutil.HexToPubKey("0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7"),
testutil.HexToPubKey("0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a"),
testutil.HexToPubKey("0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268"),
testutil.HexToPubKey("0xaec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d"),
testutil.HexToPubKey("0x9314c6de0386635e2799af798884c2ea09c63b9f079e572acc00b06a7faccce501ea4dfc0b1a23b8603680a5e3481327"),
testutil.HexToPubKey("0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c"),
testutil.HexToPubKey("0x84398f539a64cbe01cfcd8c485ea51cd6657b94df93ee9b5dc61e1f18f69da6ca9d4dba63c956a81c68d5d4d4277a60f"),
testutil.HexToPubKey("0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f"),
testutil.HexToPubKey("0x8f467e5723deac7659e1ca273e28410cbaa6d495ab66ae77014f4cd21c64b6b5ab9987c9b5537fe0279bd063fe609be7"),
testutil.HexToPubKey("0x8dde8306920812b32def3b663f7c540b49180345d3bcb8d3770790b7dc80030ebc06497feebd1bcf017d918f00bfa88f"),
testutil.HexToPubKey("0xab8d3a9bcc160e518fac0756d3e192c74789588ed4a2b1debf0c78f78479ca8edb05b12ce21103076df6af4eb8756ff9"),
testutil.HexToPubKey("0x8d5d3672a233db513df7ad1e8beafeae99a9f0199ed4d949bbedbb6f394030c0416bd99b910e14f73c65b6a11fe6b62e"),
testutil.HexToPubKey("0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc"),
testutil.HexToPubKey("0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d"),
testutil.HexToPubKey("0x954eb88ed1207f891dc3c28fa6cfdf8f53bf0ed3d838f3476c0900a61314d22d4f0a300da3cd010444dd5183e35a593c"),
testutil.HexToPubKey("0xaf344fce60dbd5fb850070e6e76a065e1a32485245ef4f413135a86ae703da88407c5d01c71f6bb06a151ff96cca7191"),
testutil.HexToPubKey("0xae241af60691fda1cf8ca44d49573c55818c53b6141800cca2d488b9a3fba71c0f869179fff50c084657831fbeb42bf4"),
testutil.HexToPubKey("0x96746aaba64dc87835ba709332f4d5d7837ada092b439c49d251aecf92aab5dc132e917bf6f59799bc093f976a7bc021"),
testutil.HexToPubKey("0xb9d1d914df3d4565465c3fd52b5b96e637f9980570cabf5b5d4aadf5a329ac36ad672819d997e735f5052e28b1f0c104"),
testutil.HexToPubKey("0x963528adb5322c2e2c54dc296ffddd2861bb103cbf64646781dfa8a3c2d8a8eda7079d2b3e95600028c44365afbf8879"),
testutil.HexToPubKey("0xb245d63d3f9d8ea1807a629fcb1b328cb4d542f35a3d5bc478be0df389dddd712fc4c816ba3fede9a96320ae6b24a7d8"),
testutil.HexToPubKey("0xa98ed496c2f464226500a6ce04602ff9ef133ed6316f372f6c744aee165149f7e578b12780e0eacec307ae6907351d99"),
testutil.HexToPubKey("0xae00fc3de831b09661a0ac02873c45c84cb2b58cffb6430a3f607e4c3fa1e0932397f11307cd169cdc6f79c463527260"),
testutil.HexToPubKey("0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d"),
}))
tests := []struct {
name string
validatorPubKeys []spec.BLSPubKey
expected int
}{
{
name: "Nil",
expected: 0,
},
{
name: "Empty",
validatorPubKeys: []spec.BLSPubKey{},
expected: 0,
},
{
name: "Good",
validatorPubKeys: []spec.BLSPubKey{
testutil.HexToPubKey("0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc"),
testutil.HexToPubKey("0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d"),
testutil.HexToPubKey("0x954eb88ed1207f891dc3c28fa6cfdf8f53bf0ed3d838f3476c0900a61314d22d4f0a300da3cd010444dd5183e35a593c"),
},
expected: 3,
},
{
name: "Missing",
validatorPubKeys: []spec.BLSPubKey{
testutil.HexToPubKey("0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc"),
testutil.HexToPubKey("0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d"),
testutil.HexToPubKey("0x97d1108d128ead4d11e9fd2325547ad1cf57de92bcbcc79b7a15ba22057df0bf9026eb93569f2e57edee7a7bc4bf2a23"),
},
expected: 2,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
validators := s.ValidatorsByPubKey(ctx, test.validatorPubKeys)
require.Len(t, validators, test.expected)
})
}
}

View File

@ -0,0 +1,33 @@
// 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"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/pkg/errors"
)
// ValidatorStateAtEpoch returns the validator's state at the given epoch.
func (s *Service) ValidatorStateAtEpoch(ctx context.Context, index spec.ValidatorIndex, epoch spec.Epoch) (api.ValidatorState, error) {
s.validatorsMutex.RLock()
validator, exists := s.validatorsByIndex[index]
s.validatorsMutex.RUnlock()
if !exists {
return api.ValidatorStateUnknown, errors.New("not found")
}
return api.ValidatorToState(validator, epoch, s.farFutureEpoch), nil
}

View File

@ -0,0 +1,101 @@
// 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_test
import (
"context"
"testing"
api "github.com/attestantio/go-eth2-client/api/v1"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
nullmetrics "github.com/attestantio/vouch/services/metrics/null"
"github.com/attestantio/vouch/services/validatorsmanager/standard"
"github.com/attestantio/vouch/testutil"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestValidatorStateAtEpoch(t *testing.T) {
ctx := context.Background()
s, err := standard.New(ctx,
standard.WithLogLevel(zerolog.Disabled),
standard.WithMonitor(nullmetrics.New(context.Background())),
standard.WithClientMonitor(nullmetrics.New(context.Background())),
standard.WithFarFutureEpoch(spec.Epoch(0xffffffffffffffff)),
standard.WithValidatorsProvider(mock.NewValidatorsProvider()),
)
require.NoError(t, err)
require.NoError(t, s.RefreshValidatorsFromBeaconNode(ctx, []spec.BLSPubKey{
testutil.HexToPubKey("0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c"),
testutil.HexToPubKey("0xb89bebc699769726a318c8e9971bd3171297c61aea4a6578a7a4f94b547dcba5bac16a89108b6b6a1fe3695d1a874a0b"),
testutil.HexToPubKey("0xa3a32b0f8b4ddb83f1a0a853d81dd725dfe577d4f4c3db8ece52ce2b026eca84815c1a7e8e92a4de3d755733bf7e4a9b"),
testutil.HexToPubKey("0x88c141df77cd9d8d7a71a75c826c41a9c9f03c6ee1b180f3e7852f6a280099ded351b58d66e653af8e42816a4d8f532e"),
testutil.HexToPubKey("0x81283b7a20e1ca460ebd9bbd77005d557370cabb1f9a44f530c4c4c66230f675f8df8b4c2818851aa7d77a80ca5a4a5e"),
testutil.HexToPubKey("0xab0bdda0f85f842f431beaccf1250bf1fd7ba51b4100fd64364b6401fda85bb0069b3e715b58819684e7fc0b10a72a34"),
testutil.HexToPubKey("0x9977f1c8b731a8d5558146bfb86caea26434f3c5878b589bf280a42c9159e700e9df0e4086296c20b011d2e78c27d373"),
testutil.HexToPubKey("0xa8d4c7c27795a725961317ef5953a7032ed6d83739db8b0e8a72353d1b8b4439427f7efa2c89caa03cc9f28f8cbab8ac"),
testutil.HexToPubKey("0xa6d310dbbfab9a22450f59993f87a4ce5db6223f3b5f1f30d2c4ec718922d400e0b3c7741de8e59960f72411a0ee10a7"),
testutil.HexToPubKey("0x9893413c00283a3f9ed9fd9845dda1cea38228d22567f9541dccc357e54a2d6a6e204103c92564cbc05f4905ac7c493a"),
testutil.HexToPubKey("0x876dd4705157eb66dc71bc2e07fb151ea53e1a62a0bb980a7ce72d15f58944a8a3752d754f52f4a60dbfc7b18169f268"),
testutil.HexToPubKey("0xaec922bd7a9b7b1dc21993133b586b0c3041c1e2e04b513e862227b9d7aecaf9444222f7e78282a449622ffc6278915d"),
testutil.HexToPubKey("0x9314c6de0386635e2799af798884c2ea09c63b9f079e572acc00b06a7faccce501ea4dfc0b1a23b8603680a5e3481327"),
testutil.HexToPubKey("0x903e2989e7442ee0a8958d020507a8bd985d3974f5e8273093be00db3935f0500e141b252bd09e3728892c7a8443863c"),
testutil.HexToPubKey("0x84398f539a64cbe01cfcd8c485ea51cd6657b94df93ee9b5dc61e1f18f69da6ca9d4dba63c956a81c68d5d4d4277a60f"),
testutil.HexToPubKey("0x872c61b4a7f8510ec809e5b023f5fdda2105d024c470ddbbeca4bc74e8280af0d178d749853e8f6a841083ac1b4db98f"),
testutil.HexToPubKey("0x8f467e5723deac7659e1ca273e28410cbaa6d495ab66ae77014f4cd21c64b6b5ab9987c9b5537fe0279bd063fe609be7"),
testutil.HexToPubKey("0x8dde8306920812b32def3b663f7c540b49180345d3bcb8d3770790b7dc80030ebc06497feebd1bcf017d918f00bfa88f"),
testutil.HexToPubKey("0xab8d3a9bcc160e518fac0756d3e192c74789588ed4a2b1debf0c78f78479ca8edb05b12ce21103076df6af4eb8756ff9"),
testutil.HexToPubKey("0x8d5d3672a233db513df7ad1e8beafeae99a9f0199ed4d949bbedbb6f394030c0416bd99b910e14f73c65b6a11fe6b62e"),
testutil.HexToPubKey("0xa1c76af1545d7901214bb6be06be5d9e458f8e989c19373a920f0018327c83982f6a2ac138260b8def732cb366411ddc"),
testutil.HexToPubKey("0x8dd74e1bb5228fc1fca274fda02b971c1003a4f409bbdfbcfec6426bf2f52addcbbebccdbf45eee6ae11eb5b5ee7244d"),
testutil.HexToPubKey("0x954eb88ed1207f891dc3c28fa6cfdf8f53bf0ed3d838f3476c0900a61314d22d4f0a300da3cd010444dd5183e35a593c"),
testutil.HexToPubKey("0xaf344fce60dbd5fb850070e6e76a065e1a32485245ef4f413135a86ae703da88407c5d01c71f6bb06a151ff96cca7191"),
testutil.HexToPubKey("0xae241af60691fda1cf8ca44d49573c55818c53b6141800cca2d488b9a3fba71c0f869179fff50c084657831fbeb42bf4"),
testutil.HexToPubKey("0x96746aaba64dc87835ba709332f4d5d7837ada092b439c49d251aecf92aab5dc132e917bf6f59799bc093f976a7bc021"),
testutil.HexToPubKey("0xb9d1d914df3d4565465c3fd52b5b96e637f9980570cabf5b5d4aadf5a329ac36ad672819d997e735f5052e28b1f0c104"),
testutil.HexToPubKey("0x963528adb5322c2e2c54dc296ffddd2861bb103cbf64646781dfa8a3c2d8a8eda7079d2b3e95600028c44365afbf8879"),
testutil.HexToPubKey("0xb245d63d3f9d8ea1807a629fcb1b328cb4d542f35a3d5bc478be0df389dddd712fc4c816ba3fede9a96320ae6b24a7d8"),
testutil.HexToPubKey("0xa98ed496c2f464226500a6ce04602ff9ef133ed6316f372f6c744aee165149f7e578b12780e0eacec307ae6907351d99"),
testutil.HexToPubKey("0xae00fc3de831b09661a0ac02873c45c84cb2b58cffb6430a3f607e4c3fa1e0932397f11307cd169cdc6f79c463527260"),
testutil.HexToPubKey("0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d"),
}))
tests := []struct {
name string
validatorIndex spec.ValidatorIndex
epoch spec.Epoch
state api.ValidatorState
err string
}{
{
name: "Good",
validatorIndex: 1,
epoch: 0,
state: api.ValidatorStateActiveOngoing,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
state, err := s.ValidatorStateAtEpoch(ctx, test.validatorIndex, test.epoch)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.Equal(t, test.state, state)
}
})
}
}