Add sync committee contribution strategy.

This commit is contained in:
Jim McDonald 2021-08-15 00:00:02 +01:00
parent d7efbd856b
commit 945c85fb30
No known key found for this signature in database
GPG Key ID: 89CEB61B2AD2A5E7
28 changed files with 1299 additions and 37 deletions

13
go.mod
View File

@ -1,24 +1,21 @@
module github.com/attestantio/vouch
go 1.14
go 1.16
require (
cloud.google.com/go v0.81.0 // indirect
github.com/OneOfOne/xxhash v1.2.5 // indirect
github.com/attestantio/go-eth2-client v0.7.0-beta.2
github.com/attestantio/go-eth2-client v0.7.0
github.com/aws/aws-sdk-go v1.38.30
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect
github.com/opentracing/opentracing-go v1.2.0
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.9.0
github.com/prometheus/procfs v0.6.0 // indirect
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d
github.com/rs/zerolog v1.21.0
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7
github.com/rs/zerolog v1.23.0
github.com/sasha-s/go-deadlock v0.2.0
github.com/sirupsen/logrus v1.6.0
github.com/spf13/afero v1.5.1 // indirect
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.1
@ -35,7 +32,7 @@ require (
github.com/wealdtech/go-eth2-wallet-store-scratch v1.6.2
github.com/wealdtech/go-eth2-wallet-types/v2 v2.8.4
github.com/wealdtech/go-majordomo v1.0.1
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/atomic v1.7.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
google.golang.org/grpc v1.38.0
gotest.tools v2.2.0+incompatible

28
go.sum
View File

@ -47,8 +47,6 @@ github.com/HdrHistogram/hdrhistogram-go v0.9.0 h1:dpujRju0R4M/QZzcnR1LH1qm+TVG3U
github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.5 h1:zl/OfRA6nftbBK9qTohYBJ5xvw6C/oNKizR7cZGl3cI=
github.com/OneOfOne/xxhash v1.2.5/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
@ -70,10 +68,8 @@ github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6l
github.com/attestantio/dirk v1.0.2 h1:CYIRgQIbPqshwvgNJt98vV/ljhZmnAqfhQujoIpvGKg=
github.com/attestantio/dirk v1.0.2/go.mod h1:QHXxAnKD9cpuPC7STamSW2nPXEn7YTypcNFPAKfWTFQ=
github.com/attestantio/go-eth2-client v0.6.15/go.mod h1:Hya4fp1ZLWAFI64qMhNbQgfY4StWiHulW4CFwu+vP3s=
github.com/attestantio/go-eth2-client v0.6.28 h1:FmyUTzO/Lhhl10kINqzhbnShmg3hUGvex2H8m1BVUKw=
github.com/attestantio/go-eth2-client v0.6.28/go.mod h1:OlJziQa8y46JEBzjOsvmA+n72yHqui+xNLAHOUbg/VU=
github.com/attestantio/go-eth2-client v0.7.0-beta.2 h1:fYkoAd66aKpzVq2hu5zq+bTtYvpJhXFyUvo6RLd2dMM=
github.com/attestantio/go-eth2-client v0.7.0-beta.2/go.mod h1:kEK9iAAOBoADO5wEkd84FEOzjT1zXgVWveQsqn+uBGg=
github.com/attestantio/go-eth2-client v0.7.0 h1:3N4TfKIqS2DS5jCBMqbAzUeL+9LB9Te0froT4NtIDoE=
github.com/attestantio/go-eth2-client v0.7.0/go.mod h1:kEK9iAAOBoADO5wEkd84FEOzjT1zXgVWveQsqn+uBGg=
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.17/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
@ -103,7 +99,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
@ -113,6 +108,7 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
@ -152,7 +148,6 @@ github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGE
github.com/ferranbt/fastssz v0.0.0-20200728110133-0b6e349af87a/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20201030134205-9b9624098321/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20210120143747-11b9eff30ea9/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20210316165225-412ceaa5950e/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/ferranbt/fastssz v0.0.0-20210526181520-7df50c8568f8 h1:zhTRgKvm7CQxlGwJ7KfqT1AYDr2Q/caS6qrC7fwEtxU=
github.com/ferranbt/fastssz v0.0.0-20210526181520-7df50c8568f8/go.mod h1:DyEu2iuLBnb/T51BlsiO3yLYdJC6UbGMrIkqK1KmQxM=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
@ -170,7 +165,6 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@ -184,6 +178,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/goccy/go-yaml v1.8.4/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/goccy/go-yaml v1.8.9 h1:4AEXg2qx+/w29jXnXpMY6mTckmYu1TMoHteKuMf0HFg=
github.com/goccy/go-yaml v1.8.9/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -226,7 +221,6 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw=
github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
@ -348,7 +342,6 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.6 h1:dQ5ueTiftKxp0gyjKSx5+8BtPWkyQbd95m8Gys/RarI=
github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -407,8 +400,6 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA=
github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
@ -486,10 +477,9 @@ github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3 h1:0f++
github.com/prysmaticlabs/ethereumapis v0.0.0-20200812153649-a842fc47c2c3/go.mod h1:k7b2dxy6RppCG6kmOJkNOXzRpEoTdsPygc2aQhsUsZk=
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/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210202205921-7fcea7c45dc8 h1:18+Qqobq3HAUY0hgIhPGSqmLFnaLLocemmU7+Sj2aYQ=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210202205921-7fcea7c45dc8/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d h1:46gKr69IlRpv/ENdlzG0SWo5nMLKJxS3tI5NOSdZndQ=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210607200045-4da71aaf6c2d/go.mod h1:hCwmef+4qXWjv0jLDbQdWnL0Ol7cS7/lCSS26WR+u6s=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw=
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4=
github.com/r3labs/sse/v2 v2.3.0 h1:R/UMa0ML6AYKQ8irQNHhY+204lz1LytDIdKhCxSVAd8=
github.com/r3labs/sse/v2 v2.3.0/go.mod h1:hUrYMKfu9WquG9MyI0r6TKiNH+6Sw/QPKm2YbNbU5g8=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@ -499,8 +489,9 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo=
github.com/rs/zerolog v1.21.0 h1:Q3vdXlfLNT+OftyBHsU0Y445MD+8m8axjKgf2si0QcM=
github.com/rs/zerolog v1.21.0/go.mod h1:ZPhntP/xmq1nnND05hhpAh2QMhSsA4UN3MGZ6O2J3hM=
github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g=
github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -513,7 +504,6 @@ github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7Awj
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
@ -523,7 +513,6 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.4.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
@ -550,7 +539,6 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=

64
main.go
View File

@ -70,6 +70,8 @@ import (
firstattestationdatastrategy "github.com/attestantio/vouch/strategies/attestationdata/first"
bestbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/beaconblockproposal/best"
firstbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/beaconblockproposal/first"
bestsynccommitteecontributionstrategy "github.com/attestantio/vouch/strategies/synccommitteecontribution/best"
firstsynccommitteecontributionstrategy "github.com/attestantio/vouch/strategies/synccommitteecontribution/first"
"github.com/attestantio/vouch/util"
"github.com/aws/aws-sdk-go/aws/credentials"
homedir "github.com/mitchellh/go-homedir"
@ -89,7 +91,7 @@ import (
)
// ReleaseVersion is the release version for the code.
var ReleaseVersion = "1.1.0"
var ReleaseVersion = "1.2.0-pre"
func main() {
os.Exit(main2())
@ -424,6 +426,12 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
return errors.Wrap(err, "failed to start beacon committee subscriber service")
}
log.Trace().Msg("Selecting sync committee contribution provider")
syncCommitteeContributionProvider, err := selectSyncCommitteeContributionProvider(ctx, monitor, eth2Client)
if err != nil {
return errors.Wrap(err, "failed to select sync committee contribution provider")
}
log.Trace().Msg("Starting sync committee aggregator")
syncCommitteeAggregator, err = standardsynccommitteeaggregator.New(ctx,
standardsynccommitteeaggregator.WithLogLevel(logLevel(viper.GetString("synccommitteeaggregator.log-level"))),
@ -432,7 +440,7 @@ func startServices(ctx context.Context, majordomo majordomo.Service) error {
standardsynccommitteeaggregator.WithBeaconBlockRootProvider(eth2Client.(eth2client.BeaconBlockRootProvider)),
standardsynccommitteeaggregator.WithContributionAndProofSigner(signerSvc.(signer.ContributionAndProofSigner)),
standardsynccommitteeaggregator.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)),
standardsynccommitteeaggregator.WithSyncCommitteeContributionProvider(eth2Client.(eth2client.SyncCommitteeContributionProvider)),
standardsynccommitteeaggregator.WithSyncCommitteeContributionProvider(syncCommitteeContributionProvider),
standardsynccommitteeaggregator.WithSyncCommitteeContributionsSubmitter(submitterStrategy.(submitter.SyncCommitteeContributionsSubmitter)),
)
if err != nil {
@ -932,6 +940,58 @@ func selectBeaconBlockProposalProvider(ctx context.Context,
return beaconBlockProposalProvider, nil
}
func selectSyncCommitteeContributionProvider(ctx context.Context,
monitor metrics.Service,
eth2Client eth2client.Service,
) (eth2client.SyncCommitteeContributionProvider, error) {
var syncCommitteeContributionProvider eth2client.SyncCommitteeContributionProvider
var err error
switch viper.GetString("strategies.synccommitteecontribution.style") {
case "best":
log.Info().Msg("Starting best sync committee contribution strategy")
syncCommitteeContributionProviders := make(map[string]eth2client.SyncCommitteeContributionProvider)
for _, address := range viper.GetStringSlice("strategies.synccommitteecontribution.beacon-node-addresses") {
client, err := fetchClient(ctx, address)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for sync committee contribution strategy", address))
}
syncCommitteeContributionProviders[address] = client.(eth2client.SyncCommitteeContributionProvider)
}
syncCommitteeContributionProvider, err = bestsynccommitteecontributionstrategy.New(ctx,
bestsynccommitteecontributionstrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)),
bestsynccommitteecontributionstrategy.WithProcessConcurrency(util.ProcessConcurrency("strategies.synccommitteecontribution.best")),
bestsynccommitteecontributionstrategy.WithLogLevel(logLevel(viper.GetString("strategies.synccommitteecontribution.log-level"))),
bestsynccommitteecontributionstrategy.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start best sync committee contribution strategy")
}
case "first":
log.Info().Msg("Starting first sync committee contribution strategy")
syncCommitteeContributionProviders := make(map[string]eth2client.SyncCommitteeContributionProvider)
for _, address := range viper.GetStringSlice("strategies.synccommitteecontribution.beacon-node-addresses") {
client, err := fetchClient(ctx, address)
if err != nil {
return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for sync committee contribution strategy", address))
}
syncCommitteeContributionProviders[address] = client.(eth2client.SyncCommitteeContributionProvider)
}
syncCommitteeContributionProvider, err = firstsynccommitteecontributionstrategy.New(ctx,
firstsynccommitteecontributionstrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)),
firstsynccommitteecontributionstrategy.WithLogLevel(logLevel(viper.GetString("strategies.synccommitteecontribution.log-level"))),
firstsynccommitteecontributionstrategy.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
)
if err != nil {
return nil, errors.Wrap(err, "failed to start first sync committee contribution strategy")
}
default:
log.Info().Msg("Starting simple sync committee contribution strategy")
syncCommitteeContributionProvider = eth2Client.(eth2client.SyncCommitteeContributionProvider)
}
return syncCommitteeContributionProvider, nil
}
func selectSubmitterStrategy(ctx context.Context, monitor metrics.Service, eth2Client eth2client.Service) (submitter.Service, error) {
var submitter submitter.Service
var err error

View File

@ -1381,3 +1381,82 @@ func (m *ValidatorsWithoutBalanceProvider) ValidatorsWithoutBalance(ctx context.
}
return res, nil
}
// SyncCommitteeContributionProvider is a mock for eth2client.SyncCommitteeContributionProvider.
type SyncCommitteeContributionProvider struct{}
// NewSyncCommitteeContributionProvider returns a mock attestation data provider.
func NewSyncCommitteeContributionProvider() eth2client.SyncCommitteeContributionProvider {
return &SyncCommitteeContributionProvider{}
}
// SyncCommitteeContribution is a mock.
func (m *SyncCommitteeContributionProvider) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
aggregationBits := bitfield.NewBitvector128()
aggregationBits.SetBitAt(1, true)
aggregationBits.SetBitAt(3, true)
aggregationBits.SetBitAt(8, true)
aggregationBits.SetBitAt(12, true)
aggregationBits.SetBitAt(65, true)
aggregationBits.SetBitAt(77, true)
return &altair.SyncCommitteeContribution{
Slot: slot,
BeaconBlockRoot: beaconBlockRoot,
SubcommitteeIndex: subcommitteeIndex,
AggregationBits: aggregationBits,
Signature: phase0.BLSSignature([96]byte{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
}),
}, nil
}
// ErroringSyncCommitteeContributionProvider is a mock for eth2client.SyncCommitteeContributionProvider.
type ErroringSyncCommitteeContributionProvider struct{}
// NewErroringSyncCommitteeContributionProvider returns a mock attestation data provider.
func NewErroringSyncCommitteeContributionProvider() eth2client.SyncCommitteeContributionProvider {
return &ErroringSyncCommitteeContributionProvider{}
}
// SyncCommitteeContribution is a mock.
func (m *ErroringSyncCommitteeContributionProvider) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
return nil, errors.New("mock error")
}
// NilSyncCommitteeContributionProvider is a mock for eth2client.SyncCommitteeContributionProvider.
type NilSyncCommitteeContributionProvider struct{}
// NewNilSyncCommitteeContributionProvider returns a mock attestation data provider.
func NewNilSyncCommitteeContributionProvider() eth2client.SyncCommitteeContributionProvider {
return &NilSyncCommitteeContributionProvider{}
}
// SyncCommitteeContribution is a mock.
func (m *NilSyncCommitteeContributionProvider) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
return nil, nil
}
// SleepySyncCommitteeContributionProvider is a mock for eth2client.SyncCommitteeContributionProvider.
type SleepySyncCommitteeContributionProvider struct {
wait time.Duration
next eth2client.SyncCommitteeContributionProvider
}
// NewSleepySyncCommitteeContributionProvider returns a mock attestation data provider.
func NewSleepySyncCommitteeContributionProvider(wait time.Duration, next eth2client.SyncCommitteeContributionProvider) eth2client.SyncCommitteeContributionProvider {
return &SleepySyncCommitteeContributionProvider{
wait: wait,
next: next,
}
}
// SyncCommitteeContribution is a mock.
func (m *SleepySyncCommitteeContributionProvider) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
time.Sleep(m.wait)
return m.next.SyncCommitteeContribution(ctx, slot, subcommitteeIndex, beaconBlockRoot)
}

View File

@ -19,6 +19,7 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
@ -31,6 +32,7 @@ type aggregateAttestationResponse struct {
// AggregateAttestation provides the aggregate attestation from a number of beacon nodes.
func (s *Service) AggregateAttestation(ctx context.Context, slot phase0.Slot, attestationDataRoot phase0.Root) (*phase0.Attestation, error) {
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
// We create a cancelable context with a timeout. If the context times out we take the best to date.
ctx, cancel := context.WithTimeout(ctx, s.timeout)

View File

@ -74,7 +74,7 @@ func WithAggregateAttestationProviders(providers map[string]eth2client.Aggregate
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -19,12 +19,14 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
// AggregateAttestation provides the aggregate attestation from a number of beacon nodes.
func (s *Service) AggregateAttestation(ctx context.Context, slot phase0.Slot, attestationDataRoot phase0.Root) (*phase0.Attestation, error) {
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
// We create a cancelable context with a timeout. When a provider responds we cancel the context to cancel the other requests.
ctx, cancel := context.WithTimeout(ctx, s.timeout)

View File

@ -65,7 +65,7 @@ func WithAggregateAttestationProviders(providers map[string]eth2client.Aggregate
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -19,6 +19,7 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
@ -31,7 +32,7 @@ type attestationDataResponse struct {
// AttestationData provides the best attestation data from a number of beacon nodes.
func (s *Service) AttestationData(ctx context.Context, slot phase0.Slot, committeeIndex phase0.CommitteeIndex) (*phase0.AttestationData, error) {
started := time.Now()
log := log.With().Uint64("slot", uint64(slot)).Logger()
log := util.LogWithID(ctx, log, "strategy_id").With().Uint64("slot", uint64(slot)).Logger()
// We create a cancelable context with a timeout. If the context times out we take the best to date.
ctx, cancel := context.WithTimeout(ctx, s.timeout)

View File

@ -74,7 +74,7 @@ func WithAttestationDataProviders(providers map[string]eth2client.AttestationDat
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -19,12 +19,14 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
// AttestationData provides the first attestation data from a number of beacon nodes.
func (s *Service) AttestationData(ctx context.Context, slot phase0.Slot, committeeIndex phase0.CommitteeIndex) (*phase0.AttestationData, error) {
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
// We create a cancelable context with a timeout. When a provider responds we cancel the context to cancel the other requests.
ctx, cancel := context.WithTimeout(ctx, s.timeout)

View File

@ -65,7 +65,7 @@ func WithAttestationDataProviders(providers map[string]eth2client.AttestationDat
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -22,6 +22,7 @@ import (
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"golang.org/x/sync/semaphore"
)
@ -33,6 +34,7 @@ func (s *Service) BeaconBlockProposal(ctx context.Context, slot phase0.Slot, ran
bestProvider := ""
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
sem := semaphore.NewWeighted(s.processConcurrency)
var wg sync.WaitGroup
for name, provider := range s.beaconBlockProposalProviders {

View File

@ -53,7 +53,7 @@ func WithLogLevel(logLevel zerolog.Level) Parameter {
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -65,7 +65,7 @@ func WithBeaconBlockProposalProviders(providers map[string]eth2client.BeaconBloc
})
}
// WithTimeout sets the timeout for beacon block proposal requests.
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout

View File

@ -0,0 +1,112 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package best is a strategy that obtains sync committee contributions
// from multiple nodes and selects the best one.
package best
import (
"context"
"runtime"
"time"
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
clientMonitor metrics.ClientMonitor
processConcurrency int64
syncCommitteeContributionProviders map[string]eth2client.SyncCommitteeContributionProvider
timeout time.Duration
}
// 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
})
}
// WithClientMonitor sets the client monitor for the service.
func WithClientMonitor(monitor metrics.ClientMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.clientMonitor = monitor
})
}
// WithProcessConcurrency sets the concurrency for the service.
func WithProcessConcurrency(concurrency int64) Parameter {
return parameterFunc(func(p *parameters) {
p.processConcurrency = concurrency
})
}
// WithSyncCommitteeContributionProviders sets the sync committee contribution providers.
func WithSyncCommitteeContributionProviders(providers map[string]eth2client.SyncCommitteeContributionProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionProviders = providers
})
}
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
timeout: 2 * time.Second,
clientMonitor: nullmetrics.New(context.Background()),
processConcurrency: int64(runtime.GOMAXPROCS(-1)),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.timeout == 0 {
return nil, errors.New("no timeout specified")
}
if parameters.clientMonitor == nil {
return nil, errors.New("no client monitor specified")
}
if parameters.processConcurrency == 0 {
return nil, errors.New("no process concurrency specified")
}
if len(parameters.syncCommitteeContributionProviders) == 0 {
return nil, errors.New("no sync committee contribution providers specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,43 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best
import (
"context"
"fmt"
"github.com/attestantio/go-eth2-client/spec/altair"
)
// scoreSyncCommitteeContribution generates a score for an aggregate attestation.
// The score is relative to the completeness of the aggregate.
func (s *Service) scoreSyncCommitteeContribution(ctx context.Context,
name string,
contribution *altair.SyncCommitteeContribution,
) float64 {
if contribution == nil {
return 0
}
score := float64(contribution.AggregationBits.Count())
log.Trace().
Str("provider", name).
Uint64("sync_committee_slot", uint64(contribution.Slot)).
Uint64("subcommittee_index", contribution.SubcommitteeIndex).
Str("beacon_block_root", fmt.Sprintf("%#x", contribution.BeaconBlockRoot)).
Float64("score", score).
Msg("Scored sync committee contribution")
return score
}

View File

@ -0,0 +1,60 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is the provider for attestation data.
type Service struct {
clientMonitor metrics.ClientMonitor
processConcurrency int64
syncCommitteeContributionProviders map[string]eth2client.SyncCommitteeContributionProvider
timeout time.Duration
}
// module-wide log.
var log zerolog.Logger
// New creates a new attestation data strategy.
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("strategy", "synccommitteecontribution").Str("impl", "best").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
s := &Service{
timeout: parameters.timeout,
clientMonitor: parameters.clientMonitor,
processConcurrency: parameters.processConcurrency,
syncCommitteeContributionProviders: parameters.syncCommitteeContributionProviders,
}
log.Trace().Int64("process_concurrency", s.processConcurrency).Msg("Set process concurrency")
return s, nil
}

View File

@ -0,0 +1,114 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best_test
import (
"context"
"testing"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/strategies/synccommitteecontribution/best"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
syncCommitteeContributionProviders := map[string]eth2client.SyncCommitteeContributionProvider{
"localhost:1": mock.NewSyncCommitteeContributionProvider(),
}
tests := []struct {
name string
params []best.Parameter
err string
}{
{
name: "TimeoutZero",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithTimeout(0),
best.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
err: "problem with parameters: no timeout specified",
},
{
name: "ClientMonitorMissing",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithClientMonitor(nil),
best.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
err: "problem with parameters: no client monitor specified",
},
{
name: "SyncCommitteeContributionProvidersNil",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithSyncCommitteeContributionProviders(nil),
},
err: "problem with parameters: no sync committee contribution providers specified",
},
{
name: "ProcessConcurrencyZero",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
best.WithProcessConcurrency(0),
},
err: "problem with parameters: no process concurrency specified",
},
{
name: "SyncCommitteeContributionProvidersEmpty",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{}),
},
err: "problem with parameters: no sync committee contribution providers specified",
},
{
name: "Good",
params: []best.Parameter{
best.WithLogLevel(zerolog.TraceLevel),
best.WithTimeout(10 * time.Second),
best.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := best.New(context.Background(), test.params...)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestInterfaces(t *testing.T) {
syncCommitteeContributionProviders := map[string]eth2client.SyncCommitteeContributionProvider{
"localhost:1": mock.NewSyncCommitteeContributionProvider(),
}
s, err := best.New(context.Background(),
best.WithLogLevel(zerolog.Disabled),
best.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
)
require.NoError(t, err)
require.Implements(t, (*eth2client.SyncCommitteeContributionProvider)(nil), s)
}

View File

@ -0,0 +1,109 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
type syncCommitteeContributionResponse struct {
provider string
contribution *altair.SyncCommitteeContribution
score float64
}
// SyncCommitteeContribution provides the sync committee contribution from a number of beacon nodes.
func (s *Service) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
// We create a cancelable context with a timeout. If the context times out we take the best to date.
ctx, cancel := context.WithTimeout(ctx, s.timeout)
respCh := make(chan *syncCommitteeContributionResponse, len(s.syncCommitteeContributionProviders))
errCh := make(chan error, len(s.syncCommitteeContributionProviders))
// Kick off the requests.
for name, provider := range s.syncCommitteeContributionProviders {
go func(ctx context.Context,
name string,
provider eth2client.SyncCommitteeContributionProvider,
respCh chan *syncCommitteeContributionResponse,
errCh chan error,
) {
contribution, err := provider.SyncCommitteeContribution(ctx, slot, subcommitteeIndex, beaconBlockRoot)
s.clientMonitor.ClientOperation(name, "sync committee contribution", err == nil, time.Since(started))
if err != nil {
errCh <- err
return
}
log.Trace().Str("provider", name).Dur("elapsed", time.Since(started)).Msg("Obtained sync committee contribution")
if contribution == nil {
return
}
score := s.scoreSyncCommitteeContribution(ctx, name, contribution)
respCh <- &syncCommitteeContributionResponse{
provider: name,
contribution: contribution,
score: score,
}
}(ctx, name, provider, respCh, errCh)
}
// Wait for all responses (or context done).
responded := 0
errored := 0
bestScore := float64(0)
var bestSyncCommitteeContribution *altair.SyncCommitteeContribution
bestProvider := ""
for responded+errored != len(s.syncCommitteeContributionProviders) {
select {
case <-ctx.Done():
// Anyone not responded by now is considered errored.
errored = len(s.syncCommitteeContributionProviders) - responded
log.Debug().Dur("elapsed", time.Since(started)).Msg("Timed out waiting for responses")
case err := <-errCh:
errored++
log.Warn().Dur("elapsed", time.Since(started)).Err(err).Msg("Error")
case resp := <-respCh:
responded++
if bestSyncCommitteeContribution == nil || resp.score > bestScore {
bestSyncCommitteeContribution = resp.contribution
bestScore = resp.score
bestProvider = resp.provider
}
log.Trace().Dur("elapsed", time.Since(started)).Msg("Response")
}
}
log.Trace().Dur("elapsed", time.Since(started)).Int("responded", responded).Int("errored", errored).Str("best_provider", bestProvider).Msg("Complete")
cancel()
if bestSyncCommitteeContribution == nil {
return nil, errors.New("no sync committee contribution received")
}
log.Trace().Stringer("sync_committee_contribution", bestSyncCommitteeContribution).Float64("score", bestScore).Msg("Selected best sync committee contribution")
if bestProvider != "" {
s.clientMonitor.StrategyOperation("best", bestProvider, "sync committee contribution", time.Since(started))
}
return bestSyncCommitteeContribution, nil
}

View File

@ -0,0 +1,84 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best
import (
"context"
"testing"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/vouch/mock"
"github.com/prysmaticlabs/go-bitfield"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
// populatedBitvector creates a populated bitlist.
func populatedBitvector(set uint64) bitfield.Bitvector128 {
res := bitfield.NewBitvector128()
for i := uint64(0); i < set; i++ {
res.SetBitAt(i, true)
}
return res
}
func TestScore(t *testing.T) {
ctx := context.Background()
s, err := New(ctx,
WithLogLevel(zerolog.Disabled),
WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"good": mock.NewSyncCommitteeContributionProvider(),
}),
)
require.NoError(t, err)
tests := []struct {
name string
contribution *altair.SyncCommitteeContribution
score float64
}{
{
name: "Nil",
score: 0,
},
{
name: "Empty",
contribution: &altair.SyncCommitteeContribution{
Slot: 1,
SubcommitteeIndex: 2,
AggregationBits: populatedBitvector(0),
},
score: 0,
},
{
name: "Full",
contribution: &altair.SyncCommitteeContribution{
Slot: 1,
SubcommitteeIndex: 2,
AggregationBits: populatedBitvector(128),
},
score: 128,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
score := s.scoreSyncCommitteeContribution(ctx, "test", test.contribution)
require.Equal(t, test.score, score)
})
}
}

View File

@ -0,0 +1,120 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package best_test
import (
"context"
"testing"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/strategies/synccommitteecontribution/best"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestSyncCommitteeContribution(t *testing.T) {
tests := []struct {
name string
params []best.Parameter
slot phase0.Slot
subcommitteeIndex uint64
beaconBlockRoot phase0.Root
err string
}{
{
name: "Good",
params: []best.Parameter{
best.WithLogLevel(zerolog.Disabled),
best.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"good": mock.NewSyncCommitteeContributionProvider(),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
},
{
name: "Timeout",
params: []best.Parameter{
best.WithLogLevel(zerolog.Disabled),
best.WithTimeout(time.Second),
best.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"sleepy": mock.NewSleepySyncCommitteeContributionProvider(5*time.Second, mock.NewSyncCommitteeContributionProvider()),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
err: "no sync committee contribution received",
},
{
name: "NilResponse",
params: []best.Parameter{
best.WithLogLevel(zerolog.Disabled),
best.WithTimeout(time.Second),
best.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"nil": mock.NewNilSyncCommitteeContributionProvider(),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
err: "no sync committee contribution received",
},
{
name: "GoodMixed",
params: []best.Parameter{
best.WithLogLevel(zerolog.Disabled),
best.WithTimeout(2 * time.Second),
best.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"error": mock.NewErroringSyncCommitteeContributionProvider(),
"sleepy": mock.NewSleepySyncCommitteeContributionProvider(time.Second, mock.NewSyncCommitteeContributionProvider()),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
},
}
for _, test := range tests {
s, err := best.New(context.Background(), test.params...)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
contribution, err := s.SyncCommitteeContribution(context.Background(), test.slot, test.subcommitteeIndex, test.beaconBlockRoot)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.NotNil(t, contribution)
}
})
}
}

View File

@ -0,0 +1,99 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package first is a strategy that obtains sync committee contributions
// from multiple nodes and selects the first one returned.
package first
import (
"context"
"time"
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
clientMonitor metrics.ClientMonitor
syncCommitteeContributionProviders map[string]eth2client.SyncCommitteeContributionProvider
timeout time.Duration
}
// 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
})
}
// WithClientMonitor sets the client monitor for the service.
func WithClientMonitor(monitor metrics.ClientMonitor) Parameter {
return parameterFunc(func(p *parameters) {
p.clientMonitor = monitor
})
}
// WithSyncCommitteeContributionProviders sets the sync committee contribution providers.
func WithSyncCommitteeContributionProviders(providers map[string]eth2client.SyncCommitteeContributionProvider) Parameter {
return parameterFunc(func(p *parameters) {
p.syncCommitteeContributionProviders = providers
})
}
// WithTimeout sets the timeout for requests.
func WithTimeout(timeout time.Duration) Parameter {
return parameterFunc(func(p *parameters) {
p.timeout = timeout
})
}
// parseAndCheckParameters parses and checks parameters to ensure that mandatory parameters are present and correct.
func parseAndCheckParameters(params ...Parameter) (*parameters, error) {
parameters := parameters{
logLevel: zerolog.GlobalLevel(),
timeout: 2 * time.Second,
clientMonitor: nullmetrics.New(context.Background()),
}
for _, p := range params {
if params != nil {
p.apply(&parameters)
}
}
if parameters.timeout == 0 {
return nil, errors.New("no timeout specified")
}
if parameters.clientMonitor == nil {
return nil, errors.New("no client monitor specified")
}
if len(parameters.syncCommitteeContributionProviders) == 0 {
return nil, errors.New("no sync committee contribution providers specified")
}
return &parameters, nil
}

View File

@ -0,0 +1,57 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package first
import (
"context"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/services/metrics"
"github.com/pkg/errors"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
)
// Service is the provider for sync committee contributions.
type Service struct {
clientMonitor metrics.ClientMonitor
syncCommitteeContributionProviders map[string]eth2client.SyncCommitteeContributionProvider
timeout time.Duration
}
// module-wide log.
var log zerolog.Logger
// New creates a new attestation data strategy.
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("strategy", "synccommitteecontribution").Str("impl", "first").Logger()
if parameters.logLevel != log.GetLevel() {
log = log.Level(parameters.logLevel)
}
s := &Service{
syncCommitteeContributionProviders: parameters.syncCommitteeContributionProviders,
timeout: parameters.timeout,
clientMonitor: parameters.clientMonitor,
}
return s, nil
}

View File

@ -0,0 +1,105 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package first_test
import (
"context"
"testing"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/strategies/synccommitteecontribution/first"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestService(t *testing.T) {
syncCommitteeContributionProviders := map[string]eth2client.SyncCommitteeContributionProvider{
"localhost:1": mock.NewSyncCommitteeContributionProvider(),
}
tests := []struct {
name string
params []first.Parameter
err string
}{
{
name: "TimeoutZero",
params: []first.Parameter{
first.WithLogLevel(zerolog.TraceLevel),
first.WithTimeout(0),
first.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
err: "problem with parameters: no timeout specified",
},
{
name: "ClientMonitorMissing",
params: []first.Parameter{
first.WithLogLevel(zerolog.TraceLevel),
first.WithClientMonitor(nil),
first.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
err: "problem with parameters: no client monitor specified",
},
{
name: "SyncCommitteeContributionProvidersNil",
params: []first.Parameter{
first.WithLogLevel(zerolog.TraceLevel),
first.WithSyncCommitteeContributionProviders(nil),
},
err: "problem with parameters: no sync committee contribution providers specified",
},
{
name: "SyncCommitteeContributionProvidersEmpty",
params: []first.Parameter{
first.WithLogLevel(zerolog.TraceLevel),
first.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{}),
},
err: "problem with parameters: no sync committee contribution providers specified",
},
{
name: "Good",
params: []first.Parameter{
first.WithLogLevel(zerolog.TraceLevel),
first.WithTimeout(10 * time.Second),
first.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
_, err := first.New(context.Background(), test.params...)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
}
})
}
}
func TestInterfaces(t *testing.T) {
syncCommitteeContributionProviders := map[string]eth2client.SyncCommitteeContributionProvider{
"localhost:1": mock.NewSyncCommitteeContributionProvider(),
}
s, err := first.New(context.Background(),
first.WithLogLevel(zerolog.Disabled),
first.WithSyncCommitteeContributionProviders(syncCommitteeContributionProviders),
)
require.NoError(t, err)
require.Implements(t, (*eth2client.SyncCommitteeContributionProvider)(nil), s)
}

View File

@ -0,0 +1,69 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package first
import (
"context"
"fmt"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/altair"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/util"
"github.com/pkg/errors"
)
// SyncCommitteeContribution provides the sync committee contribution from a number of beacon nodes.
func (s *Service) SyncCommitteeContribution(ctx context.Context, slot phase0.Slot, subcommitteeIndex uint64, beaconBlockRoot phase0.Root) (*altair.SyncCommitteeContribution, error) {
started := time.Now()
log := util.LogWithID(ctx, log, "strategy_id")
// We create a cancelable context with a timeout. When a provider responds we cancel the context to cancel the other requests.
ctx, cancel := context.WithTimeout(ctx, s.timeout)
respCh := make(chan *altair.SyncCommitteeContribution, 1)
for name, provider := range s.syncCommitteeContributionProviders {
go func(ctx context.Context,
name string,
provider eth2client.SyncCommitteeContributionProvider,
ch chan *altair.SyncCommitteeContribution) {
log := log.With().Str("provider", name).Uint64("slot", uint64(slot)).Uint64("subcommittee_index", subcommitteeIndex).Str("beacon_block_root", fmt.Sprintf("%#x", beaconBlockRoot)).Logger()
contribution, err := provider.SyncCommitteeContribution(ctx, slot, subcommitteeIndex, beaconBlockRoot)
s.clientMonitor.ClientOperation(name, "sync committee contribution", err == nil, time.Since(started))
if err != nil {
log.Warn().Dur("elapsed", time.Since(started)).Err(err).Msg("Failed to obtain sync committee contribution")
return
}
if contribution == nil {
log.Warn().Dur("elapsed", time.Since(started)).Err(err).Msg("Returned empty sync committee contribution")
return
}
log.Trace().Str("provider", name).Dur("elapsed", time.Since(started)).Msg("Obtained sync committee contribution")
ch <- contribution
}(ctx, name, provider, respCh)
}
select {
case <-ctx.Done():
cancel()
log.Warn().Msg("Failed to obtain sync committee contribution before timeout")
return nil, errors.New("failed to obtain sync committee contribution before timeout")
case aggregate := <-respCh:
cancel()
return aggregate, nil
}
}

View File

@ -0,0 +1,121 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package first_test
import (
"context"
"testing"
"time"
eth2client "github.com/attestantio/go-eth2-client"
"github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/mock"
"github.com/attestantio/vouch/strategies/synccommitteecontribution/first"
"github.com/rs/zerolog"
"github.com/stretchr/testify/require"
)
func TestSyncCommitteeContribution(t *testing.T) {
tests := []struct {
name string
params []first.Parameter
slot phase0.Slot
subcommitteeIndex uint64
beaconBlockRoot phase0.Root
err string
}{
{
name: "Good",
params: []first.Parameter{
first.WithLogLevel(zerolog.Disabled),
first.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"good": mock.NewSyncCommitteeContributionProvider(),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
},
{
name: "Timeout",
params: []first.Parameter{
first.WithLogLevel(zerolog.Disabled),
first.WithTimeout(time.Second),
first.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"sleepy": mock.NewSleepySyncCommitteeContributionProvider(5*time.Second, mock.NewSyncCommitteeContributionProvider()),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
err: "failed to obtain sync committee contribution before timeout",
},
{
name: "NilResponse",
params: []first.Parameter{
first.WithLogLevel(zerolog.Disabled),
first.WithTimeout(time.Second),
first.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"nil": mock.NewNilSyncCommitteeContributionProvider(),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
// Nil response is invalid, so expect a timeout.
err: "failed to obtain sync committee contribution before timeout",
},
{
name: "GoodMixed",
params: []first.Parameter{
first.WithLogLevel(zerolog.Disabled),
first.WithTimeout(2 * time.Second),
first.WithSyncCommitteeContributionProviders(map[string]eth2client.SyncCommitteeContributionProvider{
"error": mock.NewErroringSyncCommitteeContributionProvider(),
"sleepy": mock.NewSleepySyncCommitteeContributionProvider(time.Second, mock.NewSyncCommitteeContributionProvider()),
}),
},
slot: 12345,
subcommitteeIndex: 1,
beaconBlockRoot: phase0.Root{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
},
},
}
for _, test := range tests {
s, err := first.New(context.Background(), test.params...)
require.NoError(t, err)
t.Run(test.name, func(t *testing.T) {
contribution, err := s.SyncCommitteeContribution(context.Background(), test.slot, test.subcommitteeIndex, test.beaconBlockRoot)
if test.err != "" {
require.EqualError(t, err, test.err)
} else {
require.NoError(t, err)
require.NotNil(t, contribution)
}
})
}
}

36
util/id.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright © 2021 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"context"
"fmt"
"math/rand"
"time"
"github.com/rs/zerolog"
)
func init() {
// We seed math.rand here so that we can obtain different IDs for requests.
// This is purely used as a way to match request and response entries in logs, so there is no
// requirement for this to cryptographically secure.
rand.Seed(time.Now().UnixNano())
}
// LogWithID returns a new logger based on the supplied logger with an additional ID field.
func LogWithID(ctx context.Context, log zerolog.Logger, tag string) zerolog.Logger {
// #nosec G404
return log.With().Str(tag, fmt.Sprintf("%02x", rand.Int31())).Logger()
}