├── .github └── workflows │ └── pr.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── bitlist ├── bitlist.go └── bitlist_test.go ├── bls ├── bls_cgo.go ├── bls_nocgo.go ├── bls_test.go ├── fixtures │ └── keystore.json ├── key.go └── key_test.go ├── chaintime ├── chaintime.go └── chaintime_test.go ├── cmp.go ├── deposit ├── deposit.abi ├── deposit.bin ├── deposit.go ├── deposit_artifacts.go ├── input.go └── input_test.go ├── domain.go ├── go.mod ├── go.sum ├── http ├── beacon.go ├── beacon_test.go ├── builder.go ├── builder_encoding.go ├── builder_test.go ├── config.go ├── config_test.go ├── encoding.go ├── events.go ├── events_test.go ├── http.go ├── http_test.go ├── node.go ├── node_test.go ├── testcases │ └── events.json ├── validator.go └── validator_test.go ├── interfaces.go ├── scripts ├── download-spec-tests.sh └── openapi-mock.sh ├── spec.go ├── spec ├── epoch_processing.go ├── epoch_processing_test.go ├── operations.go ├── operations_test.go ├── presets │ └── phase0.yaml ├── rewards.go ├── rewards_test.go ├── shuffle.go ├── shuffle_test.go ├── spec.go ├── spec_test.go └── testing.go ├── structs.go ├── structs_encoding.go ├── structs_test.go ├── utils.go └── utils_test.go /.github/workflows/pr.yaml: -------------------------------------------------------------------------------- 1 | name: Unit tests 2 | on: [push, pull_request] 3 | env: 4 | CGO_CFLAGS: "-O -D__BLST_PORTABLE__" 5 | CGO_ENABLED: 1 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | name: Go test 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Setup go 13 | uses: actions/setup-go@v1 14 | with: 15 | go-version: "1.18.1" 16 | - name: Download spec tests 17 | run: make get-spec-tests 18 | - name: Start openapi mock 19 | run: ./scripts/openapi-mock.sh 20 | - name: Unit tests 21 | run: go test -v ./... -timeout 10m 22 | - name: BLS Non-Cgo 23 | run: CGO_ENABLED=0 go test -v ./bls/... 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | eth2.0-spec-tests -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.3 (Unreleased) 2 | 3 | # 0.1.2 (12 Jan, 2022) 4 | 5 | - feat: Add non-cgo bls library [[GH-24](https://github.com/umbracle/eth2-validator/issues/24)] 6 | - feat: Add bls spec tests [[GH-23](https://github.com/umbracle/eth2-validator/issues/23)] 7 | - feat: Add `Capella` types [[GH-19](https://github.com/umbracle/eth2-validator/issues/19)] 8 | - feat: Add `Altair` light client forks [[GH-20](https://github.com/umbracle/eth2-validator/issues/20)] 9 | 10 | # 0.1.1 (28 Dec, 2022) 11 | 12 | - feat: add `builder` types and http methods [[GH-4](https://github.com/umbracle/eth2-validator/issues/4)] 13 | - feat: return wrap status code errors in Http client [[GH-8](https://github.com/umbracle/eth2-validator/issues/8)] 14 | - feat: `BeaconState` structs for `phase0`, `altair` and `bellatrix` forks [[GH-5](https://github.com/umbracle/eth2-validator/issues/5)] 15 | - feat: subscription endpoints for the beacon validator namespace [[GH-3](https://github.com/umbracle/eth2-validator/issues/3)] 16 | 17 | # 0.1.0 (29 July, 2022) 18 | 19 | - Initial public release. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | sszgen: 3 | sszgen --path structs.go --exclude-objs Root,Signature,Uint256 4 | sszgen --path ./http/validator.go --objs RegisterValidatorRequest --output ./http/builder_encoding.go 5 | 6 | get-spec-tests: 7 | ./scripts/download-spec-tests.sh v1.3.0-rc.2 8 | 9 | abigen-deposit: 10 | ethgo abigen --source ./internal/deposit/deposit.abi --package deposit --output ./internal/deposit/ 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go-eth-consensus [![Godoc](https://godoc.org/github.com/umbracle/go-eth-consensus?status.svg)](https://godoc.org/github.com/umbracle/go-eth-consensus) [![Chat Badge]][chat link] 2 | 3 | [chat badge]: https://img.shields.io/badge/chat-discord-%237289da 4 | [chat link]: https://discord.gg/yCqYxwZNYD 5 | 6 | Go-eth-consensus is a suite of Go utilities to interact with the Ethereum consensus layer. 7 | 8 | The core of this library was initially part of [eth2-validator](https://github.com/umbracle/eth2-validator). However, as [other](https://github.com/umbracle/viewpoint) projects started to mature, it became necessary to create a unified library to reduce code duplication and increase consistency. 9 | 10 | ## Features 11 | 12 | **Consensus data types**. Full set of data types (up to Bellatrix) in `structs.go` at root. It includes the SSZ encoding for each one using [`fastssz`](https://github.com/ferranbt/fastssz). Each type is end-to-end tested with the official consensus spec tests. 13 | 14 | **Http client**. Lightweight implementation for the [Beacon](https://ethereum.github.io/beacon-APIs) and [Builder](https://ethereum.github.io/builder-specs) OpenAPI spec. For usage and examples see the [Godoc](https://pkg.go.dev/github.com/umbracle/go-eth-consensus/http). The endpoints are tested against a real server that mocks the OpenAPI spec. 15 | 16 | **Chaintime**. Simple utilities to interact with slot times and epochs. 17 | 18 | **BLS**. Abstraction to sign, recover and store (with keystore format) BLS keys. It includes two implementations: [blst](https://github.com/supranational/blst) with cgo and [kilic/bls12-381](https://github.com/kilic/bls12-381) with pure Go. The build flag `CGO_ENABLED` determines which library is used. 19 | 20 | ## Installation 21 | 22 | ``` 23 | $ go get github.com/umbracle/go-eth-consensus 24 | ``` 25 | 26 | ## Bls benchmark 27 | 28 | Benchmark for both BLS implementations: 29 | 30 | ``` 31 | $ CGO_ENABLED=0 go test -v ./bls/... -run=XXX -bench=. 32 | goos: linux 33 | goarch: amd64 34 | pkg: github.com/umbracle/go-eth-consensus/bls 35 | cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz 36 | BenchmarkBLS_Sign 37 | BenchmarkBLS_Sign-4 2330 515495 ns/op 30192 B/op 282 allocs/op 38 | BenchmarkBLS_Verify 39 | BenchmarkBLS_Verify-4 814 1646769 ns/op 74048 B/op 190 allocs/op 40 | PASS 41 | ok github.com/umbracle/go-eth-consensus/bls 3.739s 42 | $ CGO_ENABLED=1 go test -v ./bls/... -run=XXX -bench=. 43 | goos: linux 44 | goarch: amd64 45 | pkg: github.com/umbracle/go-eth-consensus/bls 46 | cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz 47 | BenchmarkBLS_Sign 48 | BenchmarkBLS_Sign-4 2833 431271 ns/op 480 B/op 2 allocs/op 49 | BenchmarkBLS_Verify 50 | BenchmarkBLS_Verify-4 1282 903174 ns/op 4545 B/op 15 allocs/op 51 | PASS 52 | ok github.com/umbracle/go-eth-consensus/bls 2.525s 53 | ``` 54 | -------------------------------------------------------------------------------- /bitlist/bitlist.go: -------------------------------------------------------------------------------- 1 | package bitlist 2 | 3 | import ( 4 | "bytes" 5 | "math/bits" 6 | ) 7 | 8 | // BitList is a bitlist 9 | type BitList []byte 10 | 11 | func NewBitlist(n uint64) BitList { 12 | ret := make(BitList, n/8+1) 13 | 14 | i := uint8(1 << (n % 8)) 15 | ret[n/8] |= i 16 | 17 | return ret 18 | } 19 | 20 | // Len returns the length of the bitlist 21 | func (b BitList) Len() uint64 { 22 | if len(b) == 0 { 23 | return 0 24 | } 25 | msb := bits.Len8(b[len(b)-1]) 26 | if msb == 0 { 27 | return 0 28 | } 29 | return uint64(8*(len(b)-1) + msb - 1) 30 | } 31 | 32 | // SetBitAt sets the bit at a given position. 33 | func (b BitList) SetBitAt(indx uint64, val bool) { 34 | if len := b.Len(); indx >= len { 35 | return 36 | } 37 | 38 | bit := uint8(1 << (indx % 8)) 39 | if val { 40 | b[indx/8] |= bit 41 | } else { 42 | b[indx/8] &^= bit 43 | } 44 | } 45 | 46 | // BitAt returns the bit at a given position 47 | func (b BitList) BitAt(indx uint64) bool { 48 | if len := b.Len(); indx >= len { 49 | return false 50 | } 51 | 52 | bit := uint8(1 << (indx % 8)) 53 | return b[indx/8]&bit == bit 54 | } 55 | 56 | // Copy copies the bitlist 57 | func (b BitList) Copy() BitList { 58 | bb := make(BitList, len(b)) 59 | copy(bb[:], b[:]) 60 | 61 | return bb 62 | } 63 | 64 | // Equal checks whether two bitlist are equal 65 | func (b BitList) Equal(bb BitList) bool { 66 | return bytes.Equal(b, bb) 67 | } 68 | -------------------------------------------------------------------------------- /bitlist/bitlist_test.go: -------------------------------------------------------------------------------- 1 | package bitlist 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | ) 8 | 9 | func TestBitmap_SetOutOfBounds(t *testing.T) { 10 | b := NewBitlist(10) 11 | 12 | bb := b.Copy() 13 | b.SetBitAt(10, true) 14 | 15 | require.True(t, bb.Equal(b)) 16 | } 17 | 18 | func TestBitmap_SetIndx(t *testing.T) { 19 | size := uint64(10) 20 | b := NewBitlist(uint64(size)) 21 | 22 | // all empty 23 | for i := uint64(0); i < size; i++ { 24 | require.False(t, b.BitAt(i)) 25 | } 26 | 27 | // set indexes to true 28 | for i := uint64(0); i < size; i++ { 29 | b.SetBitAt(i, true) 30 | require.True(t, b.BitAt(i)) 31 | 32 | for j := uint64(0); j < size; j++ { 33 | require.Equal(t, b.BitAt(j), j <= i) 34 | } 35 | } 36 | 37 | // all indexes are full 38 | for i := uint64(0); i < size; i++ { 39 | require.True(t, b.BitAt(i)) 40 | } 41 | 42 | // set indexes to false 43 | for i := uint64(0); i < size; i++ { 44 | b.SetBitAt(i, false) 45 | require.False(t, b.BitAt(i)) 46 | 47 | for j := uint64(0); j < size; j++ { 48 | require.Equal(t, b.BitAt(j), j > i) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bls/bls_cgo.go: -------------------------------------------------------------------------------- 1 | //go:build cgo 2 | // +build cgo 3 | 4 | package bls 5 | 6 | import ( 7 | "crypto/rand" 8 | "fmt" 9 | 10 | blst "github.com/supranational/blst/bindings/go" 11 | ) 12 | 13 | type blstPublicKey = blst.P1Affine 14 | type blstSignature = blst.P2Affine 15 | 16 | var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") 17 | 18 | // Signature is a Bls signature 19 | type Signature struct { 20 | sig *blstSignature 21 | } 22 | 23 | func (s *Signature) Deserialize(buf []byte) error { 24 | sig := new(blstSignature).Uncompress(buf) 25 | if sig == nil { 26 | return fmt.Errorf("failed to deserialize") 27 | } 28 | if !sig.SigValidate(false) { 29 | return fmt.Errorf("signature not in group") 30 | } 31 | s.sig = sig 32 | return nil 33 | } 34 | 35 | func (s *Signature) Serialize() (buf [96]byte) { 36 | copy(buf[:], s.sig.Compress()) 37 | return 38 | } 39 | 40 | func (s *Signature) VerifyByte(pub *PublicKey, msg []byte) (bool, error) { 41 | return s.sig.Verify(false, pub.pub, false, msg, dst), nil 42 | } 43 | 44 | func (s *Signature) FastAggregateVerify(pubKeys []*PublicKey, msg []byte) (bool, error) { 45 | raw := make([]*blstPublicKey, len(pubKeys)) 46 | for indx, i := range pubKeys { 47 | raw[indx] = i.pub 48 | } 49 | 50 | return s.sig.FastAggregateVerify(true, raw, msg, dst), nil 51 | } 52 | 53 | func AggregateSignatures(sigs []*Signature) *Signature { 54 | if len(sigs) == 0 { 55 | return nil 56 | } 57 | raw := make([]*blstSignature, len(sigs)) 58 | for indx, i := range sigs { 59 | raw[indx] = i.sig 60 | } 61 | 62 | sig := new(blst.P2Aggregate) 63 | sig.Aggregate(raw, false) 64 | 65 | return &Signature{sig: sig.ToAffine()} 66 | } 67 | 68 | // PublicKey is a Bls public key 69 | type PublicKey struct { 70 | pub *blstPublicKey 71 | } 72 | 73 | func (p *PublicKey) Deserialize(buf []byte) error { 74 | pub := new(blstPublicKey).Uncompress(buf) 75 | if pub == nil { 76 | return fmt.Errorf("failed to deserialize") 77 | } 78 | if !pub.KeyValidate() { 79 | return fmt.Errorf("point at infinity") 80 | } 81 | p.pub = pub 82 | return nil 83 | } 84 | 85 | func (p *PublicKey) Serialize() (res [48]byte) { 86 | copy(res[:], p.pub.Compress()) 87 | return 88 | } 89 | 90 | // SecretKey is a Bls secret key 91 | type SecretKey struct { 92 | key *blst.SecretKey 93 | } 94 | 95 | func (s *SecretKey) Unmarshal(data []byte) error { 96 | s.key = new(blst.SecretKey).Deserialize(data) 97 | return nil 98 | } 99 | 100 | func (s *SecretKey) Marshal() ([]byte, error) { 101 | return s.key.Serialize(), nil 102 | } 103 | 104 | func (s *SecretKey) GetPublicKey() *PublicKey { 105 | pub := new(blstPublicKey).From(s.key) 106 | return &PublicKey{pub: pub} 107 | } 108 | 109 | func (s *SecretKey) Sign(msg []byte) (*Signature, error) { 110 | sig := new(blstSignature).Sign(s.key, msg, dst) 111 | return &Signature{sig: sig}, nil 112 | } 113 | 114 | func RandomKey() *SecretKey { 115 | var ikm [32]byte 116 | _, _ = rand.Read(ikm[:]) 117 | sk := blst.KeyGen(ikm[:]) 118 | 119 | sec := &SecretKey{ 120 | key: sk, 121 | } 122 | return sec 123 | } 124 | -------------------------------------------------------------------------------- /bls/bls_nocgo.go: -------------------------------------------------------------------------------- 1 | //go:build !cgo 2 | // +build !cgo 3 | 4 | package bls 5 | 6 | import ( 7 | "crypto/rand" 8 | "fmt" 9 | "math/big" 10 | 11 | bls12381 "github.com/kilic/bls12-381" 12 | ) 13 | 14 | type blstPublicKey = bls12381.PointG1 15 | type blstSignature = bls12381.PointG2 16 | 17 | var domain = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_") 18 | 19 | // Signature is a Bls signature 20 | type Signature struct { 21 | sig *blstSignature 22 | } 23 | 24 | func (s *Signature) Deserialize(buf []byte) error { 25 | g2, err := bls12381.NewG2().FromCompressed(buf) 26 | if err != nil { 27 | return err 28 | } 29 | s.sig = g2 30 | return nil 31 | } 32 | 33 | func (s *Signature) Serialize() (res [96]byte) { 34 | buf := bls12381.NewG2().ToCompressed(s.sig) 35 | copy(res[:], buf) 36 | return 37 | } 38 | 39 | func (s *Signature) VerifyByte(pub *PublicKey, msg []byte) (bool, error) { 40 | return s.verifyImpl(pub.pub, msg) 41 | } 42 | 43 | func (s *Signature) verifyImpl(g1 *bls12381.PointG1, msg []byte) (bool, error) { 44 | hash, err := bls12381.NewG2().HashToCurve(msg, domain) 45 | if err != nil { 46 | return false, err 47 | } 48 | 49 | e := bls12381.NewEngine() 50 | e.AddPairInv(e.G1.One(), s.sig) 51 | e.AddPair(g1, hash) 52 | 53 | return e.Check(), nil 54 | } 55 | 56 | func (s *Signature) FastAggregateVerify(pubKeys []*PublicKey, msg []byte) (bool, error) { 57 | if bls12381.NewG2().IsZero(s.sig) { 58 | // signature is infinite 59 | return false, nil 60 | } 61 | 62 | // aggregate public keys 63 | aggPub := new(bls12381.PointG1) 64 | g1 := bls12381.NewG1() 65 | 66 | for _, pub := range pubKeys { 67 | aggPub = g1.Add(aggPub, aggPub, pub.pub) 68 | } 69 | 70 | ok, err := s.verifyImpl(aggPub, msg) 71 | if err != nil { 72 | return false, err 73 | } 74 | return ok, nil 75 | } 76 | 77 | func AggregateSignatures(sigs []*Signature) *Signature { 78 | if len(sigs) == 0 { 79 | return nil 80 | } 81 | 82 | aggSig := new(bls12381.PointG2) 83 | g2 := bls12381.NewG2() 84 | 85 | for _, sig := range sigs { 86 | aggSig = g2.Add(aggSig, aggSig, sig.sig) 87 | } 88 | 89 | return &Signature{sig: aggSig} 90 | } 91 | 92 | // PublicKey is a Bls public key 93 | type PublicKey struct { 94 | pub *blstPublicKey 95 | } 96 | 97 | func (p *PublicKey) Deserialize(buf []byte) error { 98 | g1, err := bls12381.NewG1().FromCompressed(buf) 99 | if err != nil { 100 | return err 101 | } 102 | if bls12381.NewG1().IsZero(g1) { 103 | return fmt.Errorf("infinity") 104 | } 105 | p.pub = g1 106 | return nil 107 | } 108 | 109 | func (p *PublicKey) Serialize() (res [48]byte) { 110 | buf := bls12381.NewG1().ToCompressed(p.pub) 111 | copy(res[:], buf) 112 | return 113 | } 114 | 115 | // SecretKey is a Bls secret key 116 | type SecretKey struct { 117 | key *big.Int 118 | } 119 | 120 | func (s *SecretKey) Unmarshal(data []byte) error { 121 | s.key = new(big.Int).SetBytes(data) 122 | return nil 123 | } 124 | 125 | func (s *SecretKey) Marshal() ([]byte, error) { 126 | return s.key.Bytes(), nil 127 | } 128 | 129 | func (s *SecretKey) GetPublicKey() *PublicKey { 130 | p := new(blstPublicKey) 131 | p = bls12381.NewG1().MulScalarBig(p, &bls12381.G1One, s.key) 132 | return &PublicKey{pub: p} 133 | } 134 | 135 | func (s *SecretKey) Sign(msg []byte) (*Signature, error) { 136 | hash, err := bls12381.NewG2().HashToCurve(msg, domain) 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | g2 := bls12381.NewG2() 142 | g2.MulScalarBig(hash, hash, s.key) 143 | 144 | return &Signature{sig: hash}, nil 145 | } 146 | 147 | var curveOrder, _ = new(big.Int).SetString("73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001", 16) 148 | 149 | func RandomKey() *SecretKey { 150 | k, err := rand.Int(rand.Reader, curveOrder) 151 | if err != nil { 152 | panic(err) 153 | } 154 | return &SecretKey{key: k} 155 | } 156 | -------------------------------------------------------------------------------- /bls/bls_test.go: -------------------------------------------------------------------------------- 1 | package bls 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "io/ioutil" 7 | "path/filepath" 8 | "reflect" 9 | "strings" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | func TestBLS_Simple(t *testing.T) { 16 | msg := []byte("msg") 17 | priv := RandomKey() 18 | 19 | sig0, err := priv.Sign(msg) 20 | require.NoError(t, err) 21 | 22 | sig0B := sig0.Serialize() 23 | 24 | sig1 := &Signature{} 25 | if err := sig1.Deserialize(sig0B[:]); err != nil { 26 | t.Fatal(err) 27 | } 28 | 29 | pub0 := priv.GetPublicKey().Serialize() 30 | pub1 := &PublicKey{} 31 | if err := pub1.Deserialize(pub0[:]); err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | valid, err := sig1.VerifyByte(pub1, msg) 36 | require.NoError(t, err) 37 | require.True(t, valid) 38 | } 39 | 40 | func TestBLS_Aggregate(t *testing.T) { 41 | type ref struct { 42 | Input []argBytes 43 | Output argBytes 44 | } 45 | 46 | readBLSDir(t, "aggregate", new(ref), func(name string, i interface{}) { 47 | obj := i.(*ref) 48 | 49 | var sigs []*Signature 50 | for _, o := range obj.Input { 51 | sig := new(Signature) 52 | require.NoError(t, sig.Deserialize(o)) 53 | 54 | sigs = append(sigs, sig) 55 | } 56 | 57 | if len(obj.Output) == 0 { 58 | return 59 | } 60 | 61 | sig := AggregateSignatures(sigs).Serialize() 62 | require.Equal(t, sig[:], []byte(obj.Output)) 63 | }) 64 | } 65 | 66 | func TestBLS_FastAggregateVerify(t *testing.T) { 67 | type ref struct { 68 | Input struct { 69 | Pubkeys []argBytes 70 | Message argBytes 71 | Signature argBytes 72 | } 73 | Output bool 74 | } 75 | 76 | readBLSDir(t, "fast_aggregate_verify", new(ref), func(name string, i interface{}) { 77 | obj := i.(*ref) 78 | 79 | pubKeys := []*PublicKey{} 80 | for _, elem := range obj.Input.Pubkeys { 81 | 82 | pub := new(PublicKey) 83 | if err := pub.Deserialize(elem); err != nil { 84 | if !obj.Output { 85 | return 86 | } 87 | t.Fatal(err) 88 | } 89 | 90 | pubKeys = append(pubKeys, pub) 91 | } 92 | 93 | sig := new(Signature) 94 | if err := sig.Deserialize(obj.Input.Signature); err != nil { 95 | if !obj.Output { 96 | return 97 | } 98 | t.Fatal(err) 99 | } 100 | 101 | ok, err := sig.FastAggregateVerify(pubKeys, obj.Input.Message) 102 | require.NoError(t, err) 103 | require.Equal(t, ok, obj.Output) 104 | }) 105 | } 106 | 107 | func TestBLS_DeserializationG1(t *testing.T) { 108 | type ref struct { 109 | Input struct { 110 | Pubkey argBytes 111 | } 112 | Output bool 113 | } 114 | 115 | readBLSDir(t, "deserialization_G1", new(ref), func(name string, i interface{}) { 116 | obj := i.(*ref) 117 | 118 | pub := new(PublicKey) 119 | err := pub.Deserialize(obj.Input.Pubkey) 120 | 121 | if name == "deserialization_succeeds_infinity_with_true_b_flag.json" { 122 | // we also fail if point is at inifinity 123 | return 124 | } 125 | 126 | if err == nil && !obj.Output { 127 | t.Fatal("it should fail") 128 | } else if err != nil && obj.Output { 129 | t.Fatal(err) 130 | } 131 | }) 132 | } 133 | 134 | func TestBLS_DeserializationG2(t *testing.T) { 135 | type ref struct { 136 | Input struct { 137 | Signature argBytes 138 | } 139 | Output bool 140 | } 141 | 142 | readBLSDir(t, "deserialization_G2", new(ref), func(name string, i interface{}) { 143 | obj := i.(*ref) 144 | 145 | sig := new(Signature) 146 | err := sig.Deserialize(obj.Input.Signature) 147 | 148 | if err == nil && !obj.Output { 149 | t.Fatal("it should fail") 150 | } else if err != nil && obj.Output { 151 | t.Fatal(err) 152 | } 153 | }) 154 | } 155 | 156 | func TestBLS_Verify(t *testing.T) { 157 | type ref struct { 158 | Input struct { 159 | Pubkey argBytes 160 | Message argBytes 161 | Signature argBytes 162 | } 163 | Output bool 164 | } 165 | 166 | readBLSDir(t, "verify", new(ref), func(name string, i interface{}) { 167 | obj := i.(*ref) 168 | 169 | pub := new(PublicKey) 170 | if err := pub.Deserialize(obj.Input.Pubkey); err != nil { 171 | if !obj.Output { 172 | return 173 | } 174 | t.Fatal("failed to unmarshal pubkey") 175 | } 176 | 177 | sig := new(Signature) 178 | if err := sig.Deserialize(obj.Input.Signature); err != nil { 179 | if !obj.Output { 180 | return 181 | } 182 | t.Fatal("failed to unmarshal signature") 183 | } 184 | 185 | output, err := sig.VerifyByte(pub, obj.Input.Message) 186 | require.NoError(t, err) 187 | require.Equal(t, obj.Output, output) 188 | }) 189 | } 190 | 191 | func TestBLS_Sign(t *testing.T) { 192 | type ref struct { 193 | Input struct { 194 | Privkey argBytes 195 | Message argBytes 196 | } 197 | Output *argBytes 198 | } 199 | 200 | readBLSDir(t, "sign", new(ref), func(name string, i interface{}) { 201 | obj := i.(*ref) 202 | 203 | if obj.Output == nil { 204 | return 205 | } 206 | 207 | sec := new(SecretKey) 208 | require.NoError(t, sec.Unmarshal(obj.Input.Privkey)) 209 | 210 | sig, err := sec.Sign(obj.Input.Message) 211 | require.NoError(t, err) 212 | 213 | // we should be able to verify a signature 214 | verify, err := sig.VerifyByte(sec.GetPublicKey(), obj.Input.Message) 215 | require.NoError(t, err) 216 | require.True(t, verify) 217 | 218 | sigB := sig.Serialize() 219 | require.Equal(t, []byte(*obj.Output), sigB[:]) 220 | }) 221 | } 222 | 223 | func readBLSDir(t *testing.T, path string, ref interface{}, callback func(string, interface{})) { 224 | fullPath := filepath.Join("../eth2.0-spec-tests/bls", path) 225 | 226 | files, err := ioutil.ReadDir(fullPath) 227 | require.NoError(t, err) 228 | 229 | for _, file := range files { 230 | data, err := ioutil.ReadFile(filepath.Join(fullPath, file.Name())) 231 | require.NoError(t, err) 232 | 233 | obj := reflect.New(reflect.TypeOf(ref).Elem()).Interface() 234 | 235 | err = json.Unmarshal(data, obj) 236 | require.NoError(t, err) 237 | 238 | callback(file.Name(), obj) 239 | } 240 | } 241 | 242 | func BenchmarkBLS_Sign(b *testing.B) { 243 | msg := []byte("msg") 244 | priv := RandomKey() 245 | 246 | b.ReportAllocs() 247 | b.ResetTimer() 248 | 249 | for i := 0; i < b.N; i++ { 250 | priv.Sign(msg) 251 | } 252 | } 253 | 254 | func BenchmarkBLS_Verify(b *testing.B) { 255 | msg := []byte("msg") 256 | priv := RandomKey() 257 | pub := priv.GetPublicKey() 258 | 259 | sign, err := priv.Sign(msg) 260 | if err != nil { 261 | b.Fatal(err) 262 | } 263 | 264 | b.ReportAllocs() 265 | b.ResetTimer() 266 | 267 | for i := 0; i < b.N; i++ { 268 | sign.VerifyByte(pub, msg) 269 | } 270 | } 271 | 272 | func BenchmarkBLS_AggregateVerify(b *testing.B) { 273 | msg := []byte("msg") 274 | 275 | num := 10 276 | pubs := make([]*PublicKey, num) 277 | sigs := make([]*Signature, num) 278 | 279 | for i := 0; i < num; i++ { 280 | priv := RandomKey() 281 | 282 | sign, err := priv.Sign(msg) 283 | if err != nil { 284 | b.Fatal(err) 285 | } 286 | 287 | sigs[i] = sign 288 | pubs[i] = priv.GetPublicKey() 289 | } 290 | 291 | sig := AggregateSignatures(sigs) 292 | 293 | b.ReportAllocs() 294 | b.ResetTimer() 295 | 296 | for i := 0; i < b.N; i++ { 297 | sig.FastAggregateVerify(pubs, msg) 298 | } 299 | } 300 | 301 | type argBytes []byte 302 | 303 | func (b *argBytes) UnmarshalText(input []byte) error { 304 | if len(input) == 0 { 305 | // some tests have empty inputs 306 | return nil 307 | } 308 | str := string(input) 309 | if strings.HasPrefix(str, "0x") { 310 | // some values have 0x prefix 311 | str = str[2:] 312 | } 313 | buf, err := hex.DecodeString(str) 314 | if err != nil { 315 | return nil 316 | } 317 | aux := make([]byte, len(buf)) 318 | copy(aux[:], buf[:]) 319 | *b = aux 320 | return nil 321 | } 322 | -------------------------------------------------------------------------------- /bls/fixtures/keystore.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "crypto": { 4 | "kdf": { 5 | "function": "scrypt", 6 | "params": { 7 | "dklen": 32, 8 | "n": 262144, 9 | "p": 1, 10 | "r": 8, 11 | "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" 12 | }, 13 | "message": "" 14 | }, 15 | "checksum": { 16 | "function": "sha256", 17 | "params": {}, 18 | "message": "d2217fe5f3e9a1e34581ef8a78f7c9928e436d36dacc5e846690a5581e8ea484" 19 | }, 20 | "cipher": { 21 | "function": "aes-128-ctr", 22 | "params": { 23 | "iv": "264daa3f303d7259501c93d997d84fe6" 24 | }, 25 | "message": "06ae90d55fe0a6e9c5c3bc5b170827b2e5cce3929ed3f116c2811e6366dfe20f" 26 | } 27 | }, 28 | "description": "This is a test keystore that uses scrypt to secure the secret.", 29 | "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", 30 | "path": "m/12381/60/3141592653/589793238", 31 | "uuid": "1d85ae20-35c5-4611-98e8-aa14a633906f", 32 | "version": 4 33 | }, 34 | { 35 | "crypto": { 36 | "kdf": { 37 | "function": "pbkdf2", 38 | "params": { 39 | "dklen": 32, 40 | "c": 262144, 41 | "prf": "hmac-sha256", 42 | "salt": "d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" 43 | }, 44 | "message": "" 45 | }, 46 | "checksum": { 47 | "function": "sha256", 48 | "params": {}, 49 | "message": "8a9f5d9912ed7e75ea794bc5a89bca5f193721d30868ade6f73043c6ea6febf1" 50 | }, 51 | "cipher": { 52 | "function": "aes-128-ctr", 53 | "params": { 54 | "iv": "264daa3f303d7259501c93d997d84fe6" 55 | }, 56 | "message": "cee03fde2af33149775b7223e7845e4fb2c8ae1792e5f99fe9ecf474cc8c16ad" 57 | } 58 | }, 59 | "description": "This is a test keystore that uses PBKDF2 to secure the secret.", 60 | "pubkey": "9612d7a727c9d0a22e185a1c768478dfe919cada9266988cb32359c11f2b7b27f4ae4040902382ae2910c15e2b420d07", 61 | "path": "m/12381/60/0/0", 62 | "uuid": "64625def-3331-4eea-ab6f-782f3ed16a83", 63 | "version": 4 64 | } 65 | ] -------------------------------------------------------------------------------- /bls/key.go: -------------------------------------------------------------------------------- 1 | package bls 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | 9 | uuid "github.com/hashicorp/go-uuid" 10 | "github.com/umbracle/ethgo/keystore" 11 | ) 12 | 13 | // Key is a reference to a key in the keymanager 14 | type Key struct { 15 | Id string 16 | Pub *PublicKey 17 | Prv *SecretKey 18 | } 19 | 20 | func (k *Key) Unmarshal(data []byte) error { 21 | k.Prv = &SecretKey{} 22 | if err := k.Prv.Unmarshal(data); err != nil { 23 | return err 24 | } 25 | k.Pub = k.Prv.GetPublicKey() 26 | return nil 27 | } 28 | 29 | func (k *Key) Marshal() ([]byte, error) { 30 | return k.Prv.Marshal() 31 | } 32 | 33 | func (k *Key) Equal(kk *Key) bool { 34 | a := k.Pub.Serialize() 35 | b := kk.Pub.Serialize() 36 | return bytes.Equal(a[:], b[:]) 37 | } 38 | 39 | func (k *Key) PubKey() (out [48]byte) { 40 | return k.Pub.Serialize() 41 | } 42 | 43 | func (k *Key) Sign(root [32]byte) ([96]byte, error) { 44 | signed, err := k.Prv.Sign(root[:]) 45 | if err != nil { 46 | return [96]byte{}, err 47 | } 48 | return signed.Serialize(), nil 49 | } 50 | 51 | func NewKeyFromPriv(priv []byte) (*Key, error) { 52 | k := &Key{} 53 | if err := k.Unmarshal(priv); err != nil { 54 | return nil, err 55 | } 56 | return k, nil 57 | } 58 | 59 | func NewRandomKey() *Key { 60 | sec := RandomKey() 61 | id, _ := uuid.GenerateUUID() 62 | 63 | k := &Key{ 64 | Id: id, 65 | Prv: sec, 66 | Pub: sec.GetPublicKey(), 67 | } 68 | return k 69 | } 70 | 71 | func FromKeystore(content []byte, password string) (*Key, error) { 72 | var dec map[string]interface{} 73 | if err := json.Unmarshal(content, &dec); err != nil { 74 | return nil, err 75 | } 76 | 77 | priv, err := keystore.DecryptV4(content, password) 78 | if err != nil { 79 | return nil, err 80 | } 81 | key, err := NewKeyFromPriv(priv) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | pub := key.PubKey() 87 | if hex.EncodeToString(pub[:]) != dec["pubkey"] { 88 | return nil, fmt.Errorf("pub key does not match") 89 | } 90 | key.Id = dec["uuid"].(string) 91 | return key, nil 92 | } 93 | 94 | func ToKeystore(k *Key, password string) ([]byte, error) { 95 | priv, err := k.Prv.Marshal() 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | keystore, err := keystore.EncryptV4(priv, password) 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | var dec map[string]interface{} 106 | if err := json.Unmarshal(keystore, &dec); err != nil { 107 | return nil, err 108 | } 109 | 110 | serializePub := k.Pub.Serialize() 111 | 112 | dec["pubkey"] = hex.EncodeToString(serializePub[:]) 113 | dec["uuid"] = k.Id 114 | 115 | // small error here, params is set to nil in ethgo 116 | a := dec["crypto"].(map[string]interface{}) 117 | b := a["checksum"].(map[string]interface{}) 118 | b["params"] = map[string]interface{}{} 119 | 120 | return json.Marshal(dec) 121 | } 122 | -------------------------------------------------------------------------------- /bls/key_test.go: -------------------------------------------------------------------------------- 1 | package bls 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestKey_Encoding(t *testing.T) { 12 | key := NewRandomKey() 13 | 14 | data, err := key.Marshal() 15 | assert.NoError(t, err) 16 | 17 | key1 := &Key{} 18 | assert.NoError(t, key1.Unmarshal(data)) 19 | 20 | assert.True(t, key.Equal(key1)) 21 | } 22 | 23 | func TestKey_Keystore_Fixture(t *testing.T) { 24 | content, err := ioutil.ReadFile("./fixtures/keystore.json") 25 | assert.NoError(t, err) 26 | 27 | var data []json.RawMessage 28 | assert.NoError(t, json.Unmarshal(content, &data)) 29 | 30 | _, err = FromKeystore(data[0], "𝔱𝔢𝔰𝔱𝔭𝔞𝔰𝔰𝔴𝔬𝔯𝔡🔑") 31 | assert.NoError(t, err) 32 | } 33 | -------------------------------------------------------------------------------- /chaintime/chaintime.go: -------------------------------------------------------------------------------- 1 | package chaintime 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type Chaintime struct { 8 | Genesis time.Time 9 | SecondsPerSlot uint64 10 | SlotsPerEpoch uint64 11 | } 12 | 13 | func New(genesis time.Time, secondsPerSlot, slotsPerEpoch uint64) *Chaintime { 14 | return &Chaintime{ 15 | Genesis: genesis, 16 | SecondsPerSlot: secondsPerSlot, 17 | SlotsPerEpoch: slotsPerEpoch, 18 | } 19 | } 20 | 21 | func (c *Chaintime) IsActive() bool { 22 | return c.Genesis.Before(now()) 23 | } 24 | 25 | func (c *Chaintime) SlotToEpoch(slot uint64) uint64 { 26 | return slot / c.SlotsPerEpoch 27 | } 28 | 29 | func (c *Chaintime) newTime(seconds uint64) time.Time { 30 | return c.Genesis.Add(time.Duration(seconds) * time.Second) 31 | } 32 | 33 | func (c *Chaintime) CurrentEpoch() Epoch { 34 | numEpoch := uint64(now().Sub(c.Genesis).Seconds()) / (c.SecondsPerSlot * c.SlotsPerEpoch) 35 | return c.Epoch(numEpoch) 36 | } 37 | 38 | func (c *Chaintime) CurrentSlot() Slot { 39 | numSlot := uint64(now().Sub(c.Genesis).Seconds()) / c.SecondsPerSlot 40 | return c.Slot(numSlot) 41 | } 42 | 43 | func (c *Chaintime) Slot(slot uint64) Slot { 44 | s := Slot{ 45 | Number: slot, 46 | Time: c.newTime(slot * c.SecondsPerSlot), 47 | Epoch: c.SlotToEpoch(slot), 48 | } 49 | return s 50 | } 51 | 52 | func (c *Chaintime) Epoch(epoch uint64) Epoch { 53 | e := Epoch{ 54 | Number: epoch, 55 | Time: c.newTime(epoch * c.SlotsPerEpoch * c.SecondsPerSlot), 56 | } 57 | return e 58 | } 59 | 60 | type Epoch struct { 61 | Number uint64 62 | Time time.Time 63 | } 64 | 65 | func (e Epoch) Until() time.Duration { 66 | return e.Time.Sub(now()) 67 | } 68 | 69 | func (e Epoch) C() *time.Timer { 70 | return time.NewTimer(e.Time.Sub(now())) 71 | } 72 | 73 | type Slot struct { 74 | Number uint64 75 | Time time.Time 76 | Epoch uint64 77 | } 78 | 79 | func (s Slot) C() *time.Timer { 80 | return time.NewTimer(s.Time.Sub(now())) 81 | } 82 | 83 | var now = time.Now 84 | -------------------------------------------------------------------------------- /chaintime/chaintime_test.go: -------------------------------------------------------------------------------- 1 | package chaintime 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func restoreHooks() func() { 11 | n := now 12 | return func() { 13 | now = n 14 | } 15 | } 16 | 17 | func TestChainTime_SlotToEpoch(t *testing.T) { 18 | c := New(time.Now(), 0, 10) 19 | 20 | var cases = []struct { 21 | slot uint64 22 | epoch uint64 23 | }{ 24 | {0, 0}, 25 | {1, 0}, 26 | {9, 0}, 27 | {10, 1}, 28 | {11, 1}, 29 | {19, 1}, 30 | {20, 2}, 31 | {21, 2}, 32 | } 33 | for _, cc := range cases { 34 | assert.Equal(t, c.SlotToEpoch(cc.slot), cc.epoch) 35 | } 36 | } 37 | 38 | func TestChainTime_Active(t *testing.T) { 39 | defer restoreHooks() 40 | 41 | c := New(time.Unix(10, 0), 10, 1) 42 | 43 | now = func() time.Time { return time.Unix(0, 0) } 44 | assert.False(t, c.IsActive()) 45 | 46 | now = func() time.Time { return time.Unix(11, 0) } 47 | assert.True(t, c.IsActive()) 48 | } 49 | 50 | func TestChainTime_Current(t *testing.T) { 51 | defer restoreHooks() 52 | 53 | c := New(time.Unix(0, 0), 10, 1) 54 | 55 | now = func() time.Time { return time.Unix(21, 0) } 56 | e := c.CurrentEpoch() 57 | assert.Equal(t, e.Number, uint64(2)) 58 | 59 | s := c.CurrentSlot() 60 | assert.Equal(t, s.Number, uint64(2)) 61 | } 62 | 63 | func TestChainTime_GetEpoch(t *testing.T) { 64 | defer restoreHooks() 65 | 66 | c := New(time.Unix(10, 0), 10, 10) 67 | 68 | s := c.Epoch(3) 69 | assert.Equal(t, s.Number, uint64(3)) 70 | 71 | expectedTime := int64(10 + 3*10*10) 72 | assert.Equal(t, s.Time, time.Unix(expectedTime, 0)) 73 | 74 | // one second to epoch 75 | now = func() time.Time { return time.Unix(expectedTime-1, 0) } 76 | 77 | select { 78 | case <-s.C().C: 79 | case <-time.After(2 * time.Second): 80 | t.Fatal("timeout") 81 | } 82 | } 83 | 84 | func TestChainTime_GetSlot(t *testing.T) { 85 | defer restoreHooks() 86 | 87 | c := New(time.Unix(10, 0), 10, 10) 88 | 89 | s := c.Slot(20) 90 | assert.Equal(t, s.Number, uint64(20)) 91 | assert.Equal(t, s.Epoch, uint64(2)) 92 | 93 | expectedTime := int64(10 + 20*10) 94 | assert.Equal(t, s.Time, time.Unix(expectedTime, 0)) 95 | 96 | // one second to slot time 97 | now = func() time.Time { return time.Unix(expectedTime-1, 0) } 98 | 99 | select { 100 | case <-s.C().C: 101 | case <-time.After(2 * time.Second): 102 | t.Fatal("timeout") 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /cmp.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func deepEqualImpl(v1, v2 reflect.Value, depth int) bool { 9 | if !v1.IsValid() || !v2.IsValid() { 10 | return v1.IsValid() == v2.IsValid() 11 | } 12 | if v1.Type() != v2.Type() { 13 | return false 14 | } 15 | 16 | switch v1.Kind() { 17 | case reflect.Array: 18 | for i := 0; i < v1.Len(); i++ { 19 | if !deepEqualImpl(v1.Index(i), v2.Index(i), depth+1) { 20 | return false 21 | } 22 | } 23 | return true 24 | 25 | case reflect.Slice: 26 | v1Empty := v1.IsNil() || v1.Len() == 0 27 | v2Empty := v2.IsNil() || v2.Len() == 0 28 | 29 | if v1Empty && v2Empty { 30 | return true 31 | } 32 | if v1.Len() != v2.Len() { 33 | return false 34 | } 35 | if v1.Pointer() == v2.Pointer() { 36 | return true 37 | } 38 | for i := 0; i < v1.Len(); i++ { 39 | if !deepEqualImpl(v1.Index(i), v2.Index(i), depth+1) { 40 | return false 41 | } 42 | } 43 | return true 44 | 45 | case reflect.Ptr: 46 | if v1.Pointer() == v2.Pointer() { 47 | return true 48 | } 49 | return deepEqualImpl(v1.Elem(), v2.Elem(), depth+1) 50 | 51 | case reflect.Struct: 52 | for i, n := 0, v1.NumField(); i < n; i++ { 53 | if !deepEqualImpl(v1.Field(i), v2.Field(i), depth+1) { 54 | return false 55 | } 56 | } 57 | return true 58 | 59 | default: 60 | // basic types 61 | if v2.Kind() != v1.Kind() { 62 | panic("BUG") 63 | } 64 | switch v1.Kind() { 65 | case reflect.Uint8, reflect.Uint64: 66 | return v1.Uint() == v2.Uint() 67 | 68 | case reflect.Bool: 69 | return v1.Bool() == v2.Bool() 70 | 71 | default: 72 | panic(fmt.Errorf("comparison for type %s not supported", v1.Kind().String())) 73 | } 74 | } 75 | } 76 | 77 | func deepEqual(x, y interface{}) bool { 78 | if x == nil || y == nil { 79 | return x == y 80 | } 81 | v1 := reflect.ValueOf(x) 82 | v2 := reflect.ValueOf(y) 83 | if v1.Type() != v2.Type() { 84 | return false 85 | } 86 | return deepEqualImpl(v1, v2, 0) 87 | } 88 | -------------------------------------------------------------------------------- /deposit/deposit.abi: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "inputs": [ 4 | 5 | ], 6 | "stateMutability": "nonpayable", 7 | "type": "constructor" 8 | }, 9 | { 10 | "anonymous": false, 11 | "inputs": [ 12 | { 13 | "indexed": false, 14 | "internalType": "bytes", 15 | "name": "pubkey", 16 | "type": "bytes" 17 | }, 18 | { 19 | "indexed": false, 20 | "internalType": "bytes", 21 | "name": "withdrawal_credentials", 22 | "type": "bytes" 23 | }, 24 | { 25 | "indexed": false, 26 | "internalType": "bytes", 27 | "name": "amount", 28 | "type": "bytes" 29 | }, 30 | { 31 | "indexed": false, 32 | "internalType": "bytes", 33 | "name": "signature", 34 | "type": "bytes" 35 | }, 36 | { 37 | "indexed": false, 38 | "internalType": "bytes", 39 | "name": "index", 40 | "type": "bytes" 41 | } 42 | ], 43 | "name": "DepositEvent", 44 | "type": "event" 45 | }, 46 | { 47 | "inputs": [ 48 | { 49 | "internalType": "bytes", 50 | "name": "pubkey", 51 | "type": "bytes" 52 | }, 53 | { 54 | "internalType": "bytes", 55 | "name": "withdrawal_credentials", 56 | "type": "bytes" 57 | }, 58 | { 59 | "internalType": "bytes", 60 | "name": "signature", 61 | "type": "bytes" 62 | }, 63 | { 64 | "internalType": "bytes32", 65 | "name": "deposit_data_root", 66 | "type": "bytes32" 67 | } 68 | ], 69 | "name": "deposit", 70 | "outputs": [ 71 | 72 | ], 73 | "stateMutability": "payable", 74 | "type": "function" 75 | }, 76 | { 77 | "inputs": [ 78 | 79 | ], 80 | "name": "get_deposit_count", 81 | "outputs": [ 82 | { 83 | "internalType": "bytes", 84 | "name": "", 85 | "type": "bytes" 86 | } 87 | ], 88 | "stateMutability": "view", 89 | "type": "function" 90 | }, 91 | { 92 | "inputs": [ 93 | 94 | ], 95 | "name": "get_deposit_root", 96 | "outputs": [ 97 | { 98 | "internalType": "bytes32", 99 | "name": "", 100 | "type": "bytes32" 101 | } 102 | ], 103 | "stateMutability": "view", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "bytes4", 110 | "name": "interfaceId", 111 | "type": "bytes4" 112 | } 113 | ], 114 | "name": "supportsInterface", 115 | "outputs": [ 116 | { 117 | "internalType": "bool", 118 | "name": "", 119 | "type": "bool" 120 | } 121 | ], 122 | "stateMutability": "pure", 123 | "type": "function" 124 | } 125 | ] -------------------------------------------------------------------------------- /deposit/deposit.bin: -------------------------------------------------------------------------------- 1 | 0x608060405234801561001057600080fd5b5060005b601f8110156101025760026021826020811061002c57fe5b01546021836020811061003b57fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b602083106100925780518252601f199092019160209182019101610073565b51815160209384036101000a60001901801990921691161790526040519190930194509192505080830381855afa1580156100d1573d6000803e3d6000fd5b5050506040513d60208110156100e657600080fd5b5051602160018301602081106100f857fe5b0155600101610014565b506118d680620001136000396000f3fe60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a264697066735822122048c9c1aefe892e05fe034c24a651f00a2a8c0eb7e7c569d35ac1920c1a6894bc64736f6c63430006080033 -------------------------------------------------------------------------------- /deposit/deposit.go: -------------------------------------------------------------------------------- 1 | // Code generated by ethgo/abigen. DO NOT EDIT. 2 | // Hash: c7ac9f62b1ca54bf9b6bcedde8041ba5961c2ef62683c4ff6b77a19ae09bab7a 3 | // Version: 0.1.2 4 | package deposit 5 | 6 | import ( 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/umbracle/ethgo" 11 | "github.com/umbracle/ethgo/contract" 12 | "github.com/umbracle/ethgo/jsonrpc" 13 | ) 14 | 15 | var ( 16 | _ = big.NewInt 17 | _ = jsonrpc.NewClient 18 | ) 19 | 20 | // Deposit is a solidity contract 21 | type Deposit struct { 22 | c *contract.Contract 23 | } 24 | 25 | // DeployDeposit deploys a new Deposit contract 26 | func DeployDeposit(provider *jsonrpc.Client, from ethgo.Address, args []interface{}, opts ...contract.ContractOption) (contract.Txn, error) { 27 | return contract.DeployContract(abiDeposit, binDeposit, args, opts...) 28 | } 29 | 30 | // NewDeposit creates a new instance of the contract at a specific address 31 | func NewDeposit(addr ethgo.Address, opts ...contract.ContractOption) *Deposit { 32 | return &Deposit{c: contract.NewContract(addr, abiDeposit, opts...)} 33 | } 34 | 35 | // calls 36 | 37 | // GetDepositCount calls the get_deposit_count method in the solidity contract 38 | func (d *Deposit) GetDepositCount(block ...ethgo.BlockNumber) (retval0 []byte, err error) { 39 | var out map[string]interface{} 40 | var ok bool 41 | 42 | out, err = d.c.Call("get_deposit_count", ethgo.EncodeBlock(block...)) 43 | if err != nil { 44 | return 45 | } 46 | 47 | // decode outputs 48 | retval0, ok = out["0"].([]byte) 49 | if !ok { 50 | err = fmt.Errorf("failed to encode output at index 0") 51 | return 52 | } 53 | 54 | return 55 | } 56 | 57 | // GetDepositRoot calls the get_deposit_root method in the solidity contract 58 | func (d *Deposit) GetDepositRoot(block ...ethgo.BlockNumber) (retval0 [32]byte, err error) { 59 | var out map[string]interface{} 60 | var ok bool 61 | 62 | out, err = d.c.Call("get_deposit_root", ethgo.EncodeBlock(block...)) 63 | if err != nil { 64 | return 65 | } 66 | 67 | // decode outputs 68 | retval0, ok = out["0"].([32]byte) 69 | if !ok { 70 | err = fmt.Errorf("failed to encode output at index 0") 71 | return 72 | } 73 | 74 | return 75 | } 76 | 77 | // SupportsInterface calls the supportsInterface method in the solidity contract 78 | func (d *Deposit) SupportsInterface(interfaceId [4]byte, block ...ethgo.BlockNumber) (retval0 bool, err error) { 79 | var out map[string]interface{} 80 | var ok bool 81 | 82 | out, err = d.c.Call("supportsInterface", ethgo.EncodeBlock(block...), interfaceId) 83 | if err != nil { 84 | return 85 | } 86 | 87 | // decode outputs 88 | retval0, ok = out["0"].(bool) 89 | if !ok { 90 | err = fmt.Errorf("failed to encode output at index 0") 91 | return 92 | } 93 | 94 | return 95 | } 96 | 97 | // txns 98 | 99 | // Deposit sends a deposit transaction in the solidity contract 100 | func (d *Deposit) Deposit(pubkey []byte, withdrawalCredentials []byte, signature []byte, depositDataRoot [32]byte) (contract.Txn, error) { 101 | return d.c.Txn("deposit", pubkey, withdrawalCredentials, signature, depositDataRoot) 102 | } 103 | 104 | // events 105 | 106 | func (d *Deposit) DepositEventEventSig() ethgo.Hash { 107 | return d.c.GetABI().Events["DepositEvent"].ID() 108 | } 109 | -------------------------------------------------------------------------------- /deposit/deposit_artifacts.go: -------------------------------------------------------------------------------- 1 | package deposit 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | "github.com/umbracle/ethgo/abi" 8 | ) 9 | 10 | var abiDeposit *abi.ABI 11 | 12 | // DepositAbi returns the abi of the Deposit contract 13 | func DepositAbi() *abi.ABI { 14 | return abiDeposit 15 | } 16 | 17 | var binDeposit []byte 18 | 19 | // DepositBin returns the bin of the Deposit contract 20 | func DepositBin() []byte { 21 | return binDeposit 22 | } 23 | 24 | func init() { 25 | var err error 26 | abiDeposit, err = abi.NewABI(abiDepositStr) 27 | if err != nil { 28 | panic(fmt.Errorf("cannot parse Deposit abi: %v", err)) 29 | } 30 | if len(binDepositStr) != 0 { 31 | binDeposit, err = hex.DecodeString(binDepositStr[2:]) 32 | if err != nil { 33 | panic(fmt.Errorf("cannot parse Deposit bin: %v", err)) 34 | } 35 | } 36 | } 37 | 38 | var binDepositStr = "0x608060405234801561001057600080fd5b5060005b601f8110156101025760026021826020811061002c57fe5b01546021836020811061003b57fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b602083106100925780518252601f199092019160209182019101610073565b51815160209384036101000a60001901801990921691161790526040519190930194509192505080830381855afa1580156100d1573d6000803e3d6000fd5b5050506040513d60208110156100e657600080fd5b5051602160018301602081106100f857fe5b0155600101610014565b506118d680620001136000396000f3fe60806040526004361061003f5760003560e01c806301ffc9a71461004457806322895118146100a4578063621fd130146101ba578063c5f2892f14610244575b600080fd5b34801561005057600080fd5b506100906004803603602081101561006757600080fd5b50357fffffffff000000000000000000000000000000000000000000000000000000001661026b565b604080519115158252519081900360200190f35b6101b8600480360360808110156100ba57600080fd5b8101906020810181356401000000008111156100d557600080fd5b8201836020820111156100e757600080fd5b8035906020019184600183028401116401000000008311171561010957600080fd5b91939092909160208101903564010000000081111561012757600080fd5b82018360208201111561013957600080fd5b8035906020019184600183028401116401000000008311171561015b57600080fd5b91939092909160208101903564010000000081111561017957600080fd5b82018360208201111561018b57600080fd5b803590602001918460018302840111640100000000831117156101ad57600080fd5b919350915035610304565b005b3480156101c657600080fd5b506101cf6110b5565b6040805160208082528351818301528351919283929083019185019080838360005b838110156102095781810151838201526020016101f1565b50505050905090810190601f1680156102365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561025057600080fd5b506102596110c7565b60408051918252519081900360200190f35b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f01ffc9a70000000000000000000000000000000000000000000000000000000014806102fe57507fffffffff0000000000000000000000000000000000000000000000000000000082167f8564090700000000000000000000000000000000000000000000000000000000145b92915050565b6030861461035d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118056026913960400191505060405180910390fd5b602084146103b6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603681526020018061179c6036913960400191505060405180910390fd5b6060821461040f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806118786029913960400191505060405180910390fd5b670de0b6b3a7640000341015610470576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806118526026913960400191505060405180910390fd5b633b9aca003406156104cd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260338152602001806117d26033913960400191505060405180910390fd5b633b9aca00340467ffffffffffffffff811115610535576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602781526020018061182b6027913960400191505060405180910390fd5b6060610540826114ba565b90507f649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c589898989858a8a6105756020546114ba565b6040805160a0808252810189905290819060208201908201606083016080840160c085018e8e80828437600083820152601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690910187810386528c815260200190508c8c808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01690920188810386528c5181528c51602091820193918e019250908190849084905b83811015610648578181015183820152602001610630565b50505050905090810190601f1680156106755780820380516001836020036101000a031916815260200191505b5086810383528881526020018989808284376000838201819052601f9091017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169092018881038452895181528951602091820193918b019250908190849084905b838110156106ef5781810151838201526020016106d7565b50505050905090810190601f16801561071c5780820380516001836020036101000a031916815260200191505b509d505050505050505050505050505060405180910390a1600060028a8a600060801b604051602001808484808284377fffffffffffffffffffffffffffffffff0000000000000000000000000000000090941691909301908152604080517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0818403018152601090920190819052815191955093508392506020850191508083835b602083106107fc57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016107bf565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610859573d6000803e3d6000fd5b5050506040513d602081101561086e57600080fd5b5051905060006002806108846040848a8c6116fe565b6040516020018083838082843780830192505050925050506040516020818303038152906040526040518082805190602001908083835b602083106108f857805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016108bb565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610955573d6000803e3d6000fd5b5050506040513d602081101561096a57600080fd5b5051600261097b896040818d6116fe565b60405160009060200180848480828437919091019283525050604080518083038152602092830191829052805190945090925082918401908083835b602083106109f457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090920191602091820191016109b7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610a51573d6000803e3d6000fd5b5050506040513d6020811015610a6657600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610ada57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610a9d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610b37573d6000803e3d6000fd5b5050506040513d6020811015610b4c57600080fd5b50516040805160208101858152929350600092600292839287928f928f92018383808284378083019250505093505050506040516020818303038152906040526040518082805190602001908083835b60208310610bd957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610b9c565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610c36573d6000803e3d6000fd5b5050506040513d6020811015610c4b57600080fd5b50516040518651600291889160009188916020918201918291908601908083835b60208310610ca957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610c6c565b6001836020036101000a0380198251168184511680821785525050505050509050018367ffffffffffffffff191667ffffffffffffffff1916815260180182815260200193505050506040516020818303038152906040526040518082805190602001908083835b60208310610d4e57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610d11565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610dab573d6000803e3d6000fd5b5050506040513d6020811015610dc057600080fd5b5051604080516020818101949094528082019290925280518083038201815260609092019081905281519192909182918401908083835b60208310610e3457805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610df7565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015610e91573d6000803e3d6000fd5b5050506040513d6020811015610ea657600080fd5b50519050858114610f02576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260548152602001806117486054913960600191505060405180910390fd5b60205463ffffffff11610f60576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806117276021913960400191505060405180910390fd5b602080546001019081905560005b60208110156110a9578160011660011415610fa0578260008260208110610f9157fe5b0155506110ac95505050505050565b600260008260208110610faf57fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061102557805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101610fe8565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa158015611082573d6000803e3d6000fd5b5050506040513d602081101561109757600080fd5b50519250600282049150600101610f6e565b50fe5b50505050505050565b60606110c26020546114ba565b905090565b6020546000908190815b60208110156112f05781600116600114156111e6576002600082602081106110f557fe5b01548460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061116b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161112e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156111c8573d6000803e3d6000fd5b5050506040513d60208110156111dd57600080fd5b505192506112e2565b600283602183602081106111f657fe5b015460405160200180838152602001828152602001925050506040516020818303038152906040526040518082805190602001908083835b6020831061126b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161122e565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa1580156112c8573d6000803e3d6000fd5b5050506040513d60208110156112dd57600080fd5b505192505b6002820491506001016110d1565b506002826112ff6020546114ba565b600060401b6040516020018084815260200183805190602001908083835b6020831061135a57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161131d565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790527fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000095909516920191825250604080518083037ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8018152601890920190819052815191955093508392850191508083835b6020831061143f57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101611402565b51815160209384036101000a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01801990921691161790526040519190930194509192505080830381855afa15801561149c573d6000803e3d6000fd5b5050506040513d60208110156114b157600080fd5b50519250505090565b60408051600880825281830190925260609160208201818036833701905050905060c082901b8060071a60f81b826000815181106114f457fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060061a60f81b8260018151811061153757fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060051a60f81b8260028151811061157a57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060041a60f81b826003815181106115bd57fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060031a60f81b8260048151811061160057fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060021a60f81b8260058151811061164357fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060011a60f81b8260068151811061168657fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053508060001a60f81b826007815181106116c957fe5b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535050919050565b6000808585111561170d578182fd5b83861115611719578182fd5b505082019391909203915056fe4465706f736974436f6e74726163743a206d65726b6c6520747265652066756c6c4465706f736974436f6e74726163743a207265636f6e7374727563746564204465706f7369744461746120646f6573206e6f74206d6174636820737570706c696564206465706f7369745f646174615f726f6f744465706f736974436f6e74726163743a20696e76616c6964207769746864726177616c5f63726564656e7469616c73206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c7565206e6f74206d756c7469706c65206f6620677765694465706f736974436f6e74726163743a20696e76616c6964207075626b6579206c656e6774684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f20686967684465706f736974436f6e74726163743a206465706f7369742076616c756520746f6f206c6f774465706f736974436f6e74726163743a20696e76616c6964207369676e6174757265206c656e677468a264697066735822122048c9c1aefe892e05fe034c24a651f00a2a8c0eb7e7c569d35ac1920c1a6894bc64736f6c63430006080033" 39 | 40 | var abiDepositStr = `[ 41 | { 42 | "inputs": [ 43 | 44 | ], 45 | "stateMutability": "nonpayable", 46 | "type": "constructor" 47 | }, 48 | { 49 | "anonymous": false, 50 | "inputs": [ 51 | { 52 | "indexed": false, 53 | "internalType": "bytes", 54 | "name": "pubkey", 55 | "type": "bytes" 56 | }, 57 | { 58 | "indexed": false, 59 | "internalType": "bytes", 60 | "name": "withdrawal_credentials", 61 | "type": "bytes" 62 | }, 63 | { 64 | "indexed": false, 65 | "internalType": "bytes", 66 | "name": "amount", 67 | "type": "bytes" 68 | }, 69 | { 70 | "indexed": false, 71 | "internalType": "bytes", 72 | "name": "signature", 73 | "type": "bytes" 74 | }, 75 | { 76 | "indexed": false, 77 | "internalType": "bytes", 78 | "name": "index", 79 | "type": "bytes" 80 | } 81 | ], 82 | "name": "DepositEvent", 83 | "type": "event" 84 | }, 85 | { 86 | "inputs": [ 87 | { 88 | "internalType": "bytes", 89 | "name": "pubkey", 90 | "type": "bytes" 91 | }, 92 | { 93 | "internalType": "bytes", 94 | "name": "withdrawal_credentials", 95 | "type": "bytes" 96 | }, 97 | { 98 | "internalType": "bytes", 99 | "name": "signature", 100 | "type": "bytes" 101 | }, 102 | { 103 | "internalType": "bytes32", 104 | "name": "deposit_data_root", 105 | "type": "bytes32" 106 | } 107 | ], 108 | "name": "deposit", 109 | "outputs": [ 110 | 111 | ], 112 | "stateMutability": "payable", 113 | "type": "function" 114 | }, 115 | { 116 | "inputs": [ 117 | 118 | ], 119 | "name": "get_deposit_count", 120 | "outputs": [ 121 | { 122 | "internalType": "bytes", 123 | "name": "", 124 | "type": "bytes" 125 | } 126 | ], 127 | "stateMutability": "view", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [ 132 | 133 | ], 134 | "name": "get_deposit_root", 135 | "outputs": [ 136 | { 137 | "internalType": "bytes32", 138 | "name": "", 139 | "type": "bytes32" 140 | } 141 | ], 142 | "stateMutability": "view", 143 | "type": "function" 144 | }, 145 | { 146 | "inputs": [ 147 | { 148 | "internalType": "bytes4", 149 | "name": "interfaceId", 150 | "type": "bytes4" 151 | } 152 | ], 153 | "name": "supportsInterface", 154 | "outputs": [ 155 | { 156 | "internalType": "bool", 157 | "name": "", 158 | "type": "bool" 159 | } 160 | ], 161 | "stateMutability": "pure", 162 | "type": "function" 163 | } 164 | ]` 165 | -------------------------------------------------------------------------------- /deposit/input.go: -------------------------------------------------------------------------------- 1 | package deposit 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | ssz "github.com/ferranbt/fastssz" 8 | "github.com/umbracle/ethgo/abi" 9 | consensus "github.com/umbracle/go-eth-consensus" 10 | "github.com/umbracle/go-eth-consensus/bls" 11 | ) 12 | 13 | const MinGweiAmount = uint64(320) 14 | 15 | // DepositEvent is the eth2 deposit event 16 | var DepositEvent = abi.MustNewEvent(`event DepositEvent( 17 | bytes pubkey, 18 | bytes whitdrawalcred, 19 | bytes amount, 20 | bytes signature, 21 | bytes index 22 | )`) 23 | 24 | var depositDomain [32]byte 25 | 26 | func init() { 27 | // the domain for the deposit signing is hardcoded 28 | buf, _ := hex.DecodeString("03000000f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a9") 29 | copy(depositDomain[:], buf) 30 | } 31 | 32 | func Input(depositKey *bls.Key, withdrawalKey *bls.Key, amountInGwei uint64) (*consensus.DepositData, error) { 33 | // withdrawalCredentialsHash forms a 32 byte hash of the withdrawal public address. 34 | // withdrawal_credentials[:1] == BLS_WITHDRAWAL_PREFIX_BYTE 35 | // withdrawal_credentials[1:] == hash(withdrawal_pubkey)[1:] 36 | // TODO 37 | 38 | unsignedMsgRoot, err := ssz.HashWithDefaultHasher(&consensus.DepositMessage{ 39 | Pubkey: depositKey.Pub.Serialize(), 40 | Amount: amountInGwei, 41 | WithdrawalCredentials: [32]byte{}, 42 | }) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | rootToSign, err := ssz.HashWithDefaultHasher(&consensus.SigningData{ 48 | ObjectRoot: unsignedMsgRoot, 49 | Domain: depositDomain, 50 | }) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | signature, err := depositKey.Sign(rootToSign) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | msg := &consensus.DepositData{ 61 | Pubkey: depositKey.Pub.Serialize(), 62 | Amount: amountInGwei, 63 | WithdrawalCredentials: [32]byte{}, 64 | Signature: signature, 65 | } 66 | root, err := msg.HashTreeRoot() 67 | if err != nil { 68 | return nil, err 69 | } 70 | msg.Root = root 71 | return msg, nil 72 | } 73 | 74 | func signingData(obj ssz.HashRoot) ([32]byte, error) { 75 | unsignedMsgRoot, err := ssz.HashWithDefaultHasher(obj) 76 | if err != nil { 77 | return [32]byte{}, err 78 | } 79 | 80 | root, err := ssz.HashWithDefaultHasher(&consensus.SigningData{ 81 | ObjectRoot: unsignedMsgRoot, 82 | Domain: depositDomain, 83 | }) 84 | if err != nil { 85 | return [32]byte{}, err 86 | } 87 | return root, nil 88 | } 89 | 90 | func Verify(data *consensus.DepositData) error { 91 | pub := &bls.PublicKey{} 92 | if err := pub.Deserialize(data.Pubkey[:]); err != nil { 93 | return err 94 | } 95 | 96 | sig := &bls.Signature{} 97 | if err := sig.Deserialize(data.Signature[:]); err != nil { 98 | return err 99 | } 100 | 101 | deposit := consensus.DepositMessage{ 102 | Pubkey: data.Pubkey, 103 | Amount: data.Amount, 104 | WithdrawalCredentials: data.WithdrawalCredentials, 105 | } 106 | root, err := signingData(&deposit) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | ok, err := sig.VerifyByte(pub, root[:]) 112 | if err != nil { 113 | return err 114 | } 115 | if !ok { 116 | return fmt.Errorf("bad signature") 117 | } 118 | 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /deposit/input_test.go: -------------------------------------------------------------------------------- 1 | package deposit 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "github.com/umbracle/ethgo" 9 | "github.com/umbracle/ethgo/contract" 10 | "github.com/umbracle/ethgo/jsonrpc" 11 | "github.com/umbracle/ethgo/testutil" 12 | "github.com/umbracle/ethgo/wallet" 13 | "github.com/umbracle/go-eth-consensus/bls" 14 | ) 15 | 16 | func TestDeposit_Signing(t *testing.T) { 17 | kk := bls.NewRandomKey() 18 | data, err := Input(kk, nil, ethgo.Gwei(MinGweiAmount).Uint64()) 19 | if err != nil { 20 | t.Fatal(err) 21 | } 22 | 23 | err = Verify(data) 24 | require.NoError(t, err) 25 | } 26 | 27 | func TestDeposit_EndToEnd(t *testing.T) { 28 | server := testutil.NewTestServer(t, nil) 29 | defer server.Close() 30 | 31 | ecdsaKey, _ := wallet.GenerateKey() 32 | server.Transfer(ecdsaKey.Address(), ethgo.Ether(MinGweiAmount+1)) 33 | 34 | // deploy the contract 35 | receipt, err := server.SendTxn(ðgo.Transaction{ 36 | Input: DepositBin(), 37 | }) 38 | assert.NoError(t, err) 39 | 40 | client, _ := jsonrpc.NewClient(server.HTTPAddr()) 41 | code, err := client.Eth().GetCode(receipt.ContractAddress, ethgo.Latest) 42 | assert.NoError(t, err) 43 | assert.NotEqual(t, code, "0x") 44 | 45 | // sign the deposit 46 | key := bls.NewRandomKey() 47 | 48 | input, err := Input(key, nil, ethgo.Gwei(MinGweiAmount).Uint64()) 49 | assert.NoError(t, err) 50 | 51 | // deploy transaction 52 | depositContract := NewDeposit(receipt.ContractAddress, contract.WithSender(ecdsaKey), contract.WithJsonRPC(client.Eth())) 53 | 54 | txn, err := depositContract.Deposit(input.Pubkey[:], input.WithdrawalCredentials[:], input.Signature[:], input.Root) 55 | assert.NoError(t, err) 56 | 57 | txn.WithOpts(&contract.TxnOpts{Value: ethgo.Ether(MinGweiAmount)}) 58 | 59 | assert.NoError(t, txn.Do()) 60 | 61 | _, err = txn.Wait() 62 | assert.NoError(t, err) 63 | 64 | // query the contract 65 | count, err := depositContract.GetDepositCount() 66 | assert.NoError(t, err) 67 | assert.Equal(t, int(count[0]), 1) 68 | } 69 | -------------------------------------------------------------------------------- /domain.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strings" 7 | 8 | ssz "github.com/ferranbt/fastssz" 9 | ) 10 | 11 | type Domain [4]byte 12 | 13 | func (d *Domain) UnmarshalText(data []byte) error { 14 | domainStr := string(data) 15 | if !strings.HasPrefix(domainStr, "0x") { 16 | return fmt.Errorf("not prefixed") 17 | } 18 | buf, err := hex.DecodeString(domainStr[2:]) 19 | if err != nil { 20 | return err 21 | } 22 | if len(buf) != 4 { 23 | return fmt.Errorf("bad size") 24 | } 25 | copy(d[:], buf) 26 | return nil 27 | } 28 | 29 | func ToBytes96(b []byte) (res [96]byte) { 30 | copy(res[:], b) 31 | return 32 | } 33 | 34 | func ToBytes32(b []byte) (res [32]byte) { 35 | copy(res[:], b) 36 | return 37 | } 38 | 39 | func ComputeDomain(domain Domain, forkVersion [4]byte, genesisValidatorsRoot Root) ([32]byte, error) { 40 | // compute_fork_data_root 41 | // this returns the 32byte fork data root for the ``current_version`` and ``genesis_validators_root``. 42 | // This is used primarily in signature domains to avoid collisions across forks/chains. 43 | forkData := ForkData{ 44 | CurrentVersion: forkVersion, 45 | GenesisValidatorsRoot: genesisValidatorsRoot, 46 | } 47 | forkRoot, err := forkData.HashTreeRoot() 48 | if err != nil { 49 | return [32]byte{}, err 50 | } 51 | return ToBytes32(append(domain[:], forkRoot[:28]...)), nil 52 | } 53 | 54 | func ComputeSigningRoot(domain [32]byte, obj ssz.HashRoot) ([32]byte, error) { 55 | unsignedMsgRoot, err := obj.HashTreeRoot() 56 | if err != nil { 57 | return [32]byte{}, err 58 | } 59 | 60 | root, err := ssz.HashWithDefaultHasher(&SigningData{ 61 | ObjectRoot: unsignedMsgRoot, 62 | Domain: domain, 63 | }) 64 | if err != nil { 65 | return [32]byte{}, err 66 | } 67 | 68 | return root, nil 69 | } 70 | 71 | type DomainType Domain 72 | 73 | var ( 74 | DomainBeaconProposerType = Domain{0, 0, 0, 0} 75 | DomainBeaconAttesterType = Domain{1, 0, 0, 0} 76 | DomainRandaomType = Domain{2, 0, 0, 0} 77 | DomainDepositType = Domain{3, 0, 0, 0} 78 | DomainVoluntaryExitType = Domain{4, 0, 0, 0} 79 | DomainSelectionProofType = Domain{5, 0, 0, 0} 80 | DomainAggregateAndProofType = Domain{6, 0, 0, 0} 81 | DomainSyncCommitteeType = Domain{7, 0, 0, 0} 82 | DomainSyncCommitteeSelectionProof = Domain{8, 0, 0, 0} 83 | DomainContributionAndProof = Domain{9, 0, 0, 0} 84 | ) 85 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/umbracle/go-eth-consensus 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/ferranbt/fastssz v0.1.3 7 | github.com/golang/snappy v0.0.3 8 | github.com/hashicorp/go-uuid v1.0.3 9 | github.com/kilic/bls12-381 v0.1.0 10 | github.com/mitchellh/mapstructure v1.3.2 11 | github.com/protolambda/eth2-shuffle v1.1.0 12 | github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc 13 | github.com/stretchr/testify v1.8.1 14 | github.com/supranational/blst v0.3.10 15 | github.com/umbracle/ethgo v0.1.3 16 | gopkg.in/yaml.v2 v2.3.0 17 | ) 18 | 19 | require ( 20 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 21 | github.com/Microsoft/go-winio v0.4.13 // indirect 22 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect 23 | github.com/btcsuite/btcd v0.22.1 // indirect 24 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect 25 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce // indirect 26 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect 27 | github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b // indirect 28 | github.com/davecgh/go-spew v1.1.1 // indirect 29 | github.com/docker/go-connections v0.4.0 // indirect 30 | github.com/docker/go-units v0.4.0 // indirect 31 | github.com/google/gofuzz v1.2.0 // indirect 32 | github.com/gorilla/websocket v1.4.1 // indirect 33 | github.com/klauspost/compress v1.4.1 // indirect 34 | github.com/klauspost/cpuid v1.2.0 // indirect 35 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 36 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect 37 | github.com/kr/text v0.2.0 // indirect 38 | github.com/minio/sha256-simd v1.0.0 // indirect 39 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect 40 | github.com/opencontainers/image-spec v1.0.1 // indirect 41 | github.com/opencontainers/runc v0.1.1 // indirect 42 | github.com/ory/dockertest v3.3.5+incompatible // indirect 43 | github.com/pkg/errors v0.8.1 // indirect 44 | github.com/pmezard/go-difflib v1.0.0 // indirect 45 | github.com/sirupsen/logrus v1.4.2 // indirect 46 | github.com/tyler-smith/go-bip39 v1.1.0 // indirect 47 | github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 // indirect 48 | github.com/valyala/bytebufferpool v1.0.0 // indirect 49 | github.com/valyala/fasthttp v1.4.0 // indirect 50 | github.com/valyala/fastjson v1.4.1 // indirect 51 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect 52 | golang.org/x/net v0.0.0-20191116160921-f9c825593386 // indirect 53 | golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 // indirect 54 | golang.org/x/text v0.3.2 // indirect 55 | gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | ) 58 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= 2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 3 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 4 | github.com/Microsoft/go-winio v0.4.13 h1:Hmi80lzZuI/CaYmlJp/b+FjZdRZhKu9c2mDVqKlLWVs= 5 | github.com/Microsoft/go-winio v0.4.13/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 6 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= 7 | github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= 8 | github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 9 | github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 10 | github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= 11 | github.com/btcsuite/btcd v0.22.1/go.mod h1:wqgTSL29+50LRkmOVknEdmt8ZojIzhuWvgu/iptuN7Y= 12 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= 13 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 14 | github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= 15 | github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= 16 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pYCvA5t0RPmAaLUhREsKuKd+SLhxFbFeQ= 17 | github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= 18 | github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= 19 | github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= 20 | github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= 21 | github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= 22 | github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= 23 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= 24 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= 25 | github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b h1:pik3LX++5O3UiNWv45wfP/WT81l7ukBJzd3uUiifbSU= 26 | github.com/containerd/continuity v0.0.0-20191214063359-1097c8bae83b/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= 27 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 28 | github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 29 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 31 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 33 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 34 | github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= 35 | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 36 | github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 37 | github.com/ferranbt/fastssz v0.1.3 h1:ZI+z3JH05h4kgmFXdHuR1aWYsgrg7o+Fw7/NCzM16Mo= 38 | github.com/ferranbt/fastssz v0.1.3/go.mod h1:0Y9TEd/9XuFlh7mskMPfXiI2Dkw4Ddg9EyXt1W7MRvE= 39 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 40 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= 42 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 43 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 44 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 45 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 46 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 47 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 48 | github.com/gotestyourself/gotestyourself v2.2.0+incompatible h1:AQwinXlbQR2HvPjQZOmDhRqsv5mZf+Jb1RnSLxcqZcI= 49 | github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= 50 | github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 51 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 52 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 53 | github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 54 | github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= 55 | github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= 56 | github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= 57 | github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= 58 | github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 59 | github.com/klauspost/compress v1.4.1 h1:8VMb5+0wMgdBykOV96DwNwKFQ+WTI4pzYURP99CcB9E= 60 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 61 | github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 62 | github.com/klauspost/cpuid v1.2.0 h1:NMpwD2G9JSFOE1/TJjGSo5zG7Yb2bTe7eq1jH+irmeE= 63 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 64 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 65 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 66 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 67 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 68 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 69 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 70 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 71 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 72 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 73 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= 74 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= 75 | github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg= 76 | github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 77 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 78 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 79 | github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 80 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 81 | github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 82 | github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= 83 | github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= 84 | github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= 85 | github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= 86 | github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= 87 | github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= 88 | github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= 89 | github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= 90 | github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 91 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 92 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 95 | github.com/protolambda/eth2-shuffle v1.1.0 h1:gixIBI84IeugTwwHXm8vej1bSSEhueBCSryA4lAKRLU= 96 | github.com/protolambda/eth2-shuffle v1.1.0/go.mod h1:FhA2c0tN15LTC+4T9DNVm+55S7uXTTjQ8TQnBuXlkF8= 97 | github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc h1:zAsgcP8MhzAbhMnB1QQ2O7ZhWYVGYSR2iVcjzQuPV+o= 98 | github.com/r3labs/sse v0.0.0-20210224172625-26fe804710bc/go.mod h1:S8xSOnV3CgpNrWd0GQ/OoQfMtlg2uPRSuTzcSGrzwK8= 99 | github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 100 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 101 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 102 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 103 | github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 104 | github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 105 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 106 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 107 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 108 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 109 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 110 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 111 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 112 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 113 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 114 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 115 | github.com/supranational/blst v0.3.10 h1:CMciDZ/h4pXDDXQASe8ZGTNKUiVNxVVA5hpci2Uuhuk= 116 | github.com/supranational/blst v0.3.10/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= 117 | github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= 118 | github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= 119 | github.com/umbracle/ethgo v0.1.3 h1:s8D7Rmphnt71zuqrgsGTMS5gTNbueGO1zKLh7qsFzTM= 120 | github.com/umbracle/ethgo v0.1.3/go.mod h1:g9zclCLixH8liBI27Py82klDkW7Oo33AxUOr+M9lzrU= 121 | github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722 h1:10Nbw6cACsnQm7r34zlpJky+IzxVLRk6MKTS2d3Vp0E= 122 | github.com/umbracle/fastrlp v0.0.0-20220527094140-59d5dd30e722/go.mod h1:c8J0h9aULj2i3umrfyestM6jCq0LK0U6ly6bWy96nd4= 123 | github.com/umbracle/gohashtree v0.0.2-alpha.0.20230207094856-5b775a815c10 h1:CQh33pStIp/E30b7TxDlXfM0145bn2e8boI30IxAhTg= 124 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 125 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 126 | github.com/valyala/fasthttp v1.4.0 h1:PuaTGZIw3mjYhhhbVbCQp8aciRZN9YdoB7MGX9Ko76A= 127 | github.com/valyala/fasthttp v1.4.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= 128 | github.com/valyala/fastjson v1.4.1 h1:hrltpHpIpkaxll8QltMU8c3QZ5+qIiCL8yKqPFJI/yE= 129 | github.com/valyala/fastjson v1.4.1/go.mod h1:nV6MsjxL2IMJQUoHDIrjEI7oLyeqK6aBD7EFWPsvP8o= 130 | github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= 131 | golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 132 | golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 133 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 134 | golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 135 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 136 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= 137 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 138 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 139 | golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 140 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 141 | golang.org/x/net v0.0.0-20191116160921-f9c825593386 h1:ktbWvQrW08Txdxno1PiDpSxPXG6ndGsfnJjRRtkM0LQ= 142 | golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 143 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 147 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= 152 | golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 153 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 154 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 155 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 156 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 157 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 158 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 159 | gopkg.in/cenkalti/backoff.v1 v1.1.0 h1:Arh75ttbsvlpVA7WtVpH4u9h6Zl46xuptxqLxPiSo4Y= 160 | gopkg.in/cenkalti/backoff.v1 v1.1.0/go.mod h1:J6Vskwqd+OMVJl8C33mmtxTBs2gyzfv7UDAkHu8BrjI= 161 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 162 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 163 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 164 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 165 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 166 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 167 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 168 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 169 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 170 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 171 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 172 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 173 | gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= 174 | -------------------------------------------------------------------------------- /http/beacon.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | 6 | consensus "github.com/umbracle/go-eth-consensus" 7 | ) 8 | 9 | type BeaconEndpoint struct { 10 | c *Client 11 | } 12 | 13 | func (c *Client) Beacon() *BeaconEndpoint { 14 | return &BeaconEndpoint{c: c} 15 | } 16 | 17 | type GenesisInfo struct { 18 | Time uint64 `json:"genesis_time"` 19 | Root [32]byte `json:"genesis_validators_root"` 20 | Fork string `json:"genesis_fork_version"` 21 | } 22 | 23 | func (b *BeaconEndpoint) Genesis() (*GenesisInfo, error) { 24 | var out GenesisInfo 25 | err := b.c.Get("/eth/v1/beacon/genesis", &out) 26 | return &out, err 27 | } 28 | 29 | func (b *BeaconEndpoint) SubmitCommitteeDuties(duties []*consensus.SyncCommitteeMessage) error { 30 | err := b.c.Post("/eth/v1/beacon/pool/sync_committees", duties, nil) 31 | return err 32 | } 33 | 34 | type ValidatorStatus string 35 | 36 | const ( 37 | ValidatorStatusUnknown ValidatorStatus = "unknown" 38 | ValidatorStatusActive ValidatorStatus = "active" 39 | ValidatorStatusPending ValidatorStatus = "pending" 40 | ValidatorStatusExited ValidatorStatus = "exited" 41 | ) 42 | 43 | type Validator struct { 44 | Index uint64 `json:"index"` 45 | Balance uint64 `json:"balance"` 46 | Status ValidatorStatus `json:"status"` 47 | Validator *ValidatorMetadata `json:"validator"` 48 | } 49 | 50 | type ValidatorMetadata struct { 51 | PubKey string `json:"pubkey"` 52 | WithdrawalCredentials string `json:"withdrawal_credentials"` 53 | EffectiveBalance uint64 `json:"effective_balance"` 54 | Slashed bool `json:"slashed"` 55 | ActivationElegibilityEpoch uint64 `json:"activation_eligibility_epoch"` 56 | ActivationEpoch uint64 `json:"activation_epoch"` 57 | ExitEpoch uint64 `json:"exit_epoch"` 58 | WithdrawableEpoch uint64 `json:"withdrawable_epoch"` 59 | } 60 | 61 | type StateId interface { 62 | StateID() string 63 | } 64 | 65 | type BlockId interface { 66 | BlockID() string 67 | } 68 | 69 | type plainStateId string 70 | 71 | func (s plainStateId) StateID() string { 72 | return string(s) 73 | } 74 | 75 | func (s plainStateId) BlockID() string { 76 | return s.StateID() 77 | } 78 | 79 | type Slot uint64 80 | 81 | func (s Slot) StateID() string { 82 | return fmt.Sprintf("%d", s) 83 | } 84 | 85 | func (s Slot) BlockID() string { 86 | return s.StateID() 87 | } 88 | 89 | const ( 90 | Head plainStateId = "head" 91 | Genesis plainStateId = "genesis" 92 | Finalized plainStateId = "finalized" 93 | ) 94 | 95 | func (b *BeaconEndpoint) GetRoot(id StateId) ([32]byte, error) { 96 | var out struct { 97 | Root [32]byte 98 | } 99 | err := b.c.Get("/eth/v1/beacon/states/"+id.StateID()+"/root", &out) 100 | return out.Root, err 101 | } 102 | 103 | func (b *BeaconEndpoint) GetFork(id StateId) (*consensus.Fork, error) { 104 | var out *consensus.Fork 105 | err := b.c.Get("/eth/v1/beacon/states/"+id.StateID()+"/fork", &out) 106 | return out, err 107 | } 108 | 109 | type FinalizedCheckpoints struct { 110 | PreviousJustifiedCheckpoint *consensus.Checkpoint `json:"previous_justified"` 111 | CurrentJustifiedCheckpoint *consensus.Checkpoint `json:"current_justified"` 112 | FinalizedCheckpoint *consensus.Checkpoint `json:"finalized"` 113 | } 114 | 115 | func (b *BeaconEndpoint) GetFinalityCheckpoints(id StateId) (*FinalizedCheckpoints, error) { 116 | var out *FinalizedCheckpoints 117 | err := b.c.Get("/eth/v1/beacon/states/"+id.StateID()+"/finality_checkpoints", &out) 118 | return out, err 119 | } 120 | 121 | func (b *BeaconEndpoint) GetValidators(id StateId) ([]*Validator, error) { 122 | var out []*Validator 123 | err := b.c.Get("/eth/v1/beacon/states/"+id.StateID()+"/validators", &out) 124 | return out, err 125 | } 126 | 127 | func (b *BeaconEndpoint) GetValidatorByPubKey(pub string, id StateId) (*Validator, error) { 128 | var out *Validator 129 | err := b.c.Get("/eth/v1/beacon/states/"+id.StateID()+"/validators/"+pub, &out) 130 | return out, err 131 | } 132 | 133 | func (b *BeaconEndpoint) PublishSignedBlock(block consensus.SignedBeaconBlock) error { 134 | err := b.c.Post("/eth/v1/beacon/blocks", block, nil) 135 | return err 136 | } 137 | 138 | func (b *BeaconEndpoint) PublishAttestations(data []*consensus.Attestation) error { 139 | err := b.c.Post("/eth/v1/beacon/pool/attestations", data, nil) 140 | return err 141 | } 142 | 143 | type Block struct { 144 | Message consensus.BeaconBlock 145 | Signature [96]byte 146 | } 147 | 148 | func (b *BeaconEndpoint) GetBlock(id BlockId, block consensus.BeaconBlock) (*Block, error) { 149 | out := &Block{ 150 | Message: block, 151 | } 152 | err := b.c.Get("/eth/v2/beacon/blocks/"+id.BlockID(), out) 153 | return out, err 154 | } 155 | 156 | type BlockHeaderResponse struct { 157 | Root [32]byte `json:"root"` 158 | Canonical bool `json:"canonical"` 159 | Header *BlockHeader `json:"header"` 160 | } 161 | 162 | type BlockHeader struct { 163 | Message *consensus.BeaconBlockHeader `json:"message"` 164 | Signature [96]byte `json:"signature"` 165 | } 166 | 167 | func (b *BeaconEndpoint) GetBlockHeader(id BlockId) (*BlockHeaderResponse, error) { 168 | var out *BlockHeaderResponse 169 | err := b.c.Get("/eth/v1/beacon/headers/"+id.BlockID(), &out) 170 | return out, err 171 | } 172 | 173 | func (b *BeaconEndpoint) GetBlockRoot(id BlockId) ([32]byte, error) { 174 | var data struct { 175 | Root [32]byte 176 | } 177 | err := b.c.Get("/eth/v1/beacon/blocks/"+id.BlockID()+"/root", &data) 178 | return data.Root, err 179 | } 180 | 181 | func (b *BeaconEndpoint) GetBlockAttestations(id BlockId) ([]*consensus.Attestation, error) { 182 | var out []*consensus.Attestation 183 | err := b.c.Get("/eth/v1/beacon/blocks/"+id.BlockID()+"/attestations", &out) 184 | return out, err 185 | } 186 | -------------------------------------------------------------------------------- /http/beacon_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func TestBeaconEndpoint(t *testing.T) { 11 | n := New("http://127.0.0.1:4010", WithUntrackedKeys()).Beacon() 12 | 13 | t.Run("Genesis", func(t *testing.T) { 14 | _, err := n.Genesis() 15 | assert.NoError(t, err) 16 | }) 17 | 18 | t.Run("SubmitCommitteeDuties", func(t *testing.T) { 19 | err := n.SubmitCommitteeDuties([]*consensus.SyncCommitteeMessage{}) 20 | assert.NoError(t, err) 21 | }) 22 | 23 | t.Run("GetRoot", func(t *testing.T) { 24 | _, err := n.GetRoot(Finalized) 25 | assert.NoError(t, err) 26 | }) 27 | 28 | t.Run("GetFork", func(t *testing.T) { 29 | _, err := n.GetFork(Finalized) 30 | assert.NoError(t, err) 31 | }) 32 | 33 | t.Run("GetFinalityCheckpoints", func(t *testing.T) { 34 | _, err := n.GetFinalityCheckpoints(Finalized) 35 | assert.NoError(t, err) 36 | }) 37 | 38 | t.Run("GetValidators", func(t *testing.T) { 39 | _, err := n.GetValidators(Finalized) 40 | assert.NoError(t, err) 41 | }) 42 | 43 | t.Run("GetValidatorByPubKey", func(t *testing.T) { 44 | _, err := n.GetValidatorByPubKey("0x1", Slot(1)) 45 | assert.NoError(t, err) 46 | }) 47 | 48 | t.Run("PublishAttestations", func(t *testing.T) { 49 | err := n.PublishAttestations([]*consensus.Attestation{}) 50 | assert.NoError(t, err) 51 | }) 52 | 53 | t.Run("GetBlock", func(t *testing.T) { 54 | t.Skip("graffiti TODO") 55 | 56 | var out consensus.BeaconBlockPhase0 57 | _, err := n.GetBlock(Slot(1), &out) 58 | assert.NoError(t, err) 59 | }) 60 | 61 | t.Run("GetBlockHeader", func(t *testing.T) { 62 | _, err := n.GetBlockHeader(Finalized) 63 | assert.NoError(t, err) 64 | }) 65 | 66 | t.Run("GetBlockRoot", func(t *testing.T) { 67 | _, err := n.GetBlockRoot(Head) 68 | assert.NoError(t, err) 69 | }) 70 | 71 | t.Run("GetBlockAttestations", func(t *testing.T) { 72 | _, err := n.GetBlockAttestations(Genesis) 73 | assert.NoError(t, err) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /http/builder.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "fmt" 5 | 6 | consensus "github.com/umbracle/go-eth-consensus" 7 | ) 8 | 9 | type BuilderEndpoint struct { 10 | c *Client 11 | } 12 | 13 | func (c *Client) Builder() *BuilderEndpoint { 14 | return &BuilderEndpoint{c: c} 15 | } 16 | 17 | func (b *BuilderEndpoint) RegisterValidator(msg []*SignedValidatorRegistration) error { 18 | err := b.c.Post("/eth/v1/builder/validators", msg, nil) 19 | return err 20 | } 21 | 22 | type BuilderBid struct { 23 | Header *consensus.ExecutionPayloadHeader `json:"header"` 24 | Value consensus.Uint256 `json:"value" ssz-size:"32"` 25 | Pubkey [48]byte `json:"pubkey" ssz-size:"48"` 26 | } 27 | 28 | type SignedBuilderBid struct { 29 | Message *BuilderBid `json:"message"` 30 | Signature [96]byte `json:"signature" ssz-size:"96"` 31 | } 32 | 33 | func (b *BuilderEndpoint) GetExecutionPayload(slot uint64, parentHash [32]byte, pubKey [48]byte) (*SignedBuilderBid, error) { 34 | var out *SignedBuilderBid 35 | err := b.c.Get(fmt.Sprintf("/eth/v1/builder/header/%d/0x%x/0x%x", slot, parentHash[:], pubKey[:]), &out) 36 | return out, err 37 | } 38 | 39 | func (b *BuilderEndpoint) SubmitBlindedBlock(msg *consensus.SignedBlindedBeaconBlock) (*consensus.ExecutionPayload, error) { 40 | var out *consensus.ExecutionPayload 41 | err := b.c.Post("/eth/v1/builder/blinded_blocks", msg, &out) 42 | return out, err 43 | } 44 | 45 | func (b *BuilderEndpoint) Status() (bool, error) { 46 | return b.c.Status("/eth/v1/builder/status") 47 | } 48 | -------------------------------------------------------------------------------- /http/builder_encoding.go: -------------------------------------------------------------------------------- 1 | // Code generated by fastssz. DO NOT EDIT. 2 | // Hash: 60c1a274a556ac9f9ed1cc9e50fdb5cf4e6c97d297106e385648c8306d4cb095 3 | // Version: 0.1.3 4 | package http 5 | 6 | import ( 7 | ssz "github.com/ferranbt/fastssz" 8 | ) 9 | 10 | // MarshalSSZ ssz marshals the RegisterValidatorRequest object 11 | func (r *RegisterValidatorRequest) MarshalSSZ() ([]byte, error) { 12 | return ssz.MarshalSSZ(r) 13 | } 14 | 15 | // MarshalSSZTo ssz marshals the RegisterValidatorRequest object to a target array 16 | func (r *RegisterValidatorRequest) MarshalSSZTo(buf []byte) (dst []byte, err error) { 17 | dst = buf 18 | 19 | // Field (0) 'FeeRecipient' 20 | dst = append(dst, r.FeeRecipient[:]...) 21 | 22 | // Field (1) 'GasLimit' 23 | dst = ssz.MarshalUint64(dst, r.GasLimit) 24 | 25 | // Field (2) 'Timestamp' 26 | dst = ssz.MarshalUint64(dst, r.Timestamp) 27 | 28 | // Field (3) 'Pubkey' 29 | dst = append(dst, r.Pubkey[:]...) 30 | 31 | return 32 | } 33 | 34 | // UnmarshalSSZ ssz unmarshals the RegisterValidatorRequest object 35 | func (r *RegisterValidatorRequest) UnmarshalSSZ(buf []byte) error { 36 | var err error 37 | size := uint64(len(buf)) 38 | if size != 84 { 39 | return ssz.ErrSize 40 | } 41 | 42 | // Field (0) 'FeeRecipient' 43 | copy(r.FeeRecipient[:], buf[0:20]) 44 | 45 | // Field (1) 'GasLimit' 46 | r.GasLimit = ssz.UnmarshallUint64(buf[20:28]) 47 | 48 | // Field (2) 'Timestamp' 49 | r.Timestamp = ssz.UnmarshallUint64(buf[28:36]) 50 | 51 | // Field (3) 'Pubkey' 52 | copy(r.Pubkey[:], buf[36:84]) 53 | 54 | return err 55 | } 56 | 57 | // SizeSSZ returns the ssz encoded size in bytes for the RegisterValidatorRequest object 58 | func (r *RegisterValidatorRequest) SizeSSZ() (size int) { 59 | size = 84 60 | return 61 | } 62 | 63 | // HashTreeRoot ssz hashes the RegisterValidatorRequest object 64 | func (r *RegisterValidatorRequest) HashTreeRoot() ([32]byte, error) { 65 | return ssz.HashWithDefaultHasher(r) 66 | } 67 | 68 | // HashTreeRootWith ssz hashes the RegisterValidatorRequest object with a hasher 69 | func (r *RegisterValidatorRequest) HashTreeRootWith(hh ssz.HashWalker) (err error) { 70 | indx := hh.Index() 71 | 72 | // Field (0) 'FeeRecipient' 73 | hh.PutBytes(r.FeeRecipient[:]) 74 | 75 | // Field (1) 'GasLimit' 76 | hh.PutUint64(r.GasLimit) 77 | 78 | // Field (2) 'Timestamp' 79 | hh.PutUint64(r.Timestamp) 80 | 81 | // Field (3) 'Pubkey' 82 | hh.PutBytes(r.Pubkey[:]) 83 | 84 | hh.Merkleize(indx) 85 | return 86 | } 87 | 88 | // GetTree ssz hashes the RegisterValidatorRequest object 89 | func (r *RegisterValidatorRequest) GetTree() (*ssz.Node, error) { 90 | return ssz.ProofTree(r) 91 | } 92 | -------------------------------------------------------------------------------- /http/builder_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func TestBuilderEndpoint(t *testing.T) { 11 | n := New("http://127.0.0.1:4011").Builder() 12 | 13 | t.Run("RegisterValidator", func(t *testing.T) { 14 | obj := []*SignedValidatorRegistration{ 15 | {Message: &RegisterValidatorRequest{}}, 16 | } 17 | err := n.RegisterValidator(obj) 18 | assert.NoError(t, err) 19 | }) 20 | 21 | t.Run("GetExecutionPayload", func(t *testing.T) { 22 | _, err := n.GetExecutionPayload(1, [32]byte{}, [48]byte{}) 23 | assert.NoError(t, err) 24 | }) 25 | 26 | t.Run("SubmitBlindedBlock", func(t *testing.T) { 27 | obj := &consensus.SignedBlindedBeaconBlock{} 28 | _, err := n.SubmitBlindedBlock(obj) 29 | assert.NoError(t, err) 30 | }) 31 | 32 | t.Run("Status", func(t *testing.T) { 33 | ok, err := n.Status() 34 | assert.NoError(t, err) 35 | assert.True(t, ok) 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /http/config.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import consensus "github.com/umbracle/go-eth-consensus" 4 | 5 | type ConfigEndpoint struct { 6 | c *Client 7 | } 8 | 9 | func (c *Client) Config() *ConfigEndpoint { 10 | return &ConfigEndpoint{c: c} 11 | } 12 | 13 | func (c *ConfigEndpoint) ForkSchedule() ([]*consensus.Fork, error) { 14 | var out []*consensus.Fork 15 | err := c.c.Get("/eth/v1/config/fork_schedule", &out) 16 | return out, err 17 | } 18 | 19 | func (c *ConfigEndpoint) Spec() (*consensus.Spec, error) { 20 | var spec *consensus.Spec 21 | err := c.c.Get("/eth/v1/config/spec", &spec) 22 | return spec, err 23 | } 24 | 25 | type DepositContract struct { 26 | ChainID uint64 `json:"chain_id"` 27 | Address string `json:"address"` 28 | } 29 | 30 | func (c *ConfigEndpoint) DepositContract() (*DepositContract, error) { 31 | var depositContract *DepositContract 32 | err := c.c.Get("/eth/v1/config/deposit_contract", &depositContract) 33 | return depositContract, err 34 | } 35 | -------------------------------------------------------------------------------- /http/config_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestConfigEndpoint(t *testing.T) { 10 | n := New("http://127.0.0.1:4010", WithUntrackedKeys()).Config() 11 | 12 | t.Run("ForkSchedule", func(t *testing.T) { 13 | _, err := n.ForkSchedule() 14 | assert.NoError(t, err) 15 | }) 16 | 17 | t.Run("Spec", func(t *testing.T) { 18 | t.Skip("due to lack of 'data'") 19 | // the Spec example marshals without a 'data' field. 20 | // It has been addressed on master but it has not been released yet. 21 | 22 | _, err := n.Spec() 23 | assert.NoError(t, err) 24 | }) 25 | 26 | t.Run("DepositContract", func(t *testing.T) { 27 | _, err := n.DepositContract() 28 | assert.NoError(t, err) 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /http/encoding.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | "reflect" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/mitchellh/mapstructure" 13 | ) 14 | 15 | func Marshal(obj interface{}) ([]byte, error) { 16 | val := reflect.ValueOf(obj) 17 | if val.Kind() == reflect.Ptr { 18 | val = val.Elem() 19 | } 20 | 21 | raw, err := marshalImpl(val) 22 | if err != nil { 23 | return nil, err 24 | } 25 | data, err := json.Marshal(raw) 26 | if err != nil { 27 | return nil, err 28 | } 29 | return data, nil 30 | } 31 | 32 | func marshalImpl(v reflect.Value) (interface{}, error) { 33 | if v.Kind() == reflect.Ptr && v.IsNil() { 34 | // create the element if nil 35 | v = reflect.New(v.Type().Elem()) 36 | } 37 | typ := v.Type() 38 | if isByteArray(typ) { 39 | // [n]byte 40 | return "0x" + hex.EncodeToString(convertArrayToBytes(v).Bytes()), nil 41 | } 42 | if isByteSlice(typ) { 43 | // []byte 44 | return "0x" + hex.EncodeToString(v.Bytes()), nil 45 | } 46 | 47 | // marshal with encoding.TextUnmarshaler 48 | result := v.Interface() 49 | marshaller, ok := result.(encoding.TextMarshaler) 50 | if ok { 51 | res, err := marshaller.MarshalText() 52 | if err != nil { 53 | return nil, err 54 | } 55 | return res, nil 56 | } 57 | 58 | switch v.Kind() { 59 | case reflect.Ptr: 60 | return marshalImpl(v.Elem()) 61 | 62 | case reflect.Array, reflect.Slice: 63 | out := []interface{}{} 64 | for i := 0; i < v.Len(); i++ { 65 | elem, err := marshalImpl(v.Index(i)) 66 | if err != nil { 67 | return nil, err 68 | } 69 | out = append(out, elem) 70 | } 71 | return out, nil 72 | 73 | case reflect.Struct: 74 | out := map[string]interface{}{} 75 | for i := 0; i < v.NumField(); i++ { 76 | f := typ.Field(i) 77 | 78 | val, err := marshalImpl(v.Field(i)) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | tagValue := f.Tag.Get("json") 84 | if tagValue == "-" { 85 | continue 86 | } 87 | 88 | name := f.Name 89 | if tagValue != "" { 90 | name = tagValue 91 | } 92 | out[name] = val 93 | } 94 | return out, nil 95 | 96 | case reflect.String: 97 | return v.String(), nil 98 | 99 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 100 | return strconv.Itoa(int(v.Uint())), nil 101 | 102 | case reflect.Bool: 103 | return v.Bool(), nil 104 | 105 | default: 106 | panic(fmt.Sprintf("BUG: Cannot marshal type %s", v.Kind())) 107 | } 108 | } 109 | 110 | func convertArrayToBytes(value reflect.Value) reflect.Value { 111 | slice := reflect.MakeSlice(reflect.TypeOf([]byte{}), value.Len(), value.Len()) 112 | reflect.Copy(slice, value) 113 | return slice 114 | } 115 | 116 | func Unmarshal(data []byte, obj interface{}, trackUnusedKeys bool) error { 117 | var out1 interface{} 118 | if err := json.Unmarshal(data, &out1); err != nil { 119 | return err 120 | } 121 | 122 | metadata := &mapstructure.Metadata{} 123 | dc := &mapstructure.DecoderConfig{ 124 | Result: obj, 125 | WeaklyTypedInput: true, 126 | DecodeHook: customWeb3Hook, 127 | TagName: "json", 128 | Metadata: metadata, 129 | } 130 | ms, err := mapstructure.NewDecoder(dc) 131 | if err != nil { 132 | return err 133 | } 134 | if err = ms.Decode(out1); err != nil { 135 | return err 136 | } 137 | if len(metadata.Unused) != 0 && trackUnusedKeys { 138 | return fmt.Errorf("unmarshal error unused keys: %s", metadata.Unused) 139 | } 140 | return nil 141 | } 142 | 143 | func isByteArray(t reflect.Type) bool { 144 | return t.Kind() == reflect.Array && t.Elem().Kind() == reflect.Uint8 145 | } 146 | 147 | func isByteSlice(t reflect.Type) bool { 148 | return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8 149 | } 150 | 151 | func customWeb3Hook(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { 152 | if f.Kind() != reflect.String { 153 | return data, nil 154 | } 155 | 156 | // encode with text unmarshaller 157 | result := reflect.New(t).Interface() 158 | unmarshaller, ok := result.(encoding.TextUnmarshaler) 159 | if ok { 160 | if err := unmarshaller.UnmarshalText([]byte(data.(string))); err != nil { 161 | return nil, err 162 | } 163 | return result, nil 164 | } 165 | 166 | // []byte 167 | if isByteArray(t) { 168 | raw := data.(string) 169 | if !strings.HasPrefix(raw, "0x") { 170 | return nil, fmt.Errorf("0x prefix not found for []byte") 171 | } 172 | elem, err := hex.DecodeString(raw[2:]) 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | // [n]byte 178 | if t.Len() != len(elem) { 179 | return nil, fmt.Errorf("incorrect array length: %d %d", t.Len(), len(elem)) 180 | } 181 | 182 | v := reflect.New(t) 183 | reflect.Copy(v.Elem(), reflect.ValueOf(elem)) 184 | return v.Interface(), nil 185 | } 186 | 187 | // [n]byte 188 | if isByteSlice(t) { 189 | raw := data.(string) 190 | if !strings.HasPrefix(raw, "0x") { 191 | return nil, fmt.Errorf("0x prefix not found for [n]byte") 192 | } 193 | elem, err := hex.DecodeString(raw[2:]) 194 | if err != nil { 195 | return nil, err 196 | } 197 | return elem, nil 198 | } 199 | 200 | return data, nil 201 | } 202 | -------------------------------------------------------------------------------- /http/events.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/r3labs/sse" 9 | consensus "github.com/umbracle/go-eth-consensus" 10 | ) 11 | 12 | type HeadEvent struct { 13 | Slot uint64 `json:"slot"` 14 | Block [32]byte `json:"block"` 15 | State [32]byte `json:"state"` 16 | EpochTransition bool `json:"epoch_transition"` 17 | CurrentDutyDependentRoot [32]byte `json:"current_duty_dependent_root"` 18 | PreviousDutyDependentRoot [32]byte `json:"previous_duty_dependent_root"` 19 | ExecutionOptimistic bool `json:"execution_optimistic"` 20 | } 21 | 22 | type BlockEvent struct { 23 | Slot uint64 `json:"slot"` 24 | Block [32]byte `json:"block"` 25 | ExecutionOptimistic bool `json:"execution_optimistic"` 26 | } 27 | 28 | type FinalizedCheckpointEvent struct { 29 | Block [32]byte `json:"block"` 30 | State [32]byte `json:"state"` 31 | Epoch uint64 `json:"epoch"` 32 | ExecutionOptimistic bool `json:"execution_optimistic"` 33 | } 34 | 35 | type ChainReorgEvent struct { 36 | Slot uint64 `json:"slot"` 37 | Depth uint64 `json:"depth"` 38 | OldHeadBlock [32]byte `json:"old_head_block"` 39 | NewHeadBlock [32]byte `json:"new_head_block"` 40 | OldHeadState [32]byte `json:"old_head_state"` 41 | NewHeadState [32]byte `json:"new_head_state"` 42 | Epoch uint64 `json:"epoch"` 43 | ExecutionOptimistic bool `json:"execution_optimistic"` 44 | } 45 | 46 | var eventObjMap = map[string]func() interface{}{ 47 | "head": func() interface{} { return new(HeadEvent) }, 48 | "block": func() interface{} { return new(BlockEvent) }, 49 | "attestation": func() interface{} { return new(consensus.Attestation) }, 50 | "voluntary_exit": func() interface{} { return new(consensus.SignedVoluntaryExit) }, 51 | "finalized_checkpoint": func() interface{} { return new(FinalizedCheckpointEvent) }, 52 | "chain_reorg": func() interface{} { return new(ChainReorgEvent) }, 53 | "contribution_and_proof": func() interface{} { return new(consensus.SignedContributionAndProof) }, 54 | } 55 | 56 | func (c *Client) Events(ctx context.Context, topics []string, handler func(obj interface{})) error { 57 | for _, topic := range topics { 58 | if _, ok := eventObjMap[topic]; !ok { 59 | return fmt.Errorf("topic '%s' is not valid", topic) 60 | } 61 | } 62 | 63 | client := sse.NewClient(c.url + "/eth/v1/events?topics=" + strings.Join(topics, ",")) 64 | if err := client.SubscribeRawWithContext(ctx, func(msg *sse.Event) { 65 | codec, ok := eventObjMap[string(msg.Event)] 66 | if !ok { 67 | c.config.logger.Printf("[DEBUG]: event not tracked: %s", string(msg.Event)) 68 | return 69 | } 70 | obj := codec() 71 | 72 | if err := Unmarshal(msg.Data, obj, c.config.untrackedKeys); err != nil { 73 | c.config.logger.Printf("[ERROR]: failed to decode %s event: %v", string(msg.Event), err) 74 | return 75 | } 76 | handler(obj) 77 | }); err != nil { 78 | return err 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /http/events_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | "os" 9 | "strings" 10 | "testing" 11 | "time" 12 | 13 | _ "embed" 14 | 15 | "github.com/r3labs/sse" 16 | ) 17 | 18 | //go:embed testcases/events.json 19 | var testcasesEvents []byte 20 | 21 | var testStreamId = "stream" 22 | 23 | func TestEvents(t *testing.T) { 24 | // events cannot be tested with prism.Mock since it does 25 | // not support sse streams. 26 | 27 | server := sse.New() 28 | server.CreateStream(testStreamId) 29 | 30 | doneCh := make(chan struct{}) 31 | 32 | handler := func(m *http.ServeMux) { 33 | m.HandleFunc("/eth/v1/events", func(w http.ResponseWriter, r *http.Request) { 34 | 35 | // r3labs/sse requires an url field 'stream' with the name of the stream to subscribe. 36 | // Unsure if that is part of the official sse spec or something specific of r3labs/sse 37 | q := r.URL.Query() 38 | q.Set("stream", testStreamId) 39 | r.URL.RawQuery = q.Encode() 40 | 41 | close(doneCh) 42 | server.HTTPHandler(w, r) 43 | }) 44 | } 45 | 46 | addr := newMockHttpServer(t, handler) 47 | clt := New("http://"+addr, WithLogger(log.New(os.Stdout, "", 0)), WithUntrackedKeys()) 48 | 49 | errCh := make(chan error) 50 | objCh := make(chan interface{}) 51 | 52 | go func() { 53 | err := clt.Events(context.Background(), []string{"head"}, func(obj interface{}) { 54 | objCh <- obj 55 | }) 56 | errCh <- err 57 | }() 58 | 59 | // wait for the request to be made 60 | select { 61 | case <-doneCh: 62 | case <-time.After(1 * time.Second): 63 | t.Fatal("timeout to ping sse server") 64 | } 65 | 66 | var cases []struct { 67 | Event string 68 | Data json.RawMessage 69 | } 70 | if err := json.Unmarshal(testcasesEvents, &cases); err != nil { 71 | t.Fatal(err) 72 | } 73 | 74 | for indx, c := range cases { 75 | // we need to remove any '\n\t' since it is used by sse to split 76 | // the message data during transport 77 | data := string(c.Data) 78 | data = strings.Replace(data, "\n", "", -1) 79 | data = strings.Replace(data, "\t", "", -1) 80 | 81 | server.Publish(testStreamId, &sse.Event{ 82 | Event: []byte(c.Event), 83 | Data: []byte(data), 84 | }) 85 | 86 | select { 87 | case <-errCh: 88 | t.Fatal("sse client closed unexpectedly") 89 | case <-objCh: 90 | case <-time.After(1 * time.Second): 91 | t.Fatalf("message %d not received", indx) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /http/http.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "log" 9 | "net/http" 10 | ) 11 | 12 | // https://ethereum.github.io/beacon-APIs/#/ 13 | 14 | type Config struct { 15 | logger *log.Logger 16 | untrackedKeys bool 17 | } 18 | 19 | type ConfigOption func(*Config) 20 | 21 | func WithLogger(logger *log.Logger) ConfigOption { 22 | return func(c *Config) { 23 | c.logger = logger 24 | } 25 | } 26 | 27 | func WithUntrackedKeys() ConfigOption { 28 | return func(c *Config) { 29 | c.untrackedKeys = true 30 | } 31 | } 32 | 33 | type Client struct { 34 | url string 35 | config *Config 36 | } 37 | 38 | func New(url string, opts ...ConfigOption) *Client { 39 | config := &Config{ 40 | logger: log.New(io.Discard, "", 0), 41 | } 42 | for _, opt := range opts { 43 | opt(config) 44 | } 45 | 46 | return &Client{url: url, config: config} 47 | } 48 | 49 | func (c *Client) SetLogger(logger *log.Logger) { 50 | c.config.logger = logger 51 | } 52 | 53 | func (c *Client) Post(path string, input interface{}, out interface{}) error { 54 | postBody, err := Marshal(input) 55 | if err != nil { 56 | return err 57 | } 58 | responseBody := bytes.NewBuffer(postBody) 59 | 60 | resp, err := http.Post(c.url+path, "application/json", responseBody) 61 | if err != nil { 62 | return err 63 | } 64 | defer resp.Body.Close() 65 | 66 | if err := c.decodeResp(resp, out); err != nil { 67 | return err 68 | } 69 | return nil 70 | } 71 | 72 | func (c *Client) Status(path string) (bool, error) { 73 | resp, err := http.Get(c.url + path) 74 | if err != nil { 75 | return false, err 76 | } 77 | defer resp.Body.Close() 78 | 79 | if resp.StatusCode == http.StatusOK { 80 | return true, nil 81 | } 82 | errorMsg, ok := httpErrorMapping[resp.StatusCode] 83 | if ok { 84 | return false, errorMsg 85 | } 86 | return false, fmt.Errorf("status code != 200: %d", resp.StatusCode) 87 | } 88 | 89 | func (c *Client) Get(path string, out interface{}) error { 90 | resp, err := http.Get(c.url + path) 91 | if err != nil { 92 | return err 93 | } 94 | defer resp.Body.Close() 95 | 96 | c.config.logger.Printf("[TRACE] Get request: path, %s", path) 97 | 98 | if err := c.decodeResp(resp, out); err != nil { 99 | return err 100 | } 101 | return nil 102 | } 103 | 104 | var ( 105 | ErrorIncompleteData = fmt.Errorf("incomplete data (206)") 106 | ErrorBadRequest = fmt.Errorf("bad request (400)") 107 | ErrorNotFound = fmt.Errorf("not found (404)") 108 | ErrorInternalServerError = fmt.Errorf("internal server error (500)") 109 | ErrorServiceUnavailable = fmt.Errorf("service unavailable (503)") 110 | ) 111 | 112 | var httpErrorMapping = map[int]error{ 113 | http.StatusPartialContent: ErrorIncompleteData, 114 | http.StatusBadRequest: ErrorBadRequest, 115 | http.StatusNotFound: ErrorNotFound, 116 | http.StatusInternalServerError: ErrorInternalServerError, 117 | http.StatusServiceUnavailable: ErrorServiceUnavailable, 118 | } 119 | 120 | type httpErrorMessage struct { 121 | Code uint64 `json:"code"` 122 | Message string `json:"message"` 123 | } 124 | 125 | func (c *Client) decodeResp(resp *http.Response, out interface{}) error { 126 | data, err := io.ReadAll(resp.Body) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | if resp.StatusCode != http.StatusOK { 132 | // decode the error message 133 | var msg httpErrorMessage 134 | if err := json.Unmarshal(data, &msg); err != nil { 135 | return err 136 | } 137 | 138 | errorMsgCode, ok := httpErrorMapping[resp.StatusCode] 139 | if ok { 140 | return fmt.Errorf("%w: %v", errorMsgCode, msg.Message) 141 | } 142 | 143 | // return the error message as is 144 | return fmt.Errorf(msg.Message) 145 | } 146 | 147 | c.config.logger.Printf("[TRACE] Http response: data, %s", string(data)) 148 | 149 | if resp.Request.Method == http.MethodPost && out == nil { 150 | // post methods that expects no output 151 | if string(data) == `{"data":null}` { 152 | return nil 153 | } 154 | if string(data) == "null" { 155 | return nil 156 | } 157 | if string(data) == "" { 158 | return nil 159 | } 160 | return fmt.Errorf("json failed to decode post message: '%s'", string(data)) 161 | } 162 | 163 | var output struct { 164 | Data json.RawMessage `json:"data,omitempty"` 165 | } 166 | if err := json.Unmarshal(data, &output); err != nil { 167 | return err 168 | } 169 | if err := Unmarshal(output.Data, &out, c.config.untrackedKeys); err != nil { 170 | return err 171 | } 172 | return nil 173 | } 174 | -------------------------------------------------------------------------------- /http/http_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/require" 9 | ) 10 | 11 | func TestHttp_Post(t *testing.T) { 12 | handler := func(m *http.ServeMux) { 13 | m.HandleFunc("/do", func(w http.ResponseWriter, r *http.Request) { 14 | switch r.URL.Query().Get("t") { 15 | case "a": 16 | w.Write([]byte(`{"data":null}`)) 17 | case "b": 18 | w.Write([]byte(`null`)) 19 | case "c": 20 | w.Write([]byte(``)) 21 | case "d": 22 | w.Write([]byte(`x`)) 23 | } 24 | }) 25 | } 26 | 27 | addr := newMockHttpServer(t, handler) 28 | clt := New("http://" + addr) 29 | 30 | var input struct{} 31 | 32 | require.NoError(t, clt.Post("/do?t=a", &input, nil)) 33 | require.NoError(t, clt.Post("/do?t=b", &input, nil)) 34 | require.NoError(t, clt.Post("/do?t=c", &input, nil)) 35 | require.Error(t, clt.Post("/do?t=d", &input, nil)) 36 | } 37 | 38 | func TestHttp_ErrorStatus(t *testing.T) { 39 | handler := func(m *http.ServeMux) { 40 | m.HandleFunc("/do", func(w http.ResponseWriter, r *http.Request) { 41 | errorCode := r.URL.Query().Get("t") 42 | 43 | switch errorCode { 44 | case "400": 45 | w.WriteHeader(http.StatusBadRequest) 46 | case "404": 47 | w.WriteHeader(http.StatusNotFound) 48 | case "500": 49 | w.WriteHeader(http.StatusInternalServerError) 50 | case "503": 51 | w.WriteHeader(http.StatusServiceUnavailable) 52 | } 53 | 54 | w.Write([]byte(`{"message": "incorrect data", "code": ` + errorCode + `}`)) 55 | }) 56 | } 57 | 58 | addr := newMockHttpServer(t, handler) 59 | clt := New("http://" + addr) 60 | 61 | require.ErrorIs(t, clt.Get("/do?t=400", nil), ErrorBadRequest) 62 | require.ErrorIs(t, clt.Get("/do?t=404", nil), ErrorNotFound) 63 | require.ErrorIs(t, clt.Get("/do?t=500", nil), ErrorInternalServerError) 64 | require.ErrorIs(t, clt.Get("/do?t=503", nil), ErrorServiceUnavailable) 65 | } 66 | 67 | func newMockHttpServer(t *testing.T, handler func(m *http.ServeMux)) string { 68 | m := http.NewServeMux() 69 | if handler != nil { 70 | handler(m) 71 | } 72 | 73 | addr := "127.0.0.1:8000" 74 | s := &http.Server{ 75 | Addr: addr, 76 | Handler: m, 77 | } 78 | 79 | go func() { 80 | s.ListenAndServe() 81 | }() 82 | time.Sleep(500 * time.Millisecond) 83 | 84 | t.Cleanup(func() { 85 | s.Close() 86 | }) 87 | return addr 88 | } 89 | -------------------------------------------------------------------------------- /http/node.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | type NodeEndpoint struct { 4 | c *Client 5 | } 6 | 7 | func (c *Client) Node() *NodeEndpoint { 8 | return &NodeEndpoint{c: c} 9 | } 10 | 11 | type Identity struct { 12 | PeerID string `json:"peer_id"` 13 | ENR string `json:"enr"` 14 | P2PAddresses []string `json:"p2p_addresses"` 15 | DiscoveryAddresses []string `json:"discovery_addresses"` 16 | Metadata *IdentityMetadata `json:"metadata"` 17 | } 18 | 19 | type IdentityMetadata struct { 20 | SeqNumber uint64 `json:"seq_number"` 21 | AttNets string `json:"attnets"` 22 | SyncNets string `json:"syncnets"` 23 | } 24 | 25 | // Identity returns the node network identity 26 | func (n *NodeEndpoint) Identity() (*Identity, error) { 27 | var out *Identity 28 | err := n.c.Get("/eth/v1/node/identity", &out) 29 | return out, err 30 | } 31 | 32 | type Peer struct { 33 | PeerID string `json:"peer_id"` 34 | Enr string `json:"enr"` 35 | LastSeendP2PAddress string `json:"last_seen_p2p_address"` 36 | State string `json:"state"` 37 | Direction string `json:"direction"` 38 | } 39 | 40 | func (n *NodeEndpoint) Peers() ([]*Peer, error) { 41 | var peers []*Peer 42 | err := n.c.Get("/eth/v1/node/peers", &peers) 43 | return peers, err 44 | } 45 | 46 | func (n *NodeEndpoint) GetPeer(peerID string) (*Peer, error) { 47 | var peer *Peer 48 | err := n.c.Get("/eth/v1/node/peers/"+peerID, &peer) 49 | return peer, err 50 | } 51 | 52 | type PeerCount struct { 53 | Disconnected uint64 `json:"disconnected"` 54 | Connecting uint64 `json:"connecting"` 55 | Connected uint64 `json:"connected"` 56 | Disconnecting uint64 `json:"disconnecting"` 57 | } 58 | 59 | func (n *NodeEndpoint) PeerCount() (*PeerCount, error) { 60 | var peerCount *PeerCount 61 | err := n.c.Get("/eth/v1/node/peer_count", &peerCount) 62 | return peerCount, err 63 | } 64 | 65 | func (n *NodeEndpoint) Version() (string, error) { 66 | var out struct { 67 | Version string `json:"version"` 68 | } 69 | err := n.c.Get("/eth/v1/node/version", &out) 70 | return out.Version, err 71 | } 72 | 73 | type Syncing struct { 74 | HeadSlot uint64 `json:"head_slot"` 75 | SyncDistance string `json:"sync_distance"` 76 | IsSyncing bool `json:"is_syncing"` 77 | IsOptimistic bool `json:"is_optimistic"` 78 | } 79 | 80 | func (n *NodeEndpoint) Syncing() (*Syncing, error) { 81 | var out Syncing 82 | err := n.c.Get("/eth/v1/node/syncing", &out) 83 | return &out, err 84 | } 85 | 86 | func (n *NodeEndpoint) Health() (bool, error) { 87 | status, err := n.c.Status("/eth/v1/node/health") 88 | return status, err 89 | } 90 | -------------------------------------------------------------------------------- /http/node_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestNodeEndpoint(t *testing.T) { 10 | n := New("http://127.0.0.1:4010", WithUntrackedKeys()).Node() 11 | 12 | t.Run("Identity", func(t *testing.T) { 13 | _, err := n.Identity() 14 | assert.NoError(t, err) 15 | }) 16 | 17 | t.Run("Peers", func(t *testing.T) { 18 | _, err := n.Peers() 19 | assert.NoError(t, err) 20 | }) 21 | 22 | t.Run("GetPeer", func(t *testing.T) { 23 | _, err := n.GetPeer("a") 24 | assert.NoError(t, err) 25 | }) 26 | 27 | t.Run("PeerCount", func(t *testing.T) { 28 | _, err := n.PeerCount() 29 | assert.NoError(t, err) 30 | }) 31 | 32 | t.Run("Version", func(t *testing.T) { 33 | _, err := n.Version() 34 | assert.NoError(t, err) 35 | }) 36 | 37 | t.Run("Syncing", func(t *testing.T) { 38 | _, err := n.Syncing() 39 | assert.NoError(t, err) 40 | }) 41 | 42 | t.Run("Health", func(t *testing.T) { 43 | status, err := n.Health() 44 | assert.NoError(t, err) 45 | assert.True(t, status) 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /http/testcases/events.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "event": "head", 4 | "data": { 5 | "slot": "10", 6 | "block": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", 7 | "state": "0x600e852a08c1200654ddf11025f1ceacb3c2e74bdd5c630cde0838b2591b69f9", 8 | "epoch_transition": false, 9 | "previous_duty_dependent_root": "0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91", 10 | "current_duty_dependent_root": "0x5e0043f107cb57913498fbf2f99ff55e730bf1e151f02f221e977c91a90a0e91", 11 | "execution_optimistic": false 12 | } 13 | }, 14 | { 15 | "event": "block", 16 | "data": { 17 | "slot": "10", 18 | "block": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", 19 | "execution_optimistic": false 20 | } 21 | }, 22 | { 23 | "event": "attestation", 24 | "data": { 25 | "aggregation_bits": "0x01", 26 | "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505", 27 | "data": { 28 | "slot": "1", 29 | "index": "1", 30 | "beacon_block_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", 31 | "source": { 32 | "epoch": "1", 33 | "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" 34 | }, 35 | "target": { 36 | "epoch": "1", 37 | "root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2" 38 | } 39 | } 40 | } 41 | }, 42 | { 43 | "event": "voluntary_exit", 44 | "data": { 45 | "message": { 46 | "epoch": "1", 47 | "validator_index": "1" 48 | }, 49 | "signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505" 50 | } 51 | }, 52 | { 53 | "event": "finalized_checkpoint", 54 | "data": { 55 | "block": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", 56 | "state": "0x600e852a08c1200654ddf11025f1ceacb3c2e74bdd5c630cde0838b2591b69f9", 57 | "epoch": "2", 58 | "execution_optimistic": false 59 | } 60 | }, 61 | { 62 | "event": "chain_reorg", 63 | "data": { 64 | "slot": "200", 65 | "depth": "50", 66 | "old_head_block": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", 67 | "new_head_block": "0x76262e91970d375a19bfe8a867288d7b9cde43c8635f598d93d39d041706fc76", 68 | "old_head_state": "0x9a2fefd2fdb57f74993c7780ea5b9030d2897b615b89f808011ca5aebed54eaf", 69 | "new_head_state": "0x600e852a08c1200654ddf11025f1ceacb3c2e74bdd5c630cde0838b2591b69f9", 70 | "epoch": "2", 71 | "execution_optimistic": false 72 | } 73 | }, 74 | { 75 | "event": "contribution_and_proof", 76 | "data": { 77 | "message": { 78 | "aggregator_index": "997", 79 | "contribution": { 80 | "slot": "168097", 81 | "beacon_block_root": "0x56f1fd4262c08fa81e27621c370e187e621a67fc80fe42340b07519f84b42ea1", 82 | "subcommittee_index": "0", 83 | "aggregation_bits": "0xffffffffffffffffffffffffffffffff", 84 | "signature": "0x85ab9018e14963026476fdf784cc674da144b3dbdb47516185438768774f077d882087b90ad642469902e782a8b43eed0cfc1b862aa9a473b54c98d860424a702297b4b648f3f30bdaae8a8b7627d10d04cb96a2cc8376af3e54a9aa0c8145e3" 85 | }, 86 | "selection_proof": "0x87c305f04bfe5db27c2b19fc23e00d7ac496ec7d3e759cbfdd1035cb8cf6caaa17a36a95a08ba78c282725e7b66a76820ca4eb333822bd399ceeb9807a0f2926c67ce67cfe06a0b0006838203b493505a8457eb79913ce1a3bcd1cc8e4ef30ed" 87 | }, 88 | "signature": "0xac118511474a94f857300b315c50585c32a713e4452e26a6bb98cdb619936370f126ed3b6bb64469259ee92e69791d9e12d324ce6fd90081680ce72f39d85d50b0ff977260a8667465e613362c6d6e6e745e1f9323ec1d6f16041c4e358839ac" 89 | } 90 | } 91 | ] -------------------------------------------------------------------------------- /http/validator.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | type ValidatorEndpoint struct { 11 | c *Client 12 | } 13 | 14 | func (c *Client) Validator() *ValidatorEndpoint { 15 | return &ValidatorEndpoint{c: c} 16 | } 17 | 18 | type AttesterDuty struct { 19 | PubKey string `json:"pubkey"` 20 | ValidatorIndex uint `json:"validator_index"` 21 | Slot uint64 `json:"slot"` 22 | CommitteeIndex uint64 `json:"committee_index"` 23 | CommitteeLength uint64 `json:"committee_length"` 24 | CommitteeAtSlot uint64 `json:"committees_at_slot"` 25 | ValidatorCommitteeIndex uint64 `json:"validator_committee_index"` 26 | } 27 | 28 | func (v *ValidatorEndpoint) GetAttesterDuties(epoch uint64, indexes []string) ([]*AttesterDuty, error) { 29 | var out []*AttesterDuty 30 | err := v.c.Post(fmt.Sprintf("/eth/v1/validator/duties/attester/%d", epoch), indexes, &out) 31 | return out, err 32 | } 33 | 34 | type ProposerDuty struct { 35 | PubKey string `json:"pubkey"` 36 | ValidatorIndex uint `json:"validator_index"` 37 | Slot uint64 `json:"slot"` 38 | } 39 | 40 | func (v *ValidatorEndpoint) GetProposerDuties(epoch uint64) ([]*ProposerDuty, error) { 41 | var out []*ProposerDuty 42 | err := v.c.Get(fmt.Sprintf("/eth/v1/validator/duties/proposer/%d", epoch), &out) 43 | return out, err 44 | } 45 | 46 | type CommitteeSyncDuty struct { 47 | PubKey string `json:"pubkey"` 48 | ValidatorIndex uint `json:"validator_index"` 49 | ValidatorSyncCommitteeIndices []string `json:"validator_sync_committee_indices"` 50 | } 51 | 52 | func (v *ValidatorEndpoint) GetCommitteeSyncDuties(epoch uint64, indexes []string) ([]*CommitteeSyncDuty, error) { 53 | var out []*CommitteeSyncDuty 54 | err := v.c.Post(fmt.Sprintf("/eth/v1/validator/duties/sync/%d", epoch), indexes, &out) 55 | return out, err 56 | } 57 | 58 | func (v *ValidatorEndpoint) GetBlock(out consensus.BeaconBlock, slot uint64, randao [96]byte) error { 59 | buf := "0x" + hex.EncodeToString(randao[:]) 60 | err := v.c.Get(fmt.Sprintf("/eth/v2/validator/blocks/%d?randao_reveal=%s", slot, buf), &out) 61 | return err 62 | } 63 | 64 | func (v *ValidatorEndpoint) RequestAttestationData(slot uint64, committeeIndex uint64) (*consensus.AttestationData, error) { 65 | var out *consensus.AttestationData 66 | err := v.c.Get(fmt.Sprintf("/eth/v1/validator/attestation_data?slot=%d&committee_index=%d", slot, committeeIndex), &out) 67 | return out, err 68 | } 69 | 70 | func (v *ValidatorEndpoint) AggregateAttestation(slot uint64, root [32]byte) (*consensus.Attestation, error) { 71 | var out *consensus.Attestation 72 | err := v.c.Get(fmt.Sprintf("/eth/v1/validator/aggregate_attestation?slot=%d&attestation_data_root=0x%s", slot, hex.EncodeToString(root[:])), &out) 73 | return out, err 74 | } 75 | 76 | func (v *ValidatorEndpoint) PublishAggregateAndProof(data []*consensus.SignedAggregateAndProof) error { 77 | err := v.c.Post("/eth/v1/validator/aggregate_and_proofs", data, nil) 78 | return err 79 | } 80 | 81 | type BeaconCommitteeSubscription struct { 82 | ValidatorIndex uint64 `json:"validator_index"` 83 | Slot uint64 `json:"slot"` 84 | CommitteeIndex uint64 `json:"committee_index"` 85 | CommitteesAtSlot uint64 `json:"committee_at_slot"` 86 | IsAggregator bool `json:"is_aggregator"` 87 | } 88 | 89 | func (v *ValidatorEndpoint) BeaconCommitteeSubscriptions(subs []*BeaconCommitteeSubscription) error { 90 | err := v.c.Post("/eth/v1/validator/beacon_committee_subscriptions", subs, nil) 91 | return err 92 | } 93 | 94 | type SyncCommitteeSubscription struct { 95 | ValidatorIndex uint64 `json:"validator_index"` 96 | SyncCommitteeIndices []uint64 `json:"sync_committee_indices"` 97 | UntilEpoch uint64 `json:"until_epoch"` 98 | } 99 | 100 | func (v *ValidatorEndpoint) SyncCommitteeSubscriptions(subs []*SyncCommitteeSubscription) error { 101 | err := v.c.Post("/eth/v1/validator/sync_committee_subscriptions", subs, nil) 102 | return err 103 | } 104 | 105 | // produces a sync committee contribution 106 | func (v *ValidatorEndpoint) SyncCommitteeContribution(slot uint64, subCommitteeIndex uint64, root [32]byte) (*consensus.SyncCommitteeContribution, error) { 107 | var out *consensus.SyncCommitteeContribution 108 | err := v.c.Get(fmt.Sprintf("/eth/v1/validator/sync_committee_contribution?slot=%d&subcommittee_index=%d&beacon_block_root=0x%s", slot, subCommitteeIndex, hex.EncodeToString(root[:])), &out) 109 | return out, err 110 | } 111 | 112 | func (v *ValidatorEndpoint) SubmitSignedContributionAndProof(signedContribution []*consensus.SignedContributionAndProof) error { 113 | err := v.c.Post("/eth/v1/validator/contribution_and_proofs", signedContribution, nil) 114 | return err 115 | } 116 | 117 | type ProposalPreparation struct { 118 | ValidatorIndex uint64 119 | FeeRecipient [20]byte 120 | } 121 | 122 | func (v *ValidatorEndpoint) PrepareBeaconProposer(input []*ProposalPreparation) error { 123 | err := v.c.Post("/eth/v1/validator/prepare_beacon_proposer", input, nil) 124 | return err 125 | } 126 | 127 | type RegisterValidatorRequest struct { 128 | FeeRecipient [20]byte `json:"fee_recipient" ssz-size:"20"` 129 | GasLimit uint64 `json:"gas_limit,string"` 130 | Timestamp uint64 `json:"timestamp,string"` 131 | Pubkey [48]byte `json:"pubkey" ssz-size:"48"` 132 | } 133 | 134 | type SignedValidatorRegistration struct { 135 | Message *RegisterValidatorRequest `json:"message"` 136 | Signature [96]byte `json:"signature" ssz-size:"96"` 137 | } 138 | 139 | func (v *ValidatorEndpoint) RegisterValidator(msg []*SignedValidatorRegistration) error { 140 | err := v.c.Post("/eth/v1/validator/register_validator", msg, nil) 141 | return err 142 | } 143 | -------------------------------------------------------------------------------- /http/validator_test.go: -------------------------------------------------------------------------------- 1 | package http 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func TestValidatorEndpoint(t *testing.T) { 11 | n := New("http://127.0.0.1:4010", WithUntrackedKeys()).Validator() 12 | 13 | t.Run("GetAttesterDuties", func(t *testing.T) { 14 | _, err := n.GetAttesterDuties(1, []string{"1"}) 15 | assert.NoError(t, err) 16 | }) 17 | 18 | t.Run("GetProposerDuties", func(t *testing.T) { 19 | _, err := n.GetProposerDuties(1) 20 | assert.NoError(t, err) 21 | }) 22 | 23 | t.Run("GetCommitteeSyncDuties", func(t *testing.T) { 24 | _, err := n.GetCommitteeSyncDuties(1, []string{"1"}) 25 | assert.NoError(t, err) 26 | }) 27 | 28 | t.Run("GetBlock", func(t *testing.T) { 29 | t.Skip("due to graffiti") 30 | 31 | block := consensus.BeaconBlockAltair{} 32 | err := n.GetBlock(&block, 1, [96]byte{}) 33 | assert.NoError(t, err) 34 | }) 35 | 36 | t.Run("RequestAttestationData", func(t *testing.T) { 37 | _, err := n.RequestAttestationData(1, 1) 38 | assert.NoError(t, err) 39 | }) 40 | 41 | t.Run("AggregateAttestation", func(t *testing.T) { 42 | _, err := n.AggregateAttestation(1, [32]byte{}) 43 | assert.NoError(t, err) 44 | }) 45 | 46 | t.Run("SyncCommitteeContribution", func(t *testing.T) { 47 | _, err := n.SyncCommitteeContribution(1, 1, [32]byte{}) 48 | assert.NoError(t, err) 49 | }) 50 | 51 | t.Run("BeaconCommitteeSubscriptions", func(t *testing.T) { 52 | err := n.BeaconCommitteeSubscriptions([]*BeaconCommitteeSubscription{{}}) 53 | assert.NoError(t, err) 54 | }) 55 | 56 | t.Run("SyncCommitteeSubscriptions", func(t *testing.T) { 57 | err := n.SyncCommitteeSubscriptions([]*SyncCommitteeSubscription{{}}) 58 | assert.NoError(t, err) 59 | }) 60 | 61 | t.Run("PrepareBeaconProposer", func(t *testing.T) { 62 | err := n.PrepareBeaconProposer([]*ProposalPreparation{{}}) 63 | assert.NoError(t, err) 64 | }) 65 | 66 | t.Run("RegisterValidator", func(t *testing.T) { 67 | obj := []*SignedValidatorRegistration{ 68 | {Message: &RegisterValidatorRequest{}}, 69 | } 70 | err := n.RegisterValidator(obj) 71 | assert.NoError(t, err) 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /interfaces.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | type SignedBeaconBlock interface { 4 | isSignedBeaconBlock() 5 | } 6 | 7 | func (s *SignedBeaconBlockAltair) isSignedBeaconBlock() { 8 | } 9 | 10 | func (s *SignedBeaconBlockPhase0) isSignedBeaconBlock() { 11 | } 12 | 13 | func (s *SignedBeaconBlockBellatrix) isSignedBeaconBlock() { 14 | } 15 | 16 | func (s *SignedBeaconBlockCapella) isSignedBeaconBlock() { 17 | } 18 | 19 | type BeaconBlock interface { 20 | isBeaconBlock() 21 | } 22 | 23 | func (s *BeaconBlockAltair) isBeaconBlock() { 24 | } 25 | 26 | func (s *BeaconBlockPhase0) isBeaconBlock() { 27 | } 28 | 29 | func (s *BeaconBlockBellatrix) isBeaconBlock() { 30 | } 31 | 32 | func (s *BeaconBlockCapella) isBeaconBlock() { 33 | } 34 | 35 | type BeaconState interface { 36 | isBeaconState() 37 | } 38 | 39 | func (s *BeaconStatePhase0) isBeaconState() { 40 | } 41 | 42 | func (s *BeaconStateAltair) isBeaconState() { 43 | } 44 | 45 | func (s *BeaconStateBellatrix) isBeaconState() { 46 | } 47 | 48 | func (s *BeaconStateCapella) isBeaconState() { 49 | } 50 | -------------------------------------------------------------------------------- /scripts/download-spec-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | VERSION=$1 4 | REPO_NAME=eth2.0-spec-tests 5 | 6 | # Remove dir if it already exists 7 | rm -rf $REPO_NAME 8 | mkdir $REPO_NAME 9 | 10 | function download { 11 | OUTPUT=$1.tar.gz 12 | DOWNLOAD_URL=https://github.com/ethereum/$REPO_NAME/releases/download/$VERSION/$OUTPUT 13 | wget $DOWNLOAD_URL -O $OUTPUT 14 | tar -xzf $OUTPUT -C $REPO_NAME 15 | rm $OUTPUT 16 | } 17 | 18 | download "mainnet" 19 | download "general" 20 | 21 | # Download bls tests 22 | mkdir $REPO_NAME/bls 23 | wget https://github.com/ethereum/bls12-381-tests/releases/download/v0.1.2/bls_tests_json.tar.gz -O - | tar -xz -C eth2.0-spec-tests/bls 24 | -------------------------------------------------------------------------------- /scripts/openapi-mock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start beacon open api mock 4 | docker run --init --name prism-beacon -d -p 4010:4010 stoplight/prism:4.10.3 mock -h 0.0.0.0 https://github.com/ethereum/beacon-APIs/releases/download/v2.3.0/beacon-node-oapi.json 5 | 6 | # Wait for beacon mock api 7 | grep -m 1 "Prism is listening" <(docker logs prism-beacon -f) 8 | 9 | # Start builder api mock 10 | docker run --init --name prism-builder -d -p 4011:4010 stoplight/prism:4.10.3 mock -h 0.0.0.0 https://github.com/ethereum/builder-specs/releases/download/v0.2.0/builder-oapi.yaml 11 | 12 | # Wait for builder mock api 13 | grep -m 1 "Prism is listening" <(docker logs prism-builder -f) 14 | -------------------------------------------------------------------------------- /spec.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | type Spec struct { 4 | // GenesisSlot represents the first canonical slot number of the beacon chain. 5 | GenesisSlot uint64 `json:"GENESIS_SLOT"` 6 | 7 | // GenesisEpoch represents the first canonical epoch number of the beacon chain. 8 | GenesisEpoch uint64 `json:"GENESIS_EPOCH"` 9 | 10 | // SecondsPerSlot is how many seconds are in a single slot. 11 | SecondsPerSlot uint64 `json:"SECONDS_PER_SLOT"` 12 | 13 | // SlotsPerEpoch is the number of slots in an epoch. 14 | SlotsPerEpoch uint64 `json:"SLOTS_PER_EPOCH"` 15 | 16 | MaxCommitteesPerSlot uint64 `json:"MAX_COMMITTEES_PER_SLOT"` 17 | 18 | EpochsPerHistoricalVector uint64 `json:"EPOCHS_PER_HISTORICAL_VECTOR"` 19 | MinSeedLookAhead uint64 `json:"MIN_SEED_LOOKAHEAD"` 20 | 21 | MinEpochsToInactivityPenalty uint64 `json:"MIN_EPOCHS_TO_INACTIVITY_PENALTY"` 22 | EffectiveBalanceIncrement uint64 `json:"EFFECTIVE_BALANCE_INCREMENT"` 23 | 24 | BaseRewardFactor uint64 `json:"BASE_REWARD_FACTOR"` 25 | BaseRewardsPerEpoch uint64 `json:"BASE_REWARDS_PER_EPOCH"` 26 | 27 | HysteresisQuotient uint64 `json:"HYSTERESIS_QUOTIENT"` 28 | HysteresisDownwardMultiplier uint64 `json:"HYSTERESIS_DOWNWARD_MULTIPLIER"` 29 | HysteresisUpwardMultiplier uint64 `json:"HYSTERESIS_UPWARD_MULTIPLIER"` 30 | 31 | EjectionBalance uint64 `json:"EJECTION_BALANCE"` 32 | 33 | TargetCommitteeSize uint64 `json:"TARGET_COMMITTEE_SIZE"` 34 | 35 | MaxEffectiveBalance uint64 `json:"MAX_EFFECTIVE_BALANCE"` 36 | 37 | EpochsPerEth1VotingPeriod uint64 `json:"EPOCHS_PER_ETH1_VOTING_PERIOD"` 38 | 39 | ProportionalSlashingsMultiplier uint64 `json:"PROPORTIONAL_SLASHING_MULTIPLIER"` 40 | SlotsPerHistoricalRoot uint64 `json:"SLOTS_PER_HISTORICAL_ROOT"` 41 | SyncCommitteeSize uint64 `json:"SYNC_COMMITTEE_SIZE"` 42 | MaxSeedLookAhead uint64 `json:"MAX_SEED_LOOKAHEAD"` 43 | ShardCommiteePeriod uint64 `json:"SHARD_COMMITTEE_PERIOD"` 44 | WhistleblowerRewardQuotient uint64 `json:"WHISTLEBLOWER_REWARD_QUOTIENT"` 45 | ProposerRewardQuotient uint64 `json:"PROPOSER_REWARD_QUOTIENT"` 46 | MinSlashingPenaltyQuotient uint64 `json:"MIN_SLASHING_PENALTY_QUOTIENT"` 47 | 48 | InactivityPenaltyQuotient uint64 `json:"INACTIVITY_PENALTY_QUOTIENT"` 49 | MinAttestationInclusionDelay uint64 `json:"MIN_ATTESTATION_INCLUSION_DELAY"` 50 | MinValidatorWithdrawabilityDelay uint64 `json:"MIN_VALIDATOR_WITHDRAWABILITY_DELAY"` 51 | EpochsPerSlashingsVector uint64 `json:"EPOCHS_PER_SLASHINGS_VECTOR"` 52 | 53 | ChurnLimitQuotient uint64 `json:"CHURN_LIMIT_QUOTIENT"` 54 | MinPerEpochChurnLimit uint64 `json:"MIN_PER_EPOCH_CHURN_LIMIT"` 55 | 56 | // TargetAggregatorsPerCommittee defines the number of aggregators inside one committee. 57 | TargetAggregatorsPerCommittee uint64 `json:"TARGET_AGGREGATORS_PER_COMMITTEE"` 58 | 59 | // GenesisForkVersion is used to track fork version between state transitions. 60 | GenesisForkVersion Domain `json:"GENESIS_FORK_VERSION"` 61 | 62 | AltairForkVersion Domain `json:"ALTAIR_FORK_VERSION"` 63 | AltairForkEpoch uint64 `json:"ALTAIR_FORK_EPOCH"` 64 | 65 | BellatrixForkVersion Domain `json:"BELLATRIX_FORK_VERSION"` 66 | BellatrixForkEpoch uint64 `json:"BELLATRIX_FORK_EPOCH"` 67 | } 68 | -------------------------------------------------------------------------------- /spec/epoch_processing.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "sort" 6 | 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func processEffectiveBalanceUpdates(state *consensus.BeaconStatePhase0) error { 11 | for indx, validator := range state.Validators { 12 | balance := state.Balances[indx] 13 | 14 | hysteresisIncrement := Spec.EffectiveBalanceIncrement / Spec.HysteresisQuotient 15 | downwardThreshold := hysteresisIncrement * Spec.HysteresisDownwardMultiplier 16 | upwardThreshold := hysteresisIncrement * Spec.HysteresisUpwardMultiplier 17 | 18 | if balance+downwardThreshold < validator.EffectiveBalance || validator.EffectiveBalance+upwardThreshold < balance { 19 | validator.EffectiveBalance = min(balance-balance%Spec.EffectiveBalanceIncrement, Spec.MaxEffectiveBalance) 20 | } 21 | } 22 | return nil 23 | } 24 | 25 | func processEth1DataReset(state *consensus.BeaconStatePhase0) error { 26 | nextEpoch := getCurrentEpoch(state) + 1 27 | 28 | if nextEpoch%Spec.EpochsPerEth1VotingPeriod == 0 { 29 | state.Eth1DataVotes = []*consensus.Eth1Data{} 30 | } 31 | return nil 32 | } 33 | 34 | func processHistoricalRootsUpdate(state *consensus.BeaconStatePhase0) error { 35 | nextEpoch := getCurrentEpoch(state) + 1 36 | 37 | if nextEpoch%(Spec.SlotsPerHistoricalRoot/Spec.SlotsPerEpoch) == 0 { 38 | historicalBatch := consensus.HistoricalBatch{ 39 | BlockRoots: state.BlockRoots, 40 | StateRoots: state.StateRoots, 41 | } 42 | root, err := historicalBatch.HashTreeRoot() 43 | if err != nil { 44 | return err 45 | } 46 | state.HistoricalRoots = append(state.HistoricalRoots, root) 47 | } 48 | return nil 49 | } 50 | 51 | func computeStartSlotAtEpoch(epoch uint64) uint64 { 52 | return epoch * Spec.SlotsPerEpoch 53 | } 54 | 55 | func getBlockRootAtSlot(state *consensus.BeaconStatePhase0, slot uint64) [32]byte { 56 | // Return the block root at a recent ``slot``. 57 | return state.BlockRoots[slot%Spec.SlotsPerHistoricalRoot] 58 | } 59 | 60 | func getBlockRoot(state *consensus.BeaconStatePhase0, epoch uint64) [32]byte { 61 | return getBlockRootAtSlot(state, computeStartSlotAtEpoch(epoch)) 62 | } 63 | 64 | func getMatchingTargetAttestations(state *consensus.BeaconStatePhase0, epoch uint64) []*consensus.PendingAttestation { 65 | root := getBlockRoot(state, epoch) 66 | 67 | res := []*consensus.PendingAttestation{} 68 | for _, a := range getMatchingSourceAttestations(state, epoch) { 69 | if bytes.Equal(a.Data.Target.Root[:], root[:]) { 70 | res = append(res, a) 71 | } 72 | } 73 | 74 | return res 75 | } 76 | 77 | func getAttestingBalance(state *consensus.BeaconStatePhase0, attestations []*consensus.PendingAttestation) uint64 { 78 | // Return the combined effective balance of the set of unslashed validators participating in ``attestations``. 79 | // Note: ``get_total_balance`` returns ``EFFECTIVE_BALANCE_INCREMENT`` Gwei minimum to avoid divisions by zero 80 | indices, err := getUnslashedAttestingIndices(state, attestations) 81 | if err != nil { 82 | panic(err) 83 | } 84 | return getTotalBalance(state, indices) 85 | } 86 | 87 | func processJustificationAndFinalization(state *consensus.BeaconStatePhase0) error { 88 | // Initial FFG checkpoint values have a `0x00` stub for `root`. 89 | // Skip FFG updates in the first two epochs to avoid corner cases that might result in modifying this stub. 90 | if getCurrentEpoch(state) <= Spec.GenesisEpoch+1 { 91 | return nil 92 | } 93 | 94 | previousAttestations := getMatchingSourceAttestations(state, getPreviousEpoch(state)) 95 | currentAttestations := getMatchingTargetAttestations(state, getCurrentEpoch(state)) 96 | totalActiveBalance := getTotalActiveBalance(state) 97 | 98 | previousTargetBalalnce := getAttestingBalance(state, previousAttestations) 99 | currentTargetBalance := getAttestingBalance(state, currentAttestations) 100 | 101 | weighJustificationAndFinalization(state, totalActiveBalance, previousTargetBalalnce, currentTargetBalance) 102 | return nil 103 | } 104 | 105 | func weighJustificationAndFinalization(state *consensus.BeaconStatePhase0, totalActiveBalance uint64, previousEpochTargetBalance uint64, currentEpochTargetBalance uint64) { 106 | previousEpoch := getPreviousEpoch(state) 107 | currentEpoch := getCurrentEpoch(state) 108 | 109 | oldPreviousJustifiedCheckpoint := state.PreviousJustifiedCheckpoint 110 | oldCurrentJustifiedCheckpoint := state.CurrentJustifiedCheckpoint 111 | 112 | // Process justifications 113 | state.PreviousJustifiedCheckpoint = state.CurrentJustifiedCheckpoint 114 | state.JustificationBits[0] = (state.JustificationBits[0] << 1) & 0x0f 115 | 116 | if previousEpochTargetBalance*3 >= totalActiveBalance*2 { 117 | state.CurrentJustifiedCheckpoint = &consensus.Checkpoint{ 118 | Epoch: previousEpoch, 119 | Root: getBlockRoot(state, previousEpoch), 120 | } 121 | 122 | state.JustificationBits[0] |= 1 << 1 123 | } 124 | 125 | if currentEpochTargetBalance*3 >= totalActiveBalance*2 { 126 | state.CurrentJustifiedCheckpoint = &consensus.Checkpoint{ 127 | Epoch: currentEpoch, 128 | Root: getBlockRoot(state, currentEpoch), 129 | } 130 | 131 | state.JustificationBits[0] |= 1 << 0 132 | } 133 | 134 | // Process finalizations 135 | bits := state.JustificationBits[0] 136 | 137 | // the 2nd/3rd/4th most recent epochs are justified, the 2nd using the 4th as source 138 | if bits&0x0E == 0x0E && oldPreviousJustifiedCheckpoint.Epoch+3 == currentEpoch { 139 | state.FinalizedCheckpoint = oldPreviousJustifiedCheckpoint 140 | } 141 | // the 2nd/3rd most recent epochs are justified, the 2nd using the 3rd as source 142 | if bits&0x06 == 0x06 && oldPreviousJustifiedCheckpoint.Epoch+2 == currentEpoch { 143 | state.FinalizedCheckpoint = oldPreviousJustifiedCheckpoint 144 | } 145 | // the 1st/2nd/3rd most recent epochs are justified, the 1st using the 3rd as source 146 | if bits&0x07 == 0x07 && oldCurrentJustifiedCheckpoint.Epoch+2 == currentEpoch { 147 | state.FinalizedCheckpoint = oldCurrentJustifiedCheckpoint 148 | } 149 | // the 1st/2nd most recent epochs are justified, the 1st using the 2nd as source 150 | if bits&0x03 == 0x03 && oldCurrentJustifiedCheckpoint.Epoch+1 == currentEpoch { 151 | state.FinalizedCheckpoint = oldCurrentJustifiedCheckpoint 152 | } 153 | } 154 | 155 | func processParticipationRecordUpdates(state *consensus.BeaconStatePhase0) error { 156 | state.PreviousEpochAttestations = state.CurrentEpochAttestations 157 | state.CurrentEpochAttestations = []*consensus.PendingAttestation{} 158 | return nil 159 | } 160 | 161 | func processRandaoMixesReset(state *consensus.BeaconStatePhase0) error { 162 | currentEpoch := getCurrentEpoch(state) 163 | nextEpoch := currentEpoch + 1 164 | state.RandaoMixes[nextEpoch%Spec.EpochsPerHistoricalVector] = getRandaoMix(state, currentEpoch) 165 | return nil 166 | } 167 | 168 | func isElegibleForActivationQueue(validator *consensus.Validator) bool { 169 | return validator.ActivationEligibilityEpoch == farFutureEpoch && validator.EffectiveBalance == Spec.MaxEffectiveBalance 170 | } 171 | 172 | func isElegibleForActivation(state *consensus.BeaconStatePhase0, validator *consensus.Validator) bool { 173 | return validator.ActivationEligibilityEpoch <= state.FinalizedCheckpoint.Epoch && validator.ActivationEpoch == farFutureEpoch 174 | } 175 | 176 | func processRegistryUpdates(state *consensus.BeaconStatePhase0) error { 177 | // Process activation eligibility and ejections 178 | for indx, validator := range state.Validators { 179 | if isElegibleForActivationQueue(validator) { 180 | validator.ActivationEligibilityEpoch = getCurrentEpoch(state) + 1 181 | } 182 | 183 | if isActiveValidator(validator, getCurrentEpoch(state)) && validator.EffectiveBalance <= Spec.EjectionBalance { 184 | if err := initiateValidatorExit(state, uint64(indx)); err != nil { 185 | return err 186 | } 187 | } 188 | } 189 | 190 | // Queue validators eligible for activation and not yet dequeued for activation 191 | activationQueue := []uint64{} 192 | for indx, validator := range state.Validators { 193 | if isElegibleForActivation(state, validator) { 194 | activationQueue = append(activationQueue, uint64(indx)) 195 | } 196 | } 197 | 198 | // Order by the sequence of activation_eligibility_epoch setting and then index 199 | sort.Slice(activationQueue, func(i, j int) bool { 200 | if state.Validators[i].ActivationEligibilityEpoch == state.Validators[j].ActivationEligibilityEpoch { 201 | // Order by index 202 | return i < j 203 | } 204 | // Order by ActivationEligibilityEpoch 205 | return state.Validators[i].ActivationEligibilityEpoch < state.Validators[j].ActivationEligibilityEpoch 206 | }) 207 | 208 | churnLimit := min(uint64(len(activationQueue)), getValidatorChurnLimit(state)) 209 | 210 | // Dequeued validators for activation up to churn limit 211 | for _, indx := range activationQueue[:churnLimit] { 212 | validator := state.Validators[indx] 213 | validator.ActivationEpoch = computeActivationExitEpoch(getCurrentEpoch(state)) 214 | } 215 | return nil 216 | } 217 | 218 | // getInclusionDelayDeltas returns proposer and inclusion delay micro-rewards/penalties for each validator. 219 | func getInclusionDelayDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 220 | rewards := make([]uint64, len(state.Validators)) 221 | 222 | matchingSourceAttestations := getMatchingSourceAttestations(state, getPreviousEpoch(state)) 223 | 224 | unslashedAttIndex, err := getUnslashedAttestingIndices(state, matchingSourceAttestations) 225 | if err != nil { 226 | panic(err) 227 | } 228 | 229 | for _, index := range unslashedAttIndex { 230 | var attestation *consensus.PendingAttestation 231 | for _, a := range matchingSourceAttestations { 232 | attIndex, err := getAttestingIndices(state, a.Data, a.AggregationBits) 233 | if err != nil { 234 | panic(err) 235 | } 236 | if contains(attIndex, index) { 237 | if attestation != nil { 238 | if attestation.InclusionDelay < a.InclusionDelay { 239 | continue 240 | } 241 | } 242 | attestation = a 243 | } 244 | } 245 | 246 | rewards[attestation.ProposerIndex] += getProposerReward(state, index) 247 | maxAttesterReward := getBaseReward(state, index) - getProposerReward(state, index) 248 | rewards[index] += maxAttesterReward / attestation.InclusionDelay 249 | } 250 | 251 | // no penalties associated with inclusion delay 252 | penalties := make([]uint64, len(state.Validators)) 253 | 254 | return rewards, penalties 255 | } 256 | 257 | func contains(a []uint64, b uint64) bool { 258 | for _, i := range a { 259 | if i == b { 260 | return true 261 | } 262 | } 263 | return false 264 | } 265 | 266 | // getInactivityPenaltyDeltas return inactivity reward/penalty deltas for each validator. 267 | func getInactivityPenaltyDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 268 | penalties := make([]uint64, len(state.Validators)) 269 | 270 | if isInInactivityLeak(state) { 271 | matchingTargetAttestations := getMatchingTargetAttestations(state, getPreviousEpoch(state)) 272 | matchingTargetAttestingIndices, err := getUnslashedAttestingIndices(state, matchingTargetAttestations) 273 | if err != nil { 274 | panic(err) 275 | } 276 | 277 | for _, index := range getElegibleValidatorIndices(state) { 278 | // If validator is performing optimally this cancels all rewards for a neutral balance 279 | baseReward := getBaseReward(state, index) 280 | penalties[index] += Spec.BaseRewardsPerEpoch*baseReward - getProposerReward(state, index) 281 | 282 | if !contains(matchingTargetAttestingIndices, index) { 283 | effectiveBalance := state.Validators[index].EffectiveBalance 284 | penalties[index] += effectiveBalance * getFinalityDelay(state) / Spec.InactivityPenaltyQuotient 285 | } 286 | } 287 | } 288 | 289 | // No rewards associated with inactivity penalties 290 | rewards := make([]uint64, len(state.Validators)) 291 | 292 | return rewards, penalties 293 | } 294 | 295 | func getProposerReward(state *consensus.BeaconStatePhase0, attestingIndex uint64) uint64 { 296 | return getBaseReward(state, attestingIndex) / Spec.ProposerRewardQuotient 297 | } 298 | 299 | func getAttestationDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 300 | // Return attestation reward/penalty deltas for each validator. 301 | sourceRewards, sourcePenalties := getSourceDeltas(state) 302 | targetRewards, targetPenalties := getTargetDeltas(state) 303 | headRewards, headPenalties := getHeadDeltas(state) 304 | inclusionDelayRewards, _ := getInclusionDelayDeltas(state) 305 | _, inactivityPenalties := getInactivityPenaltyDeltas(state) 306 | 307 | penalties := make([]uint64, len(state.Validators)) 308 | rewards := make([]uint64, len(state.Validators)) 309 | 310 | for i := 0; i < len(state.Validators); i++ { 311 | rewards[i] = sourceRewards[i] + targetRewards[i] + headRewards[i] + inclusionDelayRewards[i] 312 | } 313 | 314 | for i := 0; i < len(state.Validators); i++ { 315 | penalties[i] = sourcePenalties[i] + targetPenalties[i] + headPenalties[i] + inactivityPenalties[i] 316 | } 317 | 318 | return rewards, penalties 319 | } 320 | 321 | func processRewardsAndPenalties(state *consensus.BeaconStatePhase0) error { 322 | // No rewards are applied at the end of `GENESIS_EPOCH` because rewards are for work done in the previous epoch 323 | if getCurrentEpoch(state) == Spec.GenesisEpoch { 324 | return nil 325 | } 326 | 327 | rewards, penalties := getAttestationDeltas(state) 328 | for indx := range state.Validators { 329 | increaseBalance(state, uint64(indx), rewards[indx]) 330 | decreaseBalance(state, uint64(indx), penalties[indx]) 331 | } 332 | return nil 333 | } 334 | 335 | func sum(i []uint64) (res uint64) { 336 | for _, j := range i { 337 | res += j 338 | } 339 | return 340 | } 341 | 342 | func processSlashings(state *consensus.BeaconStatePhase0) error { 343 | epoch := getCurrentEpoch(state) 344 | 345 | totalBalance := getTotalActiveBalance(state) 346 | adjustedTotalSlashingBalance := min(sum(state.Slashings)*Spec.ProportionalSlashingsMultiplier, totalBalance) 347 | 348 | for index, validator := range state.Validators { 349 | if validator.Slashed && epoch+Spec.EpochsPerSlashingsVector/2 == validator.WithdrawableEpoch { 350 | increment := Spec.EffectiveBalanceIncrement 351 | penaltyNumerator := (validator.EffectiveBalance / increment) * adjustedTotalSlashingBalance 352 | penalty := (penaltyNumerator / totalBalance) * increment 353 | decreaseBalance(state, uint64(index), penalty) 354 | } 355 | } 356 | return nil 357 | } 358 | 359 | func processSlashingsReset(state *consensus.BeaconStatePhase0) error { 360 | nextEpoch := getCurrentEpoch(state) + 1 361 | state.Slashings[nextEpoch%Spec.EpochsPerSlashingsVector] = 0 362 | return nil 363 | } 364 | -------------------------------------------------------------------------------- /spec/epoch_processing_test.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "path/filepath" 5 | "reflect" 6 | "testing" 7 | 8 | consensus "github.com/umbracle/go-eth-consensus" 9 | ) 10 | 11 | type epochProcessignFunc func(state *consensus.BeaconStatePhase0) error 12 | 13 | func TestEpochProcessing(t *testing.T) { 14 | type epochTest struct { 15 | Pre consensus.BeaconStatePhase0 16 | Post consensus.BeaconStatePhase0 17 | } 18 | 19 | cases := []struct { 20 | name string 21 | path string 22 | handler epochProcessignFunc 23 | }{ 24 | { 25 | "Effective balance updates", 26 | "effective_balance_updates/*/*", 27 | processEffectiveBalanceUpdates, 28 | }, 29 | { 30 | "Eth1 data reset", 31 | "eth1_data_reset/*/*", 32 | processEth1DataReset, 33 | }, 34 | { 35 | "Historical roots update", 36 | "historical_roots_update/*/*", 37 | processHistoricalRootsUpdate, 38 | }, 39 | { 40 | "Justification_and_finalization", 41 | "justification_and_finalization/*/*", 42 | processJustificationAndFinalization, 43 | }, 44 | { 45 | "Participation record", 46 | "participation_record_updates/*/*", 47 | processParticipationRecordUpdates, 48 | }, 49 | { 50 | "Randao mix", 51 | "randao_mixes_reset/*/*", 52 | processRandaoMixesReset, 53 | }, 54 | { 55 | "Registry updates", 56 | "registry_updates/*/*", 57 | processRegistryUpdates, 58 | }, 59 | { 60 | "Rewards and Penalties", 61 | "rewards_and_penalties/*/*", 62 | processRewardsAndPenalties, 63 | }, 64 | { 65 | "Process slashing", 66 | "slashings/*/*", 67 | processSlashings, 68 | }, 69 | { 70 | "Slashings reset", 71 | "slashings_reset/*/*", 72 | processSlashingsReset, 73 | }, 74 | } 75 | 76 | for _, c := range cases { 77 | t.Run(c.name, func(t *testing.T) { 78 | listTestData(t, filepath.Join("mainnet/phase0/epoch_processing/", c.path), func(th *testHandler) { 79 | eTest := &epochTest{} 80 | th.decodeFile("pre", &eTest.Pre) 81 | ok := th.decodeFile("post", &eTest.Post, true) 82 | 83 | if err := c.handler(&eTest.Pre); err != nil { 84 | if ok { 85 | t.Fatal(err) 86 | } 87 | return 88 | } 89 | 90 | if !ok { 91 | t.Fatal("it should fail") 92 | } 93 | if !reflect.DeepEqual(eTest.Pre, eTest.Post) { 94 | t.Fatal("bad") 95 | } 96 | }) 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /spec/operations_test.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func TestOpAttestation(t *testing.T) { 11 | type attestationTest struct { 12 | Attestation consensus.Attestation 13 | Pre consensus.BeaconStatePhase0 14 | Post consensus.BeaconStatePhase0 15 | } 16 | 17 | listTestData(t, "mainnet/phase0/operations/attestation/*/*", func(th *testHandler) { 18 | attestationTest := &attestationTest{} 19 | th.decodeFile("attestation", &attestationTest.Attestation) 20 | th.decodeFile("pre", &attestationTest.Pre) 21 | ok := th.decodeFile("post", &attestationTest.Post, true) 22 | 23 | if err := ProcessAttestation(&attestationTest.Pre, &attestationTest.Attestation); err != nil { 24 | if ok { 25 | t.Fatal(err) 26 | } 27 | return 28 | } 29 | 30 | if !ok { 31 | t.Fatal("it should fail") 32 | } 33 | if !reflect.DeepEqual(attestationTest.Pre, attestationTest.Post) { 34 | t.Fatal("bad") 35 | } 36 | }) 37 | } 38 | 39 | func TestOpProcessAttesterSlashing(t *testing.T) { 40 | type processAttesterSlashingTest struct { 41 | Pre consensus.BeaconStatePhase0 42 | Post consensus.BeaconStatePhase0 43 | AttesterSlashing consensus.AttesterSlashing 44 | } 45 | 46 | listTestData(t, "mainnet/phase0/operations/attester_slashing/*/*", func(th *testHandler) { 47 | slashTest := &processAttesterSlashingTest{} 48 | th.decodeFile("pre", &slashTest.Pre) 49 | ok := th.decodeFile("post", &slashTest.Post, true) 50 | th.decodeFile("attester_slashing", &slashTest.AttesterSlashing) 51 | 52 | if err := ProcessAttesterSlashing(&slashTest.Pre, &slashTest.AttesterSlashing); err != nil { 53 | if ok { 54 | t.Fatal(err) 55 | } 56 | return 57 | } 58 | 59 | if !ok { 60 | t.Fatal("it should fail") 61 | } 62 | if !reflect.DeepEqual(slashTest.Pre, slashTest.Post) { 63 | t.Fatal("bad") 64 | } 65 | }) 66 | } 67 | 68 | func TestOpProcessBlockBlockHeader(t *testing.T) { 69 | type blockHeaderTest struct { 70 | Pre consensus.BeaconStatePhase0 71 | Post consensus.BeaconStatePhase0 72 | Block consensus.BeaconBlockPhase0 73 | } 74 | 75 | listTestData(t, "mainnet/phase0/operations/block_header/*/*", func(th *testHandler) { 76 | blockHeaderTest := &blockHeaderTest{} 77 | th.decodeFile("block", &blockHeaderTest.Block) 78 | th.decodeFile("pre", &blockHeaderTest.Pre) 79 | ok := th.decodeFile("post", &blockHeaderTest.Post, true) 80 | 81 | if err := ProcessBlockHeader(&blockHeaderTest.Pre, &blockHeaderTest.Block); err != nil { 82 | if ok { 83 | t.Fatal(err) 84 | } 85 | return 86 | } 87 | 88 | if !ok { 89 | t.Fatal("it should fail") 90 | } 91 | if !reflect.DeepEqual(blockHeaderTest.Pre, blockHeaderTest.Post) { 92 | t.Fatal("bad") 93 | } 94 | }) 95 | 96 | } 97 | 98 | func TestOpDeposit(t *testing.T) { 99 | type depositTest struct { 100 | Deposit consensus.Deposit 101 | Pre consensus.BeaconStatePhase0 102 | Post consensus.BeaconStatePhase0 103 | } 104 | 105 | listTestData(t, "mainnet/phase0/operations/deposit/*/*", func(th *testHandler) { 106 | depositTest := &depositTest{} 107 | th.decodeFile("deposit", &depositTest.Deposit) 108 | th.decodeFile("pre", &depositTest.Pre) 109 | ok := th.decodeFile("post", &depositTest.Post, true) 110 | 111 | if err := ProcessDeposit(&depositTest.Pre, &depositTest.Deposit); err != nil { 112 | if ok { 113 | t.Fatal(err) 114 | } 115 | return 116 | } 117 | 118 | if !ok { 119 | t.Fatal("it should fail") 120 | } 121 | if !reflect.DeepEqual(depositTest.Pre, depositTest.Post) { 122 | t.Fatal("bad") 123 | } 124 | }) 125 | } 126 | 127 | func TestOpProposerSlashing(t *testing.T) { 128 | type proposerSlashingTest struct { 129 | ProposerSlashing consensus.ProposerSlashing 130 | Pre consensus.BeaconStatePhase0 131 | Post consensus.BeaconStatePhase0 132 | } 133 | 134 | listTestData(t, "mainnet/phase0/operations/proposer_slashing/*/*", func(th *testHandler) { 135 | proposerSlashingTest := &proposerSlashingTest{} 136 | th.decodeFile("proposer_slashing", &proposerSlashingTest.ProposerSlashing) 137 | th.decodeFile("pre", &proposerSlashingTest.Pre) 138 | ok := th.decodeFile("post", &proposerSlashingTest.Post, true) 139 | 140 | if err := ProcessProposerSlashing(&proposerSlashingTest.Pre, &proposerSlashingTest.ProposerSlashing); err != nil { 141 | if ok { 142 | t.Fatal(err) 143 | } 144 | return 145 | } 146 | 147 | if !ok { 148 | t.Fatal("it should fail") 149 | } 150 | if !reflect.DeepEqual(proposerSlashingTest.Pre, proposerSlashingTest.Post) { 151 | t.Fatal("bad") 152 | } 153 | }) 154 | } 155 | 156 | func TestOpVoluntaryExit(t *testing.T) { 157 | type voluntaryExitTest struct { 158 | VoluntaryExit consensus.SignedVoluntaryExit 159 | Pre consensus.BeaconStatePhase0 160 | Post consensus.BeaconStatePhase0 161 | } 162 | 163 | listTestData(t, "mainnet/phase0/operations/voluntary_exit/*/*", func(th *testHandler) { 164 | voluntaryExitTest := &voluntaryExitTest{} 165 | th.decodeFile("voluntary_exit", &voluntaryExitTest.VoluntaryExit) 166 | th.decodeFile("pre", &voluntaryExitTest.Pre) 167 | ok := th.decodeFile("post", &voluntaryExitTest.Post, true) 168 | 169 | if err := ProcessVoluntaryExit(&voluntaryExitTest.Pre, &voluntaryExitTest.VoluntaryExit); err != nil { 170 | if ok { 171 | t.Fatal(err) 172 | } 173 | return 174 | } 175 | 176 | if !ok { 177 | t.Fatal("it should fail") 178 | } 179 | if !reflect.DeepEqual(voluntaryExitTest.Pre, voluntaryExitTest.Post) { 180 | t.Fatal("bad") 181 | } 182 | }) 183 | } 184 | -------------------------------------------------------------------------------- /spec/presets/phase0.yaml: -------------------------------------------------------------------------------- 1 | # Mainnet preset - Phase0 2 | 3 | # Misc 4 | # --------------------------------------------------------------- 5 | # 2**6 (= 64) 6 | MAX_COMMITTEES_PER_SLOT: 64 7 | # 2**7 (= 128) 8 | TARGET_COMMITTEE_SIZE: 128 9 | # 2**11 (= 2,048) 10 | MAX_VALIDATORS_PER_COMMITTEE: 2048 11 | # See issue 563 12 | SHUFFLE_ROUND_COUNT: 90 13 | # 4 14 | HYSTERESIS_QUOTIENT: 4 15 | # 1 (minus 0.25) 16 | HYSTERESIS_DOWNWARD_MULTIPLIER: 1 17 | # 5 (plus 1.25) 18 | HYSTERESIS_UPWARD_MULTIPLIER: 5 19 | 20 | # Gwei values 21 | # --------------------------------------------------------------- 22 | # 2**0 * 10**9 (= 1,000,000,000) Gwei 23 | MIN_DEPOSIT_AMOUNT: 1000000000 24 | # 2**5 * 10**9 (= 32,000,000,000) Gwei 25 | MAX_EFFECTIVE_BALANCE: 32000000000 26 | # 2**0 * 10**9 (= 1,000,000,000) Gwei 27 | EFFECTIVE_BALANCE_INCREMENT: 1000000000 28 | 29 | # Time parameters 30 | # --------------------------------------------------------------- 31 | # 2**0 (= 1) slots 12 seconds 32 | MIN_ATTESTATION_INCLUSION_DELAY: 1 33 | # 2**5 (= 32) slots 6.4 minutes 34 | SLOTS_PER_EPOCH: 32 35 | # 2**0 (= 1) epochs 6.4 minutes 36 | MIN_SEED_LOOKAHEAD: 1 37 | # 2**2 (= 4) epochs 25.6 minutes 38 | MAX_SEED_LOOKAHEAD: 4 39 | # 2**6 (= 64) epochs ~6.8 hours 40 | EPOCHS_PER_ETH1_VOTING_PERIOD: 64 41 | # 2**13 (= 8,192) slots ~27 hours 42 | SLOTS_PER_HISTORICAL_ROOT: 8192 43 | # 2**2 (= 4) epochs 25.6 minutes 44 | MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4 45 | 46 | # State list lengths 47 | # --------------------------------------------------------------- 48 | # 2**16 (= 65,536) epochs ~0.8 years 49 | EPOCHS_PER_HISTORICAL_VECTOR: 65536 50 | # 2**13 (= 8,192) epochs ~36 days 51 | EPOCHS_PER_SLASHINGS_VECTOR: 8192 52 | # 2**24 (= 16,777,216) historical roots, ~26,131 years 53 | HISTORICAL_ROOTS_LIMIT: 16777216 54 | # 2**40 (= 1,099,511,627,776) validator spots 55 | VALIDATOR_REGISTRY_LIMIT: 1099511627776 56 | 57 | # Reward and penalty quotients 58 | # --------------------------------------------------------------- 59 | # 2**6 (= 64) 60 | BASE_REWARD_FACTOR: 64 61 | # 2**9 (= 512) 62 | WHISTLEBLOWER_REWARD_QUOTIENT: 512 63 | # 2**3 (= 8) 64 | PROPOSER_REWARD_QUOTIENT: 8 65 | # 2**26 (= 67,108,864) 66 | INACTIVITY_PENALTY_QUOTIENT: 67108864 67 | # 2**7 (= 128) (lower safety margin at Phase 0 genesis) 68 | MIN_SLASHING_PENALTY_QUOTIENT: 128 69 | # 1 (lower safety margin at Phase 0 genesis) 70 | PROPORTIONAL_SLASHING_MULTIPLIER: 1 71 | 72 | # Max operations per block 73 | # --------------------------------------------------------------- 74 | # 2**4 (= 16) 75 | MAX_PROPOSER_SLASHINGS: 16 76 | # 2**1 (= 2) 77 | MAX_ATTESTER_SLASHINGS: 2 78 | # 2**7 (= 128) 79 | MAX_ATTESTATIONS: 128 80 | # 2**4 (= 16) 81 | MAX_DEPOSITS: 16 82 | # 2**4 (= 16) 83 | MAX_VOLUNTARY_EXITS: 16 84 | -------------------------------------------------------------------------------- /spec/rewards.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha256" 6 | "encoding/binary" 7 | "fmt" 8 | "sort" 9 | 10 | ssz "github.com/ferranbt/fastssz" 11 | eth2_shuffle "github.com/protolambda/eth2-shuffle" 12 | consensus "github.com/umbracle/go-eth-consensus" 13 | "github.com/umbracle/go-eth-consensus/bitlist" 14 | ) 15 | 16 | type Deltas struct { 17 | Rewards []uint64 18 | Penalties []uint64 19 | } 20 | 21 | func (d *Deltas) UnmarshalSSZ(buf []byte) error { 22 | var o1, o2 uint64 23 | 24 | o1 = ssz.ReadOffset(buf[0:4]) 25 | o2 = ssz.ReadOffset(buf[4:8]) 26 | 27 | tail := buf 28 | 29 | { 30 | buf = tail[o1:o2] 31 | num, err := ssz.DivideInt2(len(buf), 8, 1099511627776) 32 | if err != nil { 33 | return err 34 | } 35 | d.Rewards = ssz.ExtendUint64(d.Rewards, num) 36 | for ii := 0; ii < num; ii++ { 37 | d.Rewards[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) 38 | } 39 | } 40 | 41 | { 42 | buf = tail[o2:] 43 | num, err := ssz.DivideInt2(len(buf), 8, 1099511627776) 44 | if err != nil { 45 | return err 46 | } 47 | d.Penalties = ssz.ExtendUint64(d.Penalties, num) 48 | for ii := 0; ii < num; ii++ { 49 | d.Penalties[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) 50 | } 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func getMatchingHeadAttestations(state *consensus.BeaconStatePhase0, epoch uint64) []*consensus.PendingAttestation { 57 | res := []*consensus.PendingAttestation{} 58 | for _, a := range getMatchingTargetAttestations(state, epoch) { 59 | root := getBlockRootAtSlot(state, a.Data.Slot) 60 | 61 | if bytes.Equal(a.Data.BeaconBlockHash[:], root[:]) { 62 | res = append(res, a) 63 | } 64 | } 65 | 66 | return res 67 | } 68 | 69 | func getMatchingSourceAttestations(state *consensus.BeaconStatePhase0, epoch uint64) []*consensus.PendingAttestation { 70 | if epoch == getCurrentEpoch(state) { 71 | return state.CurrentEpochAttestations 72 | } 73 | return state.PreviousEpochAttestations 74 | } 75 | 76 | func getTargetDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 77 | return getAttestationComponentDeltas(state, getMatchingTargetAttestations(state, getPreviousEpoch(state))) 78 | } 79 | 80 | func getHeadDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 81 | return getAttestationComponentDeltas(state, getMatchingHeadAttestations(state, getPreviousEpoch(state))) 82 | } 83 | 84 | func getSourceDeltas(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) { 85 | return getAttestationComponentDeltas(state, getMatchingSourceAttestations(state, getPreviousEpoch(state))) 86 | } 87 | 88 | func getPreviousEpoch(state *consensus.BeaconStatePhase0) uint64 { 89 | curEpoch := getCurrentEpoch(state) 90 | if curEpoch == 0 { 91 | return 0 92 | } 93 | return curEpoch - 1 94 | } 95 | 96 | func getCurrentEpoch(state *consensus.BeaconStatePhase0) uint64 { 97 | return state.Slot / Spec.SlotsPerEpoch 98 | } 99 | 100 | func getAttestationComponentDeltas(state *consensus.BeaconStatePhase0, attestations []*consensus.PendingAttestation) ([]uint64, []uint64) { 101 | numValidators := len(state.Validators) 102 | rewards := make([]uint64, numValidators) 103 | penalties := make([]uint64, numValidators) 104 | 105 | totalBalance := getTotalActiveBalance(state) 106 | 107 | unslashedAttestingIndices, err := getUnslashedAttestingIndices(state, attestations) 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | attestingBalance := getTotalBalance(state, unslashedAttestingIndices) 113 | 114 | unslashedAttestingIndicesMap := make(map[uint64]bool) 115 | for _, i := range unslashedAttestingIndices { 116 | unslashedAttestingIndicesMap[i] = true 117 | } 118 | 119 | for _, indx := range getElegibleValidatorIndices(state) { 120 | if unslashedAttestingIndicesMap[indx] { 121 | // reward 122 | increment := Spec.EffectiveBalanceIncrement 123 | if isInInactivityLeak(state) { 124 | rewards[indx] += getBaseReward(state, indx) 125 | } else { 126 | rewardNumerator := getBaseReward(state, indx) * (attestingBalance / increment) 127 | rewards[indx] += rewardNumerator / (totalBalance / increment) 128 | } 129 | } else { 130 | // penalty 131 | penalties[indx] += getBaseReward(state, indx) 132 | } 133 | } 134 | 135 | return rewards, penalties 136 | } 137 | 138 | func getBaseReward(state *consensus.BeaconStatePhase0, index uint64) uint64 { 139 | totalBalance := getTotalActiveBalance(state) 140 | effectiveBalance := state.Validators[index].EffectiveBalance 141 | 142 | return effectiveBalance * Spec.BaseRewardFactor / integerSquareRoot(totalBalance) / Spec.BaseRewardsPerEpoch 143 | } 144 | 145 | func getFinalityDelay(state *consensus.BeaconStatePhase0) uint64 { 146 | return getPreviousEpoch(state) - state.FinalizedCheckpoint.Epoch 147 | } 148 | 149 | func isInInactivityLeak(state *consensus.BeaconStatePhase0) bool { 150 | return getFinalityDelay(state) > Spec.MinEpochsToInactivityPenalty 151 | } 152 | 153 | func getElegibleValidatorIndices(state *consensus.BeaconStatePhase0) []uint64 { 154 | previousEpoch := getPreviousEpoch(state) 155 | 156 | res := []uint64{} 157 | for indx, val := range state.Validators { 158 | if isActiveValidator(val, previousEpoch) || (val.Slashed && previousEpoch+1 < val.WithdrawableEpoch) { 159 | res = append(res, uint64(indx)) 160 | } 161 | } 162 | 163 | return res 164 | } 165 | 166 | func getUnslashedAttestingIndices(state *consensus.BeaconStatePhase0, attestations []*consensus.PendingAttestation) ([]uint64, error) { 167 | output := make([]uint64, 0) 168 | seen := make(map[uint64]bool) 169 | 170 | for _, a := range attestations { 171 | indices, err := getAttestingIndices(state, a.Data, a.AggregationBits) 172 | if err != nil { 173 | return nil, err 174 | } 175 | for _, i := range indices { 176 | if !seen[i] { 177 | output = append(output, i) 178 | } 179 | seen[i] = true 180 | } 181 | } 182 | 183 | // Sort the attesting set indices by increasing order. 184 | sort.Slice(output, func(i, j int) bool { 185 | return output[i] < output[j] 186 | }) 187 | 188 | // Remove slashed validator indices. 189 | ret := make([]uint64, 0) 190 | for i := range output { 191 | val := state.Validators[output[i]] 192 | if !val.Slashed { 193 | ret = append(ret, output[i]) 194 | } 195 | } 196 | return ret, nil 197 | } 198 | 199 | func getAttestingIndices(state *consensus.BeaconStatePhase0, data *consensus.AttestationData, bits []byte) ([]uint64, error) { 200 | blist := bitlist.BitList(bits) 201 | 202 | committee := getBeaconCommittee(state, data.Slot, data.Index) 203 | 204 | if blist.Len() != uint64(len(committee)) { 205 | return nil, fmt.Errorf("bad size") 206 | } 207 | 208 | res := []uint64{} 209 | for indx, c := range committee { 210 | if blist.BitAt(uint64(indx)) { 211 | res = append(res, c) 212 | } 213 | } 214 | return res, nil 215 | } 216 | 217 | func getBeaconCommittee(state *consensus.BeaconStatePhase0, slot uint64, index uint64) []uint64 { 218 | epoch := computeEpochAtSlot(slot) 219 | committeesPerSlot := getCommitteeCountPerSlot(state, epoch) 220 | 221 | seed := getSeed(state, epoch, consensus.DomainBeaconAttesterType) 222 | active := getActiveValidatorIndices(state, epoch) 223 | 224 | return computeCommittee( 225 | active, 226 | seed, 227 | (slot%Spec.SlotsPerEpoch)*committeesPerSlot+index, 228 | committeesPerSlot*Spec.SlotsPerEpoch, 229 | ) 230 | } 231 | 232 | func getCommitteeCountPerSlot(state *consensus.BeaconStatePhase0, epoch uint64) uint64 { 233 | return max(1, min(Spec.MaxCommitteesPerSlot, uint64(len(getActiveValidatorIndices(state, epoch)))/Spec.SlotsPerEpoch/Spec.TargetCommitteeSize)) 234 | } 235 | 236 | func getSeed(state *consensus.BeaconStatePhase0, epoch uint64, domain consensus.Domain) consensus.Root { 237 | mix := getRandaoMix(state, epoch+Spec.EpochsPerHistoricalVector-Spec.MinSeedLookAhead-1) 238 | 239 | epochBuf := make([]byte, 8) 240 | binary.LittleEndian.PutUint64(epochBuf, epoch) 241 | 242 | hash := sha256.New() 243 | hash.Write(domain[:]) 244 | hash.Write(epochBuf) 245 | hash.Write(mix[:]) 246 | 247 | root := consensus.Root{} 248 | copy(root[:], hash.Sum(nil)) 249 | 250 | return root 251 | } 252 | 253 | func getRandaoMix(state *consensus.BeaconStatePhase0, epoch uint64) [32]byte { 254 | return state.RandaoMixes[epoch%Spec.EpochsPerHistoricalVector] 255 | } 256 | 257 | func max(i, j uint64) uint64 { 258 | if i > j { 259 | return i 260 | } 261 | return j 262 | } 263 | 264 | func min(i, j uint64) uint64 { 265 | if i < j { 266 | return i 267 | } 268 | return j 269 | } 270 | 271 | func computeEpochAtSlot(slot uint64) uint64 { 272 | return slot / Spec.SlotsPerEpoch 273 | } 274 | 275 | func getActiveValidatorIndices(state *consensus.BeaconStatePhase0, epoch uint64) []uint64 { 276 | activeValidators := []uint64{} 277 | for indx, val := range state.Validators { 278 | if isActiveValidator(val, epoch) { 279 | activeValidators = append(activeValidators, uint64(indx)) 280 | } 281 | } 282 | 283 | return activeValidators 284 | } 285 | 286 | func isActiveValidator(val *consensus.Validator, epoch uint64) bool { 287 | return val.ActivationEpoch <= epoch && epoch < val.ExitEpoch 288 | } 289 | 290 | func getTotalActiveBalance(state *consensus.BeaconStatePhase0) uint64 { 291 | return getTotalBalance(state, getActiveValidatorIndices(state, getCurrentEpoch(state))) 292 | } 293 | 294 | func getTotalBalance(state *consensus.BeaconStatePhase0, indices []uint64) uint64 { 295 | balance := uint64(0) 296 | 297 | for _, indx := range indices { 298 | balance += state.Validators[indx].EffectiveBalance 299 | } 300 | 301 | balance = max(balance, Spec.EffectiveBalanceIncrement) 302 | return balance 303 | } 304 | 305 | func computeCommittee(indices []uint64, seed consensus.Root, index, count uint64) []uint64 { 306 | numActiveValidators := uint64(len(indices)) 307 | 308 | start := (numActiveValidators * index) / count 309 | end := (numActiveValidators * (index + 1)) / count 310 | 311 | eth2ShuffleHashFunc := func(data []byte) []byte { 312 | hash := sha256.New() 313 | hash.Write(data) 314 | return hash.Sum(nil) 315 | } 316 | 317 | commmittee := make([]uint64, len(indices)) 318 | copy(commmittee[:], indices) 319 | 320 | eth2_shuffle.UnshuffleList(eth2ShuffleHashFunc, commmittee, shuffleRoundCount, seed) 321 | 322 | return commmittee[start:end] 323 | } 324 | 325 | func integerSquareRoot(n uint64) uint64 { 326 | x := n 327 | y := (x + 1) >> 1 328 | 329 | for y < x { 330 | x = y 331 | y = (x + n/x) >> 1 332 | } 333 | return x 334 | } 335 | -------------------------------------------------------------------------------- /spec/rewards_test.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | type rewardFunc func(state *consensus.BeaconStatePhase0) ([]uint64, []uint64) 11 | 12 | func TestRewards(t *testing.T) { 13 | listTestData(t, "mainnet/phase0/rewards/basic/pyspec_tests/*", func(th *testHandler) { 14 | test := &specRewardTest{} 15 | test.Decode(th) 16 | 17 | cases := []struct { 18 | name string 19 | fn rewardFunc 20 | delta Deltas 21 | }{ 22 | {"source", getSourceDeltas, test.SourceDeltas}, 23 | {"target", getTargetDeltas, test.TargetDeltas}, 24 | {"head", getHeadDeltas, test.HeadDeltas}, 25 | {"inactivity", getInactivityPenaltyDeltas, test.InactivityPenaltyDeltas}, 26 | } 27 | 28 | for _, c := range cases { 29 | rewards, penalties := c.fn(&test.Pre) 30 | 31 | if !reflect.DeepEqual(rewards, c.delta.Rewards) { 32 | t.Fatalf("bad '%s' rewards: %s", c.name, th.path) 33 | } 34 | if !reflect.DeepEqual(penalties, c.delta.Penalties) { 35 | t.Fatalf("bad '%s' penalties: %s", c.name, th.path) 36 | } 37 | } 38 | }) 39 | } 40 | 41 | type specRewardTest struct { 42 | Pre consensus.BeaconStatePhase0 43 | HeadDeltas Deltas 44 | InactivityPenaltyDeltas Deltas 45 | SourceDeltas Deltas 46 | TargetDeltas Deltas 47 | } 48 | 49 | func (s *specRewardTest) Decode(th *testHandler) { 50 | th.decodeFile("pre", &s.Pre) 51 | th.decodeFile("head_deltas", &s.HeadDeltas) 52 | th.decodeFile("inactivity_penalty_deltas", &s.InactivityPenaltyDeltas) 53 | th.decodeFile("source_deltas", &s.SourceDeltas) 54 | th.decodeFile("target_deltas", &s.TargetDeltas) 55 | } 56 | -------------------------------------------------------------------------------- /spec/shuffle.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/binary" 6 | "fmt" 7 | 8 | consensus "github.com/umbracle/go-eth-consensus" 9 | ) 10 | 11 | const shuffleRoundCount = 90 12 | 13 | func computeShuffleIndex(index, indexCount uint64, seed consensus.Root) uint64 { 14 | if index >= indexCount { 15 | panic(fmt.Sprintf("BAD: index %d higher than count %d", index, indexCount)) 16 | } 17 | 18 | for i := 0; i < shuffleRoundCount; i++ { 19 | input := make([]byte, 0, len(seed)+1) 20 | input = append(input, seed[:]...) 21 | input = append(input, byte(i)) 22 | 23 | res := sha256.Sum256(input) 24 | hashValue := binary.LittleEndian.Uint64(res[:8]) 25 | pivot := hashValue % indexCount 26 | flip := (pivot + indexCount - index) % indexCount 27 | 28 | position := index 29 | if flip > index { 30 | position = flip 31 | } 32 | 33 | positionByteArray := make([]byte, 4) 34 | binary.LittleEndian.PutUint32(positionByteArray, uint32(position>>8)) 35 | 36 | input2 := make([]byte, 0, len(seed)+5) 37 | input2 = append(input2, seed[:]...) 38 | input2 = append(input2, byte(i)) 39 | input2 = append(input2, positionByteArray...) 40 | 41 | source := sha256.Sum256(input2) 42 | byteVal := source[(position%256)/8] 43 | bitVal := (byteVal >> (position % 8)) % 2 44 | if bitVal == 1 { 45 | index = flip 46 | } 47 | } 48 | 49 | return index 50 | } 51 | -------------------------------------------------------------------------------- /spec/shuffle_test.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | consensus "github.com/umbracle/go-eth-consensus" 8 | ) 9 | 10 | func TestShuffle(t *testing.T) { 11 | listTestData(t, "mainnet/phase0/shuffling/*/*/*", func(th *testHandler) { 12 | shuffleTest := &shuffleTest{} 13 | shuffleTest.Decode(th) 14 | 15 | for i := uint64(0); i < shuffleTest.Count; i++ { 16 | index := computeShuffleIndex(i, shuffleTest.Count, shuffleTest.Seed) 17 | require.Equal(t, shuffleTest.Mapping[i], index) 18 | } 19 | }) 20 | } 21 | 22 | type shuffleTest struct { 23 | Seed consensus.Root 24 | Count uint64 25 | Mapping []uint64 26 | } 27 | 28 | func (s *shuffleTest) Decode(th *testHandler) { 29 | th.decodeFile("mapping.yaml", &s) 30 | } 31 | -------------------------------------------------------------------------------- /spec/spec.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import consensus "github.com/umbracle/go-eth-consensus" 4 | 5 | var Spec = &consensus.Spec{ 6 | SecondsPerSlot: 12, 7 | SlotsPerEpoch: 32, 8 | MaxCommitteesPerSlot: 64, 9 | EpochsPerHistoricalVector: 65536, 10 | MinSeedLookAhead: 1, 11 | MinEpochsToInactivityPenalty: 4, 12 | EffectiveBalanceIncrement: 1000000000, 13 | MaxEffectiveBalance: 32000000000, 14 | BaseRewardFactor: 64, 15 | BaseRewardsPerEpoch: 4, 16 | TargetCommitteeSize: 128, 17 | ShardCommiteePeriod: 256, 18 | MaxSeedLookAhead: 4, 19 | ChurnLimitQuotient: 65536, 20 | MinPerEpochChurnLimit: 4, 21 | EpochsPerSlashingsVector: 8192, 22 | MinSlashingPenaltyQuotient: 128, 23 | WhistleblowerRewardQuotient: 512, 24 | ProposerRewardQuotient: 8, 25 | MinValidatorWithdrawabilityDelay: 256, 26 | MinAttestationInclusionDelay: 1, 27 | EpochsPerEth1VotingPeriod: 64, 28 | SlotsPerHistoricalRoot: 8192, 29 | ProportionalSlashingsMultiplier: 1, 30 | HysteresisQuotient: 4, 31 | HysteresisDownwardMultiplier: 1, 32 | HysteresisUpwardMultiplier: 5, 33 | EjectionBalance: 16000000000, // Gwei(2**4 * 10**9) 34 | InactivityPenaltyQuotient: 67108864, // Gwei(2**26) 35 | } 36 | -------------------------------------------------------------------------------- /spec/spec_test.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | _ "embed" 5 | "fmt" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/mitchellh/mapstructure" 10 | "github.com/stretchr/testify/require" 11 | "gopkg.in/yaml.v2" 12 | ) 13 | 14 | //go:embed presets/phase0.yaml 15 | var mainnetPresetPhase0 []byte 16 | 17 | func TestPresetMainnet(t *testing.T) { 18 | var out map[string]interface{} 19 | require.NoError(t, yaml.Unmarshal(mainnetPresetPhase0, &out)) 20 | 21 | var specOut map[string]interface{} 22 | require.NoError(t, mapstructure.Decode(Spec, &specOut)) 23 | 24 | // check that the preset values from 'out' match 25 | // the value from the spec struct 26 | for yamlKey, presetVal := range out { 27 | // convert the key name (i.e. ABC_DEF) to cammel case (i.e. AbcDef) 28 | lowerKey := strings.ToLower(yamlKey) 29 | var key string 30 | 31 | for i := 0; i < len(yamlKey); i++ { 32 | if string(lowerKey[i]) == "_" { 33 | key += strings.ToTitle(string(lowerKey[i+1])) 34 | i++ 35 | } else { 36 | key += string(lowerKey[i]) 37 | } 38 | } 39 | 40 | key = strings.Title(key) 41 | 42 | if val, ok := specOut[key]; ok { 43 | // avoid uint8 and int comparisions by using the fmt.Sprinot to format 44 | require.Equal(t, fmt.Sprintf("%d", val), fmt.Sprintf("%d", presetVal)) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /spec/testing.go: -------------------------------------------------------------------------------- 1 | package spec 2 | 3 | import ( 4 | "errors" 5 | "io/ioutil" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | ssz "github.com/ferranbt/fastssz" 11 | "github.com/golang/snappy" 12 | "github.com/stretchr/testify/require" 13 | ) 14 | 15 | var ( 16 | testDataFolder = "../eth2.0-spec-tests/tests" 17 | ) 18 | 19 | func listTestData(t *testing.T, path string, handlerFn func(tt *testHandler)) { 20 | matches, err := filepath.Glob(filepath.Join(testDataFolder, path)) 21 | require.NoError(t, err) 22 | 23 | if len(matches) == 0 { 24 | t.Fatal("no matches found") 25 | } 26 | 27 | for _, m := range matches { 28 | //t.Run(m, func(t *testing.T) { 29 | handler := &testHandler{ 30 | t: t, 31 | path: m, 32 | } 33 | handlerFn(handler) 34 | //}) 35 | } 36 | } 37 | 38 | type testHandler struct { 39 | t *testing.T 40 | path string 41 | } 42 | 43 | func (th *testHandler) decodeFile(subPath string, obj interface{}, maybeEmpty ...bool) bool { 44 | path := filepath.Join(th.path, subPath) 45 | var content []byte 46 | 47 | ok, err := fileExists(path) 48 | require.NoError(th.t, err) 49 | 50 | if ok { 51 | content, err = ioutil.ReadFile(path) 52 | require.NoError(th.t, err) 53 | 54 | err = ssz.UnmarshalSSZTest(content, obj) 55 | require.NoError(th.t, err) 56 | } else { 57 | // try to read the file as ssz_snappy 58 | snappyPath := path + ".ssz_snappy" 59 | 60 | ok, err := fileExists(snappyPath) 61 | require.NoError(th.t, err) 62 | 63 | if !ok { 64 | if len(maybeEmpty) != 0 && maybeEmpty[0] { 65 | // the file might not exist 66 | return false 67 | } 68 | th.t.Fatalf("file '%s' not found (neither snappy)", subPath) 69 | } 70 | 71 | snappyContent, err := ioutil.ReadFile(snappyPath) 72 | require.NoError(th.t, err) 73 | 74 | content, err = snappy.Decode(nil, snappyContent) 75 | require.NoError(th.t, err) 76 | 77 | sszObj, ok := obj.(ssz.Unmarshaler) 78 | if !ok { 79 | th.t.Fatalf("obj '%s' is not ssz for snappy decompress", subPath) 80 | } 81 | err = sszObj.UnmarshalSSZ(content) 82 | require.NoError(th.t, err) 83 | } 84 | 85 | return true 86 | } 87 | 88 | func fileExists(path string) (bool, error) { 89 | _, err := os.Stat(path) 90 | if err != nil { 91 | if errors.Is(err, os.ErrNotExist) { 92 | return false, nil 93 | } 94 | return false, err 95 | } 96 | return true, nil 97 | } 98 | -------------------------------------------------------------------------------- /structs_test.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "io/ioutil" 8 | "path/filepath" 9 | "strings" 10 | "testing" 11 | 12 | ssz "github.com/ferranbt/fastssz" 13 | "github.com/golang/snappy" 14 | 15 | "gopkg.in/yaml.v2" 16 | ) 17 | 18 | type codec interface { 19 | ssz.Marshaler 20 | ssz.Unmarshaler 21 | ssz.HashRoot 22 | } 23 | 24 | type codecTree interface { 25 | GetTreeWithWrapper(w *ssz.Wrapper) (err error) 26 | GetTree() (*ssz.Node, error) 27 | } 28 | 29 | type fork string 30 | 31 | const ( 32 | phase0Fork = "phase0" 33 | altairFork = "altair" 34 | bellatrixFork = "bellatrix" 35 | capellaFork = "capella" 36 | ) 37 | 38 | type testCallback func(f fork) codec 39 | 40 | var codecs = map[string]testCallback{ 41 | "AttestationData": func(f fork) codec { return new(AttestationData) }, 42 | "Checkpoint": func(f fork) codec { return new(Checkpoint) }, 43 | "AggregateAndProof": func(f fork) codec { return new(AggregateAndProof) }, 44 | "Attestation": func(f fork) codec { return new(Attestation) }, 45 | "AttesterSlashing": func(f fork) codec { return new(AttesterSlashing) }, 46 | "LightClientHeader": func(f fork) codec { 47 | if f == capellaFork { 48 | return new(LightClientHeaderCapella) 49 | } 50 | return new(LightClientHeader) 51 | }, 52 | "LightClientBootstrap": func(f fork) codec { 53 | if f == capellaFork { 54 | return new(LightClientBootstrapCapella) 55 | } 56 | return new(LightClientBootstrap) 57 | }, 58 | "LightClientFinalityUpdate": func(f fork) codec { 59 | if f == capellaFork { 60 | return new(LightClientFinalityUpdateCapella) 61 | } 62 | return new(LightClientFinalityUpdate) 63 | }, 64 | "LightClientOptimisticUpdate": func(f fork) codec { 65 | if f == capellaFork { 66 | return new(LightClientOptimisticUpdateCapella) 67 | } 68 | return new(LightClientOptimisticUpdate) 69 | }, 70 | "LightClientUpdate": func(f fork) codec { 71 | if f == capellaFork { 72 | return new(LightClientUpdateCapella) 73 | } 74 | return new(LightClientUpdate) 75 | }, 76 | "HistoricalBatch": func(f fork) codec { return new(HistoricalBatch) }, 77 | "BeaconBlock": func(f fork) codec { 78 | if f == capellaFork { 79 | return new(BeaconBlockCapella) 80 | } else if f == altairFork { 81 | return new(BeaconBlockAltair) 82 | } else if f == bellatrixFork { 83 | return new(BeaconBlockBellatrix) 84 | } 85 | return new(BeaconBlockPhase0) 86 | }, 87 | "BeaconBlockBody": func(f fork) codec { 88 | if f == capellaFork { 89 | return new(BeaconBlockBodyCapella) 90 | } else if f == altairFork { 91 | return new(BeaconBlockBodyAltair) 92 | } else if f == bellatrixFork { 93 | return new(BeaconBlockBodyBellatrix) 94 | } 95 | return new(BeaconBlockBodyPhase0) 96 | }, 97 | "BeaconBlockHeader": func(f fork) codec { return new(BeaconBlockHeader) }, 98 | "Deposit": func(f fork) codec { return new(Deposit) }, 99 | "DepositData": func(f fork) codec { return new(DepositData) }, 100 | "DepositMessage": func(f fork) codec { return new(DepositMessage) }, 101 | "Eth1Data": func(f fork) codec { return new(Eth1Data) }, 102 | "Fork": func(f fork) codec { return new(Fork) }, 103 | "IndexedAttestation": func(f fork) codec { return new(IndexedAttestation) }, 104 | "PendingAttestation": func(f fork) codec { return new(PendingAttestation) }, 105 | "ProposerSlashing": func(f fork) codec { return new(ProposerSlashing) }, 106 | "SignedBeaconBlock": func(f fork) codec { 107 | if f == capellaFork { 108 | return new(SignedBeaconBlockCapella) 109 | } else if f == altairFork { 110 | return new(SignedBeaconBlockAltair) 111 | } else if f == bellatrixFork { 112 | return new(SignedBeaconBlockBellatrix) 113 | } 114 | return new(SignedBeaconBlockPhase0) 115 | }, 116 | "SignedBeaconBlockHeader": func(f fork) codec { return new(SignedBeaconBlockHeader) }, 117 | "SignedVoluntaryExit": func(f fork) codec { return new(SignedVoluntaryExit) }, 118 | "SigningRoot": func(f fork) codec { return new(SigningRoot) }, 119 | "Validator": func(f fork) codec { return new(Validator) }, 120 | "VoluntaryExit": func(f fork) codec { return new(VoluntaryExit) }, 121 | "SyncCommittee": func(f fork) codec { return new(SyncCommittee) }, 122 | "SyncAggregate": func(f fork) codec { return new(SyncAggregate) }, 123 | "SyncCommitteeMessage": func(f fork) codec { return new(SyncCommitteeMessage) }, 124 | "SyncCommitteeContribution": func(f fork) codec { return new(SyncCommitteeContribution) }, 125 | "SignedContributionAndProof": func(f fork) codec { return new(SignedContributionAndProof) }, 126 | "ContributionAndProof": func(f fork) codec { return new(ContributionAndProof) }, 127 | "Eth1Block": func(f fork) codec { return new(Eth1Block) }, 128 | "SyncAggregatorSelectionData": func(f fork) codec { return new(SyncAggregatorSelectionData) }, 129 | "SigningData": func(f fork) codec { return new(SigningData) }, 130 | "ForkData": func(f fork) codec { return new(ForkData) }, 131 | "SignedAggregateAndProof": func(f fork) codec { return new(SignedAggregateAndProof) }, 132 | "PowBlock": func(f fork) codec { return new(PowBlock) }, 133 | "ExecutionPayload": func(f fork) codec { 134 | if f == capellaFork { 135 | return new(ExecutionPayloadCapella) 136 | } 137 | return new(ExecutionPayload) 138 | }, 139 | "ExecutionPayloadHeader": func(f fork) codec { 140 | if f == capellaFork { 141 | return new(ExecutionPayloadHeaderCapella) 142 | } 143 | return new(ExecutionPayloadHeader) 144 | }, 145 | "BeaconState": func(f fork) codec { 146 | if f == altairFork { 147 | return new(BeaconStateAltair) 148 | } else if f == bellatrixFork { 149 | return new(BeaconStateBellatrix) 150 | } else if f == capellaFork { 151 | return new(BeaconStateCapella) 152 | } 153 | return new(BeaconStatePhase0) 154 | }, 155 | "BLSToExecutionChange": func(f fork) codec { return new(BLSToExecutionChange) }, 156 | "HistoricalSummary": func(f fork) codec { return new(HistoricalSummary) }, 157 | "SignedBLSToExecutionChange": func(f fork) codec { return new(SignedBLSToExecutionChange) }, 158 | "Withdrawal": func(f fork) codec { return new(Withdrawal) }, 159 | } 160 | 161 | func testFork(t *testing.T, fork fork) { 162 | files := readDir(t, filepath.Join(testsPath, "/mainnet/"+string(fork)+"/ssz_static")) 163 | for _, f := range files { 164 | spl := strings.Split(f, "/") 165 | name := spl[len(spl)-1] 166 | 167 | base, ok := codecs[name] 168 | if !ok { 169 | t.Logf("type %s not found in fork %s", name, fork) 170 | continue 171 | } 172 | 173 | t.Run(name, func(t *testing.T) { 174 | files := readDir(t, filepath.Join(f, "ssz_random")) 175 | for _, f := range files { 176 | checkSSZEncoding(t, fork, f, name, base) 177 | } 178 | }) 179 | } 180 | } 181 | 182 | func TestSpecMainnet_Phase0(t *testing.T) { 183 | testFork(t, phase0Fork) 184 | } 185 | 186 | func TestSpecMainnet_Altair(t *testing.T) { 187 | testFork(t, altairFork) 188 | } 189 | 190 | func TestSpecMainnet_Bellatrix(t *testing.T) { 191 | testFork(t, bellatrixFork) 192 | } 193 | 194 | func TestSpecMainnet_Capella(t *testing.T) { 195 | testFork(t, capellaFork) 196 | } 197 | 198 | func formatSpecFailure(errHeader, specFile, structName string, err error) string { 199 | return fmt.Sprintf("%s spec file=%s, struct=%s, err=%v", 200 | errHeader, specFile, structName, err) 201 | } 202 | 203 | func checkSSZEncoding(t *testing.T, f fork, fileName, structName string, base testCallback) { 204 | obj := base(f) 205 | if obj == nil { 206 | // skip 207 | return 208 | } 209 | output := readValidGenericSSZ(t, fileName, &obj) 210 | 211 | // Marshal 212 | res, err := obj.MarshalSSZTo(nil) 213 | if err != nil { 214 | t.Fatal(err) 215 | } 216 | if !bytes.Equal(res, output.ssz) { 217 | t.Fatal("bad marshalling") 218 | } 219 | 220 | // Unmarshal 221 | obj2 := base(f) 222 | if err := obj2.UnmarshalSSZ(res); err != nil { 223 | t.Fatal(formatSpecFailure("UnmarshalSSZ error", fileName, structName, err)) 224 | } 225 | if !deepEqual(obj, obj2) { 226 | t.Fatal("bad unmarshalling") 227 | } 228 | 229 | // Root 230 | root, err := obj.HashTreeRoot() 231 | if err != nil { 232 | t.Fatal(err) 233 | } 234 | if !bytes.Equal(root[:], output.root) { 235 | fmt.Printf("%s bad root\n", fileName) 236 | } 237 | 238 | if objt, ok := obj.(codecTree); ok { 239 | // node root 240 | node, err := objt.GetTree() 241 | if err != nil { 242 | t.Fatal(err) 243 | } 244 | 245 | xx := node.Hash() 246 | if !bytes.Equal(xx, root[:]) { 247 | t.Fatal("bad node") 248 | } 249 | } 250 | } 251 | 252 | const ( 253 | testsPath = "./eth2.0-spec-tests/tests" 254 | serializedFile = "serialized.ssz_snappy" 255 | valueFile = "value.yaml" 256 | rootsFile = "roots.yaml" 257 | ) 258 | 259 | func readDir(t *testing.T, path string) []string { 260 | files, err := ioutil.ReadDir(path) 261 | if err != nil { 262 | t.Fatal(err) 263 | } 264 | res := []string{} 265 | for _, f := range files { 266 | res = append(res, filepath.Join(path, f.Name())) 267 | } 268 | return res 269 | } 270 | 271 | type output struct { 272 | root []byte 273 | ssz []byte 274 | } 275 | 276 | func readValidGenericSSZ(t *testing.T, path string, obj interface{}) *output { 277 | serializedSnappy, err := ioutil.ReadFile(filepath.Join(path, serializedFile)) 278 | if err != nil { 279 | t.Fatal(err) 280 | } 281 | serialized, err := snappy.Decode(nil, serializedSnappy) 282 | if err != nil { 283 | t.Fatal(err) 284 | } 285 | 286 | raw, err := ioutil.ReadFile(filepath.Join(path, valueFile)) 287 | if err != nil { 288 | t.Fatal(err) 289 | } 290 | raw2, err := ioutil.ReadFile(filepath.Join(path, rootsFile)) 291 | if err != nil { 292 | t.Fatal(err) 293 | } 294 | 295 | // Decode ssz root 296 | var out map[string]string 297 | if err := yaml.Unmarshal(raw2, &out); err != nil { 298 | t.Fatal(err) 299 | } 300 | root, err := hex.DecodeString(out["root"][2:]) 301 | if err != nil { 302 | t.Fatal(err) 303 | } 304 | 305 | if err := ssz.UnmarshalSSZTest(raw, obj); err != nil { 306 | t.Fatal(err) 307 | } 308 | return &output{root: root, ssz: serialized} 309 | } 310 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | ) 7 | 8 | func (u *Uint256) UnmarshalText(text []byte) error { 9 | x := new(big.Int) 10 | if err := x.UnmarshalText(text); err != nil { 11 | return err 12 | } 13 | if x.BitLen() > 256 { 14 | return fmt.Errorf("too big") 15 | } 16 | 17 | buf := reverse(x.Bytes()) 18 | copy(u[:], buf) 19 | return nil 20 | } 21 | 22 | func (u Uint256) MarshalText() (text []byte, err error) { 23 | buf := reverse(u[:]) 24 | x := new(big.Int).SetBytes(buf[:]) 25 | return []byte(x.String()), nil 26 | } 27 | 28 | func reverse(in []byte) (out []byte) { 29 | out = make([]byte, len(in)) 30 | copy(out[:], in[:]) 31 | 32 | for i, j := 0, len(out)-1; i < j; i, j = i+1, j-1 { 33 | out[i], out[j] = out[j], out[i] 34 | } 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestUint256Encoding(t *testing.T) { 10 | v := Uint256{0x1} 11 | out, err := v.MarshalText() 12 | assert.NoError(t, err) 13 | assert.Equal(t, out, []byte("1")) 14 | 15 | vv := Uint256{} 16 | assert.NoError(t, vv.UnmarshalText(out)) 17 | assert.Equal(t, v, vv) 18 | } 19 | --------------------------------------------------------------------------------