mirror of https://github.com/certusone/vouch.git
Separate accountmanager.
This commit is contained in:
parent
b53a039da9
commit
fd48090007
|
@ -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
|
||||
|
||||
|
|
|
@ -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
10
go.mod
|
@ -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
17
go.sum
|
@ -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
152
main.go
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 ¶meters, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 ¶meters, nil
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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 ¶meters, nil
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ¶meters, nil
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 ¶meters, nil
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
|
@ -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(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
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 ¶meters, nil
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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(¶meters)
|
||||
}
|
||||
}
|
||||
|
||||
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 ¶meters, nil
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue