diff --git a/CHANGELOG.md b/CHANGELOG.md index e934c3e7..fefce79b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +1.8.2: + - update go-eth2-client dependency (compatibility with lodestar 1.20.0) + - use proposer V3 APIs + - do not error if proposal graffiti has been altered + 1.8.1: - ensure proposer-config-check command operates correctly - avoid crash by suitably locking a controller read/write map diff --git a/docs/configuration.md b/docs/configuration.md index 93e1bd0c..cee3dd6f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -114,19 +114,6 @@ strategies: # timeout defines the maximum amount of time the strategy will wait for a response. Different strategies may return earlier # if they have obtained enough information from their beacon node(s). timeout: '2s' - # The blindedbeaconblockproposal strategy obtains blinded beacon block proposals from multiple beacon nodes when using the block - # relay module to obtain execution payloads from MEV relays. - blindedbeaconblockproposal: - # style can be 'best', which obtains blocks from all beacon nodes and selects the best, or 'first', which uses the first returned - style: 'best' - # beacon-node-addresses are the addresses from which to receive beacon block proposals. - beacon-node-addresses: ['localhost:4000', 'localhost:5051', 'localhost:5052'] - # timeout defines the maximum amount of time the strategy will wait for a response. As soon as a response from all beacon - # nodes has been obtained,the strategy will return with the best. Half-way through the timeout period, Vouch will check to see - # if there have been any responses from the beacon nodes, and if so will return with the best. - # This allows Vouch to remain responsive in the situation where some beacon nodes are significantly slower than others, for - # example if one is remote. - timeout: '2s' # The attestationdata strategy obtains attestation data from multiple sources. attestationdata: # style can be 'best', which obtains attestation data from all nodes and selects the best, 'first', which uses the first returned, @@ -239,7 +226,6 @@ Modules levels are used for each module, overriding the global log level. The a - **strategies.attestationdata** decisions on how to obtain information from multiple beacon nodes - **strategies.aggregateattestation** decisions on how to obtain information from multiple beacon nodes - **strategies.beaconblockproposal** decisions on how to obtain information from multiple beacon nodes - - **strategies.blindedbeaconblockproposal** decisions on how to obtain information from multiple beacon nodes - **strategies.synccommitteecontribution** 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 diff --git a/go.mod b/go.mod index 3a7c4166..3ac01779 100644 --- a/go.mod +++ b/go.mod @@ -5,14 +5,14 @@ go 1.20 require ( github.com/attestantio/go-block-relay v0.2.4 github.com/attestantio/go-builder-client v0.4.3 - github.com/attestantio/go-eth2-client v0.19.10 + github.com/attestantio/go-eth2-client v0.21.6 github.com/aws/aws-sdk-go v1.49.21 github.com/holiman/uint256 v1.2.4 github.com/mitchellh/go-homedir v1.1.0 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 - github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 - github.com/rs/zerolog v1.31.0 + github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e + github.com/rs/zerolog v1.32.0 github.com/sasha-s/go-deadlock v0.3.1 github.com/shopspring/decimal v1.3.1 github.com/spf13/pflag v1.0.5 @@ -81,7 +81,7 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.3.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -92,6 +92,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect + github.com/pk910/dynamic-ssz v0.0.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.46.0 // indirect @@ -119,12 +120,12 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.7.0 // indirect - golang.org/x/crypto v0.18.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3 // indirect - golang.org/x/net v0.20.0 // indirect + golang.org/x/net v0.21.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.156.0 // indirect @@ -133,6 +134,7 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/Knetic/govaluate.v3 v3.0.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 41b12834..295fbe6f 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,8 @@ github.com/attestantio/go-block-relay v0.2.4 h1:Rnwr+KuT0VvxRPwlayiBKtVsiomOCgXc github.com/attestantio/go-block-relay v0.2.4/go.mod h1:J8hCQMiaYdFQxW1LNCvW4WYAqNXjVaDjp+J1Pj6sCc0= github.com/attestantio/go-builder-client v0.4.3 h1:K1m/PTqY01mfAPc0h+9iR2OivY3LOevbxHxEfvI4M8M= github.com/attestantio/go-builder-client v0.4.3/go.mod h1:yeJANU1O5P3b/4+iwShz9JMcgUnZABCh5RJBtZnLiDo= -github.com/attestantio/go-eth2-client v0.19.10 h1:NLs9mcBvZpBTZ3du7Ey2NHQoj8d3UePY7pFBXX6C6qs= -github.com/attestantio/go-eth2-client v0.19.10/go.mod h1:TTz7YF6w4z6ahvxKiHuGPn6DbQn7gH6HPuWm/DEQeGE= +github.com/attestantio/go-eth2-client v0.21.6 h1:eLhQ/OFHPhWrT7LYERL1Z6EPOoec6fJeDHsjjn73KBg= +github.com/attestantio/go-eth2-client v0.21.6/go.mod h1:d7ZPNrMX8jLfIgML5u7QZxFo2AukLM+5m08iMaLdqb8= github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.49.21 h1:Rl8KW6HqkwzhATwvXhyr7vD4JFUMi7oXGAw9SrxxIFY= github.com/aws/aws-sdk-go v1.49.21/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= @@ -298,8 +298,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= -github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -337,6 +337,8 @@ github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdU github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o= github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 h1:jik8PHtAIsPlCRJjJzl4udgEf7hawInF9texMeO2jrU= github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4= +github.com/pk910/dynamic-ssz v0.0.3 h1:fCWzFowq9P6SYCc7NtJMkZcIHk+r5hSVD+32zVi6Aio= +github.com/pk910/dynamic-ssz v0.0.3/go.mod h1:b6CrLaB2X7pYA+OSEEbkgXDEcRnjLOZIxZTsMuO/Y9c= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -351,8 +353,8 @@ github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqSc github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= -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/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e h1:ATgOe+abbzfx9kCPeXIW4fiWyDdxlwHw07j8UGhdTd4= +github.com/prysmaticlabs/go-bitfield v0.0.0-20240328144219-a1caa50c3a1e/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= github.com/r3labs/sse/v2 v2.10.0 h1:hFEkLLFY4LDifoHdiCN/LlGBAdVJYsANaLqNYa1l/v0= github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktEmkNJ7I= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -361,8 +363,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U= -github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= -github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -487,8 +489,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= -golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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= @@ -572,8 +574,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= -golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -680,8 +682,8 @@ golang.org/x/sys v0.0.0-20220818161305-2296e01440c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -694,8 +696,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -955,6 +957,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= +gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index b07b6c70..8fd57d53 100644 --- a/main.go +++ b/main.go @@ -85,8 +85,6 @@ import ( firstbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/beaconblockproposal/first" firstbeaconblockrootstrategy "github.com/attestantio/vouch/strategies/beaconblockroot/first" majoritybeaconblockrootstrategy "github.com/attestantio/vouch/strategies/beaconblockroot/majority" - bestblindedbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/blindedbeaconblockproposal/best" - firstblindedbeaconblockproposalstrategy "github.com/attestantio/vouch/strategies/blindedbeaconblockproposal/first" "github.com/attestantio/vouch/strategies/builderbid" bestbuilderbidstrategy "github.com/attestantio/vouch/strategies/builderbid/best" bestsynccommitteecontributionstrategy "github.com/attestantio/vouch/strategies/synccommitteecontribution/best" @@ -108,7 +106,7 @@ import ( ) // ReleaseVersion is the release version for the code. -var ReleaseVersion = "1.8.1" +var ReleaseVersion = "1.8.2" func main() { exitCode := main2() @@ -536,7 +534,6 @@ func startProviders(ctx context.Context, ) ( graffitiprovider.Service, eth2client.ProposalProvider, - eth2client.BlindedProposalProvider, eth2client.AttestationDataProvider, eth2client.AggregateAttestationProvider, error, @@ -544,34 +541,28 @@ func startProviders(ctx context.Context, log.Trace().Msg("Starting graffiti provider") graffitiProvider, err := startGraffitiProvider(ctx, majordomo) if err != nil { - return nil, nil, nil, nil, nil, errors.Wrap(err, "failed to start graffiti provider") + return nil, nil, nil, nil, errors.Wrap(err, "failed to start graffiti provider") } log.Trace().Msg("Selecting beacon block proposal provider") beaconBlockProposalProvider, err := selectProposalProvider(ctx, monitor, eth2Client, chainTime, cache) if err != nil { - return nil, nil, nil, nil, nil, errors.Wrap(err, "failed to select beacon block proposal provider") - } - - log.Trace().Msg("Selecting blinded beacon block proposal provider") - blindedProposalProvider, err := selectBlindedProposalProvider(ctx, monitor, eth2Client, chainTime, cache) - if err != nil { - return nil, nil, nil, nil, nil, errors.Wrap(err, "failed to select blinded beacon block proposal provider") + return nil, nil, nil, nil, errors.Wrap(err, "failed to select beacon block proposal provider") } log.Trace().Msg("Selecting attestation data provider") attestationDataProvider, err := selectAttestationDataProvider(ctx, monitor, eth2Client, chainTime, cache) if err != nil { - return nil, nil, nil, nil, nil, errors.Wrap(err, "failed to select attestation data provider") + return nil, nil, nil, nil, errors.Wrap(err, "failed to select attestation data provider") } log.Trace().Msg("Selecting aggregate attestation provider") aggregateAttestationProvider, err := selectAggregateAttestationProvider(ctx, monitor, eth2Client) if err != nil { - return nil, nil, nil, nil, nil, errors.Wrap(err, "failed to select aggregate attestation provider") + return nil, nil, nil, nil, errors.Wrap(err, "failed to select aggregate attestation provider") } - return graffitiProvider, beaconBlockProposalProvider, blindedProposalProvider, attestationDataProvider, aggregateAttestationProvider, nil + return graffitiProvider, beaconBlockProposalProvider, attestationDataProvider, aggregateAttestationProvider, nil } func startAltairServices(ctx context.Context, @@ -664,7 +655,7 @@ func startSigningServices(ctx context.Context, beaconcommitteesubscriber.Service, error, ) { - graffitiProvider, proposalProvider, blindedProposalProvider, attestationDataProvider, aggregateAttestationProvider, err := startProviders(ctx, majordomo, monitor, eth2Client, chainTime, cacheSvc) + graffitiProvider, proposalProvider, attestationDataProvider, aggregateAttestationProvider, err := startProviders(ctx, majordomo, monitor, eth2Client, chainTime, cacheSvc) if err != nil { return nil, nil, nil, nil, err } @@ -673,7 +664,6 @@ func startSigningServices(ctx context.Context, standardbeaconblockproposer.WithLogLevel(util.LogLevel("beaconblockproposer")), standardbeaconblockproposer.WithChainTime(chainTime), standardbeaconblockproposer.WithProposalDataProvider(proposalProvider), - standardbeaconblockproposer.WithBlindedProposalDataProvider(blindedProposalProvider), standardbeaconblockproposer.WithBlockAuctioneer(blockRelay.(blockauctioneer.BlockAuctioneer)), standardbeaconblockproposer.WithValidatingAccountsProvider(accountManager.(accountmanager.ValidatingAccountsProvider)), standardbeaconblockproposer.WithExecutionChainHeadProvider(cacheSvc.(cache.ExecutionChainHeadProvider)), @@ -1256,69 +1246,6 @@ func selectProposalProvider(ctx context.Context, return proposalProvider, nil } -// selectBlindedProposalProvider selects the appropriate blinded proposal provider given user input. -func selectBlindedProposalProvider(ctx context.Context, - monitor metrics.Service, - eth2Client eth2client.Service, - chainTime chaintime.Service, - cacheSvc cache.Service, -) (eth2client.BlindedProposalProvider, error) { - var blindedProposalProvider eth2client.BlindedProposalProvider - var err error - switch viper.GetString("strategies.blindedbeaconblockproposal.style") { - case "best": - log.Info().Msg("Starting best blinded beacon block proposal strategy") - blindedProposalProviders := make(map[string]eth2client.BlindedProposalProvider) - for _, address := range util.BeaconNodeAddresses("strategies.blindedbeaconblockproposal.best") { - client, err := fetchClient(ctx, monitor, address) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for blinded beacon block proposal strategy", address)) - } - blindedProposalProviders[address] = client.(eth2client.BlindedProposalProvider) - } - blindedProposalProvider, err = bestblindedbeaconblockproposalstrategy.New(ctx, - bestblindedbeaconblockproposalstrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)), - bestblindedbeaconblockproposalstrategy.WithProcessConcurrency(util.ProcessConcurrency("strategies.blindedbeaconblockproposal.best")), - bestblindedbeaconblockproposalstrategy.WithLogLevel(util.LogLevel("strategies.blindedbeaconblockproposal.best")), - bestblindedbeaconblockproposalstrategy.WithEventsProvider(eth2Client.(eth2client.EventsProvider)), - bestblindedbeaconblockproposalstrategy.WithChainTimeService(chainTime), - bestblindedbeaconblockproposalstrategy.WithSpecProvider(eth2Client.(eth2client.SpecProvider)), - bestblindedbeaconblockproposalstrategy.WithBlindedProposalProviders(blindedProposalProviders), - bestblindedbeaconblockproposalstrategy.WithSignedBeaconBlockProvider(eth2Client.(eth2client.SignedBeaconBlockProvider)), - bestblindedbeaconblockproposalstrategy.WithTimeout(util.Timeout("strategies.blindedbeaconblockproposal.best")), - bestblindedbeaconblockproposalstrategy.WithBlockRootToSlotCache(cacheSvc.(cache.BlockRootToSlotProvider)), - ) - if err != nil { - return nil, errors.Wrap(err, "failed to start best blinded beacon block proposal strategy") - } - case "first": - log.Info().Msg("Starting first blinded beacon block proposal strategy") - blindedProposalProviders := make(map[string]eth2client.BlindedProposalProvider) - for _, address := range util.BeaconNodeAddresses("strategies.blindedbeaconblockproposal.first") { - client, err := fetchClient(ctx, monitor, address) - if err != nil { - return nil, errors.Wrap(err, fmt.Sprintf("failed to fetch client %s for blinded beacon block proposal strategy", address)) - } - blindedProposalProviders[address] = client.(eth2client.BlindedProposalProvider) - } - blindedProposalProvider, err = firstblindedbeaconblockproposalstrategy.New(ctx, - firstblindedbeaconblockproposalstrategy.WithClientMonitor(monitor.(metrics.ClientMonitor)), - firstblindedbeaconblockproposalstrategy.WithLogLevel(util.LogLevel("strategies.blindedbeaconblockproposal.first")), - firstblindedbeaconblockproposalstrategy.WithChainTimeService(chainTime), - firstblindedbeaconblockproposalstrategy.WithBlindedProposalProviders(blindedProposalProviders), - firstblindedbeaconblockproposalstrategy.WithTimeout(util.Timeout("strategies.blindedbeaconblockproposal.first")), - ) - if err != nil { - return nil, errors.Wrap(err, "failed to start first blinded beacon block proposal strategy") - } - default: - log.Info().Msg("Starting simple blinded beacon block proposal strategy") - blindedProposalProvider = eth2Client.(eth2client.BlindedProposalProvider) - } - - return blindedProposalProvider, nil -} - // selectSyncCommitteeContributionProvider selects the appropriate sync committee contribution provider given user input. func selectSyncCommitteeContributionProvider(ctx context.Context, monitor metrics.Service, diff --git a/mock/eth2client.go b/mock/eth2client.go index e8025223..5cf703f7 100644 --- a/mock/eth2client.go +++ b/mock/eth2client.go @@ -18,19 +18,18 @@ import ( "context" "encoding/hex" "errors" + "math/big" "strings" "time" eth2client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" apiv1 "github.com/attestantio/go-eth2-client/api/v1" - apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/services/chaintime" "github.com/prysmaticlabs/go-bitfield" ) @@ -354,7 +353,7 @@ func NewProposalSubmitter() eth2client.ProposalSubmitter { } // SubmitProposal is a mock. -func (*ProposalSubmitter) SubmitProposal(_ context.Context, _ *api.VersionedSignedProposal) error { +func (*ProposalSubmitter) SubmitProposal(_ context.Context, _ *api.SubmitProposalOpts) error { return nil } @@ -367,7 +366,7 @@ func NewErroringProposalSubmitter() eth2client.ProposalSubmitter { } // SubmitProposal is a mock. -func (*ErroringProposalSubmitter) SubmitProposal(_ context.Context, _ *api.VersionedSignedProposal) error { +func (*ErroringProposalSubmitter) SubmitProposal(_ context.Context, _ *api.SubmitProposalOpts) error { return errors.New("error") } @@ -386,7 +385,7 @@ func NewSleepyProposalSubmitter(wait time.Duration, next eth2client.ProposalSubm } // SubmitProposal is a mock. -func (m *SleepyProposalSubmitter) SubmitProposal(ctx context.Context, proposal *api.VersionedSignedProposal) error { +func (m *SleepyProposalSubmitter) SubmitProposal(ctx context.Context, proposal *api.SubmitProposalOpts) error { time.Sleep(m.wait) return m.next.SubmitProposal(ctx, proposal) } @@ -400,7 +399,7 @@ func NewBlindedProposalSubmitter() eth2client.BlindedProposalSubmitter { } // SubmitBlindedProposal is a mock. -func (*BlindedProposalSubmitter) SubmitBlindedProposal(_ context.Context, _ *api.VersionedSignedBlindedProposal) error { +func (*BlindedProposalSubmitter) SubmitBlindedProposal(_ context.Context, _ *api.SubmitBlindedProposalOpts) error { return nil } @@ -413,7 +412,7 @@ func NewErroringBlindedProposalSubmitter() eth2client.BlindedProposalSubmitter { } // SubmitBlindedProposal is a mock. -func (*ErroringBlindedProposalSubmitter) SubmitBlindedProposal(_ context.Context, _ *api.VersionedSignedBlindedProposal) error { +func (*ErroringBlindedProposalSubmitter) SubmitBlindedProposal(_ context.Context, _ *api.SubmitBlindedProposalOpts) error { return errors.New("error") } @@ -432,9 +431,9 @@ func NewSleepyBlindedProposalSubmitter(wait time.Duration, next eth2client.Blind } // SubmitBlindedProposal is a mock. -func (m *SleepyBlindedProposalSubmitter) SubmitBlindedProposal(ctx context.Context, block *api.VersionedSignedBlindedProposal) error { +func (m *SleepyBlindedProposalSubmitter) SubmitBlindedProposal(ctx context.Context, opts *api.SubmitBlindedProposalOpts) error { time.Sleep(m.wait) - return m.next.SubmitBlindedProposal(ctx, block) + return m.next.SubmitBlindedProposal(ctx, opts) } // AggregateAttestationsSubmitter is a mock for eth2client.AggregateAttestationsSubmitter. @@ -633,7 +632,9 @@ func (*ProposalProvider) Proposal(_ context.Context, } block := &api.VersionedProposal{ - Version: spec.DataVersionCapella, + Version: spec.DataVersionCapella, + ConsensusValue: big.NewInt(12345), + ExecutionValue: big.NewInt(23456), Capella: &capella.BeaconBlock{ Slot: opts.Slot, ProposerIndex: 1, @@ -775,165 +776,6 @@ func (m *SleepyBeaconBlockRootProvider) BeaconBlockRoot(ctx context.Context, opt return m.next.BeaconBlockRoot(ctx, opts) } -// BlindedProposalProvider is a mock for eth2client.BlindedProposalProvider. -type BlindedProposalProvider struct { - chainTime chaintime.Service -} - -// NewBlindedProposalProvider returns a mock blinded beacon block proposal provider. -func NewBlindedProposalProvider(chainTime chaintime.Service) eth2client.BlindedProposalProvider { - return &BlindedProposalProvider{ - chainTime: chainTime, - } -} - -// BlindedProposal is a mock. -func (m *BlindedProposalProvider) BlindedProposal(_ context.Context, - opts *api.BlindedProposalOpts, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - // Build a beacon block. - - // Create a few attestations. - attestations := make([]*phase0.Attestation, 4) - for i := uint64(0); i < 4; i++ { - aggregationBits := bitfield.NewBitlist(128) - aggregationBits.SetBitAt(i, true) - attestations[i] = &phase0.Attestation{ - AggregationBits: aggregationBits, - Data: &phase0.AttestationData{ - Slot: opts.Slot - 1, - Index: phase0.CommitteeIndex(i), - BeaconBlockRoot: phase0.Root([32]byte{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - }), - Source: &phase0.Checkpoint{ - Epoch: 0, - Root: phase0.Root([32]byte{ - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - }), - }, - Target: &phase0.Checkpoint{ - Epoch: 1, - Root: phase0.Root([32]byte{ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, - }), - }, - }, - 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, - }), - } - } - - // Create an execution payload header. - executionPayloadHeader := &bellatrix.ExecutionPayloadHeader{ - FeeRecipient: bellatrix.ExecutionAddress{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, - }, - Timestamp: uint64(m.chainTime.StartOfSlot(opts.Slot).Unix()), - } - - block := &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: opts.Slot, - ProposerIndex: 1, - ParentRoot: phase0.Root([32]byte{ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, - }), - StateRoot: phase0.Root([32]byte{ - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, - }), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - RANDAOReveal: opts.RandaoReveal, - ETH1Data: &phase0.ETH1Data{ - DepositRoot: phase0.Root([32]byte{ - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, - }), - DepositCount: 16384, - BlockHash: []byte{ - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, - }, - }, - Graffiti: opts.Graffiti, - ProposerSlashings: []*phase0.ProposerSlashing{}, - AttesterSlashings: []*phase0.AttesterSlashing{}, - Attestations: attestations, - Deposits: []*phase0.Deposit{}, - VoluntaryExits: []*phase0.SignedVoluntaryExit{}, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - ExecutionPayloadHeader: executionPayloadHeader, - }, - }, - } - - return &api.Response[*api.VersionedBlindedProposal]{ - Data: block, - Metadata: make(map[string]any), - }, nil -} - -// ErroringBlindedProposalProvider is a mock for eth2client.BlindedProposalProvider. -type ErroringBlindedProposalProvider struct{} - -// NewErroringBlindedProposalProvider returns a mock blinded beacon block proposal provider. -func NewErroringBlindedProposalProvider() eth2client.BlindedProposalProvider { - return &ErroringBlindedProposalProvider{} -} - -// BlindedProposal is a mock. -func (*ErroringBlindedProposalProvider) BlindedProposal(_ context.Context, - _ *api.BlindedProposalOpts, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - return nil, errors.New("error") -} - -// SleepyBlindedProposalProvider is a mock for eth2client.BlindedProposalProvider. -type SleepyBlindedProposalProvider struct { - wait time.Duration - next eth2client.BlindedProposalProvider -} - -// NewSleepyBlindedProposalProvider returns a mock blinded beacon block proposal. -func NewSleepyBlindedProposalProvider(wait time.Duration, next eth2client.BlindedProposalProvider) eth2client.BlindedProposalProvider { - return &SleepyBlindedProposalProvider{ - wait: wait, - next: next, - } -} - -// BlindedProposal is a mock. -func (m *SleepyBlindedProposalProvider) BlindedProposal(ctx context.Context, - opts *api.BlindedProposalOpts, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - time.Sleep(m.wait) - return m.next.BlindedProposal(ctx, opts) -} - // BeaconBlockHeadersProvider is a mock for eth2client.BeaconBlockHeadersProvider. type BeaconBlockHeadersProvider struct{} diff --git a/services/beaconblockproposer/standard/parameters.go b/services/beaconblockproposer/standard/parameters.go index 425882da..bf94ff69 100644 --- a/services/beaconblockproposer/standard/parameters.go +++ b/services/beaconblockproposer/standard/parameters.go @@ -34,7 +34,6 @@ type parameters struct { chainTime chaintime.Service blockAuctioneer blockauctioneer.BlockAuctioneer proposalProvider eth2client.ProposalProvider - blindedProposalProvider eth2client.BlindedProposalProvider validatingAccountsProvider accountmanager.ValidatingAccountsProvider executionChainHeadProvider cache.ExecutionChainHeadProvider graffitiProvider graffitiprovider.Service @@ -84,13 +83,6 @@ func WithProposalDataProvider(provider eth2client.ProposalProvider) Parameter { }) } -// WithBlindedProposalDataProvider sets the proposal data provider. -func WithBlindedProposalDataProvider(provider eth2client.BlindedProposalProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.blindedProposalProvider = provider - }) -} - // WithMonitor sets the monitor for this module. func WithMonitor(monitor metrics.Service) Parameter { return parameterFunc(func(p *parameters) { @@ -174,9 +166,6 @@ func parseAndCheckParameters(params ...Parameter) (*parameters, error) { } // Some items are required if the auctioneer is present. if parameters.blockAuctioneer != nil { - if parameters.blindedProposalProvider == nil { - return nil, errors.New("no blinded proposal data provider specified") - } if parameters.executionChainHeadProvider == nil { return nil, errors.New("no execution chain head provider specified") } diff --git a/services/beaconblockproposer/standard/prepare_test.go b/services/beaconblockproposer/standard/prepare_test.go index 29a6f1ed..d1f28bc5 100644 --- a/services/beaconblockproposer/standard/prepare_test.go +++ b/services/beaconblockproposer/standard/prepare_test.go @@ -88,7 +88,6 @@ func TestPrepare(t *testing.T) { standard.WithBeaconBlockSigner(signer), standard.WithBlobSidecarSigner(signer), standard.WithBlockAuctioneer(blockAuctioneer), - standard.WithBlindedProposalDataProvider(consensusClient), standard.WithExecutionChainHeadProvider(cacheService.(cache.ExecutionChainHeadProvider)), ) require.NoError(t, err) diff --git a/services/beaconblockproposer/standard/propose.go b/services/beaconblockproposer/standard/propose.go index c4cf1c4c..affc8607 100644 --- a/services/beaconblockproposer/standard/propose.go +++ b/services/beaconblockproposer/standard/propose.go @@ -19,7 +19,6 @@ import ( "encoding/json" "fmt" "strings" - "sync" "time" "github.com/attestantio/go-block-relay/services/blockauctioneer" @@ -57,16 +56,6 @@ type BlindedProposerWithExpectedPayload interface { ) } -// auctionResult provides information on the result of an auction process. -type auctionResult int - -const ( - auctionResultSucceeded = iota + 1 - auctionResultFailed - auctionResultFailedCanTryWithout - auctionResultNoBids -) - // Propose proposes a block. func (s *Service) Propose(ctx context.Context, data interface{}) { ctx, span := otel.Tracer("attestantio.vouch.services.beaconblockproposer.standard").Start(ctx, "Propose") @@ -167,151 +156,65 @@ func (s *Service) proposeBlock(ctx context.Context, duty *beaconblockproposer.Duty, graffiti [32]byte, ) error { - // Pre-fetch an unblinded block in parallel with the auction process. - // This ensures that we are ready to propose as quickly as possible if the auction is unsuccessful. - var wg sync.WaitGroup - var proposal *api.VersionedProposal - wg.Add(1) - go func(ctx context.Context, duty *beaconblockproposer.Duty, graffiti [32]byte) { - var err error - proposalResponse, err := s.proposalProvider.Proposal(ctx, &api.ProposalOpts{ - Slot: duty.Slot(), - RandaoReveal: duty.RANDAOReveal(), - Graffiti: graffiti, - }) - if err != nil { - log.Warn().Err(err).Msg("Failed to pre-obtain proposal data") - return - } - proposal = proposalResponse.Data - log.Trace().Msg("Pre-obtained proposal") - wg.Done() - }(ctx, duty, graffiti) - + var auctionResults *blockauctioneer.Results + var err error if s.blockAuctioneer != nil { - // There is a block auctioneer specified, try to propose the block with auction. - result := s.proposeBlockWithAuction(ctx, duty, graffiti) - switch result { - case auctionResultSucceeded: - monitorBeaconBlockProposalSource("auction") - return nil - case auctionResultFailedCanTryWithout: - log.Warn().Uint64("slot", uint64(duty.Slot())).Msg("Failed to propose with auction; attempting to propose without auction") - case auctionResultNoBids: - log.Debug().Uint64("slot", uint64(duty.Slot())).Msg("No auction bids; attempting to propose without auction") - case auctionResultFailed: - return errors.New("failed to propose with auction too late in process, cannot fall back") + auctionResults, err = s.auctionBlock(ctx, duty) + if err != nil { + log.Error().Err(err).Msg("Failed to auction block") + } else { + monitorBestBidRelayCount(len(auctionResults.Providers)) } } - wg.Wait() - - err := s.proposeBlockWithoutAuction(ctx, proposal, duty, graffiti) - if err != nil { - return err - } - - monitorBeaconBlockProposalSource("direct") - return nil -} - -// proposeBlockWithAuction proposes a block after going through an auction for the blockspace. -func (s *Service) proposeBlockWithAuction(ctx context.Context, - duty *beaconblockproposer.Duty, - graffiti [32]byte, -) auctionResult { - ctx, span := otel.Tracer("attestantio.vouch.services.beaconblockproposer.standard").Start(ctx, "proposeBlockWithAuction") - defer span.End() - - log := log.With().Uint64("slot", uint64(duty.Slot())).Logger() - - auctionResults, err := s.auctionBlock(ctx, duty) - if err != nil { - log.Error().Err(err).Msg("Failed to auction block") - return auctionResultFailedCanTryWithout - } - if auctionResults.Bid == nil { - return auctionResultNoBids - } - monitorBestBidRelayCount(len(auctionResults.Providers)) - - proposal, err := s.obtainBlindedProposal(ctx, duty, graffiti, auctionResults) + proposalResponse, err := s.proposalProvider.Proposal(ctx, &api.ProposalOpts{ + Slot: duty.Slot(), + RandaoReveal: duty.RANDAOReveal(), + Graffiti: graffiti, + }) if err != nil { - log.Error().Err(err).Msg("Failed to obtain blinded proposal") - return auctionResultFailedCanTryWithout + return errors.Wrap(err, "failed to obtain proposal") } - - // Select the relays to unblind the proposal. - providers := make([]builderclient.UnblindedProposalProvider, 0, len(auctionResults.AllProviders)) - var unblindingCandidates []builderclient.BuilderBidProvider - if s.unblindFromAllRelays { - unblindingCandidates = auctionResults.AllProviders + proposal := proposalResponse.Data + if proposal.Blinded { + monitorBeaconBlockProposalSource("auction") } else { - unblindingCandidates = auctionResults.Providers - } - for _, provider := range unblindingCandidates { - unblindedProposalProvider, isProvider := provider.(builderclient.UnblindedProposalProvider) - if !isProvider { - log.Warn().Str("provider", provider.Name()).Msg("Auctioneer cannot unblind the proposal") - continue - } - providers = append(providers, unblindedProposalProvider) + monitorBeaconBlockProposalSource("direct") } - if len(providers) == 0 { - log.Debug().Msg("No relays can unblind the block") - return auctionResultFailedCanTryWithout - } - log.Trace().Int("providers", len(providers)).Msg("Obtained relays that can unblind the proposal") - signedBlindedBlock, err := s.signBlindedProposal(ctx, duty, proposal) - if err != nil { - log.Error().Err(err).Msg("Failed to sign blinded proposal") - return auctionResultFailed + if err := s.confirmProposalData(ctx, proposal, duty); err != nil { + return err } - signedProposal, err := s.unblindBlock(ctx, signedBlindedBlock, providers) + signedProposal, err := s.signProposalData(ctx, proposal, duty) if err != nil { - log.Error().Err(err).Msg("Failed to unblind block") - return auctionResultFailed - } - - // Submit the proposal. - if err := s.proposalSubmitter.SubmitProposal(ctx, signedProposal); err != nil { - log.Error().Err(err).Msg("Failed to submit beacon block proposal") - return auctionResultFailed + return err } - - return auctionResultSucceeded -} - -func (s *Service) proposeBlockWithoutAuction(ctx context.Context, - proposal *api.VersionedProposal, - duty *beaconblockproposer.Duty, - graffiti [32]byte, -) error { - ctx, span := otel.Tracer("attestantio.vouch.services.beaconblockproposer.standard").Start(ctx, "proposeBlockWithoutAuction") - defer span.End() - - if proposal == nil { - proposalResponse, err := s.proposalProvider.Proposal(ctx, &api.ProposalOpts{ - Slot: duty.Slot(), - RandaoReveal: duty.RANDAOReveal(), - Graffiti: graffiti, - }) - if err != nil { - return errors.Wrap(err, "failed to obtain proposal data") + if signedProposal.Blinded { + // Select the relays to unblind the proposal. + providers := make([]builderclient.UnblindedProposalProvider, 0, len(auctionResults.AllProviders)) + unblindingCandidates := auctionResults.Providers + if len(unblindingCandidates) == 0 || s.unblindFromAllRelays { + log.Trace().Int("providers", len(auctionResults.AllProviders)).Msg("Unblinding from all providers") + unblindingCandidates = auctionResults.AllProviders } - proposal = proposalResponse.Data - log.Trace().Msg("Obtained proposal") - } - if err := s.confirmProposalData(ctx, proposal, duty, graffiti); err != nil { - return err - } + for _, provider := range unblindingCandidates { + unblindedProposalProvider, isProvider := provider.(builderclient.UnblindedProposalProvider) + if !isProvider { + log.Warn().Str("provider", provider.Name()).Msg("Auctioneer cannot unblind the proposal") + continue + } + providers = append(providers, unblindedProposalProvider) + } + if len(providers) == 0 { + return errors.New("no relays to unblind the block") + } - signedProposal, err := s.signProposalData(ctx, proposal, duty) - if err != nil { - return err + log.Trace().Int("providers", len(providers)).Msg("Obtained relays that can unblind the proposal") + if err := s.unblindProposal(ctx, signedProposal, providers); err != nil { + return errors.Wrap(err, "failed to unblind block") + } } if err := s.proposalSubmitter.SubmitProposal(ctx, signedProposal); err != nil { @@ -324,7 +227,6 @@ func (s *Service) proposeBlockWithoutAuction(ctx context.Context, func (*Service) confirmProposalData(_ context.Context, proposal *api.VersionedProposal, duty *beaconblockproposer.Duty, - graffiti [32]byte, ) error { proposalSlot, err := proposal.Slot() if err != nil { @@ -334,13 +236,10 @@ func (*Service) confirmProposalData(_ context.Context, return errors.New("proposal data for incorrect slot") } - proposalGraffiti, err := proposal.Graffiti() - if err != nil { - return errors.Wrap(err, "failed to obtain proposal graffiti") - } - if !bytes.Equal(proposalGraffiti[:], graffiti[:]) { - return errors.New("proposal data contains incorrect graffiti") - } + // RANDAO reveal can be different in DVT situations, so do not check it. It will have already been checked by the underlying + // library that obtained the proposal, which is DVT-aware. + + // Graffiti can be different if the consensus nodes rewrites it, e.g. to add node version information, so do not check it. return nil } @@ -379,7 +278,10 @@ func (s *Service) signProposalData(ctx context.Context, } signedProposal := &api.VersionedSignedProposal{ - Version: proposal.Version, + Version: proposal.Version, + Blinded: proposal.Blinded, + ExecutionValue: proposal.ExecutionValue, + ConsensusValue: proposal.ConsensusValue, } switch proposal.Version { @@ -394,23 +296,44 @@ func (s *Service) signProposalData(ctx context.Context, Signature: sig, } case spec.DataVersionBellatrix: - signedProposal.Bellatrix = &bellatrix.SignedBeaconBlock{ - Message: proposal.Bellatrix, - Signature: sig, + if proposal.Blinded { + signedProposal.BellatrixBlinded = &apiv1bellatrix.SignedBlindedBeaconBlock{ + Message: proposal.BellatrixBlinded, + Signature: sig, + } + } else { + signedProposal.Bellatrix = &bellatrix.SignedBeaconBlock{ + Message: proposal.Bellatrix, + Signature: sig, + } } case spec.DataVersionCapella: - signedProposal.Capella = &capella.SignedBeaconBlock{ - Message: proposal.Capella, - Signature: sig, + if proposal.Blinded { + signedProposal.CapellaBlinded = &apiv1capella.SignedBlindedBeaconBlock{ + Message: proposal.CapellaBlinded, + Signature: sig, + } + } else { + signedProposal.Capella = &capella.SignedBeaconBlock{ + Message: proposal.Capella, + Signature: sig, + } } case spec.DataVersionDeneb: - signedProposal.Deneb = &apiv1deneb.SignedBlockContents{ - SignedBlock: &deneb.SignedBeaconBlock{ - Message: proposal.Deneb.Block, + if proposal.Blinded { + signedProposal.DenebBlinded = &apiv1deneb.SignedBlindedBeaconBlock{ + Message: proposal.DenebBlinded, Signature: sig, - }, - KZGProofs: proposal.Deneb.KZGProofs, - Blobs: proposal.Deneb.Blobs, + } + } else { + signedProposal.Deneb = &apiv1deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: proposal.Deneb.Block, + Signature: sig, + }, + KZGProofs: proposal.Deneb.KZGProofs, + Blobs: proposal.Deneb.Blobs, + } } default: return nil, errors.New("unhandled proposal version") @@ -454,164 +377,10 @@ func (s *Service) auctionBlock(ctx context.Context, return auctionResults, nil } -func (s *Service) obtainBlindedProposal(ctx context.Context, - duty *beaconblockproposer.Duty, - graffiti [32]byte, - auctionResults *blockauctioneer.Results, -) ( - *api.VersionedBlindedProposal, - error, -) { - var proposal *api.VersionedBlindedProposal - var err error - if verifyingProvider, isProvider := s.blindedProposalProvider.(BlindedProposerWithExpectedPayload); isProvider { - proposal, err = verifyingProvider.BlindedProposalWithExpectedPayload(ctx, duty.Slot(), duty.RANDAOReveal(), graffiti[:], auctionResults.Bid) - } else { - proposalResponse, err := s.blindedProposalProvider.BlindedProposal(ctx, &api.BlindedProposalOpts{ - Slot: duty.Slot(), - RandaoReveal: duty.RANDAOReveal(), - Graffiti: graffiti, - }) - if err != nil { - return nil, err - } - proposal = proposalResponse.Data - } - if err != nil { - return nil, err - } - - if e := log.Trace(); e.Enabled() { - data, err := json.Marshal(proposal) - if err == nil { - e.RawJSON("proposal", data).Msg("Obtained blinded proposal") - } - } - - if err := s.validateBlindedBeaconBlockProposal(ctx, duty, auctionResults, proposal); err != nil { - return nil, err - } - - return proposal, nil -} - -func (*Service) validateBlindedBeaconBlockProposal(_ context.Context, - duty *beaconblockproposer.Duty, - auctionResults *blockauctioneer.Results, - proposal *api.VersionedBlindedProposal, -) error { - if proposal == nil { - return errors.New("obtained nil blinded beacon block proposal") - } - - proposalSlot, err := proposal.Slot() - if err != nil { - return errors.Wrap(err, "failed to obtain proposal slot") - } - if proposalSlot != duty.Slot() { - return errors.New("proposal slot mismatch") - } - - _, err = proposal.ParentRoot() - if err != nil { - return errors.Wrap(err, "failed to obtain proposal parent root") - } - - _, err = proposal.StateRoot() - if err != nil { - return errors.Wrap(err, "failed to obtain proposal state root") - } - - _, err = proposal.BodyRoot() - if err != nil { - return errors.Wrap(err, "failed ot obtain proposal body root") - } - - proposalTransactionsRoot, err := proposal.TransactionsRoot() - if err != nil { - return errors.Wrap(err, "failed to obtain proposal transactions root") - } - auctionTransactionsRoot, err := auctionResults.Bid.TransactionsRoot() - if err != nil { - return errors.Wrap(err, "failed to obtain auction transactions root") - } - if !bytes.Equal(proposalTransactionsRoot[:], auctionTransactionsRoot[:]) { - log.Debug(). - Uint64("slot", uint64(duty.Slot())). - Str("proposal_transactions_root", fmt.Sprintf("%#x", proposalTransactionsRoot[:])). - Str("auction_transactions_root", fmt.Sprintf("%#x", auctionTransactionsRoot[:])). - Msg("Transactions root mismatch") - return errors.New("transactions root mismatch") - } - - return nil -} - -func (s *Service) signBlindedProposal(ctx context.Context, - duty *beaconblockproposer.Duty, - proposal *api.VersionedBlindedProposal, -) ( - *api.VersionedSignedBlindedProposal, - error, -) { - parentRoot, err := proposal.ParentRoot() - if err != nil { - return nil, errors.Wrap(err, "failed to obtain parent root") - } - stateRoot, err := proposal.StateRoot() - if err != nil { - return nil, errors.Wrap(err, "failed to obtain state root") - } - bodyRoot, err := proposal.BodyRoot() - if err != nil { - return nil, errors.Wrap(err, "failed to obtain body root") - } - - // Sign the block. - sig, err := s.beaconBlockSigner.SignBeaconBlockProposal(ctx, - duty.Account(), - duty.Slot(), - duty.ValidatorIndex(), - parentRoot, - stateRoot, - bodyRoot) - if err != nil { - return nil, errors.Wrap(err, "failed to sign blinded beacon block proposal") - } - - signedProposal := &api.VersionedSignedBlindedProposal{ - Version: proposal.Version, - } - switch signedProposal.Version { - case spec.DataVersionBellatrix: - signedProposal.Bellatrix = &apiv1bellatrix.SignedBlindedBeaconBlock{ - Message: proposal.Bellatrix, - Signature: sig, - } - case spec.DataVersionCapella: - signedProposal.Capella = &apiv1capella.SignedBlindedBeaconBlock{ - Message: proposal.Capella, - Signature: sig, - } - case spec.DataVersionDeneb: - signedProposal.Deneb = &apiv1deneb.SignedBlindedBeaconBlock{ - Message: proposal.Deneb, - Signature: sig, - } - default: - return nil, fmt.Errorf("unknown proposal version %v", signedProposal.Version) - } - - return signedProposal, nil -} - -func (*Service) unblindBlock(ctx context.Context, - proposal *api.VersionedSignedBlindedProposal, +func (*Service) unblindProposal(ctx context.Context, + proposal *api.VersionedSignedProposal, providers []builderclient.UnblindedProposalProvider, -) ( - *api.VersionedSignedProposal, - error, -) { +) error { // We do not create a cancelable context, as if we do cancel the later-returning providers they will mark themselves // as failed even if they are just running a little slow, which isn't a useful thing to do. Instead, we use a // semaphore to track if a signed block has been returned by any provider. @@ -630,7 +399,12 @@ func (*Service) unblindBlock(ctx context.Context, var err error for retries := 3; retries > 0; retries-- { // Unblind the blinded block. - signedProposal, err = provider.UnblindProposal(ctx, proposal) + signedProposal, err = provider.UnblindProposal(ctx, &api.VersionedSignedBlindedProposal{ + Version: proposal.Version, + Bellatrix: proposal.BellatrixBlinded, + Capella: proposal.CapellaBlinded, + Deneb: proposal.DenebBlinded, + }) if !sem.TryAcquire(1) { // We failed to acquire the semaphore, which means another relay has responded already. @@ -667,7 +441,7 @@ func (*Service) unblindBlock(ctx context.Context, select { case <-ctx.Done(): log.Warn().Msg("Failed to obtain unblinded block") - return nil, errors.New("failed to obtain unblinded block") + return errors.New("failed to obtain unblinded block") case signedBlock := <-respCh: if e := log.Trace(); e.Enabled() { data, err := json.Marshal(signedBlock) @@ -675,6 +449,21 @@ func (*Service) unblindBlock(ctx context.Context, e.RawJSON("signed_block", data).Msg("Recomposed block to submit") } } - return signedBlock, nil + switch proposal.Version { + case spec.DataVersionBellatrix: + proposal.BellatrixBlinded = nil + proposal.Bellatrix = signedBlock.Bellatrix + case spec.DataVersionCapella: + proposal.CapellaBlinded = nil + proposal.Capella = signedBlock.Capella + case spec.DataVersionDeneb: + proposal.DenebBlinded = nil + proposal.Deneb = signedBlock.Deneb + default: + return fmt.Errorf("unsupported version %v", proposal.Version) + } + proposal.Blinded = false + + return nil } } diff --git a/services/beaconblockproposer/standard/propose_test.go b/services/beaconblockproposer/standard/propose_test.go index 6505e9f3..a50be11c 100644 --- a/services/beaconblockproposer/standard/propose_test.go +++ b/services/beaconblockproposer/standard/propose_test.go @@ -140,7 +140,6 @@ func TestPropose(t *testing.T) { standard.WithBeaconBlockSigner(signer), standard.WithBlobSidecarSigner(signer), standard.WithBlockAuctioneer(blockAuctioneer), - standard.WithBlindedProposalDataProvider(consensusClient), standard.WithExecutionChainHeadProvider(cacheService.(cache.ExecutionChainHeadProvider)), ) require.NoError(t, err) diff --git a/services/beaconblockproposer/standard/service.go b/services/beaconblockproposer/standard/service.go index 41ee0d70..5b0bd9ab 100644 --- a/services/beaconblockproposer/standard/service.go +++ b/services/beaconblockproposer/standard/service.go @@ -40,7 +40,6 @@ type Service struct { chainTime chaintime.Service blockAuctioneer blockauctioneer.BlockAuctioneer proposalProvider eth2client.ProposalProvider - blindedProposalProvider eth2client.BlindedProposalProvider validatingAccountsProvider accountmanager.ValidatingAccountsProvider executionChainHeadProvider cache.ExecutionChainHeadProvider graffitiProvider graffitiprovider.Service @@ -75,7 +74,6 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) { chainTime: parameters.chainTime, blockAuctioneer: parameters.blockAuctioneer, proposalProvider: parameters.proposalProvider, - blindedProposalProvider: parameters.blindedProposalProvider, validatingAccountsProvider: parameters.validatingAccountsProvider, executionChainHeadProvider: parameters.executionChainHeadProvider, graffitiProvider: parameters.graffitiProvider, diff --git a/services/beaconblockproposer/standard/service_test.go b/services/beaconblockproposer/standard/service_test.go index 9141948e..b8ebab7e 100644 --- a/services/beaconblockproposer/standard/service_test.go +++ b/services/beaconblockproposer/standard/service_test.go @@ -190,23 +190,6 @@ func TestService(t *testing.T) { standard.WithBlobSidecarSigner(signer), }, }, - { - name: "BlindedProposalDataProviderMissing", - params: []standard.Parameter{ - standard.WithLogLevel(zerolog.Disabled), - standard.WithMonitor(nullmetrics.New(context.Background())), - standard.WithProposalDataProvider(consensusClient), - standard.WithChainTime(chainTime), - standard.WithValidatingAccountsProvider(validatingAccountsProvider), - standard.WithProposalSubmitter(consensusClient), - standard.WithRANDAORevealSigner(signer), - standard.WithBeaconBlockSigner(signer), - standard.WithBlobSidecarSigner(signer), - standard.WithBlockAuctioneer(blockAuctioneer), - standard.WithExecutionChainHeadProvider(cacheService.(cache.ExecutionChainHeadProvider)), - }, - err: "problem with parameters: no blinded proposal data provider specified", - }, { name: "ExecutionChainHeadProviderMissing", params: []standard.Parameter{ @@ -220,7 +203,6 @@ func TestService(t *testing.T) { standard.WithBeaconBlockSigner(signer), standard.WithBlobSidecarSigner(signer), standard.WithBlockAuctioneer(blockAuctioneer), - standard.WithBlindedProposalDataProvider(consensusClient), }, err: "problem with parameters: no execution chain head provider specified", }, @@ -237,7 +219,6 @@ func TestService(t *testing.T) { standard.WithBeaconBlockSigner(signer), standard.WithBlobSidecarSigner(signer), standard.WithBlockAuctioneer(blockAuctioneer), - standard.WithBlindedProposalDataProvider(consensusClient), standard.WithExecutionChainHeadProvider(cacheService.(cache.ExecutionChainHeadProvider)), }, }, diff --git a/services/submitter/immediate/service.go b/services/submitter/immediate/service.go index 6b53a729..665bd45c 100644 --- a/services/submitter/immediate/service.go +++ b/services/submitter/immediate/service.go @@ -84,7 +84,9 @@ func (s *Service) SubmitProposal(ctx context.Context, proposal *api.VersionedSig } started := time.Now() - err := s.proposalSubmitter.SubmitProposal(ctx, proposal) + err := s.proposalSubmitter.SubmitProposal(ctx, &api.SubmitProposalOpts{ + Proposal: proposal, + }) if service, isService := s.proposalSubmitter.(eth2client.Service); isService { s.clientMonitor.ClientOperation(service.Address(), "submit proposal", err == nil, time.Since(started)) } else { diff --git a/services/submitter/multinode/submitproposal.go b/services/submitter/multinode/submitproposal.go index 6691e4ed..41546341 100644 --- a/services/submitter/multinode/submitproposal.go +++ b/services/submitter/multinode/submitproposal.go @@ -86,7 +86,9 @@ func (s *Service) submitProposal(ctx context.Context, _, address := s.serviceInfo(ctx, submitter) started := time.Now() - err = submitter.SubmitProposal(ctx, proposal) + err = submitter.SubmitProposal(ctx, &api.SubmitProposalOpts{ + Proposal: proposal, + }) s.clientMonitor.ClientOperation(address, "submit proposal", err == nil, time.Since(started)) if err != nil { log.Warn().Err(err).Msg("Failed to submit proposal") diff --git a/services/submitter/multinode/submitproposal_test.go b/services/submitter/multinode/submitproposal_test.go index 0aeba3fb..def1794f 100644 --- a/services/submitter/multinode/submitproposal_test.go +++ b/services/submitter/multinode/submitproposal_test.go @@ -20,8 +20,9 @@ import ( eth2client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" + apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/deneb" "github.com/attestantio/vouch/mock" "github.com/attestantio/vouch/services/submitter/multinode" "github.com/attestantio/vouch/testing/logger" @@ -104,10 +105,12 @@ func TestSubmitProposal(t *testing.T) { require.NoError(t, err) err = s.SubmitProposal(ctx, &api.VersionedSignedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 1, + Version: spec.DataVersionDeneb, + Deneb: &apiv1deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: &deneb.BeaconBlock{ + Slot: 1, + }, }, }, }) @@ -153,10 +156,12 @@ func TestSubmitProposalErroring(t *testing.T) { require.NoError(t, err) err = s.SubmitProposal(ctx, &api.VersionedSignedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 1, + Version: spec.DataVersionDeneb, + Deneb: &apiv1deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: &deneb.BeaconBlock{ + Slot: 1, + }, }, }, }) @@ -198,10 +203,12 @@ func TestSubmitProposalSleepy(t *testing.T) { require.NoError(t, err) err = s.SubmitProposal(ctx, &api.VersionedSignedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 1, + Version: spec.DataVersionDeneb, + Deneb: &apiv1deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: &deneb.BeaconBlock{ + Slot: 1, + }, }, }, }) @@ -243,10 +250,12 @@ func TestSubmitProposalSleepySuccess(t *testing.T) { require.NoError(t, err) err = s.SubmitProposal(ctx, &api.VersionedSignedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 1, + Version: spec.DataVersionDeneb, + Deneb: &apiv1deneb.SignedBlockContents{ + SignedBlock: &deneb.SignedBeaconBlock{ + Message: &deneb.BeaconBlock{ + Slot: 1, + }, }, }, }) diff --git a/strategies/beaconblockproposal/best/beaconblockproposal.go b/strategies/beaconblockproposal/best/beaconblockproposal.go index 0e4a9954..42781bd4 100644 --- a/strategies/beaconblockproposal/best/beaconblockproposal.go +++ b/strategies/beaconblockproposal/best/beaconblockproposal.go @@ -16,6 +16,7 @@ package best import ( "bytes" "context" + "math/big" "time" eth2client "github.com/attestantio/go-eth2-client" @@ -204,6 +205,10 @@ func (s *Service) Proposal(ctx context.Context, s.clientMonitor.StrategyOperation("best", bestProvider, "beacon block proposal", time.Since(started)) } + span.SetAttributes( + attribute.String("value", new(big.Int).Add(bestProposal.ConsensusValue, bestProposal.ExecutionValue).String()), + attribute.Bool("blinded", bestProposal.Blinded), + ) return &api.Response[*api.VersionedProposal]{ Data: bestProposal, Metadata: make(map[string]any), diff --git a/strategies/beaconblockproposal/best/events_internal_test.go b/strategies/beaconblockproposal/best/events_internal_test.go index cf960bb0..b6f3f58e 100644 --- a/strategies/beaconblockproposal/best/events_internal_test.go +++ b/strategies/beaconblockproposal/best/events_internal_test.go @@ -34,6 +34,14 @@ import ( "github.com/stretchr/testify/require" ) +func bitList(set uint64, total uint64) bitfield.Bitlist { + bits := bitfield.NewBitlist(total) + for i := uint64(0); i < set; i++ { + bits.SetBitAt(i, true) + } + return bits +} + // TestUpdateBlockVotes tests the internal function updateBlockVotes. func TestUpdateBlockVotes(t *testing.T) { ctx := context.Background() diff --git a/strategies/beaconblockproposal/best/score.go b/strategies/beaconblockproposal/best/score.go index 2ef2a78f..65f448db 100644 --- a/strategies/beaconblockproposal/best/score.go +++ b/strategies/beaconblockproposal/best/score.go @@ -14,680 +14,30 @@ package best import ( - "bytes" "context" - "fmt" - "sort" + "math/big" "github.com/attestantio/go-eth2-client/api" - apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/go-eth2-client/spec/capella" - "github.com/attestantio/go-eth2-client/spec/deneb" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/prysmaticlabs/go-bitfield" ) -// scoreBeaconBlockPropsal generates a score for a beacon block. -// The score is relative to the reward expected by proposing the block. -func (s *Service) scoreBeaconBlockProposal(ctx context.Context, +// scoreBeaconBlockProposal generates a score for a beacon block. +// The score is the reward expected by proposing the block. +func (*Service) scoreBeaconBlockProposal(_ context.Context, name string, blockProposal *api.VersionedProposal, ) float64 { if blockProposal == nil { return 0 } - if blockProposal.IsEmpty() { - return 0 - } - - // Obtain the slot of the block to which the proposal refers. - // We use this to allow the scorer to score blocks with earlier parents lower. - parentRoot, err := blockProposal.ParentRoot() - if err != nil { - log.Error().Str("version", blockProposal.Version.String()).Msg("Failed to obtain parent root") - return 0 - } - parentSlot, err := s.blockRootToSlotCache.BlockRootToSlot(ctx, parentRoot) - if err != nil { - log.Debug().Str("root", fmt.Sprintf("%#x", parentRoot)).Err(err).Msg("Failed to obtain parent slot; assuming 0") - parentSlot = 0 - } - - switch blockProposal.Version { - case spec.DataVersionPhase0: - return s.scorePhase0BeaconBlockProposal(ctx, name, parentSlot, blockProposal.Phase0) - case spec.DataVersionAltair: - return s.scoreAltairBeaconBlockProposal(ctx, name, parentSlot, blockProposal.Altair) - case spec.DataVersionBellatrix: - return s.scoreBellatrixBeaconBlockProposal(ctx, name, parentSlot, blockProposal.Bellatrix) - case spec.DataVersionCapella: - return s.scoreCapellaBeaconBlockProposal(ctx, name, parentSlot, blockProposal.Capella) - case spec.DataVersionDeneb: - return s.scoreDenebBeaconBlockProposal(ctx, name, parentSlot, blockProposal.Deneb) - default: - log.Error().Int("version", int(blockProposal.Version)).Msg("Unhandled block version") - return 0 - } -} - -// scorePhase0BeaconBlockPropsal generates a score for a phase 0 beacon block. -func (*Service) scorePhase0BeaconBlockProposal(_ context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *phase0.BeaconBlock, -) float64 { - immediateAttestationScore := float64(0) - attestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - // Calculate inclusion score. - inclusionDistance := float64(blockProposal.Slot - data.Slot) - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) && !attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - attestationScore += float64(0.75) + float64(0.25)/inclusionDistance - if inclusionDistance == 1 { - immediateAttestationScore += 1.0 - } - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Scale scores by the distance between the proposal and parent slots. - var scale uint64 - if blockProposal.Slot <= parentSlot { - log.Warn().Uint64("slot", uint64(blockProposal.Slot)).Uint64("parent_slot", uint64(parentSlot)).Msg("Invalid parent slot for proposal") - scale = 32 - } else { - scale = uint64(blockProposal.Slot - parentSlot) - } - - log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Uint64("scale", scale). - Float64("total", attestationScore*float64(scale)+proposerSlashingScore+attesterSlashingScore). - Msg("Scored phase 0 block") - - return attestationScore/float64(scale) + proposerSlashingScore + attesterSlashingScore -} - -// scoreAltairBeaconBlockPropsal generates a score for an altair beacon block. -func (s *Service) scoreAltairBeaconBlockProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *altair.BeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := altairHeadCorrect(blockProposal, attestation) - targetCorrect := s.altairTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore). - Msg("Scored Altair block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore -} - -// scoreBellatrixBeaconBlockPropsal generates a score for a bellatrix beacon block. -func (s *Service) scoreBellatrixBeaconBlockProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *bellatrix.BeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := bellatrixHeadCorrect(blockProposal, attestation) - targetCorrect := s.bellatrixTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - // Add execution payload score. - executionPayloadScore := float64(0) - if blockProposal.Body.ExecutionPayload != nil { - // Value is based on the gas used. Transactions are opaque, so we cannot see the gas price to calculate a true numerical value. - // We scale the gas used to normalise with the consensus value. - executionPayloadScore = float64(blockProposal.Body.ExecutionPayload.GasUsed) * s.executionPayloadFactor - } - - log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("execution_payload", executionPayloadScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore+executionPayloadScore). - Msg("Scored Bellatrix block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore + executionPayloadScore -} - -// scoreCapellaBeaconBlockPropsal generates a score for a capella beacon block. -func (s *Service) scoreCapellaBeaconBlockProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *capella.BeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := capellaHeadCorrect(blockProposal, attestation) - targetCorrect := s.capellaTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - // Add execution payload score. - executionPayloadScore := float64(0) - if blockProposal.Body.ExecutionPayload != nil { - // Value is based on the gas used. Transactions are opaque, so we cannot see the gas price to calculate a true numerical value. - // We scale the gas used to normalise with the consensus value. - executionPayloadScore = float64(blockProposal.Body.ExecutionPayload.GasUsed) * s.executionPayloadFactor - } + score, _ := new(big.Int).Add(blockProposal.ConsensusValue, blockProposal.ExecutionValue).Float64() log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("execution_payload", executionPayloadScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore+executionPayloadScore). - Msg("Scored Capella block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore + executionPayloadScore -} - -// scoreDenebBeaconBlockPropsal generates a score for a deneb beacon block. -func (s *Service) scoreDenebBeaconBlockProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *apiv1deneb.BlockContents, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Block.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.Block.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := denebHeadCorrect(blockProposal.Block, attestation) - targetCorrect := s.denebTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Block.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Block.Body.AttesterSlashings, blockProposal.Block.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Block.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - // Add execution payload score. - blobScore := float64(0) - executionPayloadScore := float64(0) - if blockProposal.Block.Body.ExecutionPayload != nil { - // Value is based on the gas used. Transactions are opaque, so we cannot see the gas price to calculate a true numerical value. - // We scale the gas used to normalise with the consensus value. - executionPayloadScore = float64(blockProposal.Block.Body.ExecutionPayload.GasUsed) * s.executionPayloadFactor - blobScore = float64(blockProposal.Block.Body.ExecutionPayload.BlobGasUsed) * s.executionPayloadFactor - } - - log.Trace(). - Uint64("slot", uint64(blockProposal.Block.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("execution_payload", executionPayloadScore). - Float64("blob", blobScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore+executionPayloadScore+blobScore). - Msg("Scored Capella block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore + executionPayloadScore + blobScore -} - -func scoreSlashings(attesterSlashings []*phase0.AttesterSlashing, - proposerSlashings []*phase0.ProposerSlashing, -) (float64, float64) { - // Slashing reward will be at most MAX_EFFECTIVE_BALANCE/WHISTLEBLOWER_REWARD_QUOTIENT, - // which is 0.0625 Ether. - // Individual attestation reward at 250K validators will be around 23,000 GWei, or .000023 Ether. - // So we state that a single slashing event has the same weight as about 2,700 attestations. - slashingWeight := float64(2700) - - // Add proposer slashing scores. - proposerSlashingScore := float64(len(proposerSlashings)) * slashingWeight - - // Add attester slashing scores. - indicesSlashed := 0 - for _, slashing := range attesterSlashings { - indicesSlashed += len(intersection(slashing.Attestation1.AttestingIndices, slashing.Attestation2.AttestingIndices)) - } - attesterSlashingScore := slashingWeight * float64(indicesSlashed) - - return attesterSlashingScore, proposerSlashingScore -} - -func (s *Service) priorVotesForAttestation(_ context.Context, - attestation *phase0.Attestation, - root phase0.Root, -) ( - bitfield.Bitlist, - error, -) { - var res bitfield.Bitlist - var err error - found := false - s.priorBlocksVotesMu.RLock() - for { - priorBlock, exists := s.priorBlocksVotes[root] - if !exists { - // This means we do not have a parent block. - break - } - if priorBlock.slot < attestation.Data.Slot-phase0.Slot(s.slotsPerEpoch) { - // Block is too far back for its attestations to count. - break - } - - slotVotes, exists := priorBlock.votes[attestation.Data.Slot] - if exists { - votes, exists := slotVotes[attestation.Data.Index] - if exists { - if !found { - res = bitfield.NewBitlist(votes.Len()) - found = true - } - res, err = res.Or(votes) - if err != nil { - return bitfield.Bitlist{}, err - } - } - } - - root = priorBlock.parent - } - s.priorBlocksVotesMu.RUnlock() - - if !found { - // No prior votes found, return an empty list. - return bitfield.NewBitlist(attestation.AggregationBits.Len()), nil - } - - return res, nil -} - -// altairHeadCorrect calculates if the head of an Altair attestation is correct. -func altairHeadCorrect(blockProposal *altair.BeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// altairTargetCorrect calculates if the target of an Altair attestation is correct. -func (s *Service) altairTargetCorrect(_ context.Context, - attestation *phase0.Attestation, -) bool { - s.priorBlocksVotesMu.RLock() - defer s.priorBlocksVotesMu.RUnlock() - root := attestation.Data.BeaconBlockRoot - maxSlot := s.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch) - for { - priorBlock, exists := s.priorBlocksVotes[root] - if !exists { - // We don't have data on this block, assume the target is correct. - // (We could assume the target is incorrect in this situation, but that - // would give false incorrects whilst the prior block cache warms up.) - log.Trace().Uint64("attestation_slot", uint64(attestation.Data.Slot)).Uint64("max_slot", uint64(maxSlot)).Str("root", fmt.Sprintf("%#x", root)).Msg("Root does not exist, assuming true") - return true - } - if priorBlock.slot <= maxSlot { - return bytes.Equal(attestation.Data.Target.Root[:], priorBlock.root[:]) - } - root = priorBlock.parent - } -} - -// bellatrixHeadCorrect calculates if the head of a Bellatrix attestation is correct. -func bellatrixHeadCorrect(blockProposal *bellatrix.BeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// bellatrixTargetCorrect calculates if the target of a Bellatrix attestation is correct. -func (s *Service) bellatrixTargetCorrect(ctx context.Context, - attestation *phase0.Attestation, -) bool { - // Same as Altair. - return s.altairTargetCorrect(ctx, attestation) -} - -// capellaHeadCorrect calculates if the head of a Capella attestation is correct. -func capellaHeadCorrect(blockProposal *capella.BeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// capellaTargetCorrect calculates if the target of a Capella attestation is correct. -func (s *Service) capellaTargetCorrect(ctx context.Context, - attestation *phase0.Attestation, -) bool { - // Same as Altair. - return s.altairTargetCorrect(ctx, attestation) -} - -// denebHeadCorrect calculates if the head of a Deneb attestation is correct. -func denebHeadCorrect(blockProposal *deneb.BeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// denebTargetCorrect calculates if the target of a Deneb attestation is correct. -func (s *Service) denebTargetCorrect(ctx context.Context, - attestation *phase0.Attestation, -) bool { - // Same as Altair. - return s.altairTargetCorrect(ctx, attestation) -} - -// intersection returns a list of items common between the two sets. -func intersection(set1 []uint64, set2 []uint64) []uint64 { - sort.Slice(set1, func(i, j int) bool { return set1[i] < set1[j] }) - sort.Slice(set2, func(i, j int) bool { return set2[i] < set2[j] }) - res := make([]uint64, 0) - - set1Pos := 0 - set2Pos := 0 - for set1Pos < len(set1) && set2Pos < len(set2) { - switch { - case set1[set1Pos] < set2[set2Pos]: - set1Pos++ - case set2[set2Pos] < set1[set1Pos]: - set2Pos++ - default: - res = append(res, set1[set1Pos]) - set1Pos++ - set2Pos++ - } - } + Str("name", name). + Stringer("consensus_value", blockProposal.ConsensusValue). + Stringer("execution_value", blockProposal.ExecutionValue). + Float64("score", score). + Msg("Scored block") - return res + return score } diff --git a/strategies/beaconblockproposal/best/score_internal_test.go b/strategies/beaconblockproposal/best/score_internal_test.go deleted file mode 100644 index 138c474f..00000000 --- a/strategies/beaconblockproposal/best/score_internal_test.go +++ /dev/null @@ -1,904 +0,0 @@ -// Copyright © 2020 - 2023 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" - "time" - - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/go-eth2-client/spec/capella" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/mock" - "github.com/attestantio/vouch/services/cache" - mockcache "github.com/attestantio/vouch/services/cache/mock" - standardchaintime "github.com/attestantio/vouch/services/chaintime/standard" - "github.com/attestantio/vouch/services/metrics/null" - "github.com/attestantio/vouch/testutil" - "github.com/prysmaticlabs/go-bitfield" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func bitList(set uint64, total uint64) bitfield.Bitlist { - bits := bitfield.NewBitlist(total) - for i := uint64(0); i < set; i++ { - bits.SetBitAt(i, true) - } - return bits -} - -func specificAggregationBits(set []uint64, total uint64) bitfield.Bitlist { - bits := bitfield.NewBitlist(total) - for _, pos := range set { - bits.SetBitAt(pos, true) - } - return bits -} - -func TestScore(t *testing.T) { - tests := []struct { - name string - priorBlocks map[phase0.Root]*priorBlockVotes - proposal *api.VersionedProposal - score float64 - err string - }{ - { - name: "Nil", - score: 0, - }, - { - name: "Empty", - proposal: &api.VersionedProposal{}, - score: 0, - }, - { - name: "SingleAttestation", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - }, - }, - }, - score: 1, - }, - { - name: "SingleAttestationParentRootDistance2", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - }, - }, - }, - score: 0.5, - }, - { - name: "SingleAttestationDistance2", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - }, - }, - }, - score: 0.875, - }, - { - name: "TwoAttestations", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(2, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12341, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - Epoch: 385, - }, - }, - }, - }, - }, - }, - }, - score: 2.8125, - }, - { - name: "AttesterSlashing", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(50, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - AttesterSlashings: []*phase0.AttesterSlashing{ - { - Attestation1: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{1, 2, 3}, - }, - Attestation2: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{2, 3, 4}, - }, - }, - }, - }, - }, - }, - score: 5450, - }, - { - name: "DuplicateAttestations", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: specificAggregationBits([]uint64{1, 2, 3}, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: specificAggregationBits([]uint64{2, 3, 4}, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - }, - }, - }, - score: 4, - }, - { - name: "Full", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(50, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - AttesterSlashings: []*phase0.AttesterSlashing{ - { - Attestation1: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{1, 2, 3}, - }, - Attestation2: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{2, 3, 4}, - }, - }, - }, - ProposerSlashings: []*phase0.ProposerSlashing{ - { - SignedHeader1: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - SignedHeader2: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - }, - }, - }, - }, - }, - score: 8150, - }, - { - name: "FullParentRootDistance2", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(50, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - AttesterSlashings: []*phase0.AttesterSlashing{ - { - Attestation1: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{1, 2, 3}, - }, - Attestation2: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{2, 3, 4}, - }, - }, - }, - ProposerSlashings: []*phase0.ProposerSlashing{ - { - SignedHeader1: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - SignedHeader2: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - }, - }, - }, - }, - }, - score: 8125, - }, - { - name: "FullParentRootDistance4", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionPhase0, - Phase0: &phase0.BeaconBlock{ - Slot: 12348, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &phase0.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(50, 128), - Data: &phase0.AttestationData{ - Slot: 12347, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - AttesterSlashings: []*phase0.AttesterSlashing{ - { - Attestation1: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{1, 2, 3}, - }, - Attestation2: &phase0.IndexedAttestation{ - AttestingIndices: []uint64{2, 3, 4}, - }, - }, - }, - ProposerSlashings: []*phase0.ProposerSlashing{ - { - SignedHeader1: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - SignedHeader2: &phase0.SignedBeaconBlockHeader{ - Message: &phase0.BeaconBlockHeader{ - Slot: 10, - ProposerIndex: 1, - ParentRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - BodyRoot: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - }, - Signature: testutil.HexToSignature("0x040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404040404"), - }, - }, - }, - }, - }, - }, - score: 8112.5, - }, - { - name: "AltairSingleAttestationDistance1", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - BeaconBlockRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375, - }, - { - name: "AltairSingleAttestationDistance1IncorrectHead", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - BeaconBlockRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "AltairSingleAttestationDistance2", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "AltairSingleAttestationDistance5", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12349, - ParentRoot: testutil.HexToRoot("0x0505050505050505050505050505050505050505050505050505050505050505"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12348, - BeaconBlockRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "AltairSingleAttestationDistance6", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12350, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12339, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0707070707070707070707070707070707070707070707070707070707070707"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.40625, - }, - { - name: "AltairOverlappingAttestations", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: bitList(2, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 1.25, - }, - { - name: "AltairParentMissing", - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - BeaconBlockRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - BeaconBlockRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375 + 0.625, - }, - { - name: "PriorVotes", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - // Chain with middle block orphaned. - testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"): { - parent: testutil.HexToRoot("0x4040404040404040404040404040404040404040404040404040404040404040"), - slot: 12341, - }, - testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"): { - parent: testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"), - slot: 12342, - votes: map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist{ - // This block is orphaned so its votes should be ignored. - 12342: { - 0: bitList(5, 128), - }, - }, - }, - testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"): { - parent: testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"), - slot: 12343, - votes: map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist{ - // This block is a recent ancestore so its votes should be considered. - 12342: { - 0: bitList(2, 128), - }, - }, - }, - }, - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12344, - ParentRoot: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(5, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"), - Slot: 12342, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 1.875, - }, - { - name: "TargetCorrect", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"): { - root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - parent: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - slot: 12344, - }, - testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"): { - root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - parent: testutil.HexToRoot("0x1919191919191919191919191919191919191919191919191919191919191919"), - slot: 12320, - }, - }, - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375, - }, - { - name: "TargetIncorrect", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"): { - root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - parent: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - slot: 12344, - }, - testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"): { - root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - parent: testutil.HexToRoot("0x1919191919191919191919191919191919191919191919191919191919191919"), - slot: 12320, - }, - }, - proposal: &api.VersionedProposal{ - Version: spec.DataVersionAltair, - Altair: &altair.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x1515151515151515151515151515151515151515151515151515151515151515"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.4375, - }, - { - name: "ExecutionPayloadBellatrix", - priorBlocks: map[phase0.Root]*priorBlockVotes{}, - proposal: &api.VersionedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &bellatrix.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &bellatrix.BeaconBlockBody{ - Attestations: []*phase0.Attestation{}, - SyncAggregate: &altair.SyncAggregate{}, - ExecutionPayload: &bellatrix.ExecutionPayload{ - GasUsed: 15000000, - }, - }, - }, - }, - score: 15000.0, - }, - { - name: "ExecutionPayloadCapella", - priorBlocks: map[phase0.Root]*priorBlockVotes{}, - proposal: &api.VersionedProposal{ - Version: spec.DataVersionCapella, - Capella: &capella.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &capella.BeaconBlockBody{ - Attestations: []*phase0.Attestation{}, - SyncAggregate: &altair.SyncAggregate{}, - ExecutionPayload: &capella.ExecutionPayload{ - GasUsed: 15000000, - }, - }, - }, - }, - score: 15000.0, - }, - { - name: "InvalidVersion", - proposal: &api.VersionedProposal{ - Version: spec.DataVersion(999), - Altair: &altair.BeaconBlock{ - Slot: 12345, - Body: &altair.BeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - }, - }, - { - AggregationBits: bitList(2, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0, - }, - } - - ctx := context.Background() - - genesisTime := time.Now() - genesisProvider := mock.NewGenesisProvider(genesisTime) - specProvider := mock.NewSpecProvider() - chainTime, err := standardchaintime.New(ctx, - standardchaintime.WithLogLevel(zerolog.Disabled), - standardchaintime.WithGenesisProvider(genesisProvider), - standardchaintime.WithSpecProvider(specProvider), - ) - require.NoError(t, err) - - cacheSvc := mockcache.New(map[phase0.Root]phase0.Slot{ - testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"): phase0.Slot(12344), - testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"): phase0.Slot(12345), - testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"): phase0.Slot(12346), - testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"): phase0.Slot(12347), - testutil.HexToRoot("0x0505050505050505050505050505050505050505050505050505050505050505"): phase0.Slot(12348), - }) - blockToSlotCache := cacheSvc.(cache.BlockRootToSlotProvider) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - s, err := New(ctx, - WithLogLevel(zerolog.Disabled), - WithTimeout(2*time.Second), - WithClientMonitor(null.New(context.Background())), - WithEventsProvider(mock.NewEventsProvider()), - WithChainTimeService(chainTime), - WithSpecProvider(specProvider), - WithProcessConcurrency(6), - WithProposalProviders(map[string]eth2client.ProposalProvider{ - "one": mock.NewProposalProvider(), - }), - WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - WithBlockRootToSlotCache(blockToSlotCache), - WithExecutionPayloadFactor(0.001), - ) - require.NoError(t, err) - if test.priorBlocks != nil { - s.priorBlocksVotes = test.priorBlocks - } - score := s.scoreBeaconBlockProposal(context.Background(), test.name, test.proposal) - assert.Equal(t, test.score, score) - }) - } -} diff --git a/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal.go b/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal.go deleted file mode 100644 index 91140312..00000000 --- a/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright © 2022 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 ( - "bytes" - "context" - "fmt" - "time" - - builderspec "github.com/attestantio/go-builder-client/spec" - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/vouch/util" - "github.com/pkg/errors" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -var zeroFeeRecipient bellatrix.ExecutionAddress - -type beaconBlockResponse struct { - provider string - proposal *api.VersionedBlindedProposal - score float64 -} - -type beaconBlockError struct { - provider string - err error -} - -// BlindedProposal provides the best blinded proposal from a number of beacon nodes. -func (s *Service) BlindedProposal(ctx context.Context, - opts *api.BlindedProposalOpts, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - return s.BlindedProposalWithExpectedPayload(ctx, opts, nil) -} - -// BlindedProposalWithExpectedPayload fetches a blinded proposal for signing. -func (s *Service) BlindedProposalWithExpectedPayload(ctx context.Context, - opts *api.BlindedProposalOpts, - bid *builderspec.VersionedSignedBuilderBid, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - ctx, span := otel.Tracer("attestantio.vouch.strategies.blindedbeaconblockproposal.best").Start(ctx, "BlindedProposal", - trace.WithAttributes( - attribute.Int64("slot", int64(opts.Slot)), - )) - defer span.End() - - started := time.Now() - log := util.LogWithID(ctx, log, "strategy_id").With().Uint64("slot", uint64(opts.Slot)).Logger() - - // We have two timeouts: a soft timeout and a hard timeout. - // At the soft timeout, we return if we have any responses so far. - // At the hard timeout, we return unconditionally. - // The soft timeout is half the duration of the hard timeout. - ctx, cancel := context.WithTimeout(ctx, s.timeout) - softCtx, softCancel := context.WithTimeout(ctx, s.timeout/2) - - requests := len(s.blindedProposalProviders) - - respCh := make(chan *beaconBlockResponse, requests) - errCh := make(chan *beaconBlockError, requests) - // Kick off the requests. - for name, provider := range s.blindedProposalProviders { - providerGraffiti := opts.Graffiti[:] - if bytes.Contains(providerGraffiti, []byte("{{CLIENT}}")) { - if nodeClientProvider, isProvider := provider.(eth2client.NodeClientProvider); isProvider { - nodeClientResponse, err := nodeClientProvider.NodeClient(ctx) - if err != nil { - log.Warn().Err(err).Msg("Failed to obtain node client; not updating graffiti") - } else { - providerGraffiti = bytes.ReplaceAll(providerGraffiti, []byte("{{CLIENT}}"), []byte(nodeClientResponse.Data)) - } - if len(providerGraffiti) > 32 { - providerGraffiti = providerGraffiti[0:32] - } - // Replace entire opts structure so the mutated graffiti does not leak to other providers. - opts = &api.BlindedProposalOpts{ - Slot: opts.Slot, - RandaoReveal: opts.RandaoReveal, - Graffiti: [32]byte(providerGraffiti), - SkipRandaoVerification: opts.SkipRandaoVerification, - } - } - } - go s.blindedProposal(ctx, started, name, provider, respCh, errCh, opts, bid) - } - - // Wait for all responses (or context done). - responded := 0 - errored := 0 - timedOut := 0 - softTimedOut := 0 - bestScore := float64(0) - var bestProposal *api.VersionedBlindedProposal - var bestProvider string - - // Loop 1: prior to soft timeout. - for responded+errored+timedOut+softTimedOut != requests { - select { - case resp := <-respCh: - responded++ - log.Trace(). - Dur("elapsed", time.Since(started)). - Str("provider", resp.provider). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Msg("Response received") - if bestProposal == nil || resp.score > bestScore { - bestProposal = resp.proposal - bestScore = resp.score - bestProvider = resp.provider - } - case err := <-errCh: - errored++ - log.Debug(). - Dur("elapsed", time.Since(started)). - Str("provider", err.provider). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Err(err.err). - Msg("Error received") - case <-softCtx.Done(): - // If we have any responses at this point we consider the non-responders timed out. - if responded > 0 { - timedOut = requests - responded - errored - log.Debug(). - Dur("elapsed", time.Since(started)). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Msg("Soft timeout reached with responses") - } else { - log.Debug(). - Dur("elapsed", time.Since(started)). - Int("errored", errored). - Msg("Soft timeout reached with no responses") - } - // Set the number of requests that have soft timed out. - softTimedOut = requests - responded - errored - timedOut - } - } - softCancel() - - // Loop 2: after soft timeout. - for responded+errored+timedOut != requests { - select { - case resp := <-respCh: - responded++ - log.Trace(). - Dur("elapsed", time.Since(started)). - Str("provider", resp.provider). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Msg("Response received") - if bestProposal == nil || resp.score > bestScore { - bestProposal = resp.proposal - bestScore = resp.score - bestProvider = resp.provider - } - case err := <-errCh: - errored++ - log.Debug(). - Dur("elapsed", time.Since(started)). - Str("provider", err.provider). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Err(err.err). - Msg("Error received") - case <-ctx.Done(): - // Anyone not responded by now is considered errored. - timedOut = requests - responded - errored - log.Debug(). - Dur("elapsed", time.Since(started)). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Msg("Hard timeout reached") - } - } - cancel() - log.Trace(). - Dur("elapsed", time.Since(started)). - Int("responded", responded). - Int("errored", errored). - Int("timed_out", timedOut). - Msg("Results") - - if bestProposal == nil { - return nil, errors.New("no proposals received") - } - log.Trace().Str("provider", bestProvider).Stringer("proposal", bestProposal).Float64("score", bestScore).Msg("Selected best proposal") - if bestProvider != "" { - s.clientMonitor.StrategyOperation("best", bestProvider, "blinded beacon block proposal", time.Since(started)) - } - - return &api.Response[*api.VersionedBlindedProposal]{ - Data: bestProposal, - Metadata: make(map[string]any), - }, nil -} - -func (s *Service) blindedProposal(ctx context.Context, - started time.Time, - name string, - provider eth2client.BlindedProposalProvider, - respCh chan *beaconBlockResponse, - errCh chan *beaconBlockError, - opts *api.BlindedProposalOpts, - bid *builderspec.VersionedSignedBuilderBid, -) { - ctx, span := otel.Tracer("attestantio.vouch.strategies.blindedbeaconblockproposal.best").Start(ctx, "blindedProposal", trace.WithAttributes( - attribute.String("provider", name), - )) - defer span.End() - - proposalResponse, err := provider.BlindedProposal(ctx, opts) - s.clientMonitor.ClientOperation(name, "blinded beacon block proposal", err == nil, time.Since(started)) - if err != nil { - errCh <- &beaconBlockError{ - provider: name, - err: err, - } - return - } - proposal := proposalResponse.Data - log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained blinded beacon block proposal") - feeRecipient, err := proposal.FeeRecipient() - if err != nil { - errCh <- &beaconBlockError{ - provider: name, - err: errors.Wrap(err, "failed to obtain blinded beacon block fee recipient"), - } - return - } - if bytes.Equal(feeRecipient[:], zeroFeeRecipient[:]) { - errCh <- &beaconBlockError{ - provider: name, - err: errors.New("blinded beacon block response has 0 fee recipient"), - } - return - } - executionTimestamp, err := proposal.Timestamp() - if err != nil { - errCh <- &beaconBlockError{ - provider: name, - err: errors.Wrap(err, "failed to obtain blinded beacon block timestamp"), - } - return - } - if int64(executionTimestamp) != s.chainTime.StartOfSlot(opts.Slot).Unix() { - errCh <- &beaconBlockError{ - provider: name, - err: errors.New("blinded beacon block response has incorrect timestamp"), - } - return - } - if bid != nil { - bidTransactionsRoot, err := bid.TransactionsRoot() - if err == nil { - proposalTransactionsRoot, err := proposal.TransactionsRoot() - if err != nil { - errCh <- &beaconBlockError{ - provider: name, - err: errors.Wrap(err, "failed to obtain transactions root"), - } - return - } - if !bytes.Equal(bidTransactionsRoot[:], proposalTransactionsRoot[:]) { - errCh <- &beaconBlockError{ - provider: name, - err: fmt.Errorf("proposal transactions root %#x does not match bid transactions root %#x", proposalTransactionsRoot, bidTransactionsRoot), - } - return - } - } - } - - score := s.scoreBlindedProposal(ctx, name, proposal) - span.SetAttributes(attribute.Float64("score", score)) - respCh <- &beaconBlockResponse{ - provider: name, - proposal: proposal, - score: score, - } -} diff --git a/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal_test.go b/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal_test.go deleted file mode 100644 index 886bc918..00000000 --- a/strategies/blindedbeaconblockproposal/best/blindedbeaconblockproposal_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2022 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/api" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/mock" - "github.com/attestantio/vouch/services/cache" - mockcache "github.com/attestantio/vouch/services/cache/mock" - standardchaintime "github.com/attestantio/vouch/services/chaintime/standard" - "github.com/attestantio/vouch/strategies/blindedbeaconblockproposal/best" - "github.com/attestantio/vouch/testing/logger" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -func TestBlindedProposal(t *testing.T) { - ctx := context.Background() - - genesisTime := time.Now() - genesisProvider := mock.NewGenesisProvider(genesisTime) - specProvider := mock.NewSpecProvider() - chainTime, err := standardchaintime.New(ctx, - standardchaintime.WithLogLevel(zerolog.Disabled), - standardchaintime.WithGenesisProvider(genesisProvider), - standardchaintime.WithSpecProvider(specProvider), - ) - require.NoError(t, err) - - signedBeaconBlockProvider := mock.NewSignedBeaconBlockProvider() - cacheSvc := mockcache.New(map[phase0.Root]phase0.Slot{}) - blockToSlotCache := cacheSvc.(cache.BlockRootToSlotProvider) - - tests := []struct { - name string - params []best.Parameter - slot phase0.Slot - committeeIndex phase0.CommitteeIndex - err string - logEntries []string - }{ - { - name: "Good", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(2 * time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "good": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - }, - { - name: "Timeout", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "sleepy": mock.NewSleepyBlindedProposalProvider(5*time.Second, mock.NewBlindedProposalProvider(chainTime)), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - err: "no proposals received", - }, - { - name: "GoodMixed", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(2 * time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "error": mock.NewErroringBlindedProposalProvider(), - "sleepy": mock.NewSleepyBlindedProposalProvider(time.Second, mock.NewBlindedProposalProvider(chainTime)), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - }, - { - name: "SoftTimeoutWithResponses", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(3 * time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "good": mock.NewBlindedProposalProvider(chainTime), - "sleepy": mock.NewSleepyBlindedProposalProvider(2*time.Second, mock.NewBlindedProposalProvider(chainTime)), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - logEntries: []string{"Soft timeout reached with responses"}, - }, - { - name: "SoftTimeoutWithoutResponses", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(3 * time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "sleepy": mock.NewSleepyBlindedProposalProvider(2*time.Second, mock.NewBlindedProposalProvider(chainTime)), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - logEntries: []string{"Soft timeout reached with no responses"}, - }, - { - name: "SoftTimeoutWithError", - params: []best.Parameter{ - best.WithLogLevel(zerolog.TraceLevel), - best.WithTimeout(3 * time.Second), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(2), - best.WithSignedBeaconBlockProvider(signedBeaconBlockProvider), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "error": mock.NewErroringBlindedProposalProvider(), - "sleepy": mock.NewSleepyBlindedProposalProvider(2*time.Second, mock.NewBlindedProposalProvider(chainTime)), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - slot: 12345, - committeeIndex: 3, - logEntries: []string{"Soft timeout reached with no responses"}, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - capture := logger.NewLogCapture() - s, err := best.New(context.Background(), test.params...) - require.NoError(t, err) - proposal, err := s.BlindedProposal(context.Background(), - &api.BlindedProposalOpts{ - Slot: 12345, - RandaoReveal: 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, - }), - }) - if test.err != "" { - require.EqualError(t, err, test.err) - } else { - require.NoError(t, err) - require.NotNil(t, proposal) - } - for _, entry := range test.logEntries { - capture.AssertHasEntry(t, entry) - } - }) - } -} diff --git a/strategies/blindedbeaconblockproposal/best/events.go b/strategies/blindedbeaconblockproposal/best/events.go deleted file mode 100644 index 470102dd..00000000 --- a/strategies/blindedbeaconblockproposal/best/events.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright © 2022 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" - "time" - - "github.com/attestantio/go-eth2-client/api" - apiv1 "github.com/attestantio/go-eth2-client/api/v1" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/prysmaticlabs/go-bitfield" -) - -// HandleHeadEvent handles the "head" events from the beacon node. -func (s *Service) HandleHeadEvent(event *apiv1.Event) { - if event.Data == nil { - return - } - - ctx := context.Background() - - data := event.Data.(*apiv1.HeadEvent) - log := log.With().Uint64("slot", uint64(data.Slot)).Logger() - log.Trace().Msg("Received head event") - - // An attestation in a block could be up to 1 epoch old. We keep an - // additional epoch's worth of attestations for target root matching, - // for a total of 2 epochs of prior block information. - if data.Slot < s.chainTime.CurrentSlot()-phase0.Slot(2*s.slotsPerEpoch) { - // Block is too old for us to care about it. - return - } - - s.priorBlocksVotesMu.RLock() - _, exists := s.priorBlocksVotes[data.Block] - s.priorBlocksVotesMu.RUnlock() - if exists { - // We already have data for this block. - return - } - - blockResponse, err := s.signedBeaconBlockProvider.SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{ - Block: fmt.Sprintf("%#x", data.Block), - }) - if err != nil { - log.Error().Err(err).Msg("Failed to obtain head beacon block") - return - } - block := blockResponse.Data - - s.updateBlockVotes(ctx, block) -} - -// updateBlockVotes updates the votes made in attestations for this block. -func (s *Service) updateBlockVotes(_ context.Context, - block *spec.VersionedSignedBeaconBlock, -) { - if block == nil { - return - } - started := time.Now() - - slot, err := block.Slot() - if err != nil { - log.Error().Err(err).Msg("Failed to obtain proposed block's slot") - return - } - attestations, err := block.Attestations() - if err != nil { - log.Error().Err(err).Msg("Failed to obtain proposed block's attestations") - return - } - - votes := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range attestations { - data := attestation.Data - _, exists := votes[data.Slot] - if !exists { - votes[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - _, exists = votes[data.Slot][data.Index] - if !exists { - votes[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - votes[data.Slot][data.Index].SetBitAt(i, true) - } - } - } - - parentRoot, err := block.ParentRoot() - if err != nil { - log.Error().Err(err).Msg("Failed to obtain proposed block's parent root") - return - } - - root, err := block.Root() - if err != nil { - log.Error().Err(err).Msg("Failed to obtain proposed block's root") - return - } - - priorBlockVotes := &priorBlockVotes{ - root: root, - parent: parentRoot, - slot: slot, - votes: votes, - } - - s.priorBlocksVotesMu.Lock() - s.priorBlocksVotes[root] = priorBlockVotes - for k, v := range s.priorBlocksVotes { - // Keep 2 epochs' worth of data as per comment above. - if v.slot < slot-phase0.Slot(2*s.slotsPerEpoch) { - delete(s.priorBlocksVotes, k) - } - } - s.priorBlocksVotesMu.Unlock() - - log.Trace().Uint64("slot", uint64(slot)).Str("root", fmt.Sprintf("%#x", root[:])).Dur("elapsed", time.Since(started)).Msg("Set votes for slot") -} diff --git a/strategies/blindedbeaconblockproposal/best/events_internal_test.go b/strategies/blindedbeaconblockproposal/best/events_internal_test.go deleted file mode 100644 index cf9ad8de..00000000 --- a/strategies/blindedbeaconblockproposal/best/events_internal_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright © 2022, 2023 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" - "time" - - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/mock" - "github.com/attestantio/vouch/services/cache" - mockcache "github.com/attestantio/vouch/services/cache/mock" - standardchaintime "github.com/attestantio/vouch/services/chaintime/standard" - "github.com/attestantio/vouch/services/metrics/null" - "github.com/attestantio/vouch/testing/logger" - "github.com/attestantio/vouch/testutil" - "github.com/prysmaticlabs/go-bitfield" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -// TestUpdateBlockVotes tests the internal function updateBlockVotes. -func TestUpdateBlockVotes(t *testing.T) { - ctx := context.Background() - - tests := []struct { - name string - block *spec.VersionedSignedBeaconBlock - logEntries []string - }{ - { - name: "Nil", - block: nil, - }, - { - name: "Empty", - block: &spec.VersionedSignedBeaconBlock{}, - logEntries: []string{ - "Failed to obtain proposed block's slot", - }, - }, - { - name: "MissingAttestations", - block: &spec.VersionedSignedBeaconBlock{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 12345, - }, - }, - }, - logEntries: []string{ - "Failed to obtain proposed block's attestations", - }, - }, - { - name: "EmptyAttestations", - block: &spec.VersionedSignedBeaconBlock{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 12345, - Body: &altair.BeaconBlockBody{ - ETH1Data: &phase0.ETH1Data{}, - Attestations: []*phase0.Attestation{}, - }, - }, - }, - }, - logEntries: []string{ - "Failed to obtain proposed block's root", - }, - }, - { - name: "SingleAttestation", - block: &spec.VersionedSignedBeaconBlock{ - Version: spec.DataVersionAltair, - Altair: &altair.SignedBeaconBlock{ - Message: &altair.BeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - StateRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Body: &altair.BeaconBlockBody{ - RANDAOReveal: testutil.HexToSignature("0x030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303"), - ETH1Data: &phase0.ETH1Data{ - DepositRoot: testutil.HexToRoot("0x1010101010101010101010101010101010101010101010101010101010101010"), - BlockHash: testutil.HexToBytes("0x1111111111111111111111111111111111111111111111111111111111111111"), - }, - Graffiti: testutil.HexToBytes32("0x0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad0bad"), - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(10, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - Index: 0, - BeaconBlockRoot: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - Source: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0505050505050505050505050505050505050505050505050505050505050505"), - Epoch: 384, - }, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0606060606060606060606060606060606060606060606060606060606060606"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - SyncCommitteeSignature: testutil.HexToSignature("0x080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808"), - }, - }, - }, - Signature: testutil.HexToSignature("0x070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707"), - }, - }, - logEntries: []string{ - "Set votes for slot", - }, - }, - } - - genesisTime := time.Now() - genesisProvider := mock.NewGenesisProvider(genesisTime) - specProvider := mock.NewSpecProvider() - chainTime, err := standardchaintime.New(ctx, - standardchaintime.WithLogLevel(zerolog.Disabled), - standardchaintime.WithGenesisProvider(genesisProvider), - standardchaintime.WithSpecProvider(specProvider), - ) - require.NoError(t, err) - - cacheSvc := mockcache.New(map[phase0.Root]phase0.Slot{}) - blockToSlotCache := cacheSvc.(cache.BlockRootToSlotProvider) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - capture := logger.NewLogCapture() - s, err := New(ctx, - WithLogLevel(zerolog.TraceLevel), - WithTimeout(2*time.Second), - WithClientMonitor(null.New(context.Background())), - WithEventsProvider(mock.NewEventsProvider()), - WithChainTimeService(chainTime), - WithSpecProvider(specProvider), - WithProcessConcurrency(6), - WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - }), - WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - WithBlockRootToSlotCache(blockToSlotCache), - ) - require.NoError(t, err) - - s.updateBlockVotes(ctx, test.block) - for _, entry := range test.logEntries { - capture.AssertHasEntry(t, entry) - } - }) - } -} diff --git a/strategies/blindedbeaconblockproposal/best/parameters.go b/strategies/blindedbeaconblockproposal/best/parameters.go deleted file mode 100644 index 7c92eae8..00000000 --- a/strategies/blindedbeaconblockproposal/best/parameters.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright © 2020 - 2022 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 beacon block proposals from multiple -// nodes and selects the best one based on its attestation load. -package best - -import ( - "context" - "time" - - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/vouch/services/cache" - "github.com/attestantio/vouch/services/chaintime" - "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 - eventsProvider eth2client.EventsProvider - chainTime chaintime.Service - specProvider eth2client.SpecProvider - blindedProposalProviders map[string]eth2client.BlindedProposalProvider - signedBeaconBlockProvider eth2client.SignedBeaconBlockProvider - timeout time.Duration - blockRootToSlotCache cache.BlockRootToSlotProvider -} - -// 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 - }) -} - -// WithTimeout sets the timeout for requests. -func WithTimeout(timeout time.Duration) Parameter { - return parameterFunc(func(p *parameters) { - p.timeout = timeout - }) -} - -// 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 - }) -} - -// WithEventsProvider sets the events provider. -func WithEventsProvider(provider eth2client.EventsProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.eventsProvider = provider - }) -} - -// WithChainTimeService sets the chain time service. -func WithChainTimeService(chainTime chaintime.Service) Parameter { - return parameterFunc(func(p *parameters) { - p.chainTime = chainTime - }) -} - -// WithSpecProvider sets the beacon spec provider. -func WithSpecProvider(provider eth2client.SpecProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.specProvider = provider - }) -} - -// WithBlindedProposalProviders sets the blinded proposal providers. -func WithBlindedProposalProviders(providers map[string]eth2client.BlindedProposalProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.blindedProposalProviders = providers - }) -} - -// WithSignedBeaconBlockProvider sets the signed beacon block provider. -func WithSignedBeaconBlockProvider(provider eth2client.SignedBeaconBlockProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.signedBeaconBlockProvider = provider - }) -} - -// WithBlockRootToSlotCache sets the block root to slot cache. -func WithBlockRootToSlotCache(cache cache.BlockRootToSlotProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.blockRootToSlotCache = cache - }) -} - -// 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(), - clientMonitor: nullmetrics.New(context.Background()), - } - for _, p := range params { - if params != nil { - p.apply(¶meters) - } - } - - 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 parameters.eventsProvider == nil { - return nil, errors.New("no events provider specified") - } - if parameters.chainTime == nil { - return nil, errors.New("no chain time service specified") - } - if parameters.specProvider == nil { - return nil, errors.New("no spec provider specified") - } - if len(parameters.blindedProposalProviders) == 0 { - return nil, errors.New("no blinded proposal providers specified") - } - if parameters.signedBeaconBlockProvider == nil { - return nil, errors.New("no signed beacon block provider specified") - } - if parameters.blockRootToSlotCache == nil { - return nil, errors.New("no block root to slot cache specified") - } - - return ¶meters, nil -} diff --git a/strategies/blindedbeaconblockproposal/best/score.go b/strategies/blindedbeaconblockproposal/best/score.go deleted file mode 100644 index 196b9e8e..00000000 --- a/strategies/blindedbeaconblockproposal/best/score.go +++ /dev/null @@ -1,474 +0,0 @@ -// Copyright © 2020 - 2022 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 ( - "bytes" - "context" - "fmt" - "sort" - - "github.com/attestantio/go-eth2-client/api" - apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" - apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" - apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/prysmaticlabs/go-bitfield" -) - -// scoreBlindedPropsal generates a score for a blinded proposal. -// The score is relative to the reward expected by proposing the block. -func (s *Service) scoreBlindedProposal(ctx context.Context, - name string, - blockProposal *api.VersionedBlindedProposal, -) float64 { - if blockProposal == nil { - return 0 - } - if blockProposal.IsEmpty() { - return 0 - } - - // Obtain the slot of the block to which the proposal refers. - // We use this to allow the scorer to score blocks with earlier parents lower. - parentRoot, err := blockProposal.ParentRoot() - if err != nil { - log.Error().Str("version", blockProposal.Version.String()).Msg("Failed to obtain parent root") - return 0 - } - parentSlot, err := s.blockRootToSlotCache.BlockRootToSlot(ctx, parentRoot) - if err != nil { - log.Debug().Str("root", fmt.Sprintf("%#x", parentRoot)).Err(err).Msg("Failed to obtain parent slot; assuming 0") - parentSlot = 0 - } - - switch blockProposal.Version { - case spec.DataVersionBellatrix: - return s.scoreBellatrixBlindedProposal(ctx, name, parentSlot, blockProposal.Bellatrix) - case spec.DataVersionCapella: - return s.scoreCapellaBlindedProposal(ctx, name, parentSlot, blockProposal.Capella) - case spec.DataVersionDeneb: - return s.scoreDenebBlindedProposal(ctx, name, parentSlot, blockProposal.Deneb) - default: - log.Error().Int("version", int(blockProposal.Version)).Msg("Unhandled block version") - return 0 - } -} - -// scoreBellatrixBlindedPropsal generates a score for a bellatrix blinded proposal. -func (s *Service) scoreBellatrixBlindedProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *apiv1bellatrix.BlindedBeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := bellatrixHeadCorrect(blockProposal, attestation) - targetCorrect := s.bellatrixTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore). - Msg("Scored Bellatrix block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore -} - -// scoreCapellaBlindedPropsal generates a score for a capella blinded proposal. -func (s *Service) scoreCapellaBlindedProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - blockProposal *apiv1capella.BlindedBeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range blockProposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, blockProposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := capellaHeadCorrect(blockProposal, attestation) - targetCorrect := s.bellatrixTargetCorrect(ctx, attestation) - inclusionDistance := blockProposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(blockProposal.Body.AttesterSlashings, blockProposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(blockProposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - log.Trace(). - Uint64("slot", uint64(blockProposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore). - Msg("Scored Capella block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore -} - -// scoreDenebBlindedPropsal generates a score for deneb capella blinded proposal. -func (s *Service) scoreDenebBlindedProposal(ctx context.Context, - name string, - parentSlot phase0.Slot, - proposal *apiv1deneb.BlindedBeaconBlock, -) float64 { - attestationScore := float64(0) - immediateAttestationScore := float64(0) - - // We need to avoid duplicates in attestations. - // Map is attestation slot -> committee index -> validator committee index -> aggregate. - attested := make(map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist) - for _, attestation := range proposal.Body.Attestations { - data := attestation.Data - if _, exists := attested[data.Slot]; !exists { - attested[data.Slot] = make(map[phase0.CommitteeIndex]bitfield.Bitlist) - } - if _, exists := attested[data.Slot][data.Index]; !exists { - if !exists { - attested[data.Slot][data.Index] = bitfield.NewBitlist(attestation.AggregationBits.Len()) - } - } - - priorVotes, err := s.priorVotesForAttestation(ctx, attestation, proposal.ParentRoot) - if err != nil { - log.Debug().Err(err).Msg("Failed to obtain prior votes for attestation; assuming no votes") - } - - votes := 0 - for i := uint64(0); i < attestation.AggregationBits.Len(); i++ { - if attestation.AggregationBits.BitAt(i) { - if attested[attestation.Data.Slot][attestation.Data.Index].BitAt(i) { - // Already attested in this block; skip. - continue - } - if priorVotes.BitAt(i) { - // Attested in a previous block; skip. - continue - } - votes++ - attested[attestation.Data.Slot][attestation.Data.Index].SetBitAt(i, true) - } - } - - // Now we know how many new votes are in this attestation we can score it. - // We can calculate if the head vote is correct, but not target so for the - // purposes of the calculation we assume that it is. - - headCorrect := denebHeadCorrect(proposal, attestation) - targetCorrect := s.bellatrixTargetCorrect(ctx, attestation) - inclusionDistance := proposal.Slot - attestation.Data.Slot - - score := 0.0 - if targetCorrect { - // Target is correct (and timely). - score += float64(s.timelyTargetWeight) / float64(s.weightDenominator) - } - if inclusionDistance <= 5 { - // Source is timely. - score += float64(s.timelySourceWeight) / float64(s.weightDenominator) - } - if headCorrect && inclusionDistance == 1 { - score += float64(s.timelyHeadWeight) / float64(s.weightDenominator) - } - score *= float64(votes) - attestationScore += score - if inclusionDistance == 1 { - immediateAttestationScore += score - } - } - - attesterSlashingScore, proposerSlashingScore := scoreSlashings(proposal.Body.AttesterSlashings, proposal.Body.ProposerSlashings) - - // Add sync committee score. - syncCommitteeScore := float64(proposal.Body.SyncAggregate.SyncCommitteeBits.Count()) * float64(s.syncRewardWeight) / float64(s.weightDenominator) - - log.Trace(). - Uint64("slot", uint64(proposal.Slot)). - Uint64("parent_slot", uint64(parentSlot)). - Str("provider", name). - Float64("immediate_attestations", immediateAttestationScore). - Float64("attestations", attestationScore). - Float64("proposer_slashings", proposerSlashingScore). - Float64("attester_slashings", attesterSlashingScore). - Float64("sync_committee", syncCommitteeScore). - Float64("total", attestationScore+proposerSlashingScore+attesterSlashingScore+syncCommitteeScore). - Msg("Scored Deneb block") - - return attestationScore + proposerSlashingScore + attesterSlashingScore + syncCommitteeScore -} - -func scoreSlashings(attesterSlashings []*phase0.AttesterSlashing, - proposerSlashings []*phase0.ProposerSlashing, -) (float64, float64) { - // Slashing reward will be at most MAX_EFFECTIVE_BALANCE/WHISTLEBLOWER_REWARD_QUOTIENT, - // which is 0.0625 Ether. - // Individual attestation reward at 250K validators will be around 23,000 GWei, or .000023 Ether. - // So we state that a single slashing event has the same weight as about 2,700 attestations. - slashingWeight := float64(2700) - - // Add proposer slashing scores. - proposerSlashingScore := float64(len(proposerSlashings)) * slashingWeight - - // Add attester slashing scores. - indicesSlashed := 0 - for _, slashing := range attesterSlashings { - indicesSlashed += len(intersection(slashing.Attestation1.AttestingIndices, slashing.Attestation2.AttestingIndices)) - } - attesterSlashingScore := slashingWeight * float64(indicesSlashed) - - return attesterSlashingScore, proposerSlashingScore -} - -func (s *Service) priorVotesForAttestation(_ context.Context, - attestation *phase0.Attestation, - root phase0.Root, -) ( - bitfield.Bitlist, - error, -) { - var res bitfield.Bitlist - var err error - found := false - s.priorBlocksVotesMu.RLock() - for { - priorBlock, exists := s.priorBlocksVotes[root] - if !exists { - // This means we do not have a parent block. - break - } - if priorBlock.slot < attestation.Data.Slot-phase0.Slot(s.slotsPerEpoch) { - // Block is too far back for its attestations to count. - break - } - - slotVotes, exists := priorBlock.votes[attestation.Data.Slot] - if exists { - votes, exists := slotVotes[attestation.Data.Index] - if exists { - if !found { - res = bitfield.NewBitlist(votes.Len()) - found = true - } - res, err = res.Or(votes) - if err != nil { - return bitfield.Bitlist{}, err - } - } - } - - root = priorBlock.parent - } - s.priorBlocksVotesMu.RUnlock() - - if !found { - // No prior votes found, return an empty list. - return bitfield.NewBitlist(attestation.AggregationBits.Len()), nil - } - - return res, nil -} - -func (s *Service) bellatrixTargetCorrect(_ context.Context, - attestation *phase0.Attestation, -) bool { - s.priorBlocksVotesMu.RLock() - defer s.priorBlocksVotesMu.RUnlock() - root := attestation.Data.BeaconBlockRoot - maxSlot := s.chainTime.FirstSlotOfEpoch(attestation.Data.Target.Epoch) - for { - priorBlock, exists := s.priorBlocksVotes[root] - if !exists { - // We don't have data on this block, assume the target is correct. - // (We could assume the target is incorrect in this situation, but that - // would give false incorrects whilst the prior block cache warms up.) - log.Trace().Uint64("attestation_slot", uint64(attestation.Data.Slot)).Uint64("max_slot", uint64(maxSlot)).Str("root", fmt.Sprintf("%#x", root)).Msg("Root does not exist, assuming true") - return true - } - if priorBlock.slot <= maxSlot { - return bytes.Equal(attestation.Data.Target.Root[:], priorBlock.root[:]) - } - root = priorBlock.parent - } -} - -// bellatrixHeadCorrect calculates if the head of a Bellatrix attestation is correct. -func bellatrixHeadCorrect(blockProposal *apiv1bellatrix.BlindedBeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// capellaHeadCorrect calculates if the head of a Capella attestation is correct. -func capellaHeadCorrect(blockProposal *apiv1capella.BlindedBeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// denebHeadCorrect calculates if the head of a Deneb attestation is correct. -func denebHeadCorrect(blockProposal *apiv1deneb.BlindedBeaconBlock, attestation *phase0.Attestation) bool { - return bytes.Equal(blockProposal.ParentRoot[:], attestation.Data.BeaconBlockRoot[:]) -} - -// intersection returns a list of items common between the two sets. -func intersection(set1 []uint64, set2 []uint64) []uint64 { - sort.Slice(set1, func(i, j int) bool { return set1[i] < set1[j] }) - sort.Slice(set2, func(i, j int) bool { return set2[i] < set2[j] }) - res := make([]uint64, 0) - - set1Pos := 0 - set2Pos := 0 - for set1Pos < len(set1) && set2Pos < len(set2) { - switch { - case set1[set1Pos] < set2[set2Pos]: - set1Pos++ - case set2[set2Pos] < set1[set1Pos]: - set2Pos++ - default: - res = append(res, set1[set1Pos]) - set1Pos++ - set2Pos++ - } - } - - return res -} diff --git a/strategies/blindedbeaconblockproposal/best/score_internal_test.go b/strategies/blindedbeaconblockproposal/best/score_internal_test.go deleted file mode 100644 index 18f20238..00000000 --- a/strategies/blindedbeaconblockproposal/best/score_internal_test.go +++ /dev/null @@ -1,530 +0,0 @@ -// Copyright © 2020 - 2022 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" - "time" - - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/api" - apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" - "github.com/attestantio/go-eth2-client/spec" - "github.com/attestantio/go-eth2-client/spec/altair" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/mock" - "github.com/attestantio/vouch/services/cache" - mockcache "github.com/attestantio/vouch/services/cache/mock" - standardchaintime "github.com/attestantio/vouch/services/chaintime/standard" - "github.com/attestantio/vouch/services/metrics/null" - "github.com/attestantio/vouch/testutil" - "github.com/prysmaticlabs/go-bitfield" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func bitList(set uint64, total uint64) bitfield.Bitlist { - bits := bitfield.NewBitlist(total) - for i := uint64(0); i < set; i++ { - bits.SetBitAt(i, true) - } - return bits -} - -func TestScore(t *testing.T) { - tests := []struct { - name string - priorBlocks map[phase0.Root]*priorBlockVotes - proposal *api.VersionedBlindedProposal - score float64 - err string - }{ - { - name: "Nil", - score: 0, - }, - { - name: "Empty", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - }, - score: 0, - }, - { - name: "SingleAttestation", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - BeaconBlockRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375, - }, - { - name: "BellatrixSingleAttestationDistance1", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - BeaconBlockRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375, - }, - { - name: "BellatrixSingleAttestationDistance1IncorrectHead", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12345, - BeaconBlockRoot: testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "BellatrixSingleAttestationDistance2", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12346, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "BellatrixSingleAttestationDistance5", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12349, - ParentRoot: testutil.HexToRoot("0x0505050505050505050505050505050505050505050505050505050505050505"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12348, - BeaconBlockRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.625, - }, - { - name: "BellatrixSingleAttestationDistance6", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12350, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12339, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x0707070707070707070707070707070707070707070707070707070707070707"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.40625, - }, - { - name: "BellatrixOverlappingAttestations", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: bitList(2, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 1.25, - }, - { - name: "BellatrixParentMissing", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12344, - BeaconBlockRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Epoch: 385, - }, - }, - }, - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - BeaconBlockRoot: testutil.HexToRoot("0x1111111111111111111111111111111111111111111111111111111111111111"), - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375 + 0.625, - }, - { - name: "PriorVotes", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - // Chain with middle block orphaned. - testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"): { - parent: testutil.HexToRoot("0x4040404040404040404040404040404040404040404040404040404040404040"), - slot: 12341, - }, - testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"): { - parent: testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"), - slot: 12342, - votes: map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist{ - // This block is orphaned so its votes should be ignored. - 12342: { - 0: bitList(5, 128), - }, - }, - }, - testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"): { - parent: testutil.HexToRoot("0x4141414141414141414141414141414141414141414141414141414141414141"), - slot: 12343, - votes: map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist{ - // This block is a recent ancestore so its votes should be considered. - 12342: { - 0: bitList(2, 128), - }, - }, - }, - }, - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12344, - ParentRoot: testutil.HexToRoot("0x4343434343434343434343434343434343434343434343434343434343434343"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(5, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"), - Slot: 12342, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x4242424242424242424242424242424242424242424242424242424242424242"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 1.875, - }, - { - name: "TargetCorrect", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"): { - root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - parent: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - slot: 12344, - }, - testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"): { - root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - parent: testutil.HexToRoot("0x1919191919191919191919191919191919191919191919191919191919191919"), - slot: 12320, - }, - }, - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.84375, - }, - { - name: "TargetIncorrect", - priorBlocks: map[phase0.Root]*priorBlockVotes{ - testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"): { - root: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - parent: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - slot: 12344, - }, - testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"): { - root: testutil.HexToRoot("0x2020202020202020202020202020202020202020202020202020202020202020"), - parent: testutil.HexToRoot("0x1919191919191919191919191919191919191919191919191919191919191919"), - slot: 12320, - }, - }, - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersionBellatrix, - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12345, - ParentRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - BeaconBlockRoot: testutil.HexToRoot("0x4444444444444444444444444444444444444444444444444444444444444444"), - Slot: 12344, - Target: &phase0.Checkpoint{ - Root: testutil.HexToRoot("0x1515151515151515151515151515151515151515151515151515151515151515"), - Epoch: 385, - }, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0.4375, - }, - { - name: "InvalidVersion", - proposal: &api.VersionedBlindedProposal{ - Version: spec.DataVersion(999), - Bellatrix: &apiv1bellatrix.BlindedBeaconBlock{ - Slot: 12345, - Body: &apiv1bellatrix.BlindedBeaconBlockBody{ - Attestations: []*phase0.Attestation{ - { - AggregationBits: bitList(1, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - }, - }, - { - AggregationBits: bitList(2, 128), - Data: &phase0.AttestationData{ - Slot: 12343, - }, - }, - }, - SyncAggregate: &altair.SyncAggregate{ - SyncCommitteeBits: bitfield.NewBitvector512(), - }, - }, - }, - }, - score: 0, - }, - } - - ctx := context.Background() - - genesisTime := time.Now() - genesisProvider := mock.NewGenesisProvider(genesisTime) - specProvider := mock.NewSpecProvider() - chainTime, err := standardchaintime.New(ctx, - standardchaintime.WithLogLevel(zerolog.Disabled), - standardchaintime.WithGenesisProvider(genesisProvider), - standardchaintime.WithSpecProvider(specProvider), - ) - require.NoError(t, err) - - cacheSvc := mockcache.New(map[phase0.Root]phase0.Slot{ - testutil.HexToRoot("0x0101010101010101010101010101010101010101010101010101010101010101"): phase0.Slot(12344), - testutil.HexToRoot("0x0202020202020202020202020202020202020202020202020202020202020202"): phase0.Slot(12345), - testutil.HexToRoot("0x0303030303030303030303030303030303030303030303030303030303030303"): phase0.Slot(12346), - testutil.HexToRoot("0x0404040404040404040404040404040404040404040404040404040404040404"): phase0.Slot(12347), - testutil.HexToRoot("0x0505050505050505050505050505050505050505050505050505050505050505"): phase0.Slot(12348), - }) - blockToSlotCache := cacheSvc.(cache.BlockRootToSlotProvider) - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - s, err := New(ctx, - WithLogLevel(zerolog.Disabled), - WithTimeout(2*time.Second), - WithClientMonitor(null.New(context.Background())), - WithEventsProvider(mock.NewEventsProvider()), - WithChainTimeService(chainTime), - WithSpecProvider(specProvider), - WithProcessConcurrency(6), - WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - }), - WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - WithBlockRootToSlotCache(blockToSlotCache), - ) - require.NoError(t, err) - if test.priorBlocks != nil { - s.priorBlocksVotes = test.priorBlocks - } - score := s.scoreBlindedProposal(context.Background(), test.name, test.proposal) - assert.Equal(t, test.score, score) - }) - } -} diff --git a/strategies/blindedbeaconblockproposal/best/service.go b/strategies/blindedbeaconblockproposal/best/service.go deleted file mode 100644 index 56377d33..00000000 --- a/strategies/blindedbeaconblockproposal/best/service.go +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright © 2020, 2022 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" - "sync" - "time" - - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/attestantio/vouch/services/cache" - "github.com/attestantio/vouch/services/chaintime" - "github.com/attestantio/vouch/services/metrics" - "github.com/pkg/errors" - "github.com/prysmaticlabs/go-bitfield" - "github.com/rs/zerolog" - zerologger "github.com/rs/zerolog/log" -) - -// Service is the provider for beacon block proposals. -type Service struct { - clientMonitor metrics.ClientMonitor - processConcurrency int64 - chainTime chaintime.Service - blindedProposalProviders map[string]eth2client.BlindedProposalProvider - signedBeaconBlockProvider eth2client.SignedBeaconBlockProvider - timeout time.Duration - blockRootToSlotCache cache.BlockRootToSlotProvider - - // Spec values for scoring proposals. - slotsPerEpoch uint64 - timelySourceWeight uint64 - timelyTargetWeight uint64 - timelyHeadWeight uint64 - syncRewardWeight uint64 - proposerWeight uint64 - weightDenominator uint64 - - priorBlocksVotes map[phase0.Root]*priorBlockVotes - priorBlocksVotesMu sync.RWMutex -} - -type priorBlockVotes struct { - root phase0.Root - parent phase0.Root - slot phase0.Slot - // votes is a map of attestation slot -> committee index -> votes - votes map[phase0.Slot]map[phase0.CommitteeIndex]bitfield.Bitlist -} - -// module-wide log. -var log zerolog.Logger - -// New creates a new beacon block proposal 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", "blindedbeaconblockproposal").Str("impl", "best").Logger() - if parameters.logLevel != log.GetLevel() { - log = log.Level(parameters.logLevel) - } - - specResponse, err := parameters.specProvider.Spec(ctx, &api.SpecOpts{}) - if err != nil { - return nil, errors.Wrap(err, "failed to obtain spec") - } - spec := specResponse.Data - - tmp, exists := spec["SLOTS_PER_EPOCH"] - if !exists { - return nil, errors.New("failed to obtain SLOTS_PER_EPOCH") - } - slotsPerEpoch, ok := tmp.(uint64) - if !ok { - return nil, errors.New("SLOTS_PER_EPOCH of unexpected type") - } - - tmp, exists = spec["TIMELY_SOURCE_WEIGHT"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(14) - } - timelySourceWeight, ok := tmp.(uint64) - if !ok { - return nil, errors.New("TIMELY_SOURCE_WEIGHT of unexpected type") - } - - tmp, exists = spec["TIMELY_TARGET_WEIGHT"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(26) - } - timelyTargetWeight, ok := tmp.(uint64) - if !ok { - return nil, errors.New("TIMELY_TARGET_WEIGHT of unexpected type") - } - - tmp, exists = spec["TIMELY_HEAD_WEIGHT"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(14) - } - timelyHeadWeight, ok := tmp.(uint64) - if !ok { - return nil, errors.New("TIMELY_HEAD_WEIGHT of unexpected type") - } - - tmp, exists = spec["SYNC_REWARD_WEIGHT"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(2) - } - syncRewardWeight, ok := tmp.(uint64) - if !ok { - return nil, errors.New("SYNC_REWARD_WEIGHT of unexpected type") - } - - tmp, exists = spec["PROPOSER_WEIGHT"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(8) - } - proposerWeight, ok := tmp.(uint64) - if !ok { - return nil, errors.New("PROPOSER_WEIGHT of unexpected type") - } - - tmp, exists = spec["WEIGHT_DENOMINATOR"] - if !exists { - // Set a default value based on the Altair spec. - tmp = uint64(64) - } - weightDenominator, ok := tmp.(uint64) - if !ok { - return nil, errors.New("WEIGHT_DENOMINATOR of unexpected type") - } - - s := &Service{ - processConcurrency: parameters.processConcurrency, - chainTime: parameters.chainTime, - blindedProposalProviders: parameters.blindedProposalProviders, - signedBeaconBlockProvider: parameters.signedBeaconBlockProvider, - timeout: parameters.timeout, - blockRootToSlotCache: parameters.blockRootToSlotCache, - clientMonitor: parameters.clientMonitor, - slotsPerEpoch: slotsPerEpoch, - timelySourceWeight: timelySourceWeight, - timelyTargetWeight: timelyTargetWeight, - timelyHeadWeight: timelyHeadWeight, - syncRewardWeight: syncRewardWeight, - proposerWeight: proposerWeight, - weightDenominator: weightDenominator, - priorBlocksVotes: make(map[phase0.Root]*priorBlockVotes), - } - log.Trace().Int64("process_concurrency", s.processConcurrency).Msg("Set process concurrency") - - // Subscribe to head events. This allows us to go early for attestations if a block arrives, as well as - // re-request duties if there is a change in beacon block. - // This also allows us to re-request duties if the dependent roots change. - if err := parameters.eventsProvider.Events(ctx, []string{"head"}, s.HandleHeadEvent); err != nil { - return nil, errors.Wrap(err, "failed to add head event handler") - } - - return s, nil -} diff --git a/strategies/blindedbeaconblockproposal/best/service_test.go b/strategies/blindedbeaconblockproposal/best/service_test.go deleted file mode 100644 index 55e88415..00000000 --- a/strategies/blindedbeaconblockproposal/best/service_test.go +++ /dev/null @@ -1,310 +0,0 @@ -// Copyright © 2020 - 2022 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/services/cache" - mockcache "github.com/attestantio/vouch/services/cache/mock" - standardchaintime "github.com/attestantio/vouch/services/chaintime/standard" - "github.com/attestantio/vouch/services/metrics/null" - "github.com/attestantio/vouch/strategies/blindedbeaconblockproposal/best" - "github.com/rs/zerolog" - "github.com/stretchr/testify/require" -) - -func TestService(t *testing.T) { - ctx := context.Background() - - genesisTime := time.Now() - genesisProvider := mock.NewGenesisProvider(genesisTime) - specProvider := mock.NewSpecProvider() - chainTime, err := standardchaintime.New(ctx, - standardchaintime.WithLogLevel(zerolog.Disabled), - standardchaintime.WithGenesisProvider(genesisProvider), - standardchaintime.WithSpecProvider(specProvider), - ) - require.NoError(t, err) - - cacheSvc := mockcache.New(map[phase0.Root]phase0.Slot{}) - blockToSlotCache := cacheSvc.(cache.BlockRootToSlotProvider) - - tests := []struct { - name string - params []best.Parameter - err string - }{ - { - name: "ClientMonitorMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(nil), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no client monitor specified", - }, - { - name: "TimeoutMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no timeout specified", - }, - { - name: "TimeoutZero", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithTimeout(0), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no timeout specified", - }, - { - name: "EventsProviderMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no events provider specified", - }, - { - name: "ChainTimeServiceMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - }, - err: "problem with parameters: no chain time service specified", - }, - { - name: "SpecProviderMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no spec provider specified", - }, - { - name: "ProcessConcurrencyBad", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(0), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no process concurrency specified", - }, - { - name: "BlindedProposalProvidersMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no blinded proposal providers specified", - }, - { - name: "BlindedProposalProvidersEmpty", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{}), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no blinded proposal providers specified", - }, - { - name: "SignedBeaconBlockProviderMissing", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "problem with parameters: no signed beacon block provider specified", - }, - { - name: "ErroringSpecProvider", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(mock.NewErroringSpecProvider()), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "failed to obtain spec: error", - }, - { - name: "ErroringEventsProvider", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewErroringEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - err: "failed to add head event handler: error", - }, - { - name: "Good", - params: []best.Parameter{ - best.WithLogLevel(zerolog.Disabled), - best.WithTimeout(2 * time.Second), - best.WithClientMonitor(null.New(context.Background())), - best.WithEventsProvider(mock.NewEventsProvider()), - best.WithChainTimeService(chainTime), - best.WithSpecProvider(specProvider), - best.WithProcessConcurrency(1), - best.WithBlindedProposalProviders(map[string]eth2client.BlindedProposalProvider{ - "one": mock.NewBlindedProposalProvider(chainTime), - "two": mock.NewBlindedProposalProvider(chainTime), - "three": mock.NewBlindedProposalProvider(chainTime), - }), - best.WithSignedBeaconBlockProvider(mock.NewSignedBeaconBlockProvider()), - best.WithBlockRootToSlotCache(blockToSlotCache), - }, - }, - } - - 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) - } - }) - } -} diff --git a/strategies/blindedbeaconblockproposal/first/parameters.go b/strategies/blindedbeaconblockproposal/first/parameters.go deleted file mode 100644 index 65d04b27..00000000 --- a/strategies/blindedbeaconblockproposal/first/parameters.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright © 2022 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 beacon block proposals 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/chaintime" - "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 - chainTime chaintime.Service - blindedProposalProviders map[string]eth2client.BlindedProposalProvider - 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 - }) -} - -// WithChainTimeService sets the chain time service. -func WithChainTimeService(chainTime chaintime.Service) Parameter { - return parameterFunc(func(p *parameters) { - p.chainTime = chainTime - }) -} - -// WithBlindedProposalProviders sets the blinded proposal providers. -func WithBlindedProposalProviders(providers map[string]eth2client.BlindedProposalProvider) Parameter { - return parameterFunc(func(p *parameters) { - p.blindedProposalProviders = 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(), - clientMonitor: nullmetrics.New(context.Background()), - } - for _, p := range params { - if params != nil { - p.apply(¶meters) - } - } - - if parameters.chainTime == nil { - return nil, errors.New("no chain time service specified") - } - if parameters.blindedProposalProviders == nil { - return nil, errors.New("no blinded proposal providers specified") - } - - return ¶meters, nil -} diff --git a/strategies/blindedbeaconblockproposal/first/service.go b/strategies/blindedbeaconblockproposal/first/service.go deleted file mode 100644 index d3a871ac..00000000 --- a/strategies/blindedbeaconblockproposal/first/service.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright © 2022, 2023 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 ( - "bytes" - "context" - "time" - - builderspec "github.com/attestantio/go-builder-client/spec" - eth2client "github.com/attestantio/go-eth2-client" - "github.com/attestantio/go-eth2-client/api" - "github.com/attestantio/go-eth2-client/spec/bellatrix" - "github.com/attestantio/vouch/services/chaintime" - "github.com/attestantio/vouch/services/metrics" - "github.com/pkg/errors" - "github.com/rs/zerolog" - zerologger "github.com/rs/zerolog/log" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/trace" -) - -// Service is the provider for beacon block proposals. -type Service struct { - clientMonitor metrics.ClientMonitor - chainTime chaintime.Service - blindedProposalProviders map[string]eth2client.BlindedProposalProvider - timeout time.Duration -} - -// module-wide log. -var log zerolog.Logger - -var zeroFeeRecipient bellatrix.ExecutionAddress - -// New creates a new beacon block proposal strategy. -func New(_ 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", "blindedbeaconblockproposal").Str("impl", "first").Logger() - if parameters.logLevel != log.GetLevel() { - log = log.Level(parameters.logLevel) - } - - s := &Service{ - chainTime: parameters.chainTime, - blindedProposalProviders: parameters.blindedProposalProviders, - timeout: parameters.timeout, - clientMonitor: parameters.clientMonitor, - } - - return s, nil -} - -// BlindedProposal provides the first blinded proposal from a number of beacon nodes. -func (s *Service) BlindedProposal(ctx context.Context, - opts *api.BlindedProposalOpts, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - return s.BlindedProposalWithExpectedPayload(ctx, opts, nil) -} - -// BlindedProposalWithExpectedPayload fetches a blinded proposal for signing. -func (s *Service) BlindedProposalWithExpectedPayload(ctx context.Context, - opts *api.BlindedProposalOpts, - bid *builderspec.VersionedSignedBuilderBid, -) ( - *api.Response[*api.VersionedBlindedProposal], - error, -) { - ctx, span := otel.Tracer("attestantio.vouch.strategies.blindedbeaconblockproposal.first").Start(ctx, "BlindedProposal", trace.WithAttributes( - attribute.Int64("slot", int64(opts.Slot)), - )) - defer span.End() - - // We create a cancelable context with a timeout. As soon as the first provider has responded we - // cancel the context to cancel the other requests. - ctx, cancel := context.WithTimeout(ctx, s.timeout) - - proposalCh := make(chan *api.Response[*api.VersionedBlindedProposal], 1) - for name, provider := range s.blindedProposalProviders { - go func(ctx context.Context, name string, provider eth2client.BlindedProposalProvider, ch chan *api.Response[*api.VersionedBlindedProposal]) { - log := log.With().Str("provider", name).Uint64("slot", uint64(opts.Slot)).Logger() - - started := time.Now() - proposalResp, err := provider.BlindedProposal(ctx, opts) - s.clientMonitor.ClientOperation(name, "blinded beacon block proposal", err == nil, time.Since(started)) - if err != nil { - log.Warn().Err(err).Msg("Failed to obtain blinded beacon block proposal") - return - } - proposal := proposalResp.Data - log.Trace().Dur("elapsed", time.Since(started)).Msg("Obtained blinded beacon block proposal") - feeRecipient, err := proposal.FeeRecipient() - if err != nil { - log.Warn().Err(err).Msg("Failed to obtain blinded beacon block fee recipient") - return - } - if bytes.Equal(feeRecipient[:], zeroFeeRecipient[:]) { - log.Warn().Msg("Blinded beacon block proposal response has 0 fee recipient") - return - } - executionTimestamp, err := proposal.Timestamp() - if err != nil { - log.Warn().Err(err).Msg("Failed to obtain blinded beacon block timestamp") - return - } - if int64(executionTimestamp) != s.chainTime.StartOfSlot(opts.Slot).Unix() { - log.Warn().Msg("Blinded beacon block proposal response has incorrect timestamp") - return - } - if bid != nil { - bidTransactionsRoot, err := bid.TransactionsRoot() - if err == nil { - proposalTransactionsRoot, err := proposal.TransactionsRoot() - if err != nil { - log.Warn().Err(err).Msg("Failed to obtain blinded beacon block transactions root") - return - } - if !bytes.Equal(bidTransactionsRoot[:], proposalTransactionsRoot[:]) { - log.Warn().Stringer("proposal_transactions_root", proposalTransactionsRoot).Stringer("bid_transactions_root", bidTransactionsRoot).Msg("Transactions root mismatch") - return - } - } - } - - ch <- proposalResp - }(ctx, name, provider, proposalCh) - } - - select { - case <-ctx.Done(): - cancel() - log.Warn().Msg("Failed to obtain blinded beacon block proposal before timeout") - return nil, errors.New("failed to obtain blinded beacon block proposal before timeout") - case proposal := <-proposalCh: - cancel() - return proposal, nil - } -} diff --git a/util/config.go b/util/config.go index 3a7b8e45..d153abcd 100644 --- a/util/config.go +++ b/util/config.go @@ -39,21 +39,6 @@ func BeaconNodeAddressesForProposing() []string { } } - switch viper.GetString("strategies.blindedbeaconblockproposal.style") { - case "best": - for _, nodeAddress := range BeaconNodeAddresses("strategies.blindedbeaconblockproposal.best") { - nodeAddresses[nodeAddress] = struct{}{} - } - case "first": - for _, nodeAddress := range BeaconNodeAddresses("strategies.blindedbeaconblockproposal.first") { - nodeAddresses[nodeAddress] = struct{}{} - } - default: - for _, nodeAddress := range BeaconNodeAddresses("") { - nodeAddresses[nodeAddress] = struct{}{} - } - } - addresses := make([]string, 0, len(nodeAddresses)) for nodeAddress := range nodeAddresses { addresses = append(addresses, nodeAddress) diff --git a/util/config_test.go b/util/config_test.go index ea0fffa1..405c0081 100644 --- a/util/config_test.go +++ b/util/config_test.go @@ -41,75 +41,30 @@ func TestBeaconNodeAddressesForProposing(t *testing.T) { name: "NoStrategy", env: map[string]string{ "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", + "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", + "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", }, expected: []string{"1", "2"}, }, { name: "FirstStrategy", env: map[string]string{ - "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "first", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", + "BEACON_NODE_ADDRESSES": "1 2", + "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "first", + "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", + "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", }, - expected: []string{"1", "2", "5", "6"}, - }, - { - name: "FirstStrategies", - env: map[string]string{ - "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "first", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_STYLE": "first", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", - }, - expected: []string{"5", "6", "9", "10"}, + expected: []string{"5", "6"}, }, { name: "BestStrategy", env: map[string]string{ - "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "best", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", - }, - expected: []string{"1", "2", "3", "4"}, - }, - { - name: "BestStrategies", - env: map[string]string{ - "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "best", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_STYLE": "best", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", + "BEACON_NODE_ADDRESSES": "1 2", + "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "best", + "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", + "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", }, - expected: []string{"3", "4", "7", "8"}, - }, - { - name: "MixedStrategies", - env: map[string]string{ - "BEACON_NODE_ADDRESSES": "1 2", - "STRATEGIES_BEACONBLOCKPROPOSAL_STYLE": "best", - "STRATEGIES_BEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "3 4", - "STRATEGIES_BEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "5 6", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_STYLE": "first", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_BEST_BEACON_NODE_ADDRESSES": "7 8", - "STRATEGIES_BLINDEDBEACONBLOCKPROPOSAL_FIRST_BEACON_NODE_ADDRESSES": "9 10", - }, - expected: []string{"3", "4", "9", "10"}, + expected: []string{"3", "4"}, }, }