├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── README.md ├── cmd └── ssz │ ├── generate.go │ ├── ir.go │ ├── main.go │ └── tests.go ├── gen-all-prysm.sh ├── go.mod ├── go.sum ├── specs ├── fork.go ├── harness.go ├── harness_test.go ├── ident.go ├── ident_test.go ├── template.go ├── testdata │ └── prysm.yaml ├── tests.go └── tests_test.go └── sszgen ├── backend ├── bool.go ├── byte.go ├── caster.go ├── container.go ├── container_test.go ├── delegate.go ├── genhtr.go ├── genhtr_test.go ├── genmarshal.go ├── genmarshal_test.go ├── gensize.go ├── gensize_test.go ├── genunmarshal.go ├── genunmarshal_test.go ├── imports.go ├── list.go ├── overlay.go ├── pointer.go ├── render.go ├── render_test.go ├── testdata │ ├── TestGenerateHashTreeRoot.expected │ ├── TestGenerateMarshalSSZ.expected │ ├── TestGenerateSizeSSZ.expected │ ├── TestGenerateUnmarshalSSZ.expected │ ├── TestGenerator_GenerateBeaconState.expected │ └── TestUnmarshalSteps.expected ├── uint.go ├── union.go ├── vector.go └── visitor.go ├── interfaces └── ssz.go ├── node.go ├── parser.go ├── parser_test.go ├── representer.go ├── representer_test.go ├── tagparse.go ├── tagparse_test.go ├── testdata ├── simple.go ├── uint256.go.fixture └── uint256.ssz.go.fixture ├── testutil ├── render.go ├── render_test.go └── testdata │ └── examplevars.go ├── types ├── bool.go ├── byte.go ├── container.go ├── container_test.go ├── list.go ├── overlay.go ├── pointer.go ├── uint.go ├── union.go ├── valrep.go └── vector.go ├── typeutil.go ├── virtualscoper.go └── virtualscoper_test.go /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: methodical-ssz 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '**' 9 | pull_request: 10 | branches: 11 | - main 12 | workflow_dispatch: 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: 1.19 23 | - name: Download golangci-lint 24 | run: wget -O- -nv https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s latest 25 | - name: Lint 26 | run: ./bin/golangci-lint run --config .golangci.yml 27 | 28 | test: 29 | runs-on: ubuntu-latest 30 | needs: lint 31 | steps: 32 | - uses: actions/checkout@v2 33 | - name: Set up Go 34 | uses: actions/setup-go@v2 35 | with: 36 | go-version: 1.19 37 | - name: Test 38 | run: go test -v ./... 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ssz-output 2 | generated 3 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | # This file configures github.com/golangci/golangci-lint. 2 | 3 | run: 4 | timeout: 20m 5 | tests: true 6 | # default is true. Enables skipping of directories: 7 | # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ 8 | skip-dirs-use-default: true 9 | 10 | linters: 11 | disable-all: true 12 | enable: 13 | - goconst 14 | - goimports 15 | - gosimple 16 | - govet 17 | - ineffassign 18 | - misspell 19 | - unconvert 20 | - typecheck 21 | # - unused 22 | - staticcheck 23 | - bidichk 24 | - durationcheck 25 | - exportloopref 26 | - whitespace 27 | 28 | # - structcheck # lots of false positives 29 | # - errcheck #lot of false positives 30 | # - contextcheck 31 | # - errchkjson # lots of false positives 32 | # - errorlint # this check crashes 33 | # - exhaustive # silly check 34 | # - makezero # false positives 35 | # - nilerr # several intentional 36 | 37 | linters-settings: 38 | gofmt: 39 | simplify: true 40 | goconst: 41 | min-len: 3 # minimum length of string constant 42 | min-occurrences: 6 # minimum number of occurrences -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | methodical-ssz 2 | -------------- 3 | 4 | This tool generates code that uses the [prysm fork](https://github.com/prysmaticlabs/fastssz/) of the [fastssz api](https://github.com/ferranbt/fastssz/) (with bindings for [go-hashtree](https://github.com/prysmaticlabs/gohashtree) coming very soon!), for marshaling, unmarshaling and merkleization of go types. 5 | 6 | Generate ssz methodsets 7 | ----------------------- 8 | 9 | The source types are parsed using `go/types`, which locates the source code for a given package path through go's local package discovery utilities, so a current limitation is that you need to fetch the package to your go package tree before using the tool. This can be done with go get: 10 | ``` 11 | go get github.com/prysmaticlabs/prysm/v3@v3.2.1 12 | ``` 13 | 14 | Once this is done, code generation can be run against go types in the desired package: 15 | ``` 16 | go run ./cmd/ssz gen --output=beacon-state.ssz.go --type-names BeaconState,BeaconStateAltair,BeaconStateBellatrix github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 17 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconState 18 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconStateAltair 19 | Generating methods for github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1/BeaconStateBellatrix 20 | ``` 21 | 22 | The generated file can then be placed in a local directory for the source package so that the generated methods become part of the methodset of the source type. 23 | 24 | Generate spectests for a package 25 | -------------------------------- 26 | 27 | This tool has a subcommand to generate go tests from the `ssz_static` tests in the ethereum/consensus-spec-tests repository. The generated tests can be run as normal go tests, which is helpful in identifying specific failure cases and walking through generated code with a debugger. 28 | 29 | spectest tarball 30 | ================ 31 | 32 | To get the spec test fixtures for codegen, go to the [consensus-spec-test repository releases page](https://github.com/ethereum/consensus-spec-tests/releases) and download the most recent `pre-release` or `latest` spec tests release. 33 | ``` 34 | curl -L https://github.com/ethereum/consensus-spec-tests/releases/download/v1.3.0-rc.2-hotfix/mainnet.tar.gz > mainnet-v1.3.0-rc.2.tar.gz 35 | ``` 36 | 37 | yaml config 38 | =========== 39 | 40 | The spectest generation tool needs a mapping between go types and consensus spec container types. This mapping is described as a yaml config file. The mappings for prysm types are committed to the repo in a go `testdata` fixture directory, in `specs/testdata/prysm.yaml`. The following snippet illustrates the format: 41 | ``` 42 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 43 | preset: mainnet 44 | defs: 45 | - fork: phase0 46 | types: 47 | - name: AggregateAndProof 48 | type_name: AggregateAttestationAndProof 49 | - name: Attestation 50 | # ... 51 | - name: BeaconBlock 52 | # ... 53 | - fork: altair 54 | types: 55 | - name: BeaconBlock 56 | type_name: BeaconBlockAltair 57 | - name: BeaconBlockBody 58 | type_name: BeaconBlockBodyAltair 59 | ``` 60 | `package` is the go package containing the types for code generation. Each yaml holds the description for one go package. `preset` is one of mainnet or minimal. At this time only mainnet is supported as preset customization is specific to an implementation's build tooling. `defs` contains a list of yaml objects, with the `fork` key matching the fork directory in the spectest layout, and the `types` key containing a list of objects connecting the `name` of the type from consensus specs / spectest directory names, to the `type_name` found in the `package`. If `type_name` is not present, `name` is the default, so for go types with the same spelling and capitalization as the consensus type, the `name` field alone is all that's needed to specify the type. 61 | 62 | The config reader processes type mappings in canonical fork order, so if a type's schema has not changed since the previous fork, it does not need to be redeclared. For instance the mapping for `Attestation` is only described once in the `phase0` mapping; the same go type will be used to execute the spectest for the Attestation value for all subsequent forks. `BeaconBlock`, on the other hand, has been redefined at every fork, so tests will use `BeaconBlockAltair` for spectests in the `altair` tree and so on. Any types observed in the tarball that don't have an entry defined in the config yaml will be skipped with a warning. 63 | 64 | spectest subcommand 65 | =================== 66 | 67 | The `generated` directory is a good place to stick generated tests as it is already in the .gitignore for the project. Assuming the tarball in the example above has been downloaded to the repo directory, running the following command there will generate spectests for all prysm types described in the test fixture yaml config: 68 | ``` 69 | go run ./cmd/ssz spectest --release-uri=file://$PWD/mainnet-v1.3.0-rc.2.tar.gz --config=$PWD/specs/testdata/prysm.yaml --output=$PWD/generated 70 | ``` 71 | 72 | Run the spectest like normal go tests: 73 | ``` 74 | go test ./generated 75 | ok github.com/OffchainLabs/methodical-ssz/generated 1.003s 76 | ``` 77 | -------------------------------------------------------------------------------- /cmd/ssz/generate.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "strings" 9 | 10 | "github.com/OffchainLabs/methodical-ssz/sszgen" 11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend" 12 | "github.com/urfave/cli/v2" 13 | ) 14 | 15 | var sourcePackage, output, typeNames string 16 | var disableDelegation bool 17 | var generate = &cli.Command{ 18 | Name: "generate", 19 | ArgsUsage: "", 20 | Aliases: []string{"gen"}, 21 | Usage: "generate methodsets for a go struct type to support ssz ser/des", 22 | Flags: []cli.Flag{ 23 | &cli.StringFlag{ 24 | Name: "output", 25 | Value: "", 26 | Usage: "directory to write generated code (same as input by default)", 27 | Destination: &output, 28 | }, 29 | &cli.StringFlag{ 30 | Name: "type-names", 31 | Value: "", 32 | Usage: "if specified, only generate methods for types specified in this comma-separated list", 33 | Destination: &typeNames, 34 | }, 35 | &cli.BoolFlag{ 36 | Name: "disable-delegation", 37 | Usage: "if specified, do not check for existing ssz method sets. helpful when the codegen source has them already", 38 | Destination: &disableDelegation, 39 | }, 40 | }, 41 | Action: func(c *cli.Context) error { 42 | sourcePackage = c.Args().Get(0) 43 | if sourcePackage == "" { 44 | cli.ShowCommandHelp(c, "generate") 45 | return fmt.Errorf("error: mising required argument") 46 | } 47 | var err error 48 | var fields []string 49 | if len(typeNames) > 0 { 50 | fields = strings.Split(strings.TrimSpace(typeNames), ",") 51 | } 52 | 53 | fmt.Printf("Parsing package %v\n", sourcePackage) 54 | ps, err := sszgen.NewGoPathScoper(sourcePackage) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | if output == "" { 60 | output = "methodical.ssz.go" 61 | } 62 | outFh, err := os.Create(output) 63 | if err != nil { 64 | return err 65 | } 66 | defer outFh.Close() 67 | 68 | g := backend.NewGenerator(sourcePackage) 69 | defs, err := sszgen.TypeDefs(ps, fields...) 70 | if err != nil { 71 | return err 72 | } 73 | opts := make([]sszgen.FieldParserOpt, 0) 74 | if disableDelegation { 75 | opts = append(opts, sszgen.WithDisableDelegation()) 76 | } 77 | for _, s := range defs { 78 | fmt.Printf("Generating methods for %s/%s\n", s.PackageName, s.Name) 79 | typeRep, err := sszgen.ParseTypeDef(s, opts...) 80 | if err != nil { 81 | return err 82 | } 83 | if err := g.Generate(typeRep); err != nil { 84 | return err 85 | } 86 | } 87 | fmt.Println("Rendering template") 88 | rbytes, err := g.Render() 89 | if err != nil { 90 | return err 91 | } 92 | _, err = io.Copy(outFh, bytes.NewReader(rbytes)) 93 | return err 94 | }, 95 | } 96 | -------------------------------------------------------------------------------- /cmd/ssz/ir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "strings" 7 | 8 | "github.com/OffchainLabs/methodical-ssz/sszgen" 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/testutil" 10 | "github.com/urfave/cli/v2" 11 | ) 12 | 13 | var ir = &cli.Command{ 14 | Name: "ir", 15 | ArgsUsage: "", 16 | Aliases: []string{"gen"}, 17 | Usage: "generate intermediate representation for a go struct type. This data structure is used by the backend code generator. Outputting it to a source file an be useful for generating test cases and debugging.", 18 | Flags: []cli.Flag{ 19 | &cli.StringFlag{ 20 | Name: "output", 21 | Value: "", 22 | Usage: "file path to write generated code", 23 | Destination: &output, 24 | Required: true, 25 | }, 26 | &cli.StringFlag{ 27 | Name: "type-names", 28 | Value: "", 29 | Usage: "if specified, only generate types specified in this comma-separated list", 30 | Destination: &typeNames, 31 | }, 32 | }, 33 | Action: func(c *cli.Context) error { 34 | if c.NArg() > 0 { 35 | sourcePackage = c.Args().Get(0) 36 | } 37 | 38 | var err error 39 | var fields []string 40 | if len(typeNames) > 0 { 41 | fields = strings.Split(strings.TrimSpace(typeNames), ",") 42 | } 43 | ps, err := sszgen.NewGoPathScoper(sourcePackage) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | outFh, err := os.Create(output) 49 | if err != nil { 50 | return err 51 | } 52 | defer outFh.Close() 53 | 54 | renderedTypes := make([]string, 0) 55 | defs, err := sszgen.TypeDefs(ps, fields...) 56 | if err != nil { 57 | return err 58 | } 59 | for _, s := range defs { 60 | typeRep, err := sszgen.ParseTypeDef(s) 61 | if err != nil { 62 | return err 63 | } 64 | rendered, err := testutil.RenderIntermediate(typeRep) 65 | if err != nil { 66 | return err 67 | } 68 | renderedTypes = append(renderedTypes, rendered) 69 | } 70 | if err != nil { 71 | return err 72 | } 73 | 74 | _, err = io.Copy(outFh, strings.NewReader(strings.Join(renderedTypes, "\n"))) 75 | return err 76 | }, 77 | } 78 | -------------------------------------------------------------------------------- /cmd/ssz/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/urfave/cli/v2" 8 | ) 9 | 10 | func main() { 11 | app := &cli.App{ 12 | Usage: "ssz support for prysm", 13 | // TODO: implement benchmark 14 | Commands: []*cli.Command{ /*benchmark,*/ generate, ir, tests}, 15 | } 16 | 17 | err := app.Run(os.Args) 18 | if err != nil { 19 | log.Fatal(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cmd/ssz/tests.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/OffchainLabs/methodical-ssz/specs" 10 | "github.com/OffchainLabs/methodical-ssz/sszgen" 11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend" 12 | "github.com/pkg/errors" 13 | "github.com/spf13/afero" 14 | "github.com/urfave/cli/v2" 15 | ) 16 | 17 | var releaseURI, configPath string 18 | var tests = &cli.Command{ 19 | Name: "spectest", 20 | Usage: "generate go test methods to execute spectests against generated types", 21 | Flags: []cli.Flag{ 22 | &cli.StringFlag{ 23 | Name: "release-uri", 24 | Usage: "url or file in file:// format pointing at a github.com/ethereum/consensus-spec-tests release", 25 | Destination: &releaseURI, 26 | }, 27 | &cli.StringFlag{ 28 | Name: "config", 29 | Usage: "path to yaml file configuring spec test relationships, see readme or prysm example for format", 30 | Required: true, 31 | Destination: &configPath, 32 | }, 33 | &cli.StringFlag{ 34 | Name: "output", 35 | Usage: "path to output directory where spec test package and copy of consensus types and ssz methods will be written", 36 | Required: true, 37 | Destination: &output, 38 | }, 39 | &cli.BoolFlag{ 40 | Name: "disable-delegation", 41 | Usage: "if specified, do not check for existing ssz method sets. helpful when the codegen source has them already", 42 | Destination: &disableDelegation, 43 | }, 44 | }, 45 | Action: func(c *cli.Context) error { 46 | return actionSpectests(c) 47 | }, 48 | } 49 | 50 | func actionSpectests(cl *cli.Context) error { 51 | if err := os.MkdirAll(output, os.ModePerm); err != nil { 52 | return errors.Wrapf(err, "failed to create output directory %s", output) 53 | } 54 | fs := afero.NewBasePathFs(afero.NewOsFs(), output) 55 | cfg, err := specs.ParseConfigFile(configPath) 56 | if err != nil { 57 | return err 58 | } 59 | types := cfg.GoTypes() 60 | ps, err := sszgen.NewGoPathScoper(cfg.Package) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | r, err := loadArchive(releaseURI) 66 | if err != nil { 67 | return errors.Wrapf(err, "failed to open spectest archive from uri %s", releaseURI) 68 | } 69 | cases, err := specs.ExtractCases(r, specs.TestIdent{Preset: specs.Mainnet}) 70 | if err != nil { 71 | return err 72 | } 73 | for ident := range cases { 74 | fmt.Printf("%s\n", ident) 75 | } 76 | 77 | g := backend.NewGenerator(cfg.Package) 78 | defs, err := sszgen.TypeDefs(ps, types...) 79 | if err != nil { 80 | return err 81 | } 82 | opts := make([]sszgen.FieldParserOpt, 0) 83 | if disableDelegation { 84 | opts = append(opts, sszgen.WithDisableDelegation()) 85 | } 86 | for _, s := range defs { 87 | fmt.Printf("Generating methods for %s/%s\n", s.PackageName, s.Name) 88 | typeRep, err := sszgen.ParseTypeDef(s, opts...) 89 | if err != nil { 90 | return err 91 | } 92 | if err := g.Generate(typeRep); err != nil { 93 | return err 94 | } 95 | } 96 | rbytes, err := g.Render() 97 | if err != nil { 98 | return err 99 | } 100 | if err := afero.WriteFile(fs, "methodical.ssz.go", rbytes, 0666); err != nil { 101 | return err 102 | } 103 | source, err := ps.TypeDefSourceCode(defs) 104 | if err != nil { 105 | return err 106 | } 107 | if err := afero.WriteFile(fs, "structs.go", source, 0666); err != nil { 108 | return err 109 | } 110 | return specs.WriteSpecTestFiles(cases, cfg, fs) 111 | } 112 | 113 | func loadArchive(uri string) (io.Reader, error) { 114 | u, err := url.Parse(uri) 115 | if err != nil { 116 | return nil, err 117 | } 118 | if u.Scheme == "file" { 119 | return os.Open(u.Path) 120 | } 121 | return nil, errors.New("unsupported url protocol") 122 | } 123 | -------------------------------------------------------------------------------- /gen-all-prysm.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | SSZ_OUT=$PWD/ssz-output 4 | 5 | # bad github.com/prysmaticlabs/prysm/v3/proto/engine/v1 6 | #dlv debug --headless --listen=:2345 --api-version=2 --accept-multiclient github.com/OffchainLabs/methodical-ssz/cmd/ssz -- gen --type-names=ExecutionPayload,ExecutionPayloadCapella github.com/prysmaticlabs/prysm/v3/proto/engine/v1 7 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=ExecutionPayload,ExecutionPayloadCapella github.com/prysmaticlabs/prysm/v3/proto/engine/v1 8 | # good github.com/prysmaticlabs/prysm/v3/proto/engine/v1 9 | ENGINE_OUT=$SSZ_OUT/proto/engine/v1 10 | mkdir -p $ENGINE_OUT 11 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ENGINE_OUT/ssz.go --type-names=ExecutionPayloadHeader,ExecutionPayloadHeaderCapella,Withdrawal github.com/prysmaticlabs/prysm/v3/proto/engine/v1 12 | 13 | ETHV1_OUT=$SSZ_OUT/proto/eth/v1 14 | mkdir -p $ETHV1_OUT 15 | # good github.com/prysmaticlabs/prysm/v3/proto/eth/v1 16 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ETHV1_OUT/ssz.go --type-names=Attestation,AggregateAttestationAndProof,SignedAggregateAttestationAndProof,AttestationData,Checkpoint,BeaconBlock,SignedBeaconBlock,BeaconBlockBody,ProposerSlashing,AttesterSlashing,Deposit,VoluntaryExit,SignedVoluntaryExit,Eth1Data,BeaconBlockHeader,SignedBeaconBlockHeader,IndexedAttestation,SyncAggregate,Deposit_Data,Validator github.com/prysmaticlabs/prysm/v3/proto/eth/v1 17 | 18 | ETHV2_OUT=$SSZ_OUT/proto/eth/v2 19 | mkdir -p $ETHV2_OUT 20 | # bad github.com/prysmaticlabs/prysm/v3/proto/eth/v2 21 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=SignedBeaconBlockBellatrix,SignedBeaconBlockCapella,BeaconBlockBellatrix,BeaconBlockCapella,BeaconBlockBodyBellatrix,BeaconBlockBodyCapella github.com/prysmaticlabs/prysm/v3/proto/eth/v2 22 | # good github.com/prysmaticlabs/prysm/v3/proto/eth/v2 23 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$ETHV2_OUT/ssz.go --type-names=SignedBlindedBeaconBlockBellatrix,SignedBlindedBeaconBlockCapella,SignedBeaconBlockAltair,BlindedBeaconBlockBellatrix,BlindedBeaconBlockCapella,BeaconBlockAltair,BlindedBeaconBlockBodyBellatrix,BlindedBeaconBlockBodyCapella,BeaconBlockBodyAltair,BLSToExecutionChange,SignedBLSToExecutionChange github.com/prysmaticlabs/prysm/v3/proto/eth/v2 24 | 25 | V1ALPHA1_OUT=$SSZ_OUT/proto/prysm/v1alpha1 26 | mkdir -p $V1ALPHA1_OUT 27 | # bad github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 28 | #go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --type-names=SignedBeaconBlockBellatrix,BeaconBlockBellatrix,BeaconBlockBodyBellatrix,SignedBeaconBlockCapella,BeaconBlockCapella,BeaconBlockBodyCapella,BuilderBidCapella,HistoricalSummary github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 29 | # good github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 30 | go run github.com/OffchainLabs/methodical-ssz/cmd/ssz gen --output=$V1ALPHA1_OUT/ssz.go --type-names=Attestation,AggregateAttestationAndProof,SignedAggregateAttestationAndProof,AttestationData,Checkpoint,BeaconBlock,SignedBeaconBlock,BeaconBlockAltair,SignedBeaconBlockAltair,BeaconBlockBody,BeaconBlockBodyAltair,ProposerSlashing,AttesterSlashing,Deposit,VoluntaryExit,SignedVoluntaryExit,Eth1Data,BeaconBlockHeader,SignedBeaconBlockHeader,IndexedAttestation,SyncAggregate,SignedBlindedBeaconBlockBellatrix,BlindedBeaconBlockBellatrix,BlindedBeaconBlockBodyBellatrix,SignedBlindedBeaconBlockCapella,BlindedBeaconBlockCapella,BlindedBeaconBlockBodyCapella,ValidatorRegistrationV1,SignedValidatorRegistrationV1,BuilderBid,Deposit_Data,BeaconState,BeaconStateAltair,Fork,PendingAttestation,HistoricalBatch,SigningData,ForkData,DepositMessage,SyncCommittee,SyncAggregatorSelectionData,BeaconStateBellatrix,BeaconStateCapella,PowBlock,Status,BeaconBlocksByRangeRequest,ENRForkID,MetaDataV0,MetaDataV1,SyncCommitteeMessage,SyncCommitteeContribution,ContributionAndProof,SignedContributionAndProof,Validator,BLSToExecutionChange,SignedBLSToExecutionChange github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 31 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/OffchainLabs/methodical-ssz 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/dave/jennifer v1.6.0 7 | github.com/ethereum/go-ethereum v1.11.2 8 | github.com/golang/snappy v0.0.4 9 | github.com/pkg/errors v0.9.1 10 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab 11 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 12 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4 13 | github.com/sirupsen/logrus v1.9.0 14 | github.com/spf13/afero v1.2.2 15 | github.com/urfave/cli/v2 v2.24.1 16 | golang.org/x/tools v0.6.0 17 | google.golang.org/protobuf v1.28.1 18 | gopkg.in/yaml.v3 v3.0.1 19 | sigs.k8s.io/yaml v1.2.0 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect 25 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 26 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 27 | github.com/d4l3k/messagediff v1.2.1 // indirect 28 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect 29 | github.com/golang/protobuf v1.5.2 // indirect 30 | github.com/klauspost/cpuid/v2 v2.2.1 // indirect 31 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 32 | github.com/minio/sha256-simd v1.0.0 // indirect 33 | github.com/mitchellh/mapstructure v1.4.1 // indirect 34 | github.com/prometheus/client_golang v1.14.0 // indirect 35 | github.com/prometheus/client_model v0.3.0 // indirect 36 | github.com/prometheus/common v0.39.0 // indirect 37 | github.com/prometheus/procfs v0.9.0 // indirect 38 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha // indirect 39 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 40 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e // indirect 41 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 42 | golang.org/x/crypto v0.5.0 // indirect 43 | golang.org/x/mod v0.8.0 // indirect 44 | golang.org/x/sys v0.5.0 // indirect 45 | golang.org/x/text v0.7.0 // indirect 46 | gopkg.in/yaml.v2 v2.4.0 // indirect 47 | ) 48 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= 2 | github.com/VictoriaMetrics/fastcache v1.12.0 h1:vnVi/y9yKDcD9akmc4NqAoqgQhJrOwUF+j9LTgn4QDE= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= 6 | github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= 7 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= 8 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 9 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 10 | github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= 11 | github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= 12 | github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 h1:ytcWPaNPhNoGMWEhDvS3zToKcDpRsLuRolQJBVGdozk= 13 | github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 15 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 16 | github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U= 17 | github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo= 18 | github.com/dave/jennifer v1.6.0 h1:MQ/6emI2xM7wt0tJzJzyUik2Q3Tcn2eE0vtYgh4GPVI= 19 | github.com/dave/jennifer v1.6.0/go.mod h1:AxTG893FiZKqxy3FP1kL80VMshSMuz2G+EgvszgGRnk= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= 24 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= 25 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= 26 | github.com/ethereum/go-ethereum v1.11.2 h1:z/luyejbevDCAMUUiu0rc80dxJxOnpoG58k5o0tSawc= 27 | github.com/ethereum/go-ethereum v1.11.2/go.mod h1:DuefStAgaxoaYGLR0FueVcVbehmn5n9QUcVrMCuOvuc= 28 | github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= 29 | github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= 30 | github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw= 31 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 32 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 33 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 34 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 35 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 36 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 37 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 38 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 39 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 40 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 41 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.0.1 h1:X2vfSnm1WC8HEo0MBHZg2TcuDUHJj6kd1TmEAQncnSA= 42 | github.com/holiman/big v0.0.0-20221017200358-a027dc42d04e h1:pIYdhNkDh+YENVNi3gto8n9hAmRxKxoar0iE6BLucjw= 43 | github.com/holiman/uint256 v1.2.1 h1:XRtyuda/zw2l+Bq/38n5XUoEF72aSOu/77Thd9pPp2o= 44 | github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= 45 | github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 46 | github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI= 47 | github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 48 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 49 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 50 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= 51 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 52 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 53 | github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= 54 | github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= 55 | github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= 56 | github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 57 | github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= 58 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 59 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 60 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 61 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 62 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 63 | github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= 64 | github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= 65 | github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= 66 | github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= 67 | github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= 68 | github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= 69 | github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= 70 | github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= 71 | github.com/prometheus/tsdb v0.10.0 h1:If5rVCMTp6W2SiRAQFlbpJNgVlgMEd+U2GZckwK38ic= 72 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab h1:Y3PcvUrnneMWLuypZpwPz8P70/DQsz6KgV9JveKpyZs= 73 | github.com/prysmaticlabs/fastssz v0.0.0-20220628121656-93dfe28febab/go.mod h1:MA5zShstUwCQaE9faGHgCGvEWUbG87p4SAXINhmCkvg= 74 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 h1:0tVE4tdWQK9ZpYygoV7+vS6QkDvQVySboMVEIxBJmXw= 75 | github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7/go.mod h1:wmuf/mdK4VMD+jA9ThwcUKjg3a2XWM9cVfFYjDyY4j4= 76 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha h1:hk5ZsDQuSkyUMhTd55qB396P1+dtyIKiSwMmYE/hyEU= 77 | github.com/prysmaticlabs/gohashtree v0.0.2-alpha/go.mod h1:4pWaT30XoEx1j8KNJf3TV+E3mQkaufn7mf+jRNb/Fuk= 78 | github.com/prysmaticlabs/protoc-gen-go-cast v0.0.0-20230228205207-28762a7b9294 h1:q9wE0ZZRdTUAAeyFP/w0SwBEnCqlVy2+on6X2/e+eAU= 79 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4 h1:P58mUzwZ4xrbsIiecFLJOaYBeltJTYmQ4bB1iZgArkA= 80 | github.com/prysmaticlabs/prysm/v3 v3.2.2-rc.1.0.20230309092345-83a294c1a5a4/go.mod h1:QyK7KxYY+0PGQaCuRtb55EpvA3Zh74wCNKl8EW+sYS0= 81 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw= 82 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 83 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 84 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 85 | github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= 86 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 87 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 88 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= 89 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 90 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 91 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 93 | github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= 94 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e h1:cR8/SYRgyQCt5cNCMniB/ZScMkhI9nk8U5C7SbISXjo= 95 | github.com/thomaso-mirodin/intmath v0.0.0-20160323211736-5dc6d854e46e/go.mod h1:Tu4lItkATkonrYuvtVjG0/rhy15qrNGNTjPdaphtZ/8= 96 | github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= 97 | github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= 98 | github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU= 99 | github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= 100 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 101 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 102 | github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= 103 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 104 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 105 | golang.org/x/exp v0.0.0-20230206171751-46f607a40771 h1:xP7rWLUr1e1n2xkK5YB4LI0hPEy3LJC6Wk+D4pGlOJg= 106 | golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= 107 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 108 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 109 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 110 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 111 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 112 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 114 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 115 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 116 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 117 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 118 | golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 119 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 120 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 121 | google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84 h1:R1r5J0u6Cx+RNl/6mezTw6oA14cmKC96FeUwL6A9bd4= 122 | google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q= 123 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 124 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 125 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 126 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 127 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 128 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 129 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 130 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 131 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 132 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 133 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 134 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 135 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 136 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 137 | -------------------------------------------------------------------------------- /specs/fork.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import "github.com/pkg/errors" 4 | 5 | type Fork string 6 | 7 | var ErrUnknownFork = errors.New("unknown fork name") 8 | 9 | func (f *Fork) UnmarshalText(t []byte) error { 10 | s := string(t) 11 | sf := stringToFork(s) 12 | if sf == ForkUnknown { 13 | return errors.Wrap(ErrUnknownFork, s) 14 | } 15 | *f = sf 16 | return nil 17 | } 18 | 19 | var ( 20 | ForkUnknown Fork = "" 21 | Phase0 Fork = "phase0" 22 | Altair Fork = "altair" 23 | Bellatrix Fork = "bellatrix" 24 | Capella Fork = "capella" 25 | //EIP4844 Fork = "eip4844" 26 | ) 27 | 28 | // var ForkOrder = []Fork{Phase0, Altair, Bellatrix, Capella, EIP4844} 29 | var ForkOrder = []Fork{Phase0, Altair, Bellatrix, Capella} 30 | 31 | func stringToFork(s string) Fork { 32 | switch s { 33 | case string(Phase0): 34 | return Phase0 35 | case string(Altair): 36 | return Altair 37 | case string(Bellatrix): 38 | return Bellatrix 39 | case string(Capella): 40 | return Capella 41 | /* 42 | case string(EIP4844): 43 | return EIP4844 44 | */ 45 | default: 46 | return ForkUnknown 47 | } 48 | } 49 | 50 | func forkIndex(f Fork) (int, error) { 51 | for i := 0; i < len(ForkOrder); i++ { 52 | if ForkOrder[i] == f { 53 | return i, nil 54 | } 55 | } 56 | return 0, ErrUnknownFork 57 | } 58 | -------------------------------------------------------------------------------- /specs/harness.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "os" 8 | "path" 9 | "strings" 10 | 11 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend" 12 | "github.com/pkg/errors" 13 | log "github.com/sirupsen/logrus" 14 | "github.com/spf13/afero" 15 | "sigs.k8s.io/yaml" 16 | ) 17 | 18 | type SpecRelationships struct { 19 | Package string `json:"package"` 20 | Preset Preset `json:"preset"` 21 | Defs []ForkTypeDefinitions `json:"defs"` 22 | } 23 | 24 | type ForkTypeDefinitions struct { 25 | Fork Fork `json:"fork"` 26 | Types []TypeRelation `json:"types"` 27 | } 28 | 29 | type TypeRelation struct { 30 | SpecName string `json:"name"` 31 | TypeName string `json:"type_name"` 32 | } 33 | 34 | func ParseConfigFile(path string) (*SpecRelationships, error) { 35 | cb, err := os.ReadFile(path) 36 | if err != nil { 37 | return nil, err 38 | } 39 | sr := &SpecRelationships{} 40 | err = yaml.Unmarshal(cb, sr) 41 | return sr, err 42 | } 43 | 44 | func (sr SpecRelationships) GoTypes() []string { 45 | tm := make(map[string]bool) 46 | gt := make([]string, 0) 47 | for _, d := range sr.Defs { 48 | for _, t := range d.Types { 49 | n := t.TypeName 50 | if n == "" { 51 | n = t.SpecName 52 | } 53 | if tm[n] { 54 | continue 55 | } 56 | gt = append(gt, n) 57 | } 58 | } 59 | return gt 60 | } 61 | 62 | func (sr SpecRelationships) RelationsAtFork(f Fork) (map[string]string, error) { 63 | // look up the position of this fork in the ordering of all forks, so we can start walking backwards from there 64 | fidx, err := forkIndex(f) 65 | if err != nil { 66 | return nil, err 67 | } 68 | // convert the list into a map for easier lookup 69 | fm := make(map[Fork][]TypeRelation) 70 | for _, d := range sr.Defs { 71 | fm[d.Fork] = d.Types 72 | } 73 | rf := make(map[string]string) 74 | // walk backwards through the forks to find the highest schema <= the requested fork 75 | for i := fidx; i >= 0; i-- { 76 | // get the fork for the current index 77 | f = ForkOrder[i] 78 | // get the list of type definitions at the given fork 79 | types, ok := fm[f] 80 | if !ok { 81 | // skip this fork if there are no type definitions for it in the given package 82 | continue 83 | } 84 | for _, t := range types { 85 | _, ok := rf[t.SpecName] 86 | // don't replace a newer version of the type with an older version 87 | if !ok { 88 | rf[t.SpecName] = t.TypeName 89 | // a blank Name property means the type name is the same as the spec name 90 | if rf[t.SpecName] == "" { 91 | rf[t.SpecName] = t.SpecName 92 | } 93 | } 94 | } 95 | } 96 | return rf, nil 97 | } 98 | 99 | type TestCaseTpl struct { 100 | ident TestIdent 101 | fixture Fixture 102 | structName string 103 | } 104 | 105 | func (tpl *TestCaseTpl) FixtureDirectory() string { 106 | return path.Join("testdata", tpl.fixture.Directory) 107 | } 108 | 109 | func (tpl *TestCaseTpl) rootPath() string { 110 | return path.Join(tpl.FixtureDirectory(), rootFilename) 111 | } 112 | 113 | func (tpl *TestCaseTpl) yamlPath() string { 114 | return path.Join(tpl.FixtureDirectory(), valueFilename) 115 | } 116 | 117 | func (tpl *TestCaseTpl) serializedPath() string { 118 | return path.Join(tpl.FixtureDirectory(), serializedFilename) 119 | } 120 | 121 | func (tpl *TestCaseTpl) ensureFixtures(fs afero.Fs) error { 122 | f := tpl.fixture 123 | if err := fs.MkdirAll(tpl.FixtureDirectory(), os.ModePerm); err != nil { 124 | return errors.Wrapf(err, "failed to create fixture directory %s", f.Directory) 125 | } 126 | if err := ensure(fs, tpl.rootPath(), f.Root.Contents, f.Root.FileMode); err != nil { 127 | return err 128 | } 129 | if err := ensure(fs, tpl.serializedPath(), f.Serialized.Contents, f.Serialized.FileMode); err != nil { 130 | return err 131 | } 132 | if err := ensure(fs, tpl.yamlPath(), f.Yaml.Contents, f.Yaml.FileMode); err != nil { 133 | return err 134 | } 135 | return nil 136 | } 137 | 138 | func ensure(fs afero.Fs, path string, contents []byte, mode os.FileMode) error { 139 | exists, err := afero.Exists(fs, path) 140 | if err != nil { 141 | return errors.Wrapf(err, "error checking for existence of %s", path) 142 | } 143 | if exists { 144 | return nil 145 | } 146 | if err := afero.WriteFile(fs, path, contents, mode); err != nil { 147 | return errors.Wrapf(err, "error writing fixture contents to %s", path) 148 | } 149 | return nil 150 | } 151 | 152 | func (tpl *TestCaseTpl) TestFuncName() string { 153 | id := tpl.ident 154 | return fmt.Sprintf("Test_%s_%s_%s_%d", id.Preset, id.Fork, tpl.structName, id.Offset) 155 | } 156 | 157 | func (tpl *TestCaseTpl) GoTypeName() string { 158 | return tpl.structName 159 | } 160 | 161 | func (tpl *TestCaseTpl) Render() (string, error) { 162 | b := bytes.NewBuffer(nil) 163 | err := testFuncBodyTpl.Execute(b, tpl) 164 | return b.String(), err 165 | } 166 | 167 | func WriteSpecTestFiles(cases map[TestIdent]Fixture, rels *SpecRelationships, fs afero.Fs) error { 168 | caseFuncs := make([]string, 0) 169 | fg := GroupByFork(cases) 170 | for _, fork := range ForkOrder { 171 | ids := fg[fork] 172 | raf, err := rels.RelationsAtFork(fork) 173 | if err != nil { 174 | return err 175 | } 176 | for _, id := range ids { 177 | structName, ok := raf[id.Name] 178 | if !ok { 179 | log.Infof("No implementation for %s, skipping test", cases[id].Directory) 180 | continue 181 | } 182 | tpl := &TestCaseTpl{ 183 | ident: id, 184 | fixture: cases[id], 185 | structName: structName, 186 | } 187 | if err := tpl.ensureFixtures(fs); err != nil { 188 | return err 189 | } 190 | cfunc, err := tpl.Render() 191 | if err != nil { 192 | return err 193 | } 194 | caseFuncs = append(caseFuncs, cfunc) 195 | } 196 | } 197 | packageDecl := "package " + backend.RenderedPackageName(rels.Package) + "\n\n" 198 | contents := packageDecl + "\n\n" + testCaseTemplateImports + "\n\n" + strings.Join(caseFuncs, "\n\n") 199 | 200 | testBytes, err := format.Source([]byte(contents)) 201 | if err != nil { 202 | return err 203 | } 204 | 205 | fname := "methodical_test.go" 206 | if err := afero.WriteFile(fs, fname, testBytes, 0666); err != nil { 207 | return errors.Wrapf(err, "error writing spectest functions to %s", fname) 208 | } 209 | 210 | return nil 211 | } 212 | -------------------------------------------------------------------------------- /specs/harness_test.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "testing" 7 | 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | "github.com/spf13/afero" 10 | "sigs.k8s.io/yaml" 11 | ) 12 | 13 | func TestHarnessYaml(t *testing.T) { 14 | input := ` 15 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 16 | preset: mainnet 17 | defs: 18 | - fork: phase0 19 | types: 20 | - name: BeaconBlock 21 | - fork: altair 22 | types: 23 | - name: BeaconBlock 24 | type_name: BeaconBlockAltair` 25 | sr := &SpecRelationships{} 26 | err := yaml.Unmarshal([]byte(input), sr) 27 | require.NoError(t, err) 28 | require.Equal(t, "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1", sr.Package) 29 | require.Equal(t, Mainnet, sr.Preset) 30 | require.Equal(t, 2, len(sr.Defs)) 31 | require.Equal(t, Phase0, sr.Defs[0].Fork) 32 | require.Equal(t, 1, len(sr.Defs[0].Types)) 33 | require.Equal(t, "BeaconBlock", sr.Defs[0].Types[0].SpecName) 34 | require.Equal(t, "", sr.Defs[0].Types[0].TypeName) 35 | require.Equal(t, Altair, sr.Defs[1].Fork) 36 | require.Equal(t, 1, len(sr.Defs[1].Types)) 37 | require.Equal(t, "BeaconBlock", sr.Defs[1].Types[0].SpecName) 38 | require.Equal(t, "BeaconBlockAltair", sr.Defs[1].Types[0].TypeName) 39 | 40 | require.Equal(t, 2, len(sr.GoTypes())) 41 | } 42 | 43 | func TestHarnessYamlFull(t *testing.T) { 44 | t.Skip("Skipping this test since no prysm.yaml file is available") 45 | sr := loadPrysmRelations(t) 46 | require.Equal(t, "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1", sr.Package) 47 | } 48 | 49 | func TestRelationsAtFork(t *testing.T) { 50 | t.Skip("Skipping this test since no prysm.yaml file is available") 51 | cases := []struct { 52 | name string 53 | specName string 54 | typeName string 55 | fork Fork 56 | err error 57 | missing bool 58 | }{ 59 | { 60 | name: "BeaconBlock at phase0", 61 | specName: "BeaconBlock", 62 | typeName: "BeaconBlock", 63 | fork: Phase0, 64 | }, 65 | { 66 | name: "BeaconBlock at altair", 67 | specName: "BeaconBlock", 68 | typeName: "BeaconBlockAltair", 69 | fork: Altair, 70 | }, 71 | { 72 | name: "Checkpoint at phase0", 73 | specName: "Checkpoint", 74 | typeName: "Checkpoint", 75 | fork: Phase0, 76 | }, 77 | { 78 | name: "Checkpoint at altair", 79 | specName: "Checkpoint", 80 | typeName: "Checkpoint", 81 | fork: Altair, 82 | }, 83 | { 84 | name: "Checkpoint at bellatrix", 85 | specName: "Checkpoint", 86 | typeName: "Checkpoint", 87 | fork: Bellatrix, 88 | }, 89 | { 90 | name: "ExecutionPayload missing at phase0", 91 | specName: "ExecutionPayload", 92 | typeName: "", 93 | fork: Phase0, 94 | missing: true, 95 | }, 96 | { 97 | name: "ExecutionPayload missing at altair", 98 | specName: "ExecutionPayload", 99 | typeName: "", 100 | fork: Altair, 101 | missing: true, 102 | }, 103 | { 104 | name: "ExecutionPayload at bellatrix", 105 | specName: "ExecutionPayload", 106 | typeName: "ExecutionPayload", 107 | fork: Bellatrix, 108 | }, 109 | { 110 | name: "ExecutionPayload at capella", 111 | specName: "ExecutionPayload", 112 | typeName: "ExecutionPayloadCapella", 113 | fork: Capella, 114 | }, 115 | } 116 | sr := loadPrysmRelations(t) 117 | for _, c := range cases { 118 | t.Run(c.name, func(t *testing.T) { 119 | r, err := sr.RelationsAtFork(c.fork) 120 | if err != nil { 121 | require.ErrorIs(t, err, c.err) 122 | return 123 | } 124 | require.NoError(t, err) 125 | tn, ok := r[c.specName] 126 | if c.missing { 127 | require.Equal(t, false, ok) 128 | return 129 | } 130 | require.Equal(t, true, ok) 131 | require.Equal(t, c.typeName, tn) 132 | }) 133 | } 134 | } 135 | 136 | func loadPrysmRelations(t *testing.T) *SpecRelationships { 137 | y, err := os.ReadFile("testdata/prysm.yaml") 138 | require.NoError(t, err) 139 | sr := &SpecRelationships{} 140 | err = yaml.Unmarshal(y, sr) 141 | require.NoError(t, err) 142 | return sr 143 | } 144 | 145 | func TestTestCaseTplFuncName(t *testing.T) { 146 | cases := []struct { 147 | name string 148 | ident TestIdent 149 | structname string 150 | }{ 151 | { 152 | name: "Test_mainnet_altair_AggregateAndProof_0", 153 | ident: TestIdent{ 154 | Preset: Mainnet, 155 | Fork: Altair, 156 | Name: "AggregateAndProof", 157 | Offset: 0, 158 | }, 159 | structname: "AggregateAttestationAndProof", 160 | }, 161 | } 162 | for _, c := range cases { 163 | t.Run(c.name, func(t *testing.T) { 164 | tpl := TestCaseTpl{ 165 | ident: c.ident, 166 | structName: c.ident.Name, 167 | } 168 | require.Equal(t, c.name, tpl.TestFuncName()) 169 | }) 170 | } 171 | } 172 | 173 | func basicFixture() Fixture { 174 | return Fixture{ 175 | Root: FixtureFile{Contents: []byte(`{root: '0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35'}`)}, 176 | Directory: "tests/mainnet/altair/ssz_static/AggregateAndProof/ssz_random/case_0", 177 | } 178 | } 179 | 180 | func TestCaseFileLayout(t *testing.T) { 181 | t.Skip("Skipping this test since no prysm.yaml file is available") 182 | fs := afero.NewMemMapFs() 183 | fix := basicFixture() 184 | cases := map[TestIdent]Fixture{ 185 | TestIdent{Preset: Mainnet, Fork: Altair, Name: "Checkpoint", Offset: 0}: fix, 186 | } 187 | rels := &SpecRelationships{ 188 | Package: "github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1", 189 | Preset: Mainnet, 190 | Defs: []ForkTypeDefinitions{ 191 | { 192 | Fork: Altair, 193 | Types: []TypeRelation{ 194 | { 195 | SpecName: "Checkpoint", 196 | }, 197 | }, 198 | }, 199 | }, 200 | } 201 | require.NoError(t, WriteSpecTestFiles(cases, rels, fs)) 202 | entries, err := afero.ReadDir(fs, fix.Directory) 203 | require.NoError(t, err) 204 | searching := map[string]bool{ 205 | rootFilename: true, 206 | serializedFilename: true, 207 | valueFilename: true, 208 | } 209 | for _, f := range entries { 210 | _, n, err := ParsePath(path.Join(fix.Directory, f.Name())) 211 | require.NoError(t, err) 212 | _, ok := searching[n] 213 | if ok { 214 | delete(searching, n) 215 | } 216 | } 217 | require.Equal(t, 0, len(searching)) 218 | } 219 | 220 | func TestRenderTestCaseTpl(t *testing.T) { 221 | tpl := TestCaseTpl{ 222 | ident: TestIdent{ 223 | Preset: Mainnet, 224 | Fork: Altair, 225 | Offset: 0, 226 | }, 227 | fixture: basicFixture(), 228 | structName: "AggregateAttestationAndProof", 229 | } 230 | rendered, err := tpl.Render() 231 | require.NoError(t, err) 232 | expected := `func Test_mainnet_altair_AggregateAttestationAndProof_0(t *testing.T) { 233 | fixtureDir := "testdata/tests/mainnet/altair/ssz_static/AggregateAndProof/ssz_random/case_0" 234 | root, serialized, err := specs.RootAndSerializedFromFixture(fixtureDir) 235 | require.NoError(t, err) 236 | v := &AggregateAttestationAndProof{} 237 | require.NoError(t, v.UnmarshalSSZ(serialized)) 238 | sroot, err := v.HashTreeRoot() 239 | require.NoError(t, err) 240 | require.Equal(t, root, sroot) 241 | }` 242 | require.Equal(t, expected, rendered) 243 | } 244 | -------------------------------------------------------------------------------- /specs/ident.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/pkg/errors" 9 | ) 10 | 11 | type Preset string 12 | 13 | var ( 14 | PresetUnknown Preset = "" 15 | Minimal Preset = "minimal" 16 | Mainnet Preset = "mainnet" 17 | ) 18 | 19 | var ErrUnknownPreset = errors.New("unknown preset name") 20 | 21 | func (p *Preset) UnmarshalText(t []byte) error { 22 | s := string(t) 23 | sp := stringToPreset(s) 24 | if sp == PresetUnknown { 25 | return errors.Wrap(ErrUnknownPreset, s) 26 | } 27 | *p = sp 28 | return nil 29 | } 30 | 31 | func stringToPreset(s string) Preset { 32 | switch s { 33 | case string(Minimal): 34 | return Minimal 35 | case string(Mainnet): 36 | return Mainnet 37 | default: 38 | return PresetUnknown 39 | } 40 | } 41 | 42 | type TestIdent struct { 43 | Preset Preset `json:"preset"` 44 | Fork Fork `json:"fork"` 45 | Name string `json:"name"` 46 | Offset int `json:"offset"` 47 | } 48 | 49 | func (ti TestIdent) LessThan(other TestIdent) bool { 50 | if ti.Name == other.Name { 51 | return ti.Offset < other.Offset 52 | } 53 | // strings.Compare will return -1 if ti.Name is < other.Name 54 | return strings.Compare(ti.Name, other.Name) == -1 55 | } 56 | 57 | func (ti TestIdent) String() string { 58 | return fmt.Sprintf("%s:%s:%s:%d", ti.Preset, ti.Fork, ti.Name, ti.Offset) 59 | } 60 | 61 | var layout = struct { 62 | testDir int 63 | preset int 64 | fork int 65 | sszStatic int 66 | typeName int 67 | sszRandom int 68 | caseNum int 69 | fileName int 70 | }{ 71 | testDir: 0, 72 | preset: 1, 73 | fork: 2, 74 | sszStatic: 3, 75 | typeName: 4, 76 | sszRandom: 5, 77 | caseNum: 6, 78 | fileName: 7, 79 | } 80 | 81 | func (ti TestIdent) Match(other TestIdent) bool { 82 | if other.Preset == PresetUnknown || other.Fork == ForkUnknown || other.Name == "" { 83 | return false 84 | } 85 | if ti.Preset != PresetUnknown && ti.Preset != other.Preset { 86 | return false 87 | } 88 | if ti.Fork != ForkUnknown && ti.Fork != other.Fork { 89 | return false 90 | } 91 | if ti.Name != "" && ti.Name != other.Name { 92 | return false 93 | } 94 | return true 95 | } 96 | 97 | var ErrPathParse = errors.New("spectest path not in expected format, could not parse identifiers") 98 | 99 | var caseOffset = len("case_") 100 | 101 | func ParsePath(p string) (TestIdent, string, error) { 102 | parts := strings.Split(p, "/") 103 | if len(parts) <= layout.fileName || parts[layout.testDir] != "tests" || parts[layout.sszStatic] != "ssz_static" || parts[layout.sszRandom] != "ssz_random" { 104 | return TestIdent{}, "", nil 105 | } 106 | var offset int 107 | if len(parts[layout.caseNum]) > caseOffset { 108 | a, err := strconv.Atoi(parts[layout.caseNum][caseOffset:]) 109 | if err != nil { 110 | return TestIdent{}, "", errors.Wrapf(err, "problem parsing case number from path %s", p) 111 | } 112 | offset = a 113 | } 114 | return TestIdent{ 115 | Preset: stringToPreset(parts[layout.preset]), 116 | Fork: stringToFork(parts[layout.fork]), 117 | Name: parts[layout.typeName], 118 | Offset: offset, 119 | }, parts[layout.fileName], nil 120 | } 121 | -------------------------------------------------------------------------------- /specs/ident_test.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | func TestParsePath(t *testing.T) { 12 | cases := []struct { 13 | name string 14 | path string 15 | err error 16 | ident TestIdent 17 | fname string 18 | match bool 19 | }{ 20 | { 21 | name: "mainnet capella", 22 | match: true, 23 | path: "tests/mainnet/capella/ssz_static/LightClientOptimisticUpdate/ssz_random/case_0/roots.yaml", 24 | ident: TestIdent{ 25 | Preset: Mainnet, 26 | Fork: Capella, 27 | }, 28 | fname: "roots.yaml", 29 | }, 30 | } 31 | for _, c := range cases { 32 | t.Run(c.name, func(t *testing.T) { 33 | other, fname, err := ParsePath(c.path) 34 | if c.err == nil { 35 | require.NoError(t, err) 36 | } 37 | require.Equal(t, c.match, c.ident.Match(other)) 38 | require.Equal(t, c.fname, fname) 39 | }) 40 | } 41 | } 42 | 43 | func TestUnmarshalIdentFields(t *testing.T) { 44 | cases := []struct { 45 | name string 46 | marshaled string 47 | err error 48 | preset *Preset 49 | fork *Fork 50 | }{ 51 | { 52 | name: "unknown fork", 53 | marshaled: `{"fork": "derp"}`, 54 | err: ErrUnknownFork, 55 | }, 56 | { 57 | name: "altair", 58 | marshaled: fmt.Sprintf(`{"fork": "%s"}`, Altair), 59 | fork: &Altair, 60 | }, 61 | { 62 | name: "phase0", 63 | marshaled: fmt.Sprintf(`{"fork": "%s"}`, Phase0), 64 | fork: &Phase0, 65 | }, 66 | { 67 | name: "unknown preset", 68 | marshaled: `{"preset": "derp"}`, 69 | err: ErrUnknownPreset, 70 | }, 71 | { 72 | name: "mainnet preset", 73 | marshaled: `{"preset": "mainnet"}`, 74 | preset: &Mainnet, 75 | }, 76 | { 77 | name: "minimal preset", 78 | marshaled: `{"preset": "minimal"}`, 79 | preset: &Minimal, 80 | }, 81 | } 82 | for _, c := range cases { 83 | t.Run(c.name, func(t *testing.T) { 84 | ti := &TestIdent{} 85 | err := json.Unmarshal([]byte(c.marshaled), ti) 86 | if c.err == nil { 87 | require.NoError(t, err) 88 | } else { 89 | require.ErrorIs(t, err, c.err) 90 | } 91 | if c.fork != nil { 92 | require.Equal(t, *c.fork, ti.Fork) 93 | } 94 | if c.preset != nil { 95 | require.Equal(t, *c.preset, ti.Preset) 96 | } 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /specs/template.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import "text/template" 4 | 5 | var testCaseTemplateBytes = `func {{.TestFuncName}}(t *testing.T) { 6 | fixtureDir := "{{.FixtureDirectory}}" 7 | root, serialized, err := specs.RootAndSerializedFromFixture(fixtureDir) 8 | require.NoError(t, err) 9 | v := &{{.GoTypeName}}{} 10 | require.NoError(t, v.UnmarshalSSZ(serialized)) 11 | sroot, err := v.HashTreeRoot() 12 | require.NoError(t, err) 13 | require.Equal(t, root, sroot) 14 | }` 15 | 16 | var testCaseTemplateImports = `import ( 17 | "testing" 18 | 19 | "github.com/OffchainLabs/methodical-ssz/specs" 20 | "github.com/prysmaticlabs/prysm/v3/testing/require" 21 | )` 22 | 23 | var testFuncBodyTpl *template.Template 24 | 25 | func init() { 26 | testFuncBodyTpl = template.Must(template.New("testFuncBodyTpl").Parse(testCaseTemplateBytes)) 27 | } 28 | -------------------------------------------------------------------------------- /specs/testdata/prysm.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/prysmaticlabs/prysm/v3/proto/prysm/v1alpha1 2 | preset: mainnet 3 | defs: 4 | - fork: phase0 5 | types: 6 | - name: AggregateAndProof 7 | type_name: AggregateAttestationAndProof 8 | - name: Attestation 9 | - name: AttestationData 10 | - name: AttesterSlashing 11 | - name: BeaconBlock 12 | - name: BeaconBlockBody 13 | - name: BeaconBlockHeader 14 | - name: BeaconState 15 | - name: Checkpoint 16 | - name: Deposit 17 | - name: DepositData 18 | type_name: Deposit_Data 19 | - name: DepositMessage 20 | - name: Eth1Data 21 | - name: Fork 22 | - name: ForkData 23 | - name: HistoricalBatch 24 | - name: IndexedAttestation 25 | - name: PendingAttestation 26 | - name: ProposerSlashing 27 | - name: SignedAggregateAndProof 28 | type_name: SignedAggregateAttestationAndProof 29 | - name: SignedBeaconBlock 30 | - name: SignedBeaconBlockHeader 31 | - name: SignedVoluntaryExit 32 | - name: SigningData 33 | - name: Validator 34 | - name: VoluntaryExit 35 | - fork: altair 36 | types: 37 | - name: BeaconBlock 38 | type_name: BeaconBlockAltair 39 | - name: BeaconBlockBody 40 | type_name: BeaconBlockBodyAltair 41 | - name: BeaconState 42 | type_name: BeaconStateAltair 43 | - name: ContributionAndProof 44 | - name: SignedBeaconBlock 45 | type_name: SignedBeaconBlockAltair 46 | - name: SignedContributionAndProof 47 | - name: SyncAggregate 48 | - name: SyncAggregatorSelectionData 49 | - name: SyncCommittee 50 | - name: SyncCommitteeContribution 51 | - name: SyncCommitteeMessage 52 | - fork: bellatrix 53 | types: 54 | - name: BeaconBlock 55 | type_name: BeaconBlockBellatrix 56 | - name: BeaconBlockBody 57 | type_name: BeaconBlockBodyBellatrix 58 | - name: BeaconState 59 | type_name: BeaconStateBellatrix 60 | - name: PowBlock 61 | - name: SignedBeaconBlock 62 | type_name: SignedBeaconBlockBellatrix 63 | - fork: capella 64 | types: 65 | - name: BeaconBlock 66 | type_name: BeaconBlockCapella 67 | - name: BeaconBlockBody 68 | type_name: BeaconBlockBodyCapella 69 | - name: BeaconState 70 | type_name: BeaconStateCapella 71 | - name: SignedBeaconBlock 72 | type_name: SignedBeaconBlockCapella 73 | - name: BLSToExecutionChange 74 | - name: SignedBLSToExecutionChange 75 | - name: HistoricalSummary 76 | -------------------------------------------------------------------------------- /specs/tests.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "archive/tar" 5 | "compress/gzip" 6 | "io" 7 | "os" 8 | "path" 9 | 10 | "github.com/ethereum/go-ethereum/common/hexutil" 11 | "github.com/golang/snappy" 12 | "github.com/pkg/errors" 13 | "github.com/spf13/afero" 14 | "gopkg.in/yaml.v3" 15 | ) 16 | 17 | type Fixture struct { 18 | Directory string 19 | Root FixtureFile 20 | Serialized FixtureFile 21 | Yaml FixtureFile 22 | } 23 | 24 | type FixtureFile struct { 25 | Contents []byte 26 | FileMode os.FileMode 27 | } 28 | 29 | func (f *Fixture) writeRoot(fs afero.Fs) error { 30 | return afero.WriteFile(fs, path.Join(f.Directory, rootFilename), f.Root.Contents, f.Root.FileMode) 31 | } 32 | 33 | var ( 34 | rootFilename = "roots.yaml" 35 | serializedFilename = "serialized.ssz_snappy" 36 | valueFilename = "value.yaml" 37 | ) 38 | 39 | func IdentFilter(ident TestIdent) func([]TestIdent) []TestIdent { 40 | return func(maybe []TestIdent) []TestIdent { 41 | matches := make([]TestIdent, 0) 42 | for _, m := range maybe { 43 | if ident.Match(m) { 44 | matches = append(matches, m) 45 | } 46 | } 47 | return matches 48 | } 49 | } 50 | 51 | func GroupByFork(cases map[TestIdent]Fixture) map[Fork][]TestIdent { 52 | m := make(map[Fork][]TestIdent) 53 | for id := range cases { 54 | switch len(m[id.Fork]) { 55 | case 0: 56 | m[id.Fork] = []TestIdent{id} 57 | case 1: 58 | m[id.Fork] = append(m[id.Fork], id) 59 | default: 60 | for i, cur := range m[id.Fork] { 61 | if id.LessThan(cur) { 62 | m[id.Fork] = append(m[id.Fork][:i], append([]TestIdent{id}, m[id.Fork][i:]...)...) 63 | break 64 | } 65 | } 66 | } 67 | } 68 | return m 69 | } 70 | 71 | func GroupByType(ti []TestIdent) map[string][]TestIdent { 72 | m := make(map[string][]TestIdent) 73 | for _, t := range ti { 74 | m[t.Name] = append(m[t.Name], t) 75 | } 76 | return m 77 | } 78 | 79 | func ExtractCases(tgz io.Reader, filter TestIdent) (map[TestIdent]Fixture, error) { 80 | cases := make(map[TestIdent]Fixture) 81 | uncompressed, err := gzip.NewReader(tgz) 82 | if err != nil { 83 | return nil, errors.Wrap(err, "unable to read gzip-compressed stream") 84 | } 85 | tr := tar.NewReader(uncompressed) 86 | for { 87 | header, err := tr.Next() 88 | if err == io.EOF { 89 | break 90 | } 91 | if err != nil { 92 | return nil, errors.Wrap(err, "failed to read file header from spectest tarball") 93 | } 94 | ident, fname, err := ParsePath(header.Name) 95 | if err != nil { 96 | return nil, err 97 | } 98 | if !filter.Match(ident) { 99 | continue 100 | } 101 | c, ok := cases[ident] 102 | if !ok { 103 | c = Fixture{Directory: path.Dir(header.Name)} 104 | } 105 | f, err := io.ReadAll(tr) 106 | if err != nil { 107 | return nil, errors.Wrapf(err, "error reading %s from spectest tarball", header.Name) 108 | } 109 | switch fname { 110 | case rootFilename: 111 | c.Root = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)} 112 | case serializedFilename: 113 | c.Serialized = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)} 114 | case valueFilename: 115 | c.Yaml = FixtureFile{Contents: f, FileMode: os.FileMode(header.Mode)} 116 | } 117 | cases[ident] = c 118 | } 119 | return cases, nil 120 | } 121 | 122 | func DecodeRootFile(f []byte) ([32]byte, error) { 123 | root := [32]byte{} 124 | ry := &struct { 125 | Root string `json:"root"` 126 | }{} 127 | if err := yaml.Unmarshal(f, ry); err != nil { 128 | return root, err 129 | } 130 | br, err := hexutil.Decode(ry.Root) 131 | if err != nil { 132 | return root, err 133 | } 134 | copy(root[:], br) 135 | return root, nil 136 | } 137 | 138 | func RootAndSerializedFromFixture(dir string) ([32]byte, []byte, error) { 139 | rpath := path.Join(dir, rootFilename) 140 | rootBytes, err := os.ReadFile(rpath) 141 | if err != nil { 142 | return [32]byte{}, nil, errors.Wrapf(err, "error reading expected root fixture file %s", rpath) 143 | } 144 | root, err := DecodeRootFile(rootBytes) 145 | if err != nil { 146 | return [32]byte{}, nil, errors.Wrapf(err, "error decoding expected root fixture file %s, hex contents=%#x", rpath, rootBytes) 147 | } 148 | 149 | spath := path.Join(dir, serializedFilename) 150 | snappySer, err := os.ReadFile(spath) 151 | if err != nil { 152 | return [32]byte{}, nil, errors.Wrapf(err, "error reading serialized fixture file %s", spath) 153 | } 154 | serialized, err := snappy.Decode(nil, snappySer) 155 | if err != nil { 156 | return [32]byte{}, nil, errors.Wrapf(err, "error snappy decoding serialized fixture file %s", spath) 157 | } 158 | 159 | return root, serialized, nil 160 | } 161 | -------------------------------------------------------------------------------- /specs/tests_test.go: -------------------------------------------------------------------------------- 1 | package specs 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ethereum/go-ethereum/common/hexutil" 7 | "github.com/prysmaticlabs/prysm/v3/testing/require" 8 | ) 9 | 10 | func TestDecodeRootFile(t *testing.T) { 11 | e, err := hexutil.Decode("0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35") 12 | expected := [32]byte{} 13 | copy(expected[:], e) 14 | require.NoError(t, err) 15 | f := []byte(`{root: '0x44de62c118d7951f5b6d9a03444e54aff47d02ff57add2a4eb2a198b3e83ae35'}`) 16 | r, err := DecodeRootFile(f) 17 | require.NoError(t, err) 18 | require.Equal(t, expected, r) 19 | } 20 | -------------------------------------------------------------------------------- /sszgen/backend/bool.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | ) 8 | 9 | type generateBool struct { 10 | valRep *types.ValueBool 11 | targetPackage string 12 | casterConfig 13 | importNamer *ImportNamer 14 | } 15 | 16 | func (g *generateBool) generateHTRPutter(fieldName string) string { 17 | return fmt.Sprintf("hh.PutBool(%s)", fieldName) 18 | } 19 | 20 | func (g *generateBool) coerce() func(string) string { 21 | return func(fieldName string) string { 22 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName) 23 | } 24 | } 25 | 26 | func (g *generateBool) generateFixedMarshalValue(fieldName string) string { 27 | return fmt.Sprintf("dst = ssz.MarshalBool(dst, %s)", fieldName) 28 | } 29 | 30 | func (g *generateBool) generateUnmarshalValue(fieldName string, offset string) string { 31 | convert := fmt.Sprintf("ssz.UnmarshalBool(%s)", offset) 32 | return fmt.Sprintf("%s = %s", fieldName, g.casterConfig.toOverlay(convert)) 33 | } 34 | 35 | func (g *generateBool) variableSizeSSZ(fieldname string) string { 36 | return "" 37 | } 38 | 39 | var _ valueGenerator = &generateBool{} 40 | -------------------------------------------------------------------------------- /sszgen/backend/byte.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | ) 8 | 9 | type generateByte struct { 10 | *types.ValueByte 11 | targetPackage string 12 | importNamer *ImportNamer 13 | } 14 | 15 | func (g *generateByte) generateHTRPutter(fieldName string) string { 16 | return "" 17 | } 18 | 19 | func (g *generateByte) coerce() func(string) string { 20 | return func(fieldName string) string { 21 | return fmt.Sprintf("%s(%s)", g.TypeName(), fieldName) 22 | } 23 | } 24 | 25 | func (g *generateByte) generateFixedMarshalValue(fieldName string) string { 26 | return "" 27 | } 28 | 29 | func (g *generateByte) generateUnmarshalValue(fieldName string, s string) string { 30 | return "" 31 | } 32 | 33 | func (g *generateByte) variableSizeSSZ(fieldname string) string { 34 | return "" 35 | } 36 | 37 | var _ valueGenerator = &generateByte{} 38 | -------------------------------------------------------------------------------- /sszgen/backend/caster.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | type caster interface { 4 | setToOverlay(func(string) string) 5 | setFromOverlay(func(string) string) 6 | } 7 | 8 | type casterConfig struct { 9 | toOverlayFunc func(string) string 10 | fromOverlayFunc func(string) string 11 | } 12 | 13 | func (c *casterConfig) setToOverlay(castFunc func(string) string) { 14 | c.toOverlayFunc = castFunc 15 | } 16 | 17 | func (c *casterConfig) toOverlay(value string) string { 18 | if c.toOverlayFunc == nil { 19 | return value 20 | } 21 | return c.toOverlayFunc(value) 22 | } 23 | 24 | func (c *casterConfig) setFromOverlay(castFunc func(string) string) { 25 | c.fromOverlayFunc = castFunc 26 | } 27 | 28 | func (c *casterConfig) fromOverlay(value string) string { 29 | if c.fromOverlayFunc == nil { 30 | return value 31 | } 32 | return c.fromOverlayFunc(value) 33 | } 34 | -------------------------------------------------------------------------------- /sszgen/backend/container.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | ) 8 | 9 | const receiverName = "c" 10 | 11 | type generateContainer struct { 12 | *types.ValueContainer 13 | targetPackage string 14 | importNamer *ImportNamer 15 | } 16 | 17 | func (g *generateContainer) generateHTRPutter(fieldName string) string { 18 | tmpl := `if err := %s.HashTreeRootWith(hh); err != nil { 19 | return err 20 | }` 21 | return fmt.Sprintf(tmpl, fieldName) 22 | } 23 | 24 | func (g *generateContainer) variableSizeSSZ(fieldName string) string { 25 | return fmt.Sprintf("%s.SizeSSZ()", fieldName) 26 | } 27 | 28 | func (g *generateContainer) generateUnmarshalValue(fieldName string, sliceName string) string { 29 | t := `if err = %s.UnmarshalSSZ(%s); err != nil { 30 | return err 31 | }` 32 | return fmt.Sprintf(t, fieldName, sliceName) 33 | } 34 | 35 | func (g *generateContainer) generateFixedMarshalValue(fieldName string) string { 36 | if g.IsVariableSized() { 37 | return fmt.Sprintf(`dst = ssz.WriteOffset(dst, offset) 38 | offset += %s.SizeSSZ()`, fieldName) 39 | } 40 | return g.generateDelegateFieldMarshalSSZ(fieldName) 41 | } 42 | 43 | // method that generates code which calls the MarshalSSZ method of the field 44 | func (g *generateContainer) generateDelegateFieldMarshalSSZ(fieldName string) string { 45 | return fmt.Sprintf(`if dst, err = %s.MarshalSSZTo(dst); err != nil { 46 | return nil, err 47 | }`, fieldName) 48 | } 49 | 50 | func (g *generateContainer) generateVariableMarshalValue(fieldName string) string { 51 | return g.generateDelegateFieldMarshalSSZ(fieldName) 52 | } 53 | 54 | func (g *generateContainer) fixedOffset() int { 55 | offset := 0 56 | for _, c := range g.Contents { 57 | offset += c.Value.FixedSize() 58 | } 59 | return offset 60 | } 61 | 62 | func (g *generateContainer) initializeValue() string { 63 | return fmt.Sprintf("new(%s)", fullyQualifiedTypeName(g.ValueContainer, g.targetPackage, g.importNamer)) 64 | } 65 | 66 | var _ valueGenerator = &generateContainer{} 67 | var _ valueInitializer = &generateContainer{} 68 | var _ htrPutter = &generateContainer{} 69 | -------------------------------------------------------------------------------- /sszgen/backend/delegate.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | ) 9 | 10 | // "Delegate" meaning a type that defines it's own ssz methodset 11 | type generateDelegate struct { 12 | types.ValRep 13 | targetPackage string 14 | importNamer *ImportNamer 15 | } 16 | 17 | func (g *generateDelegate) generateHTRPutter(fieldName string) string { 18 | fullTmpl := `if err := %s.HashTreeRootWith(hh); err != nil { 19 | return err 20 | }` 21 | lightTmpl := `if hash, err := %s.HashTreeRoot(); err != nil { 22 | return err 23 | } else { 24 | hh.AppendBytes32(hash[:]) 25 | }` 26 | if g.SatisfiesInterface(interfaces.SszFullHasher) { 27 | return fmt.Sprintf(fullTmpl, fieldName) 28 | } 29 | return fmt.Sprintf(lightTmpl, fieldName) 30 | } 31 | 32 | func (g *generateDelegate) variableSizeSSZ(fieldName string) string { 33 | return fmt.Sprintf("%s.SizeSSZ()", fieldName) 34 | } 35 | 36 | func (g *generateDelegate) generateUnmarshalValue(fieldName string, sliceName string) string { 37 | t := `if err = %s.UnmarshalSSZ(%s); err != nil { 38 | return err 39 | }` 40 | return fmt.Sprintf(t, fieldName, sliceName) 41 | } 42 | 43 | func (g *generateDelegate) generateFixedMarshalValue(fieldName string) string { 44 | if g.IsVariableSized() { 45 | return fmt.Sprintf(`dst = ssz.WriteOffset(dst, offset) 46 | offset += %s.SizeSSZ()`, fieldName) 47 | } 48 | return g.generateDelegateFieldMarshalSSZ(fieldName) 49 | } 50 | 51 | // method that generates code which calls the MarshalSSZ method of the field 52 | func (g *generateDelegate) generateDelegateFieldMarshalSSZ(fieldName string) string { 53 | return fmt.Sprintf(`if dst, err = %s.MarshalSSZTo(dst); err != nil { 54 | return nil, err 55 | }`, fieldName) 56 | } 57 | 58 | func (g *generateDelegate) generateVariableMarshalValue(fieldName string) string { 59 | return g.generateDelegateFieldMarshalSSZ(fieldName) 60 | } 61 | 62 | func (g *generateDelegate) initializeValue() string { 63 | switch vt := g.ValRep.(type) { 64 | case *types.ValuePointer: 65 | return fmt.Sprintf("new(%s)", fullyQualifiedTypeName(vt.Referent, g.targetPackage, g.importNamer)) 66 | default: 67 | return "" 68 | } 69 | } 70 | 71 | var _ valueGenerator = &generateDelegate{} 72 | var _ htrPutter = &generateDelegate{} 73 | var _ valueInitializer = &generateDelegate{} 74 | -------------------------------------------------------------------------------- /sszgen/backend/genhtr.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 10 | ) 11 | 12 | // ChunkSize is used to check if packed bytes align to the chunk sized used by the 13 | // merkleization algorithm. If not, the bytes should be zero-padded to the 14 | // nearest multiple of ChunkSize. 15 | const ChunkSize = 32 16 | 17 | var htrTmpl = `func ({{.Receiver}} {{.Type}}) HashTreeRoot() ([32]byte, error) { 18 | hh := ssz.DefaultHasherPool.Get() 19 | if err := {{.Receiver}}.HashTreeRootWith(hh); err != nil { 20 | ssz.DefaultHasherPool.Put(hh) 21 | return [32]byte{}, err 22 | } 23 | root, err := hh.HashRoot() 24 | ssz.DefaultHasherPool.Put(hh) 25 | return root, err 26 | } 27 | 28 | func ({{.Receiver}} {{.Type}}) HashTreeRootWith(hh *ssz.Hasher) (err error) { 29 | indx := hh.Index() 30 | {{.HTRSteps}} 31 | hh.Merkleize(indx) 32 | return nil 33 | }` 34 | 35 | func GenerateHashTreeRoot(g *generateContainer) (*generatedCode, error) { 36 | htrTmpl, err := template.New("GenerateHashTreeRoot").Parse(htrTmpl) 37 | if err != nil { 38 | return nil, err 39 | } 40 | buf := bytes.NewBuffer(nil) 41 | htrSteps := make([]string, 0) 42 | for i, c := range g.Contents { 43 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key) 44 | htrSteps = append(htrSteps, fmt.Sprintf("\t// Field %d: %s", i, c.Key)) 45 | vg := newValueGenerator(interfaces.SszLightHasher, c.Value, g.targetPackage, g.importNamer) 46 | htrp, ok := vg.(htrPutter) 47 | if !ok { 48 | continue 49 | } 50 | htrSteps = append(htrSteps, htrp.generateHTRPutter(fieldName)) 51 | } 52 | err = htrTmpl.Execute(buf, struct { 53 | Receiver string 54 | Type string 55 | HTRSteps string 56 | }{ 57 | Receiver: receiverName, 58 | Type: fmt.Sprintf("*%s", g.TypeName()), 59 | HTRSteps: strings.Join(htrSteps, "\n"), 60 | }) 61 | if err != nil { 62 | return nil, err 63 | } 64 | // TODO: allow GenerateHashTreeRoot to return an error since template.Execute 65 | // can technically return an error (get rid of the panics) 66 | return &generatedCode{ 67 | blocks: []string{buf.String()}, 68 | }, nil 69 | } 70 | -------------------------------------------------------------------------------- /sszgen/backend/genhtr_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | // cases left to satisfy: 12 | // list-vector-byte 13 | func TestGenerateHashTreeRoot(t *testing.T) { 14 | t.Skip("fixtures need to be updated") 15 | b, err := os.ReadFile("testdata/TestGenerateHashTreeRoot.expected") 16 | require.NoError(t, err) 17 | expected := string(b) 18 | 19 | vc, ok := testFixBeaconState.(*types.ValueContainer) 20 | require.Equal(t, true, ok) 21 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""} 22 | code, err := GenerateHashTreeRoot(gc) 23 | require.NoError(t, err) 24 | require.Equal(t, 4, len(code.imports)) 25 | actual, err := normalizeFixtureString(code.blocks[0]) 26 | require.NoError(t, err) 27 | require.Equal(t, expected, actual) 28 | } 29 | 30 | func TestHTROverlayCoerce(t *testing.T) { 31 | pkg := "derp" 32 | expected := "hh.PutUint64(uint64(b.Slot))" 33 | val := &types.ValueOverlay{ 34 | Name: "", 35 | Package: pkg, 36 | Underlying: &types.ValueUint{ 37 | Name: "uint64", 38 | Size: 64, 39 | Package: pkg, 40 | }, 41 | } 42 | gv := &generateOverlay{ValueOverlay: val, targetPackage: pkg} 43 | actual := gv.generateHTRPutter("b.Slot") 44 | require.Equal(t, expected, actual) 45 | } 46 | 47 | func TestHTRContainer(t *testing.T) { 48 | t.Skip("fixtures need to be updated") 49 | pkg := "derp" 50 | expected := `if err := b.Fork.HashTreeRootWith(hh); err != nil { 51 | return err 52 | }` 53 | val := &types.ValueContainer{} 54 | gv := &generateContainer{ValueContainer: val, targetPackage: pkg} 55 | actual := gv.generateHTRPutter("b.Fork") 56 | require.Equal(t, expected, actual) 57 | } 58 | 59 | func TestHTRByteVector(t *testing.T) { 60 | t.Skip("fixtures need to be updated") 61 | pkg := "derp" 62 | fieldName := "c.GenesisValidatorsRoot" 63 | expected := `{ 64 | if len(c.GenesisValidatorsRoot) != 32 { 65 | return ssz.ErrVectorLength 66 | } 67 | hh.PutBytes(c.GenesisValidatorsRoot) 68 | }` 69 | val := &types.ValueVector{ 70 | ElementValue: &types.ValueByte{}, 71 | Size: 32, 72 | } 73 | gv := &generateVector{ 74 | valRep: val, 75 | targetPackage: pkg, 76 | } 77 | actual := gv.generateHTRPutter(fieldName) 78 | require.Equal(t, expected, actual) 79 | } 80 | -------------------------------------------------------------------------------- /sszgen/backend/genmarshal.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 10 | ) 11 | 12 | var marshalBodyTmpl = `func ({{.Receiver}} {{.Type}}) MarshalSSZ() ([]byte, error) { 13 | buf := make([]byte, {{.Receiver}}.SizeSSZ()) 14 | return {{.Receiver}}.MarshalSSZTo(buf[:0]) 15 | } 16 | 17 | func ({{.Receiver}} {{.Type}}) MarshalSSZTo(dst []byte) ([]byte, error) { 18 | var err error 19 | {{- .OffsetDeclaration -}} 20 | {{- .ValueMarshaling }} 21 | {{- .VariableValueMarshaling }} 22 | return dst, err 23 | }` 24 | 25 | func GenerateMarshalSSZ(g *generateContainer) (*generatedCode, error) { 26 | sizeTmpl, err := template.New("GenerateMarshalSSZ").Parse(marshalBodyTmpl) 27 | if err != nil { 28 | return nil, err 29 | } 30 | buf := bytes.NewBuffer(nil) 31 | 32 | marshalValueBlocks := make([]string, 0) 33 | marshalVariableValueBlocks := make([]string, 0) 34 | offset := 0 35 | for i, c := range g.Contents { 36 | // only lists need the offset variable 37 | mg := newValueGenerator(interfaces.SszMarshaler, c.Value, g.targetPackage, g.importNamer) 38 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key) 39 | marshalValueBlocks = append(marshalValueBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key)) 40 | vi, ok := mg.(valueInitializer) 41 | if ok { 42 | ini := vi.initializeValue() 43 | if ini != "" { 44 | marshalValueBlocks = append(marshalValueBlocks, fmt.Sprintf("if %s == nil {\n\t%s = %s\n}", fieldName, fieldName, ini)) 45 | } 46 | } 47 | 48 | mv := mg.generateFixedMarshalValue(fieldName) 49 | marshalValueBlocks = append(marshalValueBlocks, "\t"+mv) 50 | offset += c.Value.FixedSize() 51 | if !c.Value.IsVariableSized() { 52 | continue 53 | } 54 | vm, ok := mg.(variableMarshaller) 55 | if !ok { 56 | continue 57 | } 58 | vmc := vm.generateVariableMarshalValue(fieldName) 59 | if vmc != "" { 60 | marshalVariableValueBlocks = append(marshalVariableValueBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key)) 61 | marshalVariableValueBlocks = append(marshalVariableValueBlocks, "\t"+vmc) 62 | } 63 | } 64 | // only set the offset declaration if we need it 65 | // otherwise we'll have an unused variable (syntax error) 66 | offsetDeclaration := "" 67 | if g.IsVariableSized() { 68 | // if there are any variable sized values in the container, we'll need to set this offset declaration 69 | // so it gets rendered to the top of the marshal method 70 | offsetDeclaration = fmt.Sprintf("\noffset := %d\n", offset) 71 | } 72 | 73 | err = sizeTmpl.Execute(buf, struct { 74 | Receiver string 75 | Type string 76 | OffsetDeclaration string 77 | ValueMarshaling string 78 | VariableValueMarshaling string 79 | }{ 80 | Receiver: receiverName, 81 | Type: fmt.Sprintf("*%s", g.TypeName()), 82 | OffsetDeclaration: offsetDeclaration, 83 | ValueMarshaling: "\n" + strings.Join(marshalValueBlocks, "\n"), 84 | VariableValueMarshaling: "\n" + strings.Join(marshalVariableValueBlocks, "\n"), 85 | }) 86 | if err != nil { 87 | return nil, err 88 | } 89 | return &generatedCode{ 90 | blocks: []string{buf.String()}, 91 | }, nil 92 | } 93 | -------------------------------------------------------------------------------- /sszgen/backend/genmarshal_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | func TestGenerateMarshalSSZ(t *testing.T) { 12 | b, err := os.ReadFile("testdata/TestGenerateMarshalSSZ.expected") 13 | require.NoError(t, err) 14 | expected := string(b) 15 | 16 | vc, ok := testFixBeaconState.(*types.ValueContainer) 17 | require.Equal(t, true, ok) 18 | inm := NewImportNamer("", nil) 19 | gc := &generateContainer{ValueContainer: vc, targetPackage: "", importNamer: inm} 20 | code, err := GenerateMarshalSSZ(gc) 21 | require.NoError(t, err) 22 | require.Equal(t, 2, len(inm.aliases)) 23 | actual, err := normalizeFixtureString(code.blocks[0]) 24 | require.NoError(t, err) 25 | require.Equal(t, expected, actual) 26 | } 27 | -------------------------------------------------------------------------------- /sszgen/backend/gensize.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 10 | ) 11 | 12 | var sizeBodyTmpl = `func ({{.Receiver}} {{.Type}}) SizeSSZ() (int) { 13 | size := {{.FixedSize}} 14 | {{- .VariableSize }} 15 | return size 16 | }` 17 | 18 | func GenerateSizeSSZ(g *generateContainer) (*generatedCode, error) { 19 | sizeTmpl, err := template.New("GenerateSizeSSZ").Parse(sizeBodyTmpl) 20 | if err != nil { 21 | return nil, err 22 | } 23 | buf := bytes.NewBuffer(nil) 24 | 25 | fixedSize := 0 26 | variableComputations := make([]string, 0) 27 | for _, c := range g.Contents { 28 | vg := newValueGenerator(interfaces.SszMarshaler, c.Value, g.targetPackage, g.importNamer) 29 | fixedSize += c.Value.FixedSize() 30 | if !c.Value.IsVariableSized() { 31 | continue 32 | } 33 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key) 34 | vi, ok := vg.(valueInitializer) 35 | if ok { 36 | ini := vi.initializeValue() 37 | if ini != "" { 38 | variableComputations = append(variableComputations, fmt.Sprintf("if %s == nil {\n\t%s = %s\n}", fieldName, fieldName, ini)) 39 | } 40 | } 41 | cv := vg.variableSizeSSZ(fieldName) 42 | if cv != "" { 43 | variableComputations = append(variableComputations, fmt.Sprintf("\tsize += %s", cv)) 44 | } 45 | } 46 | 47 | err = sizeTmpl.Execute(buf, struct { 48 | Receiver string 49 | Type string 50 | FixedSize int 51 | VariableSize string 52 | }{ 53 | Receiver: receiverName, 54 | Type: fmt.Sprintf("*%s", g.TypeName()), 55 | FixedSize: fixedSize, 56 | VariableSize: "\n" + strings.Join(variableComputations, "\n"), 57 | }) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &generatedCode{ 62 | blocks: []string{buf.String()}, 63 | }, nil 64 | } 65 | -------------------------------------------------------------------------------- /sszgen/backend/gensize_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | func TestGenerateSizeSSZ(t *testing.T) { 12 | b, err := os.ReadFile("testdata/TestGenerateSizeSSZ.expected") 13 | require.NoError(t, err) 14 | expected := string(b) 15 | 16 | ty, ok := testFixBeaconState.(*types.ValueContainer) 17 | require.Equal(t, true, ok) 18 | inm := NewImportNamer("", nil) 19 | gc, err := GenerateSizeSSZ(&generateContainer{ValueContainer: ty, targetPackage: "", importNamer: inm}) 20 | require.NoError(t, err) 21 | // the size code for BeaconState is all fixed values and calls to values inside loops, so it can safely assume nothing needs 22 | // to be initialized. 23 | // TODO: Add a test case for size code for a type like BeaconBlockBodyBellatrix that needs to init for safety 24 | // (ie actually requires imports) 25 | require.Equal(t, 0, len(gc.imports)) 26 | actual, err := normalizeFixtureString(gc.blocks[0]) 27 | require.NoError(t, err) 28 | require.Equal(t, expected, actual) 29 | } 30 | -------------------------------------------------------------------------------- /sszgen/backend/genunmarshal.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | "text/template" 8 | 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 10 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 11 | ) 12 | 13 | var generateUnmarshalSSZTmpl = `func ({{.Receiver}} {{.Type}}) UnmarshalSSZ(buf []byte) error { 14 | var err error 15 | size := uint64(len(buf)) 16 | if size {{ .SizeInequality }} {{ .FixedOffset }} { 17 | return ssz.ErrSize 18 | } 19 | 20 | {{ .SliceDeclaration }} 21 | {{ .ValueUnmarshaling }} 22 | return err 23 | }` 24 | 25 | func GenerateUnmarshalSSZ(g *generateContainer) (*generatedCode, error) { 26 | sizeInequality := "!=" 27 | if g.IsVariableSized() { 28 | sizeInequality = "<" 29 | } 30 | ums := g.unmarshalSteps() 31 | unmarshalBlocks := make([]string, 0) 32 | for i, c := range g.Contents { 33 | unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key)) 34 | mg := newValueGenerator(interfaces.SszUnmarshaler, c.Value, g.targetPackage, g.importNamer) 35 | fieldName := fmt.Sprintf("%s.%s", receiverName, c.Key) 36 | 37 | vi, ok := mg.(valueInitializer) 38 | if ok { 39 | ini := vi.initializeValue() 40 | if ini != "" { 41 | unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("%s = %s", fieldName, ini)) 42 | } 43 | } 44 | 45 | sliceName := fmt.Sprintf("s%d", i) 46 | mv := mg.generateUnmarshalValue(fieldName, sliceName) 47 | if mv != "" { 48 | //unmarshalBlocks = append(unmarshalBlocks, fmt.Sprintf("\t%s = %s", fieldName, mv)) 49 | unmarshalBlocks = append(unmarshalBlocks, mv) 50 | } 51 | 52 | /* 53 | if !c.Value.IsVariableSized() { 54 | continue 55 | } 56 | _, ok := mg.(variableUnmarshaller) 57 | if !ok { 58 | continue 59 | } 60 | vm := mg.(variableUnmarshaller) 61 | vmc := vm.generateVariableUnmarshalValue(fieldName) 62 | if vmc != "" { 63 | unmarshalVariableBlocks = append(unmarshalVariableBlocks, fmt.Sprintf("\n\t// Field %d: %s", i, c.Key)) 64 | unmarshalVariableBlocks = append(unmarshalVariableBlocks, "\t" + vmc) 65 | } 66 | */ 67 | } 68 | 69 | sliceDeclarations := strings.Join([]string{ums.fixedSlices(), "", ums.variableSlices(g.fixedOffset())}, "\n") 70 | unmTmpl, err := template.New("GenerateUnmarshalSSZTmpl").Parse(generateUnmarshalSSZTmpl) 71 | if err != nil { 72 | return nil, err 73 | } 74 | buf := bytes.NewBuffer(nil) 75 | err = unmTmpl.Execute(buf, struct { 76 | Receiver string 77 | Type string 78 | SizeInequality string 79 | FixedOffset int 80 | SliceDeclaration string 81 | ValueUnmarshaling string 82 | }{ 83 | Receiver: receiverName, 84 | Type: fmt.Sprintf("*%s", g.TypeName()), 85 | SizeInequality: sizeInequality, 86 | FixedOffset: g.fixedOffset(), 87 | SliceDeclaration: sliceDeclarations, 88 | ValueUnmarshaling: strings.Join(unmarshalBlocks, "\n"), 89 | }) 90 | if err != nil { 91 | return nil, err 92 | } 93 | return &generatedCode{ 94 | blocks: []string{buf.String()}, 95 | }, nil 96 | } 97 | 98 | type unmarshalStep struct { 99 | valRep types.ValRep 100 | fieldNumber int 101 | fieldName string 102 | beginByte int 103 | endByte int 104 | previousVariable *unmarshalStep 105 | nextVariable *unmarshalStep 106 | } 107 | 108 | type unmarshalStepSlice []*unmarshalStep 109 | 110 | func (us *unmarshalStep) fixedSize() int { 111 | return us.valRep.FixedSize() 112 | } 113 | 114 | func (us *unmarshalStep) variableOffset(outerFixedSize int) string { 115 | o := fmt.Sprintf("v%d := ssz.ReadOffset(buf[%d:%d]) // %s", us.fieldNumber, us.beginByte, us.endByte, us.fieldName) 116 | if us.previousVariable == nil { 117 | o += fmt.Sprintf("\nif v%d < %d {\n\treturn ssz.ErrInvalidVariableOffset\n}", us.fieldNumber, outerFixedSize) 118 | o += fmt.Sprintf("\nif v%d > size {\n\treturn ssz.ErrOffset\n}", us.fieldNumber) 119 | } else { 120 | o += fmt.Sprintf("\nif v%d > size || v%d < v%d {\n\treturn ssz.ErrOffset\n}", us.fieldNumber, us.fieldNumber, us.previousVariable.fieldNumber) 121 | } 122 | return o 123 | } 124 | 125 | func (us *unmarshalStep) slice() string { 126 | if us.valRep.IsVariableSized() { 127 | if us.nextVariable == nil { 128 | return fmt.Sprintf("s%d := buf[v%d:]\t\t// %s", us.fieldNumber, us.fieldNumber, us.fieldName) 129 | } 130 | return fmt.Sprintf("s%d := buf[v%d:v%d]\t\t// %s", us.fieldNumber, us.fieldNumber, us.nextVariable.fieldNumber, us.fieldName) 131 | } 132 | return fmt.Sprintf("s%d := buf[%d:%d]\t\t// %s", us.fieldNumber, us.beginByte, us.endByte, us.fieldName) 133 | } 134 | 135 | func (steps unmarshalStepSlice) fixedSlices() string { 136 | slices := make([]string, 0) 137 | for _, s := range steps { 138 | if s.valRep.IsVariableSized() { 139 | continue 140 | } 141 | slices = append(slices, s.slice()) 142 | } 143 | return strings.Join(slices, "\n") 144 | } 145 | 146 | func (steps unmarshalStepSlice) variableSlices(outerSize int) string { 147 | validate := make([]string, 0) 148 | assign := make([]string, 0) 149 | for _, s := range steps { 150 | if !s.valRep.IsVariableSized() { 151 | continue 152 | } 153 | validate = append(validate, s.variableOffset(outerSize)) 154 | assign = append(assign, s.slice()) 155 | } 156 | return strings.Join(append(validate, assign...), "\n") 157 | } 158 | 159 | func (g *generateContainer) unmarshalSteps() unmarshalStepSlice { 160 | ums := make([]*unmarshalStep, 0) 161 | var begin, end int 162 | var prevVariable *unmarshalStep 163 | for i, c := range g.Contents { 164 | begin = end 165 | end += c.Value.FixedSize() 166 | um := &unmarshalStep{ 167 | valRep: c.Value, 168 | fieldNumber: i, 169 | fieldName: fmt.Sprintf("%s.%s", receiverName, c.Key), 170 | beginByte: begin, 171 | endByte: end, 172 | } 173 | if c.Value.IsVariableSized() { 174 | if prevVariable != nil { 175 | um.previousVariable = prevVariable 176 | prevVariable.nextVariable = um 177 | } 178 | prevVariable = um 179 | } 180 | 181 | ums = append(ums, um) 182 | } 183 | return ums 184 | } 185 | -------------------------------------------------------------------------------- /sszgen/backend/genunmarshal_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 9 | "github.com/prysmaticlabs/prysm/v3/testing/require" 10 | ) 11 | 12 | func TestGenerateUnmarshalSSZ(t *testing.T) { 13 | t.Skip("fixtures need to be updated") 14 | b, err := os.ReadFile("testdata/TestGenerateUnmarshalSSZ.expected") 15 | require.NoError(t, err) 16 | expected := string(b) 17 | 18 | vc, ok := testFixBeaconState.(*types.ValueContainer) 19 | require.Equal(t, true, ok) 20 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""} 21 | code, err := GenerateUnmarshalSSZ(gc) 22 | require.NoError(t, err) 23 | require.Equal(t, 4, len(code.imports)) 24 | actual, err := normalizeFixtureString(code.blocks[0]) 25 | require.NoError(t, err) 26 | require.Equal(t, expected, actual) 27 | } 28 | 29 | func TestUnmarshalSteps(t *testing.T) { 30 | fixturePath := "testdata/TestUnmarshalSteps.expected" 31 | b, err := os.ReadFile(fixturePath) 32 | require.NoError(t, err) 33 | expected, err := normalizeFixtureBytes(b) 34 | require.NoError(t, err) 35 | 36 | vc, ok := testFixBeaconState.(*types.ValueContainer) 37 | require.Equal(t, true, ok) 38 | gc := &generateContainer{ValueContainer: vc, targetPackage: ""} 39 | ums := gc.unmarshalSteps() 40 | require.Equal(t, 21, len(ums)) 41 | require.Equal(t, ums[15].nextVariable.fieldNumber, ums[16].fieldNumber) 42 | 43 | gotRaw := strings.Join([]string{ums.fixedSlices(), "", ums.variableSlices(gc.fixedOffset())}, "\n") 44 | actual, err := normalizeFixtureString(gotRaw) 45 | require.NoError(t, err) 46 | require.Equal(t, expected, actual) 47 | } 48 | -------------------------------------------------------------------------------- /sszgen/backend/imports.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | "strings" 7 | ) 8 | 9 | type ImportNamer struct { 10 | source string 11 | aliases map[string]string // map from package path -> alias 12 | reverse map[string]string // map from alias -> package path 13 | } 14 | 15 | var pbRuntime = "google.golang.org/protobuf/runtime/protoimpl" 16 | var protoAliases = map[string]string{ 17 | pbRuntime: "protoimpl", 18 | "google.golang.org/protobuf/internal/impl": "protoimpl", 19 | } 20 | 21 | func (n *ImportNamer) NameString(p string) string { 22 | if alias, isProto := protoAliases[p]; isProto { 23 | n.reverse[alias] = pbRuntime 24 | n.aliases[pbRuntime] = alias 25 | return alias 26 | } 27 | // no package name for self 28 | if p == n.source { 29 | return "" 30 | } 31 | name, exists := n.aliases[p] 32 | if exists { 33 | return name 34 | } 35 | // build increasingly long path suffixes until a unique one is found 36 | parts := strings.Split(p, "/") 37 | for i := 0; i < len(parts); i++ { 38 | name := strings.Join(parts[len(parts)-1-i:], "_") 39 | // deal with domain portion of path for extreme case where 2 packages only differ in domain 40 | name = strings.ReplaceAll(name, ".", "_") 41 | // dashes are valid in package names but not go identifiers - like go-bitfield 42 | name = strings.ReplaceAll(name, "-", "_") 43 | _, conflict := n.reverse[name] 44 | if conflict { 45 | continue 46 | } 47 | n.reverse[name] = p 48 | n.aliases[p] = name 49 | return name 50 | } 51 | panic(fmt.Sprintf("unable to find unique name for package %s", p)) 52 | } 53 | 54 | func (n *ImportNamer) Name(p *types.Package) string { 55 | return n.NameString(p.Path()) 56 | } 57 | 58 | func (n *ImportNamer) ImportSource() string { 59 | imports := make([]string, 0) 60 | for pkg, alias := range n.aliases { 61 | if alias == "google.golang.org/protobuf/internal/impl" { 62 | imports = append(imports, fmt.Sprintf("%s \"google.golang.org/protobuf/runtime/protoimpl\"", alias)) 63 | } else { 64 | imports = append(imports, fmt.Sprintf("%s \"%s\"", alias, pkg)) 65 | } 66 | } 67 | 68 | return fmt.Sprintf("import (\n%s\n)\n", strings.Join(imports, "\n")) 69 | } 70 | 71 | func (n *ImportNamer) ImportPairs() string { 72 | imports := make([]string, 0) 73 | stdImports := make([]string, 0) 74 | for alias, pkg := range n.reverse { 75 | if pkg == "" { 76 | stdImports = append(stdImports, fmt.Sprintf("\"%s\"", alias)) 77 | } else { 78 | imports = append(imports, fmt.Sprintf("%s \"%s\"", alias, pkg)) 79 | } 80 | } 81 | 82 | if len(stdImports) > 0 { 83 | return strings.Join(stdImports, "\n") + "\n\n" + strings.Join(imports, "\n") 84 | } 85 | return strings.Join(imports, "\n") 86 | } 87 | 88 | func NewImportNamer(source string, defaults map[string]string) *ImportNamer { 89 | aliases := make(map[string]string) 90 | reverse := make(map[string]string) 91 | for pkg, alias := range defaults { 92 | if pkg != "" { 93 | aliases[pkg] = alias 94 | } 95 | reverse[alias] = pkg 96 | } 97 | return &ImportNamer{ 98 | source: source, 99 | aliases: aliases, 100 | reverse: reverse, 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /sszgen/backend/list.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 10 | ) 11 | 12 | type generateList struct { 13 | valRep *types.ValueList 14 | targetPackage string 15 | casterConfig 16 | importNamer *ImportNamer 17 | } 18 | 19 | var generateListHTRPutterTmpl = `{ 20 | if len({{.FieldName}}) > {{.MaxSize}} { 21 | return ssz.ErrListTooBig 22 | } 23 | subIndx := hh.Index() 24 | for _, {{.NestedFieldName}} := range {{.FieldName}} { 25 | {{.AppendCall}} 26 | } 27 | {{- .PadCall}} 28 | {{.Merkleize}} 29 | }` 30 | 31 | type listPutterElements struct { 32 | FieldName string 33 | NestedFieldName string 34 | MaxSize int 35 | AppendCall string 36 | PadCall string 37 | Merkleize string 38 | } 39 | 40 | func renderHtrListPutter(lpe listPutterElements) string { 41 | tmpl, err := template.New("renderHtrListPutter").Parse(generateListHTRPutterTmpl) 42 | if err != nil { 43 | panic(err) 44 | } 45 | buf := bytes.NewBuffer(nil) 46 | err = tmpl.Execute(buf, lpe) 47 | if err != nil { 48 | panic(err) 49 | } 50 | return buf.String() 51 | } 52 | 53 | func (g *generateList) generateHTRPutter(fieldName string) string { 54 | nestedFieldName := "o" 55 | if fieldName[0:1] == "o" && monoCharacter(fieldName) { 56 | nestedFieldName = fieldName + "o" 57 | } 58 | 59 | // resolve pointers and overlays to their underlying types 60 | vr := g.valRep.ElementValue 61 | if vrp, isPointer := vr.(*types.ValuePointer); isPointer { 62 | vr = vrp.Referent 63 | } 64 | if vro, isOverlay := vr.(*types.ValueOverlay); isOverlay { 65 | vr = vro.Underlying 66 | } 67 | 68 | lpe := listPutterElements{ 69 | FieldName: fieldName, 70 | NestedFieldName: nestedFieldName, 71 | MaxSize: g.valRep.MaxSize, 72 | } 73 | switch v := vr.(type) { 74 | case *types.ValueByte: 75 | t := `if len(%s) > %d { 76 | return ssz.ErrBytesLength 77 | } 78 | subIndx := hh.Index() 79 | hh.PutBytes(%s)` 80 | putBytes := fmt.Sprintf(t, fieldName, g.valRep.MaxSize, fieldName) 81 | mtmpl := `numItems := uint64(len(%s)) 82 | hh.MerkleizeWithMixin(subIndx, numItems, (%d*%d + 31)/32)` 83 | res := "\n{\n" + putBytes + "\n" + fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize()) + "\n}\n" 84 | return res 85 | case *types.ValueVector: 86 | gv := &generateVector{valRep: v, targetPackage: g.targetPackage} 87 | if gv.isByteVector() { 88 | lpe.AppendCall = gv.renderByteSliceAppend(nestedFieldName) 89 | mtmpl := `numItems := uint64(len(%s)) 90 | hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(%d, numItems, %d))` 91 | lpe.Merkleize = fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize()) 92 | return renderHtrListPutter(lpe) 93 | } 94 | case *types.ValueUint: 95 | lpe.AppendCall = fmt.Sprintf("hh.AppendUint%d(%s)", v.Size, nestedFieldName) 96 | if v.FixedSize()%ChunkSize != 0 { 97 | lpe.PadCall = "\nhh.FillUpTo32()" 98 | } 99 | mtmpl := `numItems := uint64(len(%s)) 100 | hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(%d, numItems, %d))` 101 | lpe.Merkleize = fmt.Sprintf(mtmpl, fieldName, g.valRep.MaxSize, v.FixedSize()) 102 | return renderHtrListPutter(lpe) 103 | case *types.ValueContainer: 104 | gc := newValueGenerator(interfaces.SszLightHasher, v, g.targetPackage, g.importNamer) 105 | lpe.AppendCall = gc.generateHTRPutter(nestedFieldName) 106 | lpe.Merkleize = fmt.Sprintf("hh.MerkleizeWithMixin(subIndx, uint64(len(%s)), %d)", fieldName, g.valRep.MaxSize) 107 | return renderHtrListPutter(lpe) 108 | default: 109 | panic(fmt.Sprintf("unsupported type combination - list of %v", v)) 110 | } 111 | return "" 112 | } 113 | 114 | var generateListGenerateUnmarshalValueFixedTmpl = `{ 115 | if len({{.SliceName}}) % {{.ElementSize}} != 0 { 116 | return fmt.Errorf("misaligned bytes: {{.FieldName}} length is %d, which is not a multiple of {{.ElementSize}}", len({{.SliceName}})) 117 | } 118 | numElem := len({{.SliceName}}) / {{.ElementSize}} 119 | if numElem > {{ .MaxSize }} { 120 | return fmt.Errorf("ssz-max exceeded: {{.FieldName}} has %d elements, ssz-max is {{.MaxSize}}", numElem) 121 | } 122 | {{.FieldName}} = make([]{{.TypeName}}, numElem) 123 | for {{.LoopVar}} := 0; {{.LoopVar}} < numElem; {{.LoopVar}}++ { 124 | var tmp {{.TypeName}} 125 | {{.Initializer}} 126 | tmpSlice := {{.SliceName}}[{{.LoopVar}}*{{.NestedFixedSize}}:(1+{{.LoopVar}})*{{.NestedFixedSize}}] 127 | {{.NestedUnmarshal}} 128 | {{.FieldName}}[{{.LoopVar}}] = tmp 129 | } 130 | }` 131 | 132 | var generateListGenerateUnmarshalValueVariableTmpl = `{ 133 | // empty lists are zero length, so make sure there is room for an offset 134 | // before attempting to unmarshal it 135 | if len({{.SliceName}}) > 3 { 136 | firstOffset := ssz.ReadOffset({{.SliceName}}[0:4]) 137 | if firstOffset % 4 != 0 { 138 | return fmt.Errorf("misaligned list bytes: when decoding {{.FieldName}}, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset) 139 | } 140 | listLen := firstOffset / 4 141 | if listLen > {{.MaxSize}} { 142 | return fmt.Errorf("ssz-max exceeded: {{.FieldName}} has %d elements, ssz-max is {{.MaxSize}}", listLen) 143 | } 144 | listOffsets := make([]uint64, listLen) 145 | for {{.LoopVar}} := 0; uint64({{.LoopVar}}) < listLen; {{.LoopVar}}++ { 146 | listOffsets[{{.LoopVar}}] = ssz.ReadOffset({{.SliceName}}[{{.LoopVar}}*4:({{.LoopVar}}+1)*4]) 147 | } 148 | {{.FieldName}} = make([]{{.TypeName}}, len(listOffsets)) 149 | for {{.LoopVar}} := 0; {{.LoopVar}} < len(listOffsets); {{.LoopVar}}++ { 150 | var tmp {{.TypeName}} 151 | {{.Initializer}} 152 | var tmpSlice []byte 153 | if {{.LoopVar}}+1 == len(listOffsets) { 154 | tmpSlice = {{.SliceName}}[listOffsets[{{.LoopVar}}]:] 155 | } else { 156 | tmpSlice = {{.SliceName}}[listOffsets[{{.LoopVar}}]:listOffsets[{{.LoopVar}}+1]] 157 | } 158 | {{.NestedUnmarshal}} 159 | {{.FieldName}}[{{.LoopVar}}] = tmp 160 | } 161 | } 162 | }` 163 | 164 | func (g *generateList) generateUnmarshalVariableValue(fieldName string, sliceName string) string { 165 | loopVar := "i" 166 | if fieldName[0:1] == "i" && monoCharacter(fieldName) { 167 | loopVar = fieldName + "i" 168 | } 169 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 170 | vi, ok := gg.(valueInitializer) 171 | var initializer string 172 | if ok { 173 | initializer = vi.initializeValue() 174 | if initializer != "" { 175 | initializer = "tmp = " + initializer 176 | } 177 | } 178 | tmpl, err := template.New("generateListGenerateUnmarshalValueVariableTmpl").Parse(generateListGenerateUnmarshalValueVariableTmpl) 179 | if err != nil { 180 | panic(err) 181 | } 182 | buf := bytes.NewBuffer(nil) 183 | err = tmpl.Execute(buf, struct { 184 | LoopVar string 185 | SliceName string 186 | ElementSize int 187 | TypeName string 188 | FieldName string 189 | MaxSize int 190 | Initializer string 191 | NestedFixedSize int 192 | NestedUnmarshal string 193 | }{ 194 | LoopVar: loopVar, 195 | SliceName: sliceName, 196 | ElementSize: g.valRep.ElementValue.FixedSize(), 197 | TypeName: fullyQualifiedTypeName(g.valRep.ElementValue, g.targetPackage, g.importNamer), 198 | FieldName: fieldName, 199 | MaxSize: g.valRep.MaxSize, 200 | Initializer: initializer, 201 | NestedFixedSize: g.valRep.ElementValue.FixedSize(), 202 | NestedUnmarshal: gg.generateUnmarshalValue("tmp", "tmpSlice"), 203 | }) 204 | if err != nil { 205 | panic(err) 206 | } 207 | return buf.String() 208 | } 209 | 210 | func (g *generateList) generateUnmarshalFixedValue(fieldName string, sliceName string) string { 211 | loopVar := "i" 212 | if fieldName[0:1] == "i" && monoCharacter(fieldName) { 213 | loopVar = fieldName + "i" 214 | } 215 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 216 | nestedUnmarshal := "" 217 | switch g.valRep.ElementValue.(type) { 218 | case *types.ValueByte: 219 | return fmt.Sprintf("%s = append([]byte{}, %s...)", fieldName, g.casterConfig.toOverlay(sliceName)) 220 | default: 221 | nestedUnmarshal = gg.generateUnmarshalValue("tmp", "tmpSlice") 222 | } 223 | vi, ok := gg.(valueInitializer) 224 | var initializer string 225 | if ok { 226 | initializer = vi.initializeValue() 227 | if initializer != "" { 228 | initializer = "tmp = " + initializer 229 | } 230 | } 231 | tmpl, err := template.New("generateListGenerateUnmarshalValueFixedTmpl").Parse(generateListGenerateUnmarshalValueFixedTmpl) 232 | if err != nil { 233 | panic(err) 234 | } 235 | buf := bytes.NewBuffer(nil) 236 | err = tmpl.Execute(buf, struct { 237 | LoopVar string 238 | SliceName string 239 | ElementSize int 240 | TypeName string 241 | FieldName string 242 | MaxSize int 243 | Initializer string 244 | NestedFixedSize int 245 | NestedUnmarshal string 246 | }{ 247 | LoopVar: loopVar, 248 | SliceName: sliceName, 249 | ElementSize: g.valRep.ElementValue.FixedSize(), 250 | TypeName: fullyQualifiedTypeName(g.valRep.ElementValue, g.targetPackage, g.importNamer), 251 | FieldName: fieldName, 252 | MaxSize: g.valRep.MaxSize, 253 | Initializer: initializer, 254 | NestedFixedSize: g.valRep.ElementValue.FixedSize(), 255 | NestedUnmarshal: nestedUnmarshal, 256 | }) 257 | if err != nil { 258 | panic(err) 259 | } 260 | return buf.String() 261 | } 262 | 263 | func (g *generateList) generateUnmarshalValue(fieldName string, sliceName string) string { 264 | if g.valRep.ElementValue.IsVariableSized() { 265 | return g.generateUnmarshalVariableValue(fieldName, sliceName) 266 | } else { 267 | return g.generateUnmarshalFixedValue(fieldName, sliceName) 268 | } 269 | } 270 | 271 | func (g *generateList) generateFixedMarshalValue(fieldName string) string { 272 | tmpl := `dst = ssz.WriteOffset(dst, offset) 273 | offset += %s 274 | ` 275 | offset := g.variableSizeSSZ(fieldName) 276 | 277 | return fmt.Sprintf(tmpl, offset) 278 | } 279 | 280 | var variableSizedListTmpl = `func() int { 281 | s := 0 282 | for _, o := range {{ .FieldName }} { 283 | s += 4 284 | s += {{ .SizeComputation }} 285 | } 286 | return s 287 | }()` 288 | 289 | func (g *generateList) variableSizeSSZ(fieldName string) string { 290 | if !g.valRep.ElementValue.IsVariableSized() { 291 | return fmt.Sprintf("len(%s) * %d", fieldName, g.valRep.ElementValue.FixedSize()) 292 | } 293 | 294 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 295 | vslTmpl, err := template.New("variableSizedListTmpl").Parse(variableSizedListTmpl) 296 | if err != nil { 297 | panic(err) 298 | } 299 | buf := bytes.NewBuffer(nil) 300 | err = vslTmpl.Execute(buf, struct { 301 | FieldName string 302 | SizeComputation string 303 | }{ 304 | FieldName: fieldName, 305 | SizeComputation: gg.variableSizeSSZ("o"), 306 | }) 307 | if err != nil { 308 | panic(err) 309 | } 310 | return buf.String() 311 | } 312 | 313 | var generateVariableMarshalValueTmpl = `if len({{ .FieldName }}) > {{ .MaxSize }} { 314 | return nil, ssz.ErrListTooBig 315 | } 316 | 317 | for _, o := range {{ .FieldName }} { 318 | if len(o) != {{ .ElementSize }} { 319 | return nil, ssz.ErrBytesLength 320 | } 321 | dst = append(dst, o) 322 | }` 323 | 324 | var tmplVariableOffsetManagement = `{ 325 | offset = 4 * len({{.FieldName}}) 326 | for _, {{.NestedFieldName}} := range {{.FieldName}} { 327 | dst = ssz.WriteOffset(dst, offset) 328 | offset += {{.SizeComputation}} 329 | } 330 | } 331 | ` 332 | 333 | func variableOffsetManagement(vg valueGenerator, fieldName, nestedFieldName string) string { 334 | vomt, err := template.New("tmplVariableOffsetManagement").Parse(tmplVariableOffsetManagement) 335 | if err != nil { 336 | panic(err) 337 | } 338 | buf := bytes.NewBuffer(nil) 339 | err = vomt.Execute(buf, struct { 340 | FieldName string 341 | NestedFieldName string 342 | SizeComputation string 343 | }{ 344 | FieldName: fieldName, 345 | NestedFieldName: nestedFieldName, 346 | SizeComputation: vg.variableSizeSSZ(nestedFieldName), 347 | }) 348 | if err != nil { 349 | panic(err) 350 | } 351 | return buf.String() 352 | } 353 | 354 | var tmplGenerateMarshalValueList = `if len({{.FieldName}}) > {{.MaxSize}} { 355 | return nil, ssz.ErrListTooBig 356 | } 357 | {{.OffsetManagement}}{{.MarshalValue}}` 358 | 359 | func (g *generateList) generateVariableMarshalValue(fieldName string) string { 360 | mvTmpl, err := template.New("tmplGenerateMarshalValueList").Parse(tmplGenerateMarshalValueList) 361 | if err != nil { 362 | panic(err) 363 | } 364 | var marshalValue string 365 | var offsetMgmt string 366 | switch g.valRep.ElementValue.(type) { 367 | case *types.ValueByte: 368 | marshalValue = fmt.Sprintf("dst = append(dst, %s...)", fieldName) 369 | default: 370 | nestedFieldName := "o" 371 | if fieldName[0:1] == "o" && monoCharacter(fieldName) { 372 | nestedFieldName = fieldName + "o" 373 | } 374 | t := `for _, %s := range %s { 375 | %s 376 | }` 377 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 378 | var internal string 379 | if g.valRep.ElementValue.IsVariableSized() { 380 | vm, ok := gg.(variableMarshaller) 381 | if !ok { 382 | panic(fmt.Sprintf("variable size type does not implement variableMarshaller: %v", g.valRep.ElementValue)) 383 | } 384 | internal = vm.generateVariableMarshalValue(nestedFieldName) 385 | offsetMgmt = variableOffsetManagement(gg, fieldName, nestedFieldName) 386 | } else { 387 | internal = gg.generateFixedMarshalValue(nestedFieldName) 388 | } 389 | marshalValue = fmt.Sprintf(t, nestedFieldName, fieldName, internal) 390 | } 391 | buf := bytes.NewBuffer(nil) 392 | err = mvTmpl.Execute(buf, struct { 393 | FieldName string 394 | MaxSize int 395 | MarshalValue string 396 | OffsetManagement string 397 | }{ 398 | FieldName: fieldName, 399 | MaxSize: g.valRep.MaxSize, 400 | MarshalValue: marshalValue, 401 | OffsetManagement: offsetMgmt, 402 | }) 403 | if err != nil { 404 | panic(err) 405 | } 406 | return buf.String() 407 | } 408 | 409 | var _ valueGenerator = &generateList{} 410 | -------------------------------------------------------------------------------- /sszgen/backend/overlay.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | ) 9 | 10 | type generateOverlay struct { 11 | *types.ValueOverlay 12 | targetPackage string 13 | importNamer *ImportNamer 14 | } 15 | 16 | func (g *generateOverlay) toOverlay() func(string) string { 17 | wrapper := g.TypeName() 18 | if g.targetPackage != g.PackagePath() { 19 | wrapper = g.importNamer.NameString(g.PackagePath()) + "." + wrapper 20 | } 21 | return func(value string) string { 22 | return fmt.Sprintf("%s(%s)", wrapper, value) 23 | } 24 | } 25 | 26 | func (g *generateOverlay) generateVariableMarshalValue(fieldName string) string { 27 | gg := newValueGenerator(interfaces.SszMarshaler, g.Underlying, g.targetPackage, g.importNamer) 28 | vm, ok := gg.(variableMarshaller) 29 | if !ok { 30 | return "" 31 | } 32 | return vm.generateVariableMarshalValue(fieldName) 33 | } 34 | 35 | func (g *generateOverlay) generateUnmarshalValue(fieldName string, sliceName string) string { 36 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.Underlying, g.targetPackage, g.importNamer) 37 | c, ok := gg.(caster) 38 | if ok { 39 | c.setToOverlay(g.toOverlay()) 40 | } 41 | umv := gg.generateUnmarshalValue(fieldName, sliceName) 42 | if g.IsBitfield() { 43 | switch t := g.Underlying.(type) { 44 | case *types.ValueList: 45 | return fmt.Sprintf(`if err = ssz.ValidateBitlist(%s, %d); err != nil { 46 | return err 47 | } 48 | %s`, sliceName, t.MaxSize, umv) 49 | } 50 | } 51 | return umv 52 | } 53 | 54 | func (g *generateOverlay) generateFixedMarshalValue(fieldName string) string { 55 | gg := newValueGenerator(interfaces.SszMarshaler, g.Underlying, g.targetPackage, g.importNamer) 56 | uc, ok := gg.(coercer) 57 | if ok { 58 | return gg.generateFixedMarshalValue(uc.coerce()(fieldName)) 59 | } 60 | return gg.generateFixedMarshalValue(fieldName) 61 | } 62 | 63 | func (g *generateOverlay) variableSizeSSZ(fieldname string) string { 64 | return "" 65 | } 66 | 67 | func (g *generateOverlay) generateHTRPutter(fieldName string) string { 68 | if g.IsBitfield() && g.Name == "Bitlist" { 69 | ul, ok := g.Underlying.(*types.ValueList) 70 | if !ok { 71 | panic(fmt.Sprintf("unexpected underlying type for Bitlist, expected ValueList, got %v", g.Underlying)) 72 | } 73 | t := `if len(%s) == 0 { 74 | return ssz.ErrEmptyBitlist 75 | } 76 | hh.PutBitlist(%s, %d)` 77 | return fmt.Sprintf(t, fieldName, fieldName, ul.MaxSize) 78 | } 79 | gg := newValueGenerator(interfaces.SszLightHasher, g.Underlying, g.targetPackage, g.importNamer) 80 | htrp, ok := gg.(htrPutter) 81 | if !ok { 82 | return "" 83 | } 84 | uc, ok := gg.(coercer) 85 | if ok { 86 | c := uc.coerce() 87 | return htrp.generateHTRPutter(c(fieldName)) 88 | } 89 | return htrp.generateHTRPutter(fieldName) 90 | } 91 | 92 | var _ valueGenerator = &generateOverlay{} 93 | -------------------------------------------------------------------------------- /sszgen/backend/pointer.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 8 | ) 9 | 10 | type generatePointer struct { 11 | *types.ValuePointer 12 | targetPackage string 13 | importNamer *ImportNamer 14 | } 15 | 16 | func (g *generatePointer) generateHTRPutter(fieldName string) string { 17 | gg := newValueGenerator(interfaces.SszLightHasher, g.Referent, g.targetPackage, g.importNamer) 18 | hp, ok := gg.(htrPutter) 19 | if !ok { 20 | return "" 21 | } 22 | return hp.generateHTRPutter(fieldName) 23 | } 24 | 25 | func (g *generatePointer) generateFixedMarshalValue(fieldName string) string { 26 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer) 27 | return gg.generateFixedMarshalValue(fieldName) 28 | } 29 | 30 | func (g *generatePointer) generateUnmarshalValue(fieldName string, sliceName string) string { 31 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.Referent, g.targetPackage, g.importNamer) 32 | return gg.generateUnmarshalValue(fieldName, sliceName) 33 | } 34 | 35 | func (g *generatePointer) initializeValue() string { 36 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer) 37 | iv, ok := gg.(valueInitializer) 38 | if ok { 39 | return iv.initializeValue() 40 | } 41 | return "" 42 | } 43 | 44 | func (g *generatePointer) generateVariableMarshalValue(fieldName string) string { 45 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer) 46 | vm, ok := gg.(variableMarshaller) 47 | if !ok { 48 | panic(fmt.Sprintf("variable size type does not implement variableMarshaller: %v", g.Referent)) 49 | } 50 | return vm.generateVariableMarshalValue(fieldName) 51 | } 52 | 53 | func (g *generatePointer) variableSizeSSZ(fieldName string) string { 54 | gg := newValueGenerator(interfaces.SszMarshaler, g.Referent, g.targetPackage, g.importNamer) 55 | return gg.variableSizeSSZ(fieldName) 56 | } 57 | 58 | var _ valueGenerator = &generatePointer{} 59 | var _ htrPutter = &generatePointer{} 60 | -------------------------------------------------------------------------------- /sszgen/backend/render.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "go/format" 7 | "go/types" 8 | "strings" 9 | "text/template" 10 | 11 | gentypes "github.com/OffchainLabs/methodical-ssz/sszgen/types" 12 | ) 13 | 14 | type generatedCode struct { 15 | blocks []string 16 | // key=package path, value=alias 17 | imports map[string]string 18 | } 19 | 20 | func (gc *generatedCode) renderImportPairs() string { 21 | pairs := make([]string, 0) 22 | for k, v := range gc.imports { 23 | pairs = append(pairs, fmt.Sprintf("%s \"%s\"", v, k)) 24 | } 25 | return strings.Join(pairs, "\n") 26 | } 27 | 28 | func (gc *generatedCode) renderBlocks() string { 29 | return strings.Join(gc.blocks, "\n\n") 30 | } 31 | 32 | func (gc *generatedCode) merge(right *generatedCode) { 33 | gc.blocks = append(gc.blocks, right.blocks...) 34 | } 35 | 36 | // Generator needs to be initialized with the package name, 37 | // so use the new NewGenerator func for proper setup. 38 | type Generator struct { 39 | gc []*generatedCode 40 | packagePath string 41 | importNamer *ImportNamer 42 | } 43 | 44 | var defaultSSZImports = map[string]string{ 45 | "github.com/prysmaticlabs/fastssz": "ssz", 46 | "fmt": "", 47 | } 48 | 49 | func NewGenerator(packagePath string) *Generator { 50 | importNamer := NewImportNamer(packagePath, defaultSSZImports) 51 | return &Generator{ 52 | packagePath: packagePath, 53 | importNamer: importNamer, 54 | } 55 | } 56 | 57 | // TODO Generate should be able to return an error 58 | func (g *Generator) Generate(vr gentypes.ValRep) error { 59 | if vc, ok := vr.(*gentypes.ValueContainer); ok { 60 | return g.genValueContainer(vc) 61 | } 62 | if vo, ok := vr.(*gentypes.ValueOverlay); ok { 63 | return g.genValueOverlay(vo) 64 | } 65 | return fmt.Errorf("can only generate method sets for container & overlay gentypes at this time, type: %v", vr.TypeName()) 66 | } 67 | 68 | func (g *Generator) genValueOverlay(vc *gentypes.ValueOverlay) error { 69 | // TODO (MariusVanDerWijden) implement for basic gentypes 70 | return nil 71 | } 72 | 73 | func (g *Generator) genValueContainer(vc *gentypes.ValueContainer) error { 74 | container := &generateContainer{ValueContainer: vc, targetPackage: g.packagePath, importNamer: g.importNamer} 75 | methods := []func(gc *generateContainer) (*generatedCode, error){ 76 | GenerateSizeSSZ, 77 | GenerateMarshalSSZ, 78 | GenerateUnmarshalSSZ, 79 | GenerateHashTreeRoot, 80 | } 81 | for _, method := range methods { 82 | code, err := method(container) 83 | if err != nil { 84 | return err 85 | } 86 | g.gc = append(g.gc, code) 87 | } 88 | return nil 89 | } 90 | 91 | var fileTemplate = `package {{.Package}} 92 | 93 | {{ if .Imports -}} 94 | import ( 95 | {{.Imports}} 96 | ) 97 | {{- end }} 98 | 99 | {{.Blocks}}` 100 | 101 | func (g *Generator) Render() ([]byte, error) { 102 | if g.packagePath == "" { 103 | return nil, fmt.Errorf("missing packagePath: Generator requires a packagePath for code generation.") 104 | } 105 | ft := template.New("generated.ssz.go") 106 | tmpl, err := ft.Parse(fileTemplate) 107 | if err != nil { 108 | return nil, err 109 | } 110 | final := &generatedCode{} 111 | for _, gc := range g.gc { 112 | final.merge(gc) 113 | } 114 | buf := bytes.NewBuffer(nil) 115 | err = tmpl.Execute(buf, struct { 116 | Package string 117 | Imports string 118 | Blocks string 119 | }{ 120 | Package: RenderedPackageName(g.packagePath), 121 | Imports: g.importNamer.ImportPairs(), 122 | Blocks: final.renderBlocks(), 123 | }) 124 | if err != nil { 125 | return nil, err 126 | } 127 | return format.Source(buf.Bytes()) 128 | //return buf.Bytes(), nil 129 | } 130 | 131 | type valueGenerator interface { 132 | variableSizeSSZ(fieldname string) string 133 | generateFixedMarshalValue(string) string 134 | generateUnmarshalValue(string, string) string 135 | generateHTRPutter(string) string 136 | } 137 | 138 | type valueInitializer interface { 139 | initializeValue() string 140 | } 141 | 142 | type variableMarshaller interface { 143 | generateVariableMarshalValue(string) string 144 | } 145 | 146 | type variableUnmarshaller interface { 147 | generateVariableUnmarshalValue(string) string 148 | } 149 | 150 | type coercer interface { 151 | coerce() func(string) string 152 | } 153 | 154 | type htrPutter interface { 155 | generateHTRPutter(string) string 156 | } 157 | 158 | func newValueGenerator(ifaceCtx *types.Interface, vr gentypes.ValRep, packagePath string, inm *ImportNamer) valueGenerator { 159 | if vr.SatisfiesInterface(ifaceCtx) { 160 | return &generateDelegate{ValRep: vr, targetPackage: packagePath, importNamer: inm} 161 | } 162 | switch ty := vr.(type) { 163 | case *gentypes.ValueBool: 164 | return &generateBool{valRep: ty, targetPackage: packagePath, importNamer: inm} 165 | case *gentypes.ValueByte: 166 | return &generateByte{ValueByte: ty, targetPackage: packagePath, importNamer: inm} 167 | case *gentypes.ValueContainer: 168 | return &generateContainer{ValueContainer: ty, targetPackage: packagePath, importNamer: inm} 169 | case *gentypes.ValueList: 170 | return &generateList{valRep: ty, targetPackage: packagePath, importNamer: inm} 171 | case *gentypes.ValueOverlay: 172 | return &generateOverlay{ValueOverlay: ty, targetPackage: packagePath, importNamer: inm} 173 | case *gentypes.ValuePointer: 174 | return &generatePointer{ValuePointer: ty, targetPackage: packagePath, importNamer: inm} 175 | case *gentypes.ValueUint: 176 | return &generateUint{valRep: ty, targetPackage: packagePath, importNamer: inm} 177 | case *gentypes.ValueUnion: 178 | return &generateUnion{ValueUnion: ty, targetPackage: packagePath, importNamer: inm} 179 | case *gentypes.ValueVector: 180 | return &generateVector{valRep: ty, targetPackage: packagePath, importNamer: inm} 181 | } 182 | panic(fmt.Sprintf("Cannot manage generation for unrecognized ValRep implementation %v", vr)) 183 | } 184 | 185 | func importAlias(packageName string) string { 186 | parts := strings.Split(packageName, "/") 187 | for i, p := range parts { 188 | if strings.Contains(p, ".") { 189 | continue 190 | } 191 | parts = parts[i:] 192 | break 193 | } 194 | return strings.ReplaceAll(strings.Join(parts, "_"), "-", "_") 195 | } 196 | 197 | func fullyQualifiedTypeName(v gentypes.ValRep, targetPackage string, inamer *ImportNamer) string { 198 | tn := v.TypeName() 199 | if targetPackage == v.PackagePath() || v.PackagePath() == "" { 200 | return tn 201 | } 202 | pkg := inamer.NameString(v.PackagePath()) 203 | 204 | if tn[0:1] == "*" { 205 | tn = tn[1:] 206 | pkg = "*" + pkg 207 | } 208 | return pkg + "." + tn 209 | } 210 | 211 | // RenderedPackageName reduces the fully qualified package name to the relative package name, ie 212 | // github.com/prysmaticlabs/prysm/v3/proto/eth/v1 -> v1 213 | func RenderedPackageName(n string) string { 214 | parts := strings.Split(n, "/") 215 | return parts[len(parts)-1] 216 | } 217 | -------------------------------------------------------------------------------- /sszgen/backend/render_test.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "go/format" 5 | "os" 6 | "testing" 7 | 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | var generator_generateFixture = `package derp 12 | 13 | import ( 14 | "fmt" 15 | derp "github.com/prysmaticlabs/derp/derp" 16 | ssz "github.com/prysmaticlabs/fastssz" 17 | ) 18 | 19 | func main() { 20 | fmt.printf("hello world") 21 | } 22 | ` 23 | 24 | func TestGenerator_Generate(t *testing.T) { 25 | gc := &generatedCode{ 26 | blocks: []string{"func main() {\n\tfmt.printf(\"hello world\")\n}"}, 27 | } 28 | defaultImports := map[string]string{ 29 | "github.com/prysmaticlabs/derp/derp": "derp", 30 | "github.com/prysmaticlabs/fastssz": "ssz", 31 | "fmt": "", 32 | } 33 | inm := NewImportNamer("github.com/prysmaticlabs/derp", defaultImports) 34 | g := &Generator{packagePath: "github.com/prysmaticlabs/derp", importNamer: inm} 35 | g.gc = append(g.gc, gc) 36 | rendered, err := g.Render() 37 | require.NoError(t, err) 38 | require.Equal(t, generator_generateFixture, string(rendered)) 39 | } 40 | 41 | func TestGenerator_GenerateBeaconState(t *testing.T) { 42 | t.Skip("fixtures need to be updated") 43 | b, err := os.ReadFile("testdata/TestGenerator_GenerateBeaconState.expected") 44 | require.NoError(t, err) 45 | formatted, err := format.Source(b) 46 | require.NoError(t, err) 47 | expected := string(formatted) 48 | 49 | g := &Generator{ 50 | packagePath: "github.com/prysmaticlabs/prysm/v3/proto/beacon/p2p/v1", 51 | } 52 | g.Generate(testFixBeaconState) 53 | rendered, err := g.Render() 54 | require.NoError(t, err) 55 | actual := string(rendered) 56 | require.Equal(t, expected, actual) 57 | } 58 | 59 | func TestImportAlias(t *testing.T) { 60 | cases := []struct { 61 | packageName string 62 | alias string 63 | }{ 64 | { 65 | packageName: "github.com/derp/derp", 66 | alias: "derp_derp", 67 | }, 68 | { 69 | packageName: "text/template", 70 | alias: "text_template", 71 | }, 72 | { 73 | packageName: "fmt", 74 | alias: "fmt", 75 | }, 76 | } 77 | for _, c := range cases { 78 | require.Equal(t, importAlias(c.packageName), c.alias) 79 | } 80 | } 81 | 82 | func TestRenderedPackageName(t *testing.T) { 83 | before := "github.com/prysmaticlabs/prysm/v3/proto/eth/v1" 84 | after := "v1" 85 | require.Equal(t, after, RenderedPackageName(before)) 86 | } 87 | -------------------------------------------------------------------------------- /sszgen/backend/testdata/TestGenerateHashTreeRoot.expected: -------------------------------------------------------------------------------- 1 | // HashTreeRoot ssz hashes the BeaconState object 2 | func (c *BeaconState) XXHashTreeRoot() ([32]byte, error) { 3 | hh := ssz.DefaultHasherPool.Get() 4 | if err := b.HashTreeRootWith(hh); err != nil { 5 | ssz.DefaultHasherPool.Put(hh) 6 | return [32]byte{}, err 7 | } 8 | root, err := hh.HashRoot() 9 | ssz.DefaultHasherPool.Put(hh) 10 | return root, err 11 | } 12 | 13 | func (c *BeaconState) XXHashTreeRootWith(hh *ssz.Hasher) (err error) { 14 | } 15 | -------------------------------------------------------------------------------- /sszgen/backend/testdata/TestGenerateMarshalSSZ.expected: -------------------------------------------------------------------------------- 1 | func (c *BeaconState) MarshalSSZ() ([]byte, error) { 2 | buf := make([]byte, c.SizeSSZ()) 3 | return c.MarshalSSZTo(buf[:0]) 4 | } 5 | 6 | func (c *BeaconState) MarshalSSZTo(dst []byte) ([]byte, error) { 7 | var err error 8 | offset := 2687377 9 | 10 | // Field 0: GenesisTime 11 | dst = ssz.MarshalUint64(dst, c.GenesisTime) 12 | 13 | // Field 1: GenesisValidatorsRoot 14 | if len(c.GenesisValidatorsRoot) != 32 { 15 | return nil, ssz.ErrBytesLength 16 | } 17 | dst = append(dst, c.GenesisValidatorsRoot...) 18 | 19 | // Field 2: Slot 20 | dst = ssz.MarshalUint64(dst, uint64(c.Slot)) 21 | 22 | // Field 3: Fork 23 | if c.Fork == nil { 24 | c.Fork = new(v1.Fork) 25 | } 26 | if dst, err = c.Fork.MarshalSSZTo(dst); err != nil { 27 | return nil, err 28 | } 29 | 30 | // Field 4: LatestBlockHeader 31 | if c.LatestBlockHeader == nil { 32 | c.LatestBlockHeader = new(v1alpha1.BeaconBlockHeader) 33 | } 34 | if dst, err = c.LatestBlockHeader.MarshalSSZTo(dst); err != nil { 35 | return nil, err 36 | } 37 | 38 | // Field 5: BlockRoots 39 | if len(c.BlockRoots) != 8192 { 40 | return nil, ssz.ErrBytesLength 41 | } 42 | for _, o := range c.BlockRoots { 43 | if len(o) != 32 { 44 | return nil, ssz.ErrBytesLength 45 | } 46 | dst = append(dst, o...) 47 | } 48 | 49 | // Field 6: StateRoots 50 | if len(c.StateRoots) != 8192 { 51 | return nil, ssz.ErrBytesLength 52 | } 53 | for _, o := range c.StateRoots { 54 | if len(o) != 32 { 55 | return nil, ssz.ErrBytesLength 56 | } 57 | dst = append(dst, o...) 58 | } 59 | 60 | // Field 7: HistoricalRoots 61 | dst = ssz.WriteOffset(dst, offset) 62 | offset += len(c.HistoricalRoots) * 32 63 | 64 | // Field 8: Eth1Data 65 | if c.Eth1Data == nil { 66 | c.Eth1Data = new(v1alpha1.Eth1Data) 67 | } 68 | if dst, err = c.Eth1Data.MarshalSSZTo(dst); err != nil { 69 | return nil, err 70 | } 71 | 72 | // Field 9: Eth1DataVotes 73 | dst = ssz.WriteOffset(dst, offset) 74 | offset += len(c.Eth1DataVotes) * 72 75 | 76 | // Field 10: Eth1DepositIndex 77 | dst = ssz.MarshalUint64(dst, c.Eth1DepositIndex) 78 | 79 | // Field 11: Validators 80 | dst = ssz.WriteOffset(dst, offset) 81 | offset += len(c.Validators) * 121 82 | 83 | // Field 12: Balances 84 | dst = ssz.WriteOffset(dst, offset) 85 | offset += len(c.Balances) * 8 86 | 87 | // Field 13: RandaoMixes 88 | if len(c.RandaoMixes) != 65536 { 89 | return nil, ssz.ErrBytesLength 90 | } 91 | for _, o := range c.RandaoMixes { 92 | if len(o) != 32 { 93 | return nil, ssz.ErrBytesLength 94 | } 95 | dst = append(dst, o...) 96 | } 97 | 98 | // Field 14: Slashings 99 | if len(c.Slashings) != 8192 { 100 | return nil, ssz.ErrBytesLength 101 | } 102 | for _, o := range c.Slashings { 103 | dst = ssz.MarshalUint64(dst, o) 104 | } 105 | 106 | // Field 15: PreviousEpochAttestations 107 | dst = ssz.WriteOffset(dst, offset) 108 | offset += func() int { 109 | s := 0 110 | for _, o := range c.PreviousEpochAttestations { 111 | s += 4 112 | s += o.SizeSSZ() 113 | } 114 | return s 115 | }() 116 | 117 | // Field 16: CurrentEpochAttestations 118 | dst = ssz.WriteOffset(dst, offset) 119 | offset += func() int { 120 | s := 0 121 | for _, o := range c.CurrentEpochAttestations { 122 | s += 4 123 | s += o.SizeSSZ() 124 | } 125 | return s 126 | }() 127 | 128 | // Field 17: JustificationBits 129 | if len([]byte(c.JustificationBits)) != 1 { 130 | return nil, ssz.ErrBytesLength 131 | } 132 | dst = append(dst, []byte(c.JustificationBits)...) 133 | 134 | // Field 18: PreviousJustifiedCheckpoint 135 | if c.PreviousJustifiedCheckpoint == nil { 136 | c.PreviousJustifiedCheckpoint = new(v1alpha1.Checkpoint) 137 | } 138 | if dst, err = c.PreviousJustifiedCheckpoint.MarshalSSZTo(dst); err != nil { 139 | return nil, err 140 | } 141 | 142 | // Field 19: CurrentJustifiedCheckpoint 143 | if c.CurrentJustifiedCheckpoint == nil { 144 | c.CurrentJustifiedCheckpoint = new(v1alpha1.Checkpoint) 145 | } 146 | if dst, err = c.CurrentJustifiedCheckpoint.MarshalSSZTo(dst); err != nil { 147 | return nil, err 148 | } 149 | 150 | // Field 20: FinalizedCheckpoint 151 | if c.FinalizedCheckpoint == nil { 152 | c.FinalizedCheckpoint = new(v1alpha1.Checkpoint) 153 | } 154 | if dst, err = c.FinalizedCheckpoint.MarshalSSZTo(dst); err != nil { 155 | return nil, err 156 | } 157 | 158 | // Field 7: HistoricalRoots 159 | if len(c.HistoricalRoots) > 16777216 { 160 | return nil, ssz.ErrListTooBig 161 | } 162 | for _, o := range c.HistoricalRoots { 163 | if len(o) != 32 { 164 | return nil, ssz.ErrBytesLength 165 | } 166 | dst = append(dst, o...) 167 | } 168 | 169 | // Field 9: Eth1DataVotes 170 | if len(c.Eth1DataVotes) > 2048 { 171 | return nil, ssz.ErrListTooBig 172 | } 173 | for _, o := range c.Eth1DataVotes { 174 | if dst, err = o.MarshalSSZTo(dst); err != nil { 175 | return nil, err 176 | } 177 | } 178 | 179 | // Field 11: Validators 180 | if len(c.Validators) > 1099511627776 { 181 | return nil, ssz.ErrListTooBig 182 | } 183 | for _, o := range c.Validators { 184 | if dst, err = o.MarshalSSZTo(dst); err != nil { 185 | return nil, err 186 | } 187 | } 188 | 189 | // Field 12: Balances 190 | if len(c.Balances) > 1099511627776 { 191 | return nil, ssz.ErrListTooBig 192 | } 193 | for _, o := range c.Balances { 194 | dst = ssz.MarshalUint64(dst, o) 195 | } 196 | 197 | // Field 15: PreviousEpochAttestations 198 | if len(c.PreviousEpochAttestations) > 4096 { 199 | return nil, ssz.ErrListTooBig 200 | } 201 | { 202 | offset = 4 * len(c.PreviousEpochAttestations) 203 | for _, o := range c.PreviousEpochAttestations { 204 | dst = ssz.WriteOffset(dst, offset) 205 | offset += o.SizeSSZ() 206 | } 207 | } 208 | for _, o := range c.PreviousEpochAttestations { 209 | if dst, err = o.MarshalSSZTo(dst); err != nil { 210 | return nil, err 211 | } 212 | } 213 | 214 | // Field 16: CurrentEpochAttestations 215 | if len(c.CurrentEpochAttestations) > 4096 { 216 | return nil, ssz.ErrListTooBig 217 | } 218 | { 219 | offset = 4 * len(c.CurrentEpochAttestations) 220 | for _, o := range c.CurrentEpochAttestations { 221 | dst = ssz.WriteOffset(dst, offset) 222 | offset += o.SizeSSZ() 223 | } 224 | } 225 | for _, o := range c.CurrentEpochAttestations { 226 | if dst, err = o.MarshalSSZTo(dst); err != nil { 227 | return nil, err 228 | } 229 | } 230 | return dst, err 231 | } 232 | -------------------------------------------------------------------------------- /sszgen/backend/testdata/TestGenerateSizeSSZ.expected: -------------------------------------------------------------------------------- 1 | func (c *BeaconState) SizeSSZ() int { 2 | size := 2687377 3 | size += len(c.HistoricalRoots) * 32 4 | size += len(c.Eth1DataVotes) * 72 5 | size += len(c.Validators) * 121 6 | size += len(c.Balances) * 8 7 | size += func() int { 8 | s := 0 9 | for _, o := range c.PreviousEpochAttestations { 10 | s += 4 11 | s += o.SizeSSZ() 12 | } 13 | return s 14 | }() 15 | size += func() int { 16 | s := 0 17 | for _, o := range c.CurrentEpochAttestations { 18 | s += 4 19 | s += o.SizeSSZ() 20 | } 21 | return s 22 | }() 23 | return size 24 | } 25 | -------------------------------------------------------------------------------- /sszgen/backend/testdata/TestGenerateUnmarshalSSZ.expected: -------------------------------------------------------------------------------- 1 | func (c *BeaconState) XXUnmarshalSSZ(buf []byte) error { 2 | var err error 3 | size := uint64(len(buf)) 4 | if size < 2687377 { 5 | return ssz.ErrSize 6 | } 7 | 8 | s0 := buf[0:8] // c.GenesisTime 9 | s1 := buf[8:40] // c.GenesisValidatorsRoot 10 | s2 := buf[40:48] // c.Slot 11 | s3 := buf[48:64] // c.Fork 12 | s4 := buf[64:176] // c.LatestBlockHeader 13 | s5 := buf[176:262320] // c.BlockRoots 14 | s6 := buf[262320:524464] // c.StateRoots 15 | s8 := buf[524468:524540] // c.Eth1Data 16 | s10 := buf[524544:524552] // c.Eth1DepositIndex 17 | s13 := buf[524560:2621712] // c.RandaoMixes 18 | s14 := buf[2621712:2687248] // c.Slashings 19 | s17 := buf[2687256:2687257] // c.JustificationBits 20 | s18 := buf[2687257:2687297] // c.PreviousJustifiedCheckpoint 21 | s19 := buf[2687297:2687337] // c.CurrentJustifiedCheckpoint 22 | s20 := buf[2687337:2687377] // c.FinalizedCheckpoint 23 | 24 | v7 := ssz.ReadOffset(buf[524464:524468]) // c.HistoricalRoots 25 | if v7 < 2687377 { 26 | return ssz.ErrInvalidVariableOffset 27 | } 28 | if v7 > size { 29 | return ssz.ErrOffset 30 | } 31 | v9 := ssz.ReadOffset(buf[524540:524544]) // c.Eth1DataVotes 32 | if v9 > size || v9 < v7 { 33 | return ssz.ErrOffset 34 | } 35 | v11 := ssz.ReadOffset(buf[524552:524556]) // c.Validators 36 | if v11 > size || v11 < v9 { 37 | return ssz.ErrOffset 38 | } 39 | v12 := ssz.ReadOffset(buf[524556:524560]) // c.Balances 40 | if v12 > size || v12 < v11 { 41 | return ssz.ErrOffset 42 | } 43 | v15 := ssz.ReadOffset(buf[2687248:2687252]) // c.PreviousEpochAttestations 44 | if v15 > size || v15 < v12 { 45 | return ssz.ErrOffset 46 | } 47 | v16 := ssz.ReadOffset(buf[2687252:2687256]) // c.CurrentEpochAttestations 48 | if v16 > size || v16 < v15 { 49 | return ssz.ErrOffset 50 | } 51 | s7 := buf[v7:v9] // c.HistoricalRoots 52 | s9 := buf[v9:v11] // c.Eth1DataVotes 53 | s11 := buf[v11:v12] // c.Validators 54 | s12 := buf[v12:v15] // c.Balances 55 | s15 := buf[v15:v16] // c.PreviousEpochAttestations 56 | s16 := buf[v16:] // c.CurrentEpochAttestations 57 | 58 | // Field 0: GenesisTime 59 | c.GenesisTime = ssz.UnmarshallUint64(s0) 60 | 61 | // Field 1: GenesisValidatorsRoot 62 | c.GenesisValidatorsRoot = append([]byte{}, s1...) 63 | 64 | // Field 2: Slot 65 | c.Slot = prysmaticlabs_eth2_types.Slot(ssz.UnmarshallUint64(s2)) 66 | 67 | // Field 3: Fork 68 | c.Fork = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.Fork) 69 | if err = c.Fork.UnmarshalSSZ(s3); err != nil { 70 | return err 71 | } 72 | 73 | // Field 4: LatestBlockHeader 74 | c.LatestBlockHeader = new(prysmaticlabs_prysm_proto_eth_v1alpha1.BeaconBlockHeader) 75 | if err = c.LatestBlockHeader.UnmarshalSSZ(s4); err != nil { 76 | return err 77 | } 78 | 79 | // Field 5: BlockRoots 80 | { 81 | var tmp []byte 82 | for i := 0; i < 8192; i++ { 83 | tmpSlice := s5[i*32 : (1+i)*32] 84 | tmp = append([]byte{}, tmpSlice...) 85 | c.BlockRoots = append(c.BlockRoots, tmp) 86 | } 87 | } 88 | 89 | // Field 6: StateRoots 90 | { 91 | var tmp []byte 92 | for i := 0; i < 8192; i++ { 93 | tmpSlice := s6[i*32 : (1+i)*32] 94 | tmp = append([]byte{}, tmpSlice...) 95 | c.StateRoots = append(c.StateRoots, tmp) 96 | } 97 | } 98 | 99 | // Field 7: HistoricalRoots 100 | { 101 | if len(s7)%32 != 0 { 102 | return fmt.Errorf("misaligned bytes: c.HistoricalRoots length is %d, which is not a multiple of 32", len(s7)) 103 | } 104 | numElem := len(s7) / 32 105 | if numElem > 16777216 { 106 | return fmt.Errorf("ssz-max exceeded: c.HistoricalRoots has %d elements, ssz-max is 16777216", numElem) 107 | } 108 | for i := 0; i < numElem; i++ { 109 | var tmp []byte 110 | 111 | tmpSlice := s7[i*32 : (1+i)*32] 112 | tmp = append([]byte{}, tmpSlice...) 113 | c.HistoricalRoots = append(c.HistoricalRoots, tmp) 114 | } 115 | } 116 | 117 | // Field 8: Eth1Data 118 | c.Eth1Data = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data) 119 | if err = c.Eth1Data.UnmarshalSSZ(s8); err != nil { 120 | return err 121 | } 122 | 123 | // Field 9: Eth1DataVotes 124 | { 125 | if len(s9)%72 != 0 { 126 | return fmt.Errorf("misaligned bytes: c.Eth1DataVotes length is %d, which is not a multiple of 72", len(s9)) 127 | } 128 | numElem := len(s9) / 72 129 | if numElem > 2048 { 130 | return fmt.Errorf("ssz-max exceeded: c.Eth1DataVotes has %d elements, ssz-max is 2048", numElem) 131 | } 132 | for i := 0; i < numElem; i++ { 133 | var tmp *prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data 134 | tmp = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Eth1Data) 135 | tmpSlice := s9[i*72 : (1+i)*72] 136 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil { 137 | return err 138 | } 139 | c.Eth1DataVotes = append(c.Eth1DataVotes, tmp) 140 | } 141 | } 142 | 143 | // Field 10: Eth1DepositIndex 144 | c.Eth1DepositIndex = ssz.UnmarshallUint64(s10) 145 | 146 | // Field 11: Validators 147 | { 148 | if len(s11)%121 != 0 { 149 | return fmt.Errorf("misaligned bytes: c.Validators length is %d, which is not a multiple of 121", len(s11)) 150 | } 151 | numElem := len(s11) / 121 152 | if numElem > 1099511627776 { 153 | return fmt.Errorf("ssz-max exceeded: c.Validators has %d elements, ssz-max is 1099511627776", numElem) 154 | } 155 | for i := 0; i < numElem; i++ { 156 | var tmp *prysmaticlabs_prysm_proto_eth_v1alpha1.Validator 157 | tmp = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Validator) 158 | tmpSlice := s11[i*121 : (1+i)*121] 159 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil { 160 | return err 161 | } 162 | c.Validators = append(c.Validators, tmp) 163 | } 164 | } 165 | 166 | // Field 12: Balances 167 | { 168 | if len(s12)%8 != 0 { 169 | return fmt.Errorf("misaligned bytes: c.Balances length is %d, which is not a multiple of 8", len(s12)) 170 | } 171 | numElem := len(s12) / 8 172 | if numElem > 1099511627776 { 173 | return fmt.Errorf("ssz-max exceeded: c.Balances has %d elements, ssz-max is 1099511627776", numElem) 174 | } 175 | for i := 0; i < numElem; i++ { 176 | var tmp uint64 177 | 178 | tmpSlice := s12[i*8 : (1+i)*8] 179 | tmp = ssz.UnmarshallUint64(tmpSlice) 180 | c.Balances = append(c.Balances, tmp) 181 | } 182 | } 183 | 184 | // Field 13: RandaoMixes 185 | { 186 | var tmp []byte 187 | for i := 0; i < 65536; i++ { 188 | tmpSlice := s13[i*32 : (1+i)*32] 189 | tmp = append([]byte{}, tmpSlice...) 190 | c.RandaoMixes = append(c.RandaoMixes, tmp) 191 | } 192 | } 193 | 194 | // Field 14: Slashings 195 | { 196 | var tmp uint64 197 | for i := 0; i < 8192; i++ { 198 | tmpSlice := s14[i*8 : (1+i)*8] 199 | tmp = ssz.UnmarshallUint64(tmpSlice) 200 | c.Slashings = append(c.Slashings, tmp) 201 | } 202 | } 203 | 204 | // Field 15: PreviousEpochAttestations 205 | { 206 | // empty lists are zero length, so make sure there is room for an offset 207 | // before attempting to unmarshal it 208 | if len(s15) > 3 { 209 | firstOffset := ssz.ReadOffset(s15[0:4]) 210 | if firstOffset%4 != 0 { 211 | return fmt.Errorf("misaligned list bytes: when decoding c.PreviousEpochAttestations, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset) 212 | } 213 | listLen := firstOffset / 4 214 | if listLen > 4096 { 215 | return fmt.Errorf("ssz-max exceeded: c.PreviousEpochAttestations has %d elements, ssz-max is 4096", listLen) 216 | } 217 | listOffsets := make([]uint64, listLen) 218 | for i := 0; uint64(i) < listLen; i++ { 219 | listOffsets[i] = ssz.ReadOffset(s15[i*4 : (i+1)*4]) 220 | } 221 | for i := 0; i < len(listOffsets); i++ { 222 | var tmp *prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation 223 | tmp = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation) 224 | var tmpSlice []byte 225 | if i+1 == len(listOffsets) { 226 | tmpSlice = s15[listOffsets[i]:] 227 | } else { 228 | tmpSlice = s15[listOffsets[i]:listOffsets[i+1]] 229 | } 230 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil { 231 | return err 232 | } 233 | c.PreviousEpochAttestations = append(c.PreviousEpochAttestations, tmp) 234 | } 235 | } 236 | } 237 | 238 | // Field 16: CurrentEpochAttestations 239 | { 240 | // empty lists are zero length, so make sure there is room for an offset 241 | // before attempting to unmarshal it 242 | if len(s16) > 3 { 243 | firstOffset := ssz.ReadOffset(s16[0:4]) 244 | if firstOffset%4 != 0 { 245 | return fmt.Errorf("misaligned list bytes: when decoding c.CurrentEpochAttestations, end-of-list offset is %d, which is not a multiple of 4 (offset size)", firstOffset) 246 | } 247 | listLen := firstOffset / 4 248 | if listLen > 4096 { 249 | return fmt.Errorf("ssz-max exceeded: c.CurrentEpochAttestations has %d elements, ssz-max is 4096", listLen) 250 | } 251 | listOffsets := make([]uint64, listLen) 252 | for i := 0; uint64(i) < listLen; i++ { 253 | listOffsets[i] = ssz.ReadOffset(s16[i*4 : (i+1)*4]) 254 | } 255 | for i := 0; i < len(listOffsets); i++ { 256 | var tmp *prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation 257 | tmp = new(prysmaticlabs_prysm_proto_beacon_p2p_v1.PendingAttestation) 258 | var tmpSlice []byte 259 | if i+1 == len(listOffsets) { 260 | tmpSlice = s16[listOffsets[i]:] 261 | } else { 262 | tmpSlice = s16[listOffsets[i]:listOffsets[i+1]] 263 | } 264 | if err = tmp.UnmarshalSSZ(tmpSlice); err != nil { 265 | return err 266 | } 267 | c.CurrentEpochAttestations = append(c.CurrentEpochAttestations, tmp) 268 | } 269 | } 270 | } 271 | 272 | // Field 17: JustificationBits 273 | c.JustificationBits = append([]byte{}, prysmaticlabs_go_bitfield.Bitvector4(s17)...) 274 | 275 | // Field 18: PreviousJustifiedCheckpoint 276 | c.PreviousJustifiedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint) 277 | if err = c.PreviousJustifiedCheckpoint.UnmarshalSSZ(s18); err != nil { 278 | return err 279 | } 280 | 281 | // Field 19: CurrentJustifiedCheckpoint 282 | c.CurrentJustifiedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint) 283 | if err = c.CurrentJustifiedCheckpoint.UnmarshalSSZ(s19); err != nil { 284 | return err 285 | } 286 | 287 | // Field 20: FinalizedCheckpoint 288 | c.FinalizedCheckpoint = new(prysmaticlabs_prysm_proto_eth_v1alpha1.Checkpoint) 289 | if err = c.FinalizedCheckpoint.UnmarshalSSZ(s20); err != nil { 290 | return err 291 | } 292 | return err 293 | } 294 | -------------------------------------------------------------------------------- /sszgen/backend/testdata/TestUnmarshalSteps.expected: -------------------------------------------------------------------------------- 1 | s0 := buf[0:8] // c.GenesisTime 2 | s1 := buf[8:40] // c.GenesisValidatorsRoot 3 | s2 := buf[40:48] // c.Slot 4 | s3 := buf[48:64] // c.Fork 5 | s4 := buf[64:176] // c.LatestBlockHeader 6 | s5 := buf[176:262320] // c.BlockRoots 7 | s6 := buf[262320:524464] // c.StateRoots 8 | s8 := buf[524468:524540] // c.Eth1Data 9 | s10 := buf[524544:524552] // c.Eth1DepositIndex 10 | s13 := buf[524560:2621712] // c.RandaoMixes 11 | s14 := buf[2621712:2687248] // c.Slashings 12 | s17 := buf[2687256:2687257] // c.JustificationBits 13 | s18 := buf[2687257:2687297] // c.PreviousJustifiedCheckpoint 14 | s19 := buf[2687297:2687337] // c.CurrentJustifiedCheckpoint 15 | s20 := buf[2687337:2687377] // c.FinalizedCheckpoint 16 | 17 | v7 := ssz.ReadOffset(buf[524464:524468]) // c.HistoricalRoots 18 | if v7 < 2687377 { 19 | return ssz.ErrInvalidVariableOffset 20 | } 21 | if v7 > size { 22 | return ssz.ErrOffset 23 | } 24 | v9 := ssz.ReadOffset(buf[524540:524544]) // c.Eth1DataVotes 25 | if v9 > size || v9 < v7 { 26 | return ssz.ErrOffset 27 | } 28 | v11 := ssz.ReadOffset(buf[524552:524556]) // c.Validators 29 | if v11 > size || v11 < v9 { 30 | return ssz.ErrOffset 31 | } 32 | v12 := ssz.ReadOffset(buf[524556:524560]) // c.Balances 33 | if v12 > size || v12 < v11 { 34 | return ssz.ErrOffset 35 | } 36 | v15 := ssz.ReadOffset(buf[2687248:2687252]) // c.PreviousEpochAttestations 37 | if v15 > size || v15 < v12 { 38 | return ssz.ErrOffset 39 | } 40 | v16 := ssz.ReadOffset(buf[2687252:2687256]) // c.CurrentEpochAttestations 41 | if v16 > size || v16 < v15 { 42 | return ssz.ErrOffset 43 | } 44 | s7 := buf[v7:v9] // c.HistoricalRoots 45 | s9 := buf[v9:v11] // c.Eth1DataVotes 46 | s11 := buf[v11:v12] // c.Validators 47 | s12 := buf[v12:v15] // c.Balances 48 | s15 := buf[v15:v16] // c.PreviousEpochAttestations 49 | s16 := buf[v16:] // c.CurrentEpochAttestations 50 | -------------------------------------------------------------------------------- /sszgen/backend/uint.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | ) 8 | 9 | type generateUint struct { 10 | valRep *types.ValueUint 11 | targetPackage string 12 | casterConfig 13 | importNamer *ImportNamer 14 | } 15 | 16 | func (g *generateUint) coerce() func(string) string { 17 | return func(fieldName string) string { 18 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName) 19 | } 20 | } 21 | 22 | func (g *generateUint) generateUnmarshalValue(fieldName string, offset string) string { 23 | // misspelling of Unmarshall due to misspelling of method exported by fastssz 24 | convert := fmt.Sprintf("ssz.UnmarshallUint%d(%s)", g.valRep.Size, offset) 25 | return fmt.Sprintf("%s = %s", fieldName, g.casterConfig.toOverlay(convert)) 26 | } 27 | 28 | func (g *generateUint) generateFixedMarshalValue(fieldName string) string { 29 | return fmt.Sprintf("dst = ssz.MarshalUint%d(dst, %s)", g.valRep.Size, fieldName) 30 | } 31 | 32 | func (g *generateUint) generateHTRPutter(fieldName string) string { 33 | return fmt.Sprintf("hh.PutUint%d(%s)", g.valRep.Size, fieldName) 34 | } 35 | 36 | func (g *generateUint) variableSizeSSZ(fieldname string) string { 37 | return "" 38 | } 39 | 40 | var _ valueGenerator = &generateUint{} 41 | -------------------------------------------------------------------------------- /sszgen/backend/union.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 5 | ) 6 | 7 | type generateUnion struct { 8 | *types.ValueUnion 9 | targetPackage string 10 | importNamer *ImportNamer 11 | } 12 | 13 | func (g *generateUnion) generateHTRPutter(fieldName string) string { 14 | return "" 15 | } 16 | 17 | func (g *generateUnion) generateUnmarshalValue(fieldName string, s string) string { 18 | return "" 19 | } 20 | 21 | func (g *generateUnion) generateFixedMarshalValue(fieldName string) string { 22 | return "" 23 | } 24 | 25 | func (g *generateUnion) variableSizeSSZ(fieldname string) string { 26 | return "" 27 | } 28 | 29 | var _ valueGenerator = &generateUnion{} 30 | -------------------------------------------------------------------------------- /sszgen/backend/vector.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 9 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 10 | ) 11 | 12 | type generateVector struct { 13 | valRep *types.ValueVector 14 | targetPackage string 15 | casterConfig 16 | importNamer *ImportNamer 17 | } 18 | 19 | func (g *generateVector) generateUnmarshalValue(fieldName string, sliceName string) string { 20 | gg := newValueGenerator(interfaces.SszUnmarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 21 | switch g.valRep.ElementValue.(type) { 22 | case *types.ValueByte: 23 | t := `%s = make([]byte, 0, %d) 24 | %s = append(%s, %s...)` 25 | return fmt.Sprintf(t, fieldName, g.valRep.Size, fieldName, fieldName, g.casterConfig.toOverlay(sliceName)) 26 | default: 27 | loopVar := "i" 28 | if fieldName[0:1] == "i" && monoCharacter(fieldName) { 29 | loopVar = fieldName + "i" 30 | } 31 | t := `{ 32 | var tmp {{ .TypeName }} 33 | {{.FieldName}} = make([]{{.TypeName}}, {{.NumElements}}) 34 | for {{ .LoopVar }} := 0; {{ .LoopVar }} < {{ .NumElements }}; {{ .LoopVar }} ++ { 35 | tmpSlice := {{ .SliceName }}[{{ .LoopVar }}*{{ .NestedFixedSize }}:(1+{{ .LoopVar }})*{{ .NestedFixedSize }}] 36 | {{ .NestedUnmarshal }} 37 | {{ .FieldName }}[{{.LoopVar}}] = tmp 38 | } 39 | }` 40 | tmpl, err := template.New("tmplgenerateUnmarshalValueDefault").Parse(t) 41 | if err != nil { 42 | panic(err) 43 | } 44 | buf := bytes.NewBuffer(nil) 45 | nvr := g.valRep.ElementValue 46 | err = tmpl.Execute(buf, struct { 47 | TypeName string 48 | SliceName string 49 | NumElements int 50 | NestedFixedSize int 51 | LoopVar string 52 | NestedUnmarshal string 53 | FieldName string 54 | }{ 55 | TypeName: fullyQualifiedTypeName(nvr, g.targetPackage, g.importNamer), 56 | SliceName: sliceName, 57 | NumElements: g.valRep.FixedSize() / g.valRep.ElementValue.FixedSize(), 58 | NestedFixedSize: g.valRep.ElementValue.FixedSize(), 59 | LoopVar: loopVar, 60 | NestedUnmarshal: gg.generateUnmarshalValue("tmp", "tmpSlice"), 61 | FieldName: fieldName, 62 | }) 63 | if err != nil { 64 | panic(err) 65 | } 66 | return buf.String() 67 | } 68 | } 69 | 70 | var tmplGenerateMarshalValueVector = `if len({{.FieldName}}) != {{.Size}} { 71 | return nil, ssz.ErrBytesLength 72 | } 73 | {{.MarshalValue}}` 74 | 75 | func (g *generateVector) generateFixedMarshalValue(fieldName string) string { 76 | mvTmpl, err := template.New("tmplGenerateMarshalValueVector").Parse(tmplGenerateMarshalValueVector) 77 | if err != nil { 78 | panic(err) 79 | } 80 | var marshalValue string 81 | switch g.valRep.ElementValue.(type) { 82 | case *types.ValueByte: 83 | if g.valRep.IsArray { 84 | marshalValue = fmt.Sprintf("dst = append(dst, %s[:]...)", fieldName) 85 | } else { 86 | marshalValue = fmt.Sprintf("dst = append(dst, %s...)", fieldName) 87 | } 88 | default: 89 | nestedFieldName := "o" 90 | if fieldName[0:1] == "o" && monoCharacter(fieldName) { 91 | nestedFieldName = fieldName + "o" 92 | } 93 | t := `for _, %s := range %s { 94 | %s 95 | }` 96 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 97 | internal := gg.generateFixedMarshalValue(nestedFieldName) 98 | marshalValue = fmt.Sprintf(t, nestedFieldName, fieldName, internal) 99 | } 100 | buf := bytes.NewBuffer(nil) 101 | err = mvTmpl.Execute(buf, struct { 102 | FieldName string 103 | Size int 104 | MarshalValue string 105 | }{ 106 | FieldName: fieldName, 107 | Size: g.valRep.Size, 108 | MarshalValue: marshalValue, 109 | }) 110 | if err != nil { 111 | panic(err) 112 | } 113 | return buf.String() 114 | } 115 | 116 | var generateVectorHTRPutterTmpl = `{ 117 | if len({{.FieldName}}) != {{.Size}} { 118 | return ssz.ErrVectorLength 119 | } 120 | subIndx := hh.Index() 121 | for _, {{.NestedFieldName}} := range {{.FieldName}} { 122 | {{.AppendCall}} 123 | } 124 | {{.Merkleize}} 125 | }` 126 | 127 | type vecPutterElements struct { 128 | FieldName string 129 | NestedFieldName string 130 | Size int 131 | AppendCall string 132 | Merkleize string 133 | } 134 | 135 | func renderHtrVecPutter(lpe vecPutterElements) string { 136 | tmpl, err := template.New("renderHtrVecPutter").Parse(generateVectorHTRPutterTmpl) 137 | if err != nil { 138 | panic(err) 139 | } 140 | buf := bytes.NewBuffer(nil) 141 | err = tmpl.Execute(buf, lpe) 142 | if err != nil { 143 | panic(err) 144 | } 145 | return buf.String() 146 | } 147 | 148 | func (g *generateVector) isByteVector() bool { 149 | _, isByte := g.valRep.ElementValue.(*types.ValueByte) 150 | return isByte 151 | } 152 | 153 | const ByteChunkSize = 32 154 | 155 | func (g *generateVector) renderByteSliceAppend(fieldName string) string { 156 | if g.valRep.Size%ByteChunkSize == 0 { 157 | if g.valRep.IsVariableSized() { 158 | return fmt.Sprintf(byteSliceAppendTpl, fieldName, g.valRep.Size, fieldName) 159 | } else { 160 | return fmt.Sprintf(byteSliceAppendTplFixedSize, fieldName, g.valRep.Size, fieldName) 161 | } 162 | } else { 163 | return fmt.Sprintf(byteSlicePutBytesTpl, fieldName, g.valRep.Size, fieldName) 164 | } 165 | } 166 | 167 | var byteSliceAppendTpl = `if len(%s) != %d { 168 | return ssz.ErrBytesLength 169 | } 170 | hh.Append(%s)` 171 | 172 | var byteSliceAppendTplFixedSize = `if len(%s) != %d { 173 | return ssz.ErrBytesLength 174 | } 175 | hh.Append(%s[:])` 176 | 177 | var byteSlicePutBytesTpl = `if len(%s) != %d { 178 | return ssz.ErrBytesLength 179 | } 180 | hh.PutBytes(%s)` 181 | 182 | func (g *generateVector) generateHTRPutter(fieldName string) string { 183 | nestedFieldName := "o" 184 | if fieldName[0:1] == "o" && monoCharacter(fieldName) { 185 | nestedFieldName = fieldName + "o" 186 | } 187 | 188 | // resolve pointers and overlays to their underlying types 189 | vr := g.valRep.ElementValue 190 | if vrp, isPointer := vr.(*types.ValuePointer); isPointer { 191 | vr = vrp.Referent 192 | } 193 | if vro, isOverlay := vr.(*types.ValueOverlay); isOverlay { 194 | vr = vro.Underlying 195 | } 196 | 197 | vpe := vecPutterElements{ 198 | FieldName: fieldName, 199 | NestedFieldName: nestedFieldName, 200 | Size: g.valRep.Size, 201 | } 202 | 203 | switch v := vr.(type) { 204 | case *types.ValueByte: 205 | return fmt.Sprintf(byteSlicePutBytesTpl, fieldName, g.valRep.Size, fieldName) 206 | case *types.ValueVector: 207 | gv := &generateVector{valRep: v, targetPackage: g.targetPackage} 208 | if gv.isByteVector() { 209 | vpe.AppendCall = gv.renderByteSliceAppend(nestedFieldName) 210 | vpe.Merkleize = "hh.Merkleize(subIndx)" 211 | return renderHtrVecPutter(vpe) 212 | } 213 | case *types.ValueContainer: 214 | gc := &generateContainer{ValueContainer: v, targetPackage: g.targetPackage} 215 | return gc.generateHTRPutter(fieldName) 216 | case *types.ValueUint: 217 | vpe.AppendCall = fmt.Sprintf("hh.AppendUint%d(%s)", v.Size, nestedFieldName) 218 | vpe.Merkleize = "hh.Merkleize(subIndx)" 219 | return renderHtrVecPutter(vpe) 220 | default: 221 | panic(fmt.Sprintf("unsupported type combination - %v vector of %v", fieldName, v)) 222 | } 223 | return "" 224 | } 225 | 226 | func monoCharacter(s string) bool { 227 | ch := s[0] 228 | for i := 1; i < len(s); i++ { 229 | if s[i] == ch { 230 | continue 231 | } 232 | return false 233 | } 234 | return true 235 | } 236 | 237 | var variableSizedVectorTmpl = `func() int { 238 | s := 0 239 | for _, o := range {{ .FieldName }} { 240 | s += 4 241 | s += {{ .SizeComputation }} 242 | } 243 | return s 244 | }()` 245 | 246 | func (g *generateVector) variableSizeSSZ(fieldName string) string { 247 | // TODO(rjl493456442) if the element of vector is fixed size, 248 | // then the vector itself must be fixed as well. This clause 249 | // should be removed. 250 | if !g.valRep.ElementValue.IsVariableSized() { 251 | return fmt.Sprintf("len(%s) * %d", fieldName, g.valRep.ElementValue.FixedSize()) 252 | } 253 | gg := newValueGenerator(interfaces.SszMarshaler, g.valRep.ElementValue, g.targetPackage, g.importNamer) 254 | vslTmpl, err := template.New("variableSizedVectorTmpl").Parse(variableSizedVectorTmpl) 255 | if err != nil { 256 | panic(err) 257 | } 258 | buf := bytes.NewBuffer(nil) 259 | err = vslTmpl.Execute(buf, struct { 260 | FieldName string 261 | SizeComputation string 262 | }{ 263 | FieldName: fieldName, 264 | SizeComputation: gg.variableSizeSSZ("o"), 265 | }) 266 | if err != nil { 267 | panic(err) 268 | } 269 | return buf.String() 270 | } 271 | 272 | func (g *generateVector) coerce() func(string) string { 273 | return func(fieldName string) string { 274 | return fmt.Sprintf("%s(%s)", g.valRep.TypeName(), fieldName) 275 | } 276 | } 277 | 278 | var _ valueGenerator = &generateVector{} 279 | -------------------------------------------------------------------------------- /sszgen/backend/visitor.go: -------------------------------------------------------------------------------- 1 | package backend 2 | 3 | import "github.com/OffchainLabs/methodical-ssz/sszgen/types" 4 | 5 | type visitor func(vr types.ValRep) 6 | 7 | func visit(vr types.ValRep, v visitor) { 8 | v(vr) 9 | switch t := vr.(type) { 10 | case *types.ValueContainer: 11 | for _, f := range t.Contents { 12 | visit(f.Value, v) 13 | } 14 | case *types.ValueVector: 15 | visit(t.ElementValue, v) 16 | case *types.ValueList: 17 | visit(t.ElementValue, v) 18 | case *types.ValuePointer: 19 | visit(t.Referent, v) 20 | case *types.ValueOverlay: 21 | visit(t.Underlying, v) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sszgen/interfaces/ssz.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "go/types" 5 | 6 | "golang.org/x/tools/go/packages" 7 | ) 8 | 9 | var ( 10 | SszMarshaler *types.Interface 11 | SszUnmarshaler *types.Interface 12 | SszFullHasher *types.Interface 13 | SszLightHasher *types.Interface 14 | ) 15 | 16 | func NewSSZSupportMap(t types.Type) map[*types.Interface]bool { 17 | return map[*types.Interface]bool{ 18 | SszMarshaler: types.Implements(t, SszMarshaler) || types.Implements(types.NewPointer(t), SszMarshaler), 19 | SszUnmarshaler: types.Implements(t, SszUnmarshaler) || types.Implements(types.NewPointer(t), SszUnmarshaler), 20 | SszLightHasher: types.Implements(t, SszLightHasher) || types.Implements(types.NewPointer(t), SszLightHasher), 21 | SszFullHasher: types.Implements(t, SszFullHasher) || types.Implements(types.NewPointer(t), SszFullHasher), 22 | } 23 | } 24 | 25 | func init() { 26 | pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedTypes}, "github.com/prysmaticlabs/fastssz") 27 | if err != nil { 28 | panic(err) 29 | } 30 | if len(pkgs) == 0 { 31 | panic("missing package, add github.com/prysmaticlabs/fastssz to your go.mod") 32 | } 33 | SszMarshaler = pkgs[0].Types.Scope().Lookup("Marshaler").Type().Underlying().(*types.Interface) 34 | SszUnmarshaler = pkgs[0].Types.Scope().Lookup("Unmarshaler").Type().Underlying().(*types.Interface) 35 | SszFullHasher = pkgs[0].Types.Scope().Lookup("HashRoot").Type().Underlying().(*types.Interface) 36 | 37 | for i := 0; i < SszFullHasher.NumMethods(); i++ { 38 | method := SszFullHasher.Method(i) 39 | if method.Name() == "HashTreeRoot" { 40 | SszLightHasher = types.NewInterfaceType([]*types.Func{method}, nil) 41 | break 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sszgen/node.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "fmt" 5 | "go/token" 6 | "go/types" 7 | "os" 8 | ) 9 | 10 | // TypeDef represents the intermediate struct type used during marshaling. 11 | // This is the input data to all the Go code templates. 12 | type TypeDef struct { 13 | Name string 14 | PackageName string 15 | IsStruct bool 16 | Fields []*FieldDef 17 | fs *token.FileSet 18 | orig *types.Named 19 | scope *fileScope 20 | object types.Object 21 | } 22 | 23 | // FieldDef represents a field of the intermediate marshaling type. 24 | type FieldDef struct { 25 | name string 26 | typ types.Type 27 | tag string 28 | pkg *types.Package 29 | } 30 | 31 | func newStructDef(fs *token.FileSet, imp types.Importer, typ *types.Named, packageName string) *TypeDef { 32 | mtyp := &TypeDef{ 33 | Name: typ.Obj().Name(), 34 | PackageName: packageName, 35 | IsStruct: true, 36 | fs: fs, 37 | orig: typ, 38 | } 39 | 40 | styp := typ.Underlying().(*types.Struct) 41 | mtyp.scope = newFileScope(imp, typ.Obj().Pkg()) 42 | mtyp.scope.addReferences(styp) 43 | 44 | // Add packages which are always needed. 45 | mtyp.scope.addImport("encoding/json") 46 | mtyp.scope.addImport("errors") 47 | 48 | for i := 0; i < styp.NumFields(); i++ { 49 | f := styp.Field(i) 50 | if !f.Exported() { 51 | continue 52 | } 53 | if f.Anonymous() { 54 | fmt.Fprintf(os.Stderr, "Warning: ignoring embedded field %s\n", f.Name()) 55 | continue 56 | } 57 | 58 | switch ftyp := f.Type().(type) { 59 | case *types.Pointer: 60 | fn, ok := ftyp.Elem().(*types.Named) 61 | if ok { 62 | mf := &FieldDef{ 63 | name: f.Name(), 64 | typ: f.Type(), 65 | tag: styp.Tag(i), 66 | pkg: fn.Obj().Pkg(), 67 | } 68 | mtyp.Fields = append(mtyp.Fields, mf) 69 | continue 70 | } 71 | } 72 | mf := &FieldDef{ 73 | name: f.Name(), 74 | typ: f.Type(), 75 | tag: styp.Tag(i), 76 | pkg: f.Pkg(), 77 | } 78 | mtyp.Fields = append(mtyp.Fields, mf) 79 | } 80 | return mtyp 81 | } 82 | 83 | func newPrimitiveDef(fs *token.FileSet, imp types.Importer, typ *types.Named, packageName string) *TypeDef { 84 | mtyp := &TypeDef{ 85 | Name: typ.Obj().Name(), 86 | PackageName: packageName, 87 | IsStruct: false, 88 | fs: fs, 89 | orig: typ, 90 | } 91 | mtyp.scope = newFileScope(imp, typ.Obj().Pkg()) 92 | mtyp.scope.addReferences(typ.Underlying()) 93 | 94 | // Add packages which are always needed. 95 | mtyp.scope.addImport("encoding/json") 96 | mtyp.scope.addImport("errors") 97 | 98 | fd := &FieldDef{ 99 | name: typ.Underlying().String(), 100 | typ: typ.Underlying(), 101 | tag: "", 102 | pkg: typ.Obj().Pkg(), 103 | } 104 | mtyp.Fields = append(mtyp.Fields, fd) 105 | return mtyp 106 | } 107 | -------------------------------------------------------------------------------- /sszgen/parser.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "fmt" 5 | "go/format" 6 | "go/importer" 7 | "go/token" 8 | "go/types" 9 | "regexp" 10 | "strings" 11 | 12 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend" 13 | "golang.org/x/tools/go/packages" 14 | ) 15 | 16 | type GoPathScoper struct { 17 | packagePath string 18 | fieldNames []string 19 | pkg *types.Package 20 | } 21 | 22 | func NewGoPathScoper(packageName string) (*GoPathScoper, error) { 23 | cfg := &packages.Config{ 24 | Mode: packages.NeedTypes | packages.NeedDeps | packages.NeedImports, 25 | } 26 | pkgs, err := packages.Load(cfg, []string{packageName}...) 27 | if err != nil { 28 | return nil, err 29 | } 30 | for _, pkg := range pkgs { 31 | if pkg.ID != packageName { 32 | continue 33 | } 34 | 35 | pp := &GoPathScoper{packagePath: pkg.ID, pkg: pkg.Types} 36 | return pp, nil 37 | } 38 | return nil, fmt.Errorf("package named '%s' could not be loaded from the go build system. Please make sure the current folder contains the go.mod for the target package, or that its go.mod is in a parent directory", packageName) 39 | } 40 | 41 | type PathScoper interface { 42 | Path() string 43 | Scope() *types.Scope 44 | } 45 | 46 | func (pp *GoPathScoper) Path() string { 47 | return pp.packagePath 48 | } 49 | 50 | func (pp *GoPathScoper) Scope() *types.Scope { 51 | return pp.pkg.Scope() 52 | } 53 | 54 | func TypeDefs(ps PathScoper, fieldNames ...string) ([]*TypeDef, error) { 55 | fileSet := token.NewFileSet() 56 | imp := importer.Default() 57 | 58 | // If no field names are requested, use all 59 | if fieldNames == nil { 60 | fieldNames = ps.Scope().Names() 61 | } 62 | 63 | results := make([]*TypeDef, len(fieldNames)) 64 | for i, typeName := range fieldNames { 65 | typ, obj, err := lookupType(ps.Scope(), typeName) 66 | if err != nil { 67 | return nil, err 68 | } 69 | var mtyp *TypeDef 70 | if _, ok := typ.Underlying().(*types.Struct); ok { 71 | mtyp = newStructDef(fileSet, imp, typ, ps.Path()) 72 | } else { 73 | mtyp = newPrimitiveDef(fileSet, imp, typ, ps.Path()) 74 | } 75 | mtyp.object = obj 76 | results[i] = mtyp 77 | } 78 | return results, nil 79 | } 80 | 81 | var structTagRe = regexp.MustCompile(`\s+"(.*)"$`) 82 | 83 | func reformatStructTag(line string) string { 84 | line = structTagRe.ReplaceAllString(line, " `$1`") 85 | return strings.ReplaceAll(line, `\"`, `"`) 86 | } 87 | 88 | func (pp *GoPathScoper) TypeDefSourceCode(defs []*TypeDef) ([]byte, error) { 89 | in := backend.NewImportNamer(pp.pkg.Path(), nil) 90 | structs := make([]string, 0) 91 | for _, def := range defs { 92 | obj := def.object 93 | defstring := types.ObjectString(obj, in.Name) 94 | // add a little whitespace for nicer formatting 95 | defstring = strings.ReplaceAll(defstring, ";", "\n") 96 | defstring = strings.ReplaceAll(defstring, "{", "{\n") 97 | defstring = strings.ReplaceAll(defstring, "}", "\n}\n") 98 | lines := strings.Split(defstring, "\n") 99 | for i := 0; i < len(lines); i++ { 100 | lines[i] = strings.TrimSpace(lines[i]) 101 | lines[i] = reformatStructTag(lines[i]) 102 | } 103 | structs = append(structs, strings.Join(lines, "\n")) 104 | } 105 | 106 | source := "package " + backend.RenderedPackageName(pp.pkg.Path()) + "\n\n" + 107 | in.ImportSource() + 108 | strings.Join(structs, "\n") 109 | return format.Source([]byte(source)) 110 | } 111 | -------------------------------------------------------------------------------- /sszgen/parser_test.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/prysmaticlabs/prysm/v3/testing/require" 7 | ) 8 | 9 | func TestReformatStructTags(t *testing.T) { 10 | decl := `PublicKey []byte "protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\" spec-name:\"pubkey\" ssz-size:\"48\""` 11 | // unquoted quotation marks should be converted to backticks 12 | expected := "PublicKey []byte `protobuf:\"bytes,1,opt,name=public_key,json=publicKey,proto3\" json:\"public_key,omitempty\" spec-name:\"pubkey\" ssz-size:\"48\"`" 13 | got := reformatStructTag(decl) 14 | require.Equal(t, expected, got) 15 | } 16 | -------------------------------------------------------------------------------- /sszgen/representer.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 8 | gentypes "github.com/OffchainLabs/methodical-ssz/sszgen/types" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type FieldParserOpt func(*FieldParser) 13 | 14 | func WithDisableDelegation() FieldParserOpt { 15 | return func(p *FieldParser) { 16 | p.disableDelegation = true 17 | } 18 | } 19 | 20 | func ParseTypeDef(typ *TypeDef, opts ...FieldParserOpt) (gentypes.ValRep, error) { 21 | p := &FieldParser{} 22 | for _, o := range opts { 23 | o(p) 24 | } 25 | if typ.IsStruct { 26 | vr := &gentypes.ValueContainer{ 27 | Name: typ.Name, 28 | Package: typ.orig.Obj().Pkg().Path(), 29 | } 30 | for _, f := range typ.Fields { 31 | rep, err := p.expand(f) 32 | if err != nil { 33 | return nil, err 34 | } 35 | vr.Append(f.name, rep) 36 | } 37 | return vr, nil 38 | } 39 | // PrimitiveType is stored in Fields[0] 40 | rep, err := p.expand(typ.Fields[0]) 41 | if err != nil { 42 | return nil, err 43 | } 44 | vr := &gentypes.ValueOverlay{ 45 | Name: typ.Name, 46 | Package: rep.PackagePath(), 47 | Underlying: rep, 48 | } 49 | return vr, nil 50 | } 51 | 52 | type FieldParser struct { 53 | disableDelegation bool 54 | } 55 | 56 | func (p *FieldParser) expand(f *FieldDef) (gentypes.ValRep, error) { 57 | switch ty := f.typ.(type) { 58 | case *types.Array: 59 | size := int(ty.Len()) 60 | return p.expandArray([]*SSZDimension{{VectorLength: &size}}, f) 61 | case *types.Slice: 62 | return p.expandArrayHead(f) 63 | case *types.Pointer: 64 | vr, err := p.expand(&FieldDef{name: f.name, tag: f.tag, typ: ty.Elem(), pkg: f.pkg}) 65 | if err != nil { 66 | return nil, err 67 | } 68 | v := &gentypes.ValuePointer{Referent: vr} 69 | if !p.disableDelegation { 70 | v.Interfaces = interfaces.NewSSZSupportMap(ty) 71 | } 72 | return v, nil 73 | case *types.Struct: 74 | container := gentypes.ValueContainer{ 75 | Name: f.name, 76 | Package: f.pkg.Path(), 77 | } 78 | if !p.disableDelegation { 79 | container.Interfaces = interfaces.NewSSZSupportMap(ty) 80 | } 81 | for i := 0; i < ty.NumFields(); i++ { 82 | field := ty.Field(i) 83 | if field.Name() == "" || !field.Exported() { 84 | continue 85 | } 86 | rep, err := p.expand(&FieldDef{name: field.Name(), tag: ty.Tag(i), typ: field.Type(), pkg: field.Pkg()}) 87 | if err != nil { 88 | return nil, err 89 | } 90 | container.Append(f.name, rep) 91 | } 92 | return &container, nil 93 | case *types.Named: 94 | exp, err := p.expand(&FieldDef{name: ty.Obj().Name(), tag: f.tag, typ: ty.Underlying(), pkg: f.pkg}) 95 | switch ty.Underlying().(type) { 96 | case *types.Struct: 97 | return exp, err 98 | default: 99 | v := &gentypes.ValueOverlay{ 100 | Name: ty.Obj().Name(), 101 | Package: ty.Obj().Pkg().Path(), 102 | Underlying: exp, 103 | } 104 | if !p.disableDelegation { 105 | v.Interfaces = interfaces.NewSSZSupportMap(ty) 106 | } 107 | return v, err 108 | } 109 | case *types.Basic: 110 | return p.expandIdent(ty.Kind(), ty.Name()) 111 | default: 112 | return nil, fmt.Errorf("unsupported type for %v with name: %v", ty, f.name) 113 | } 114 | } 115 | 116 | func (p *FieldParser) expandArrayHead(f *FieldDef) (gentypes.ValRep, error) { 117 | dims, err := extractSSZDimensions(fmt.Sprintf("`%v`", f.tag)) 118 | if err != nil { 119 | return nil, errors.Wrapf(err, "name=%s, package=%s, tag=%s", f.name, f.pkg.Path(), f.tag) 120 | } 121 | return p.expandArray(dims, f) 122 | } 123 | 124 | func (p *FieldParser) expandArray(dims []*SSZDimension, f *FieldDef) (gentypes.ValRep, error) { 125 | if len(dims) == 0 { 126 | return nil, fmt.Errorf("do not have dimension information for type %v", f.name) 127 | } 128 | d := dims[0] 129 | var ( 130 | elv gentypes.ValRep 131 | err error 132 | elem types.Type 133 | ) 134 | isArray := false 135 | // at this point f.typ is either and array or a slice 136 | if arr, ok := f.typ.(*types.Array); ok { 137 | isArray = true 138 | elem = arr.Elem() 139 | } else if arr, ok := f.typ.(*types.Slice); ok { 140 | elem = arr.Elem() 141 | } else { 142 | return nil, fmt.Errorf("invalid typ in expand array: %v with name: %v ", f.typ, f.name) 143 | } 144 | 145 | // Only expand the inner array if it is not a named type 146 | if _, ok := elem.(*types.Named); !ok && len(dims) > 1 { 147 | elv, err = p.expandArray(dims[1:], &FieldDef{name: f.name, typ: elem.Underlying(), pkg: f.pkg}) 148 | if err != nil { 149 | return nil, err 150 | } 151 | } else { 152 | elv, err = p.expand(&FieldDef{name: f.name, tag: f.tag, typ: elem, pkg: f.pkg}) 153 | if err != nil { 154 | return nil, err 155 | } 156 | } 157 | 158 | if d.IsVector() { 159 | return &gentypes.ValueVector{ 160 | IsArray: isArray, 161 | ElementValue: elv, 162 | Size: d.VectorLen(), 163 | }, nil 164 | } 165 | if d.IsList() { 166 | return &gentypes.ValueList{ 167 | ElementValue: elv, 168 | MaxSize: d.ListLen(), 169 | }, nil 170 | } 171 | return nil, nil 172 | } 173 | 174 | func (p *FieldParser) expandIdent(ident types.BasicKind, name string) (gentypes.ValRep, error) { 175 | switch ident { 176 | case types.Bool: 177 | return &gentypes.ValueBool{Name: name}, nil 178 | case types.Byte: 179 | return &gentypes.ValueByte{Name: name}, nil 180 | case types.Uint16: 181 | return &gentypes.ValueUint{Size: 16, Name: name}, nil 182 | case types.Uint32: 183 | return &gentypes.ValueUint{Size: 32, Name: name}, nil 184 | case types.Uint64: 185 | return &gentypes.ValueUint{Size: 64, Name: name}, nil 186 | /* 187 | case "uint128": 188 | return &gentypes.ValueUint{Size: 128, Name: ident.name}, nil 189 | case "uint256": 190 | return &gentypes.ValueUint{Size: 256, Name: ident.name}, nil 191 | */ 192 | default: 193 | return nil, fmt.Errorf("unknown ident: %v", name) 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /sszgen/tagparse.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "text/scanner" 8 | 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | type tokenState int 13 | 14 | const ( 15 | tsBegin tokenState = iota 16 | tsLabel 17 | tsValue 18 | tsCloseTick 19 | ) 20 | 21 | type TagParser struct { 22 | sc scanner.Scanner 23 | buffer string 24 | } 25 | 26 | func (tp *TagParser) Init(tag string) { 27 | sr := strings.NewReader(tag) 28 | tp.sc = scanner.Scanner{} 29 | tp.sc.Init(sr) 30 | tp.sc.Filename = "tag" 31 | tp.sc.Mode ^= scanner.ScanRawStrings 32 | } 33 | 34 | func (tp TagParser) GetSSZTags() map[string]string { 35 | var labelStr string 36 | var state tokenState 37 | tags := make(map[string]string) 38 | for tok := tp.sc.Scan(); tok != scanner.EOF; tok = tp.sc.Scan() { 39 | if state == tsCloseTick { 40 | panic("undefined beyhavior when scanning beyond the end of the tag") 41 | } 42 | txt := tp.sc.TokenText() 43 | switch txt { 44 | case "`": 45 | if state == tsLabel { 46 | state = tsCloseTick 47 | continue 48 | } 49 | if state == tsBegin { 50 | state = tsLabel 51 | continue 52 | } 53 | case ":": 54 | if state == tsLabel { 55 | state = tsValue 56 | continue 57 | } 58 | case "\"": 59 | continue 60 | default: 61 | if state == tsValue { 62 | tags[labelStr] = trimQuotes(txt) 63 | state = tsLabel 64 | labelStr = "" 65 | continue 66 | } 67 | if state == tsLabel { 68 | labelStr += txt 69 | continue 70 | } 71 | } 72 | } 73 | return tags 74 | } 75 | 76 | // cannot compare untyped nil to typed nil 77 | // this value gives us a nil with type of *int 78 | // to compare to ssz-size = '?' values 79 | var nilInt *int 80 | 81 | func extractSSZDimensions(tag string) ([]*SSZDimension, error) { 82 | tp := &TagParser{} 83 | tp.Init(tag) 84 | tags := tp.GetSSZTags() 85 | szStr, sizeDefined := tags["ssz-size"] 86 | sizes := strings.Split(szStr, ",") 87 | maxStr, maxDefined := tags["ssz-max"] 88 | dims := make([]*SSZDimension, 0) 89 | maxes := strings.Split(maxStr, ",") 90 | if !sizeDefined { 91 | if !maxDefined { 92 | return nil, fmt.Errorf("no ssz-size or ssz-max tags found for element") 93 | } 94 | for _, m := range maxes { 95 | max, err := strconv.Atoi(m) 96 | if err != nil { 97 | return nil, errors.Wrapf(err, "error parsing ssz-size=%s, ssz-max=%s", szStr, maxStr) 98 | } 99 | dims = append(dims, &SSZDimension{ListLength: &max}) 100 | } 101 | return dims, nil 102 | } 103 | for i := 0; i < len(sizes); i++ { 104 | if sizes[i] == "?" { 105 | if len(maxes) <= i { 106 | return nil, fmt.Errorf("more than one wildcard in ssz-size, or ssz-max undefined in tag %s", tag) 107 | } 108 | max, err := strconv.Atoi(maxes[i]) 109 | if err != nil { 110 | return nil, err 111 | } 112 | dims = append(dims, &SSZDimension{ListLength: &max}) 113 | } else { 114 | vsize, err := strconv.Atoi(sizes[i]) 115 | if err != nil { 116 | return nil, err 117 | } 118 | dims = append(dims, &SSZDimension{VectorLength: &vsize}) 119 | } 120 | } 121 | return dims, nil 122 | } 123 | 124 | type SSZDimension struct { 125 | VectorLength *int 126 | ListLength *int 127 | } 128 | 129 | func (dim *SSZDimension) IsVector() bool { 130 | return dim.VectorLength != nilInt 131 | } 132 | 133 | func (dim *SSZDimension) IsList() bool { 134 | return dim.ListLength != nilInt 135 | } 136 | 137 | func (dim *SSZDimension) ListLen() int { 138 | return *dim.ListLength 139 | } 140 | 141 | func (dim *SSZDimension) VectorLen() int { 142 | return *dim.VectorLength 143 | } 144 | 145 | type SSZListBounds struct { 146 | SSZSize []*int 147 | SSZMax *int 148 | } 149 | 150 | func trimQuotes(s string) string { 151 | if len(s) > 0 && s[0] == '"' { 152 | s = s[1:] 153 | } 154 | if len(s) > 0 && s[len(s)-1] == '"' { 155 | s = s[:len(s)-1] 156 | } 157 | return s 158 | } 159 | -------------------------------------------------------------------------------- /sszgen/tagparse_test.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/prysmaticlabs/prysm/v3/testing/require" 7 | ) 8 | 9 | func TestTokens(t *testing.T) { 10 | testTag := "`protobuf:\"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3\" json:\"historical_roots,omitempty\" ssz-max:\"16777216\" ssz-size:\"?,32\"`" 11 | tp := &TagParser{} 12 | tp.Init(testTag) 13 | tags := tp.GetSSZTags() 14 | sszSize, ok := tags["ssz-size"] 15 | require.Equal(t, true, ok) 16 | require.Equal(t, "?,32", sszSize) 17 | sszMax, ok := tags["ssz-max"] 18 | require.Equal(t, true, ok) 19 | require.Equal(t, "16777216", sszMax) 20 | } 21 | 22 | func TestFullTag(t *testing.T) { 23 | tag := "`protobuf:\"bytes,1002,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3\" json:\"genesis_validators_root,omitempty\" ssz-size:\"32\"`" 24 | _, err := extractSSZDimensions(tag) 25 | require.NoError(t, err) 26 | } 27 | 28 | func TestListOfVector(t *testing.T) { 29 | tag := "`protobuf:\"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3\" json:\"historical_roots,omitempty\" ssz-max:\"16777216\" ssz-size:\"?,32\"`" 30 | _, err := extractSSZDimensions(tag) 31 | require.NoError(t, err) 32 | } 33 | 34 | func TestWildcardSSZSize(t *testing.T) { 35 | tag := "`ssz-max:\"16777216\" ssz-size:\"?,32\"`" 36 | bounds, err := extractSSZDimensions(tag) 37 | require.NoError(t, err) 38 | require.Equal(t, 2, len(bounds)) 39 | require.Equal(t, true, bounds[0].IsList()) 40 | require.Equal(t, false, bounds[0].IsVector()) 41 | require.Equal(t, 16777216, bounds[0].ListLen()) 42 | require.Equal(t, false, bounds[1].IsList()) 43 | require.Equal(t, true, bounds[1].IsVector()) 44 | require.Equal(t, 32, bounds[1].VectorLen()) 45 | } 46 | 47 | func Test2DWildcardSSZSize(t *testing.T) { 48 | tag := "`protobuf:\"bytes,14,rep,name=transactions,proto3\" json:\"transactions,omitempty\" ssz-max:\"1048576,1073741824\" ssz-size:\"?,?\"`" 49 | bounds, err := extractSSZDimensions(tag) 50 | require.NoError(t, err) 51 | require.Equal(t, 2, len(bounds)) 52 | require.Equal(t, true, bounds[0].IsList()) 53 | require.Equal(t, false, bounds[0].IsVector()) 54 | require.Equal(t, 1048576, bounds[0].ListLen()) 55 | require.Equal(t, true, bounds[1].IsList()) 56 | require.Equal(t, false, bounds[1].IsVector()) 57 | require.Equal(t, 1073741824, bounds[1].ListLen()) 58 | } 59 | -------------------------------------------------------------------------------- /sszgen/testdata/simple.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import github_com_prysmaticlabs_go_bitfield "github.com/prysmaticlabs/go-bitfield" 4 | 5 | type NoImports struct { 6 | state int 7 | sizeCache int 8 | unknownFields int 9 | 10 | GenesisTime uint64 `protobuf:"varint,1001,opt,name=genesis_time,json=genesisTime,proto3" json:"genesis_time,omitempty"` 11 | GenesisValidatorsRoot []byte `protobuf:"bytes,1002,opt,name=genesis_validators_root,json=genesisValidatorsRoot,proto3" json:"genesis_validators_root,omitempty" ssz-size:"32"` 12 | BlockRoots [][]byte `protobuf:"bytes,2002,rep,name=block_roots,json=blockRoots,proto3" json:"block_roots,omitempty" ssz-size:"8192,32"` 13 | HistoricalRoots [][]byte `protobuf:"bytes,2004,rep,name=historical_roots,json=historicalRoots,proto3" json:"historical_roots,omitempty" ssz-max:"16777216" ssz-size:"?,32"` 14 | MuhPrim AliasedPrimitive 15 | ContainerField ContainerType 16 | ContainerRefField *AnotherContainerType 17 | ContainerList []ContainerType `ssz-max:"23"` 18 | ContainerVector []ContainerType `ssz-size:"42"` 19 | ContainerVectorRef []*ContainerType `ssz-size:"17"` 20 | ContainerListRef []*ContainerType `ssz-max:"9000"` 21 | OverlayList []AliasedPrimitive `ssz-max:"11"` 22 | OverlayListRef []*AliasedPrimitive `ssz-max:"58"` 23 | OverlayVector []AliasedPrimitive `ssz-size:"23"` 24 | OverlayVectorRef []*AliasedPrimitive `ssz-size:"13"` 25 | } 26 | 27 | type AliasedPrimitive uint64 28 | 29 | type ContainerType struct { 30 | MuhPrim AliasedPrimitive 31 | } 32 | 33 | type AnotherContainerType struct { 34 | MuhPrim AliasedPrimitive 35 | } 36 | 37 | type FixedSizeArray [32]byte 38 | 39 | type FixedContainer struct { 40 | Array FixedSizeArray 41 | } 42 | 43 | type TestBitlist struct { 44 | AggregationBits github_com_prysmaticlabs_go_bitfield.Bitlist `protobuf:"bytes,1,opt,name=aggregation_bits,json=aggregationBits,proto3" json:"aggregation_bits,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitlist" ssz-max:"2048"` 45 | JustificationBits github_com_prysmaticlabs_go_bitfield.Bitvector4 `protobuf:"bytes,8001,opt,name=justification_bits,json=justificationBits,proto3" json:"justification_bits,omitempty" cast-type:"github.com/prysmaticlabs/go-bitfield.Bitvector4" ssz-size:"1"` 46 | } 47 | -------------------------------------------------------------------------------- /sszgen/testdata/uint256.go.fixture: -------------------------------------------------------------------------------- 1 | package faketypes 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | ) 8 | 9 | var ( 10 | ErrBadBufferLength = errors.New("bad ssz buffer length") 11 | ErrBadEncodedLength = errors.New("bad ssz encoded length") 12 | ) 13 | 14 | type TestStruct struct { 15 | TwoFixSix *Int 16 | } 17 | 18 | // Int is represented as an array of 4 uint64, in little-endian order, 19 | // so that Int[3] is the most significant, and Int[0] is the least significant 20 | type Int [4]uint64 21 | 22 | // NewInt returns a new initialized Int. 23 | func NewInt(val uint64) *Int { 24 | z := &Int{} 25 | z.SetUint64(val) 26 | return z 27 | } 28 | 29 | // SetUint64 sets z to the value x 30 | func (z *Int) SetUint64(x uint64) *Int { 31 | z[3], z[2], z[1], z[0] = 0, 0, 0, x 32 | return z 33 | } 34 | 35 | // MarshalSSZTo implements the fastssz.Marshaler interface and serializes the 36 | // integer into an already pre-allocated buffer. 37 | func (z *Int) MarshalSSZTo(dst []byte) ([]byte, error) { 38 | if len(dst) < 32 { 39 | return nil, fmt.Errorf("%w: have %d, want %d bytes", ErrBadBufferLength, len(dst), 32) 40 | } 41 | binary.LittleEndian.PutUint64(dst[0:8], z[0]) 42 | binary.LittleEndian.PutUint64(dst[8:16], z[1]) 43 | binary.LittleEndian.PutUint64(dst[16:24], z[2]) 44 | binary.LittleEndian.PutUint64(dst[24:32], z[3]) 45 | 46 | return dst[32:], nil 47 | } 48 | 49 | // MarshalSSZ implements the fastssz.Marshaler interface and returns the integer 50 | // marshalled into a newly allocated byte slice. 51 | func (z *Int) MarshalSSZ() ([]byte, error) { 52 | blob := make([]byte, 32) 53 | _, _ = z.MarshalSSZTo(blob) // ignore error, cannot fail, surely have 32 byte space in blob 54 | return blob, nil 55 | } 56 | 57 | // SizeSSZ implements the fastssz.Marshaler interface and returns the byte size 58 | // of the 256 bit int. 59 | func (*Int) SizeSSZ() int { 60 | return 32 61 | } 62 | 63 | // UnmarshalSSZ implements the fastssz.Unmarshaler interface and parses an encoded 64 | // integer into the local struct. 65 | func (z *Int) UnmarshalSSZ(buf []byte) error { 66 | if len(buf) != 32 { 67 | return fmt.Errorf("%w: have %d, want %d bytes", ErrBadEncodedLength, len(buf), 32) 68 | } 69 | z[0] = binary.LittleEndian.Uint64(buf[0:8]) 70 | z[1] = binary.LittleEndian.Uint64(buf[8:16]) 71 | z[2] = binary.LittleEndian.Uint64(buf[16:24]) 72 | z[3] = binary.LittleEndian.Uint64(buf[24:32]) 73 | 74 | return nil 75 | } 76 | 77 | // HashTreeRoot implements the fastssz.HashRoot interface's non-dependent part. 78 | func (z *Int) HashTreeRoot() ([32]byte, error) { 79 | var hash [32]byte 80 | _, _ = z.MarshalSSZTo(hash[:]) // ignore error, cannot fail 81 | return hash, nil 82 | } 83 | -------------------------------------------------------------------------------- /sszgen/testdata/uint256.ssz.go.fixture: -------------------------------------------------------------------------------- 1 | package faketypes 2 | 3 | import ( 4 | "fmt" 5 | ssz "github.com/prysmaticlabs/fastssz" 6 | ) 7 | 8 | func (c *TestStruct) SizeSSZ() int { 9 | size := 32 10 | 11 | return size 12 | } 13 | 14 | func (c *TestStruct) MarshalSSZ() ([]byte, error) { 15 | buf := make([]byte, c.SizeSSZ()) 16 | return c.MarshalSSZTo(buf[:0]) 17 | } 18 | 19 | func (c *TestStruct) MarshalSSZTo(dst []byte) ([]byte, error) { 20 | var err error 21 | 22 | // Field 0: TwoFixSix 23 | if c.TwoFixSix == nil { 24 | c.TwoFixSix = new(Int) 25 | } 26 | if dst, err = c.TwoFixSix.MarshalSSZTo(dst); err != nil { 27 | return nil, err 28 | } 29 | 30 | return dst, err 31 | } 32 | 33 | func (c *TestStruct) UnmarshalSSZ(buf []byte) error { 34 | var err error 35 | size := uint64(len(buf)) 36 | if size != 32 { 37 | return ssz.ErrSize 38 | } 39 | 40 | s0 := buf[0:32] // c.TwoFixSix 41 | 42 | // Field 0: TwoFixSix 43 | c.TwoFixSix = new(Int) 44 | if err = c.TwoFixSix.UnmarshalSSZ(s0); err != nil { 45 | return err 46 | } 47 | return err 48 | } 49 | 50 | func (c *TestStruct) HashTreeRoot() ([32]byte, error) { 51 | hh := ssz.DefaultHasherPool.Get() 52 | if err := c.HashTreeRootWith(hh); err != nil { 53 | ssz.DefaultHasherPool.Put(hh) 54 | return [32]byte{}, err 55 | } 56 | root, err := hh.HashRoot() 57 | ssz.DefaultHasherPool.Put(hh) 58 | return root, err 59 | } 60 | 61 | func (c *TestStruct) HashTreeRootWith(hh *ssz.Hasher) (err error) { 62 | indx := hh.Index() 63 | // Field 0: TwoFixSix 64 | if hash, err := c.TwoFixSix.HashTreeRoot(); err != nil { 65 | return err 66 | } else { 67 | hh.AppendBytes32(hash[:]) 68 | } 69 | hh.Merkleize(indx) 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /sszgen/testutil/render.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "go/format" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | jen "github.com/dave/jennifer/jen" 8 | ) 9 | 10 | func RenderIntermediate(vr types.ValRep) (string, error) { 11 | file := jen.NewFile("values") 12 | evr, err := encodeValRep(vr) 13 | if err != nil { 14 | return "", err 15 | } 16 | v := jen.Var().Id(vr.TypeName()).Id("types").Dot("ValRep").Op("=").Add(evr) 17 | file.Add(v) 18 | 19 | gs := file.GoString() 20 | b, err := format.Source([]byte(gs)) 21 | return string(b), err 22 | } 23 | 24 | func encodeValRep(vr types.ValRep) (jen.Code, error) { 25 | var c jen.Code 26 | switch ty := vr.(type) { 27 | case *types.ValueByte: 28 | values := []jen.Code{jen.Id("Name").Op(":").Lit(ty.Name)} 29 | if ty.Package != "" { 30 | values = append(values, jen.Id("Package").Op(":").Lit(ty.Package)) 31 | } 32 | s := jen.Op("&").Id("types").Dot("ValueByte").Values(values...) 33 | return s, nil 34 | case *types.ValueBool: 35 | values := []jen.Code{jen.Id("Name").Op(":").Lit(ty.Name)} 36 | if ty.Package != "" { 37 | values = append(values, jen.Id("Package").Op(":").Lit(ty.Package)) 38 | } 39 | s := jen.Op("&").Id("types").Dot("ValueBool").Values(values...) 40 | return s, nil 41 | case *types.ValueUint: 42 | s := jen.Op("&").Id("types").Dot("ValueUint").Values( 43 | jen.Id("Name").Op(":").Lit(ty.Name), 44 | jen.Id("Size").Op(":").Lit(int(ty.Size)), 45 | ) 46 | return s, nil 47 | case *types.ValueVector: 48 | ev, err := encodeValRep(ty.ElementValue) 49 | if err != nil { 50 | return nil, err 51 | } 52 | s := jen.Op("&").Id("types").Dot("ValueVector").Values( 53 | jen.Id("Size").Op(":").Lit(ty.Size), 54 | jen.Id("ElementValue").Op(":").Add(ev), 55 | ) 56 | return s, nil 57 | case *types.ValueList: 58 | ev, err := encodeValRep(ty.ElementValue) 59 | if err != nil { 60 | return nil, err 61 | } 62 | s := jen.Op("&").Id("types").Dot("ValueList").Values( 63 | jen.Id("MaxSize").Op(":").Lit(ty.MaxSize), 64 | jen.Id("ElementValue").Op(":").Add(ev), 65 | ) 66 | return s, nil 67 | case *types.ValueOverlay: 68 | underlying, err := encodeValRep(ty.Underlying) 69 | if err != nil { 70 | return nil, err 71 | } 72 | s := jen.Op("&").Id("types").Dot("ValueOverlay").Values( 73 | jen.Id("Name").Op(":").Lit(ty.Name), 74 | jen.Id("Package").Op(":").Lit(ty.Package), 75 | jen.Id("Underlying").Op(":").Add(underlying), 76 | ) 77 | return s, nil 78 | case *types.ValuePointer: 79 | referent, err := encodeValRep(ty.Referent) 80 | if err != nil { 81 | return nil, err 82 | } 83 | s := jen.Op("&").Id("types").Dot("ValuePointer").Values( 84 | jen.Id("Referent").Op(":").Add(referent), 85 | ) 86 | return s, nil 87 | case *types.ValueContainer: 88 | contents := make([]jen.Code, 0) 89 | for _, c := range ty.Contents { 90 | cvr, err := encodeValRep(c.Value) 91 | if err != nil { 92 | return nil, err 93 | } 94 | kv := jen.Values(jen.Id("Key").Op(":").Lit(c.Key), 95 | jen.Id("Value").Op(":").Add(cvr)) 96 | contents = append(contents, kv) 97 | } 98 | fields := []jen.Code{ 99 | jen.Id("Name").Op(":").Lit(ty.Name), 100 | jen.Id("Package").Op(":").Lit(ty.Package), 101 | jen.Id("Contents").Op(":").Index().Id("types").Dot("ContainerField"). 102 | Values(contents...), 103 | } 104 | c = jen.Op("&").Id("types").Dot("ValueContainer").Values(fields...) 105 | case *types.ValueUnion: 106 | panic("not implemented") 107 | } 108 | return c, nil 109 | } 110 | -------------------------------------------------------------------------------- /sszgen/testutil/render_test.go: -------------------------------------------------------------------------------- 1 | package testutil 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | "github.com/prysmaticlabs/prysm/v3/testing/require" 8 | ) 9 | 10 | func TestRenderIntermediate(t *testing.T) { 11 | t.Skip("TODO: investigate this failure") 12 | s := &types.ValueContainer{ 13 | Name: "testing", 14 | Package: "github.com/prysmaticlabs/derp", 15 | Contents: []types.ContainerField{ 16 | { 17 | Key: "OverlayUint", 18 | Value: &types.ValuePointer{Referent: &types.ValueOverlay{ 19 | Name: "FakeContainer", 20 | Package: "github.com/prysmaticlabs/derp/derp", 21 | Underlying: &types.ValueUint{ 22 | Name: "uint8", 23 | Size: 8, 24 | }, 25 | }, 26 | }, 27 | }, 28 | }, 29 | } 30 | expected := "" 31 | actual, err := RenderIntermediate(s) 32 | require.NoError(t, err) 33 | require.Equal(t, expected, actual) 34 | } 35 | -------------------------------------------------------------------------------- /sszgen/testutil/testdata/examplevars.go: -------------------------------------------------------------------------------- 1 | // This file exists just to give me some bootstraping input to run through github.com/aloder/tojen 2 | // to speed up the tedious process of writing jen code 3 | package testdata 4 | 5 | import ( 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/types" 7 | ) 8 | 9 | var testing types.ValRep = &types.ValueContainer{ 10 | Name: "testing", 11 | Package: "github.com/prysmaticlabs/derp", 12 | Contents: map[string]types.ValRep{ 13 | "OverlayUint": &types.ValuePointer{ 14 | Referent: &types.ValueOverlay{ 15 | Name: "FakeContainer", 16 | Package: "github.com/prysmaticlabs/derp/derp", 17 | Underlying: &types.ValueUint{ 18 | Name: "uint8", 19 | Size: 8, 20 | }, 21 | }, 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /sszgen/types/bool.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValueBool struct { 6 | Name string 7 | Package string 8 | } 9 | 10 | func (vb *ValueBool) TypeName() string { 11 | return vb.Name 12 | } 13 | 14 | func (vb *ValueBool) PackagePath() string { 15 | return vb.Package 16 | } 17 | 18 | func (vb *ValueBool) FixedSize() int { 19 | return 1 20 | } 21 | 22 | func (vb *ValueBool) IsVariableSized() bool { 23 | return false 24 | } 25 | 26 | func (vb *ValueBool) SatisfiesInterface(*types.Interface) bool { 27 | return false 28 | } 29 | 30 | var _ ValRep = &ValueBool{} 31 | -------------------------------------------------------------------------------- /sszgen/types/byte.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValueByte struct { 6 | Name string 7 | Package string 8 | } 9 | 10 | func (vb *ValueByte) TypeName() string { 11 | return vb.Name 12 | } 13 | 14 | func (vb *ValueByte) PackagePath() string { 15 | return vb.Package 16 | } 17 | 18 | func (vb *ValueByte) FixedSize() int { 19 | return 1 20 | } 21 | 22 | func (vb *ValueByte) IsVariableSized() bool { 23 | return false 24 | } 25 | 26 | func (vb *ValueByte) SatisfiesInterface(*types.Interface) bool { 27 | return false 28 | } 29 | 30 | var _ ValRep = &ValueByte{} 31 | -------------------------------------------------------------------------------- /sszgen/types/container.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | ) 7 | 8 | type ContainerField struct { 9 | Key string 10 | Value ValRep 11 | } 12 | 13 | type ValueContainer struct { 14 | Name string 15 | Package string 16 | Contents []ContainerField 17 | HasSSZMethods bool 18 | nameMap map[string]ValRep 19 | Interfaces map[*types.Interface]bool 20 | } 21 | 22 | func (vc *ValueContainer) Fields() []ContainerField { 23 | return vc.Contents 24 | } 25 | 26 | func (vc *ValueContainer) Append(name string, value ValRep) { 27 | vc.Contents = append(vc.Contents, ContainerField{Key: name, Value: value}) 28 | if vc.nameMap == nil { 29 | vc.nameMap = make(map[string]ValRep) 30 | } 31 | vc.nameMap[name] = value 32 | } 33 | 34 | func (vc *ValueContainer) GetField(name string) (ValRep, error) { 35 | field, ok := vc.nameMap[name] 36 | if !ok { 37 | return nil, fmt.Errorf("Field named %s not found in container value mapping", name) 38 | } 39 | return field, nil 40 | } 41 | 42 | func (vc *ValueContainer) TypeName() string { 43 | return vc.Name 44 | } 45 | 46 | func (vc *ValueContainer) PackagePath() string { 47 | return vc.Package 48 | } 49 | 50 | func (vc *ValueContainer) FixedSize() int { 51 | if vc.IsVariableSized() { 52 | return 4 53 | } 54 | total := 0 55 | for _, c := range vc.Contents { 56 | o := c.Value 57 | total += o.FixedSize() 58 | } 59 | return total 60 | } 61 | 62 | func (vc *ValueContainer) IsVariableSized() bool { 63 | for _, c := range vc.Contents { 64 | if c.Value.IsVariableSized() { 65 | return true 66 | } 67 | } 68 | return false 69 | } 70 | 71 | func (vc *ValueContainer) SatisfiesInterface(ti *types.Interface) bool { 72 | return vc.Interfaces != nil && vc.Interfaces[ti] 73 | } 74 | 75 | var _ ValRep = &ValueContainer{} 76 | -------------------------------------------------------------------------------- /sszgen/types/list.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValueList struct { 6 | ElementValue ValRep 7 | MaxSize int 8 | } 9 | 10 | func (vl *ValueList) TypeName() string { 11 | return "[]" + vl.ElementValue.TypeName() 12 | } 13 | 14 | func (vl *ValueList) PackagePath() string { 15 | return vl.ElementValue.PackagePath() 16 | } 17 | 18 | func (vl *ValueList) FixedSize() int { 19 | return 4 20 | } 21 | 22 | func (vl *ValueList) IsVariableSized() bool { 23 | return true 24 | } 25 | 26 | func (vl *ValueList) SatisfiesInterface(*types.Interface) bool { 27 | return false 28 | } 29 | 30 | var _ ValRep = &ValueList{} 31 | -------------------------------------------------------------------------------- /sszgen/types/overlay.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | ) 7 | 8 | type ValueOverlay struct { 9 | Name string 10 | Package string 11 | Underlying ValRep 12 | Interfaces map[*types.Interface]bool 13 | } 14 | 15 | func (vo *ValueOverlay) TypeName() string { 16 | return vo.Name 17 | } 18 | 19 | func (vo *ValueOverlay) PackagePath() string { 20 | return vo.Package 21 | } 22 | 23 | func (vo *ValueOverlay) FixedSize() int { 24 | if vo.IsBitfield() { 25 | return vo.bitfieldFixedSize() 26 | } 27 | return vo.Underlying.FixedSize() 28 | } 29 | 30 | func (vo *ValueOverlay) IsVariableSized() bool { 31 | return vo.Underlying.IsVariableSized() 32 | } 33 | 34 | func (vo *ValueOverlay) SatisfiesInterface(ti *types.Interface) bool { 35 | return vo.Interfaces != nil && vo.Interfaces[ti] 36 | } 37 | 38 | func (vo *ValueOverlay) IsBitfield() bool { 39 | return vo.Package == "github.com/prysmaticlabs/go-bitfield" 40 | } 41 | 42 | func (vo *ValueOverlay) bitfieldFixedSize() int { 43 | switch vo.Name { 44 | case "Bitlist": 45 | return 4 46 | case "Bitlist64": 47 | return 4 48 | case "Bitvector4": 49 | return 1 50 | case "Bitvector8": 51 | return 1 52 | case "Bitvector32": 53 | return 4 54 | case "Bitvector64": 55 | return 8 56 | case "Bitvector128": 57 | return 16 58 | case "Bitvector256": 59 | return 32 60 | case "Bitvector512": 61 | return 64 62 | case "Bitvector1024": 63 | return 128 64 | } 65 | panic(fmt.Sprintf("Can't determine the correct size for bitfield type = %s", vo.Name)) 66 | } 67 | 68 | var _ ValRep = &ValueOverlay{} 69 | -------------------------------------------------------------------------------- /sszgen/types/pointer.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "go/types" 5 | 6 | "github.com/OffchainLabs/methodical-ssz/sszgen/interfaces" 7 | ) 8 | 9 | type ValuePointer struct { 10 | Referent ValRep 11 | Interfaces map[*types.Interface]bool 12 | } 13 | 14 | func (vp *ValuePointer) TypeName() string { 15 | return "*" + vp.Referent.TypeName() 16 | } 17 | 18 | func (vp *ValuePointer) PackagePath() string { 19 | return vp.Referent.PackagePath() 20 | } 21 | 22 | func (vp *ValuePointer) FixedSize() int { 23 | return vp.Referent.FixedSize() 24 | } 25 | 26 | func (vp *ValuePointer) IsVariableSized() bool { 27 | return vp.Referent.IsVariableSized() 28 | } 29 | 30 | func (vp *ValuePointer) SatisfiesInterface(ti *types.Interface) bool { 31 | if vp.Interfaces != nil && vp.Interfaces[ti] { 32 | return true 33 | } 34 | // Unmarshaler needs a pointer receiver, and the above check failed means that there isn't one, 35 | // so we shouldn't allow a value receiver to satisfy the interface. 36 | if ti == interfaces.SszUnmarshaler { 37 | return false 38 | } 39 | // since the other methods are read-only, it's ok to use a method with a value receiver 40 | return vp.Referent.SatisfiesInterface(ti) 41 | } 42 | 43 | var _ ValRep = &ValuePointer{} 44 | -------------------------------------------------------------------------------- /sszgen/types/uint.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type UintSize int 6 | 7 | const ( 8 | Uint8 UintSize = 8 9 | Uint16 UintSize = 16 10 | Uint32 UintSize = 32 11 | Uint64 UintSize = 64 12 | Uint128 UintSize = 128 13 | Uint256 UintSize = 256 14 | ) 15 | 16 | type ValueUint struct { 17 | Name string 18 | Size UintSize 19 | Package string 20 | } 21 | 22 | func (vu *ValueUint) TypeName() string { 23 | return vu.Name 24 | } 25 | 26 | func (vu *ValueUint) PackagePath() string { 27 | return vu.Package 28 | } 29 | 30 | func (vu *ValueUint) FixedSize() int { 31 | return int(vu.Size) / 8 32 | } 33 | 34 | func (vu *ValueUint) IsVariableSized() bool { 35 | return false 36 | } 37 | 38 | func (vu *ValueUint) SatisfiesInterface(*types.Interface) bool { 39 | return false 40 | } 41 | 42 | var _ ValRep = &ValueUint{} 43 | -------------------------------------------------------------------------------- /sszgen/types/union.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValueUnion struct { 6 | Name string 7 | } 8 | 9 | func (vu *ValueUnion) TypeName() string { 10 | return vu.Name 11 | } 12 | 13 | func (vu *ValueUnion) PackagePath() string { 14 | panic("not implemented") 15 | } 16 | 17 | func (vu *ValueUnion) FixedSize() int { 18 | panic("not implemented") 19 | } 20 | 21 | func (vu *ValueUnion) IsVariableSized() bool { 22 | panic("not implemented") 23 | } 24 | 25 | func (vu *ValueUnion) SatisfiesInterface(*types.Interface) bool { 26 | return false 27 | } 28 | 29 | var _ ValRep = &ValueUnion{} 30 | -------------------------------------------------------------------------------- /sszgen/types/valrep.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValRep interface { 6 | TypeName() string 7 | FixedSize() int 8 | PackagePath() string 9 | IsVariableSized() bool 10 | SatisfiesInterface(*types.Interface) bool 11 | } 12 | -------------------------------------------------------------------------------- /sszgen/types/vector.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "go/types" 4 | 5 | type ValueVector struct { 6 | ElementValue ValRep 7 | Size int 8 | IsArray bool 9 | } 10 | 11 | func (vv *ValueVector) TypeName() string { 12 | return "[]" + vv.ElementValue.TypeName() 13 | } 14 | 15 | func (vv *ValueVector) FixedSize() int { 16 | if vv.IsVariableSized() { 17 | return 4 18 | } 19 | return vv.Size * vv.ElementValue.FixedSize() 20 | } 21 | 22 | func (vv *ValueVector) PackagePath() string { 23 | return vv.ElementValue.PackagePath() 24 | } 25 | 26 | func (vv *ValueVector) IsVariableSized() bool { 27 | return vv.ElementValue.IsVariableSized() 28 | } 29 | 30 | func (vv *ValueVector) SatisfiesInterface(*types.Interface) bool { 31 | return false 32 | } 33 | 34 | var _ ValRep = &ValueVector{} 35 | -------------------------------------------------------------------------------- /sszgen/typeutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Felix Lange . 2 | // Use of this source code is governed by the MIT license, 3 | // which can be found in the LICENSE file. 4 | 5 | package sszgen 6 | 7 | import ( 8 | "fmt" 9 | "go/types" 10 | "io" 11 | "sort" 12 | "strconv" 13 | 14 | "github.com/pkg/errors" 15 | ) 16 | 17 | // walkNamedTypes runs the callback for all named types contained in the given type. 18 | func walkNamedTypes(typ types.Type, callback func(*types.Named)) { 19 | switch typ := typ.(type) { 20 | case *types.Basic: 21 | case *types.Chan: 22 | walkNamedTypes(typ.Elem(), callback) 23 | case *types.Map: 24 | walkNamedTypes(typ.Key(), callback) 25 | walkNamedTypes(typ.Elem(), callback) 26 | case *types.Named: 27 | callback(typ) 28 | case *types.Pointer: 29 | walkNamedTypes(typ.Elem(), callback) 30 | case *types.Slice: 31 | case *types.Array: 32 | walkNamedTypes(typ.Elem(), callback) 33 | case *types.Struct: 34 | for i := 0; i < typ.NumFields(); i++ { 35 | walkNamedTypes(typ.Field(i).Type(), callback) 36 | } 37 | case *types.Interface: 38 | if typ.NumMethods() > 0 { 39 | panic("BUG: can't walk non-empty interface") 40 | } 41 | default: 42 | panic(fmt.Errorf("BUG: can't walk %T", typ)) 43 | } 44 | } 45 | 46 | func lookupType(scope *types.Scope, name string) (*types.Named, types.Object, error) { 47 | if name == "" { 48 | return nil, nil, errors.Wrap(errors.New("no such identifier"), "empty name lookup") 49 | } 50 | obj := scope.Lookup(name) 51 | if obj == nil { 52 | return nil, nil, errors.Wrap(errors.New("no such identifier"), name) 53 | } 54 | typ, ok := obj.(*types.TypeName) 55 | if !ok { 56 | return nil, nil, errors.Wrap(errors.New("not a type"), name) 57 | } 58 | return typ.Type().(*types.Named), obj, nil 59 | } 60 | 61 | func isPointer(typ types.Type) bool { 62 | _, ok := typ.(*types.Pointer) 63 | return ok 64 | } 65 | 66 | func underlyingSlice(typ types.Type) *types.Slice { 67 | for { 68 | switch t := typ.(type) { 69 | case *types.Named: 70 | typ = typ.Underlying() 71 | case *types.Slice: 72 | return t 73 | default: 74 | return nil 75 | } 76 | } 77 | } 78 | 79 | func underlyingMap(typ types.Type) *types.Map { 80 | for { 81 | switch t := typ.(type) { 82 | case *types.Named: 83 | typ = typ.Underlying() 84 | case *types.Map: 85 | return t 86 | default: 87 | return nil 88 | } 89 | } 90 | } 91 | 92 | func ensureNilCheckable(typ types.Type) types.Type { 93 | orig := typ 94 | named := false 95 | for { 96 | switch typ.(type) { 97 | case *types.Named: 98 | typ = typ.Underlying() 99 | named = true 100 | case *types.Slice, *types.Map: 101 | if named { 102 | // Named slices, maps, etc. are special because they can have a custom 103 | // decoder function that prevents the JSON null value. Wrap them with a 104 | // pointer to allow null always so required/optional works as expected. 105 | return types.NewPointer(orig) 106 | } 107 | return orig 108 | case *types.Pointer, *types.Interface: 109 | return orig 110 | default: 111 | return types.NewPointer(orig) 112 | } 113 | } 114 | } 115 | 116 | // checkConvertible determines whether values of type from can be converted to type to. It 117 | // returns nil if convertible and a descriptive error otherwise. 118 | // See package documentation for this definition of 'convertible'. 119 | func checkConvertible(from, to types.Type) error { 120 | if types.ConvertibleTo(from, to) { 121 | return nil 122 | } 123 | // Slices. 124 | sfrom := underlyingSlice(from) 125 | sto := underlyingSlice(to) 126 | if sfrom != nil && sto != nil { 127 | if !types.ConvertibleTo(sfrom.Elem(), sto.Elem()) { 128 | return fmt.Errorf("slice element type %s is not convertible to %s", sfrom.Elem(), sto.Elem()) 129 | } 130 | return nil 131 | } 132 | // Maps. 133 | mfrom := underlyingMap(from) 134 | mto := underlyingMap(to) 135 | if mfrom != nil && mto != nil { 136 | if !types.ConvertibleTo(mfrom.Key(), mto.Key()) { 137 | return fmt.Errorf("map key type %s is not convertible to %s", mfrom.Key(), mto.Key()) 138 | } 139 | if !types.ConvertibleTo(mfrom.Elem(), mto.Elem()) { 140 | return fmt.Errorf("map element type %s is not convertible to %s", mfrom.Elem(), mto.Elem()) 141 | } 142 | return nil 143 | } 144 | return fmt.Errorf("type %s is not convertible to %s", from, to) 145 | } 146 | 147 | // fileScope tracks imports and other names at file scope. 148 | type fileScope struct { 149 | imports []*types.Package 150 | importsByName map[string]*types.Package 151 | importNames map[string]string 152 | otherNames map[string]bool // non-package identifiers 153 | pkg *types.Package 154 | imp types.Importer 155 | } 156 | 157 | func newFileScope(imp types.Importer, pkg *types.Package) *fileScope { 158 | return &fileScope{otherNames: make(map[string]bool), pkg: pkg, imp: imp} 159 | } 160 | 161 | func (s *fileScope) writeImportDecl(w io.Writer) { 162 | fmt.Fprintln(w, "import (") 163 | for _, pkg := range s.imports { 164 | if s.importNames[pkg.Path()] != pkg.Name() { 165 | fmt.Fprintf(w, "\t%s %q\n", s.importNames[pkg.Path()], pkg.Path()) 166 | } else { 167 | fmt.Fprintf(w, "\t%q\n", pkg.Path()) 168 | } 169 | } 170 | fmt.Fprintln(w, ")") 171 | } 172 | 173 | // addImport loads a package and adds it to the import set. 174 | func (s *fileScope) addImport(path string) { 175 | pkg, err := s.imp.Import(path) 176 | if err != nil { 177 | panic(fmt.Errorf("can't import %q: %v", path, err)) 178 | } 179 | s.insertImport(pkg) 180 | s.rebuildImports() 181 | } 182 | 183 | // addReferences marks all names referenced by typ as used. 184 | func (s *fileScope) addReferences(typ types.Type) { 185 | walkNamedTypes(typ, func(nt *types.Named) { 186 | pkg := nt.Obj().Pkg() 187 | if pkg == s.pkg { 188 | s.otherNames[nt.Obj().Name()] = true 189 | } else if pkg != nil { 190 | s.insertImport(nt.Obj().Pkg()) 191 | } 192 | }) 193 | s.rebuildImports() 194 | } 195 | 196 | // insertImport adds pkg to the list of known imports. 197 | // This method should not be used directly because it doesn't 198 | // rebuild the import name cache. 199 | func (s *fileScope) insertImport(pkg *types.Package) { 200 | i := sort.Search(len(s.imports), func(i int) bool { 201 | return s.imports[i].Path() >= pkg.Path() 202 | }) 203 | if i < len(s.imports) && s.imports[i] == pkg { 204 | return 205 | } 206 | s.imports = append(s.imports[:i], append([]*types.Package{pkg}, s.imports[i:]...)...) 207 | } 208 | 209 | // rebuildImports caches the names of imported packages. 210 | func (s *fileScope) rebuildImports() { 211 | s.importNames = make(map[string]string) 212 | s.importsByName = make(map[string]*types.Package) 213 | for _, pkg := range s.imports { 214 | s.maybeRenameImport(pkg) 215 | } 216 | } 217 | 218 | func (s *fileScope) maybeRenameImport(pkg *types.Package) { 219 | name := pkg.Name() 220 | for i := 0; s.isNameTaken(name); i++ { 221 | name = pkg.Name() 222 | if i > 0 { 223 | name += strconv.Itoa(i - 1) 224 | } 225 | } 226 | s.importNames[pkg.Path()] = name 227 | s.importsByName[name] = pkg 228 | } 229 | 230 | // isNameTaken reports whether the given name is used by an import or other identifier. 231 | func (s *fileScope) isNameTaken(name string) bool { 232 | return s.importsByName[name] != nil || s.otherNames[name] || types.Universe.Lookup(name) != nil 233 | } 234 | 235 | // qualify is a types.Qualifier that prepends the (possibly renamed) package name of 236 | // imported types to a type name. 237 | func (s *fileScope) qualify(pkg *types.Package) string { 238 | if pkg == s.pkg { 239 | return "" 240 | } 241 | return s.packageName(pkg.Path()) 242 | } 243 | 244 | func (s *fileScope) packageName(path string) string { 245 | name, ok := s.importNames[path] 246 | if !ok { 247 | panic("BUG: missing package " + path) 248 | } 249 | return name 250 | } 251 | 252 | // funcScope tracks used identifiers in a function. It can create new identifiers that do 253 | // not clash with the parent scope. 254 | type funcScope struct { 255 | used map[string]bool 256 | parent *fileScope 257 | } 258 | 259 | func newFuncScope(parent *fileScope) *funcScope { 260 | return &funcScope{make(map[string]bool), parent} 261 | } 262 | 263 | // newIdent creates a new identifier that doesn't clash with any name 264 | // in the scope or its parent file scope. 265 | func (s *funcScope) newIdent(base string) string { 266 | for i := 0; ; i++ { 267 | name := base 268 | if i > 0 { 269 | name += strconv.Itoa(i - 1) 270 | } 271 | if !s.parent.isNameTaken(name) && !s.used[name] { 272 | s.used[name] = true 273 | return name 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /sszgen/virtualscoper.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "go/ast" 5 | "go/importer" 6 | "go/parser" 7 | "go/token" 8 | "go/types" 9 | 10 | "github.com/pkg/errors" 11 | ) 12 | 13 | type VirtualFile struct { 14 | name string 15 | contents string 16 | } 17 | 18 | type VirtualPathScoper struct { 19 | vfiles []VirtualFile 20 | files []*ast.File 21 | scope *types.Scope 22 | path string 23 | } 24 | 25 | func NewVirtualPathScoper(pkgName string, vfs ...VirtualFile) (*VirtualPathScoper, error) { 26 | vps := &VirtualPathScoper{ 27 | vfiles: vfs, 28 | files: make([]*ast.File, len(vfs)), 29 | path: pkgName, 30 | } 31 | fset := token.NewFileSet() 32 | for i, vf := range vfs { 33 | f, err := parser.ParseFile(fset, vf.name, vf.contents, 0) 34 | if err != nil { 35 | return nil, err 36 | } 37 | vps.files[i] = f 38 | } 39 | conf := types.Config{Importer: importer.Default()} 40 | pkg, err := conf.Check(pkgName, fset, vps.files, nil) 41 | if err != nil { 42 | return nil, errors.Wrap(err, "error from conf.Check") 43 | } 44 | vps.scope = pkg.Scope() 45 | return vps, nil 46 | } 47 | 48 | func (vps *VirtualPathScoper) Path() string { 49 | return vps.path 50 | } 51 | 52 | func (vps *VirtualPathScoper) Scope() *types.Scope { 53 | return vps.scope 54 | } 55 | -------------------------------------------------------------------------------- /sszgen/virtualscoper_test.go: -------------------------------------------------------------------------------- 1 | package sszgen 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/OffchainLabs/methodical-ssz/sszgen/backend" 8 | "github.com/prysmaticlabs/prysm/v3/testing/require" 9 | ) 10 | 11 | func TestNewVirtualPathScoper(t *testing.T) { 12 | cbytes, err := os.ReadFile("testdata/uint256.go.fixture") 13 | require.NoError(t, err) 14 | vf := VirtualFile{ 15 | name: "uint256.go", 16 | contents: string(cbytes), 17 | } 18 | expected, err := os.ReadFile("testdata/uint256.ssz.go.fixture") 19 | require.NoError(t, err) 20 | pkgName := "github.com/ethereum/go-ethereum/faketypes" 21 | vps, err := NewVirtualPathScoper(pkgName, vf) 22 | require.NoError(t, err) 23 | require.Equal(t, pkgName, vps.Path()) 24 | defs, err := TypeDefs(vps, "TestStruct") 25 | require.NoError(t, err) 26 | require.Equal(t, 1, len(defs)) 27 | typeRep, err := ParseTypeDef(defs[0]) 28 | require.NoError(t, err) 29 | g := backend.NewGenerator(pkgName) 30 | require.NoError(t, g.Generate(typeRep)) 31 | rb, err := g.Render() 32 | require.NoError(t, err) 33 | // compare string representations so test failures will be legible 34 | require.Equal(t, string(expected), string(rb)) 35 | } 36 | --------------------------------------------------------------------------------