├── .buildkite-bazelrc ├── .gitignore ├── .well-known └── security.txt ├── BUILD.bazel ├── LICENSE ├── README.md ├── WORKSPACE ├── deep_equal.go ├── deps.bzl ├── doc.go ├── fuzz.go ├── nogo_config.json ├── proto.pb.go ├── renovate.json ├── round_trip_test.go ├── spectests ├── BUILD.bazel ├── bench_test.go ├── generic_test.go ├── generic_types.go ├── mainnet_test.go ├── mainnet_types.go ├── minimal_test.go ├── minimal_types.go └── yaml │ ├── ssz_single_block.yaml │ └── ssz_single_state.yaml ├── ssz.go ├── ssz_test.go └── types ├── BUILD.bazel ├── array_basic.go ├── array_composite.go ├── array_roots.go ├── array_roots_test.go ├── basic.go ├── bitlist.go ├── determine_size.go ├── factory.go ├── helpers.go ├── helpers_test.go ├── slice_basic.go ├── slice_composite.go ├── string.go ├── struct.go └── struct_test.go /.buildkite-bazelrc: -------------------------------------------------------------------------------- 1 | # The following flags enable the remote cache so action results can be shared 2 | # across machines, developers, and workspaces. 3 | # 4 | # This config is loaded from https://github.com/bazelbuild/bazel-toolchains/blob/master/bazelrc/latest.bazelrc 5 | build:remote-cache --remote_cache=remotebuildexecution.googleapis.com 6 | build:remote-cache --tls_enabled=true 7 | build:remote-cache --remote_timeout=3600 8 | build:remote-cache --auth_enabled=true 9 | build:remote-cache --spawn_strategy=standalone 10 | build:remote-cache --strategy=Javac=standalone 11 | build:remote-cache --strategy=Closure=standalone 12 | build:remote-cache --strategy=Genrule=standalone 13 | 14 | # Prysm specific remote-cache properties. 15 | build:remote-cache --disk_cache= 16 | build:remote-cache --jobs=50 17 | build:remote-cache --host_platform_remote_properties_override='properties:{name:\"cache-silo-key\" value:\"prysm\"}' 18 | build:remote-cache --remote_instance_name=projects/prysmaticlabs/instances/default_instance 19 | 20 | build --experimental_strict_action_env 21 | build --disk_cache=/tmp/bazelbuilds 22 | build --experimental_multi_threaded_digest 23 | build --sandbox_tmpfs_path=/tmp 24 | build --verbose_failures 25 | build --announce_rc 26 | build --show_progress_rate_limit=5 27 | build --curses=yes --color=yes 28 | build --keep_going 29 | build --test_output=errors 30 | build --flaky_test_attempts=5 31 | build --test_timeout=5,60,-1,-1 32 | # Disabled race detection due to unstable test results under constrained environment build kite 33 | # build --features=race 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore bazel directories 2 | bazel-* 3 | .idea 4 | .ijwb 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /.well-known/security.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA512 3 | 4 | Contact: mailto:security@prysmaticlabs.com 5 | Encryption: openpgp4fpr:0AE0051D647BA3C1A917AF4072E33E4DF1A5036E 6 | Encryption: openpgp4fpr:341396BAFACC28C5082327F889725027FC8EC0D4 7 | Encryption: openpgp4fpr:8B7814F1B221A8E8AA465FC7BDBF744ADE1A0033 8 | Preferred-Languages: en 9 | Canonical: https://github.com/prysmaticlabs/prysm/tree/master/.well-known/security.txt 10 | 11 | -----BEGIN PGP SIGNATURE----- 12 | 13 | iQIzBAEBCgAdFiEECuAFHWR7o8GpF69AcuM+TfGlA24FAlzi0WgACgkQcuM+TfGl 14 | A241pw/+Ks3Hxx8eGbjRIeuncuK811FkCiofNJS+MY2p4W2/tIrk48DtLRx8/k5L 15 | Dh1QyypZsqUgofrK7PbGVdEin6oEb2jYbTWUarAVTbhlsUdM4YcxwpgmGVslW7+C 16 | Hm8wMasQZhCkFfakzhfKX5hIQoFaFI/OvtVKIQsodP8dAieCDaGmtfq1Bs1LgFqi 17 | KrpeEdC2XbBQs33ADheC5SdGT1mnatP3VX8cOhLsfoPksYgTSpwK0clkoWs1eZOQ 18 | l1ImfW/FJCpSndBWgBR503ZgaU3Ic+5qxmAIuUP4chl0DFRMlPFEM5OWC6JkkCOd 19 | 5kKrXGRmrhgtQg+pA3zqJnFItRj7gxPBA/ypxCkKPrLEkRvbdpdZEl5vAlYkeBL6 20 | iKSLHnMswGKldiYxy7ofam5bM3myhYYNFb25boV5pRptrnoUmWOACHioBGQHwWNt 21 | B0XktD0j7+pCCiJyyYxmOnElsk/Y/u4Tv5pYWvfFuxTF2XOg+P/EH64AIFLWgB1U 22 | VnITxhakxqejCBxZkuVCFNSzt+TXG0NS9EIj/UOYBY+wxrBZ62ITjdA16RS/3n3z 23 | DuIDtxOOwUumbOO32+a5zIb+ARmnocYJviI7FuENb01/U6qb+nm9hQI6oIpSCNsv 24 | Pb4O/ZlOx70U/7mt4Xn/dTKH9bnKOOVhOw00KJWFfAce73AVnLA= 25 | =Uhqg 26 | -----END PGP SIGNATURE----- 27 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test", "nogo") 2 | load("@io_kubernetes_build//defs:run_in_workspace.bzl", "workspace_binary") 3 | load("@bazel_gazelle//:def.bzl", "gazelle") 4 | 5 | # gazelle:prefix github.com/prysmaticlabs/go-ssz 6 | gazelle( 7 | name = "gazelle", 8 | prefix = "github.com/prysmaticlabs/go-ssz", 9 | ) 10 | 11 | # Go build analysis. 12 | nogo( 13 | name = "nogo", 14 | deps = [ 15 | "@org_golang_x_tools//go/analysis/passes/unsafeptr:go_tool_library", 16 | "@org_golang_x_tools//go/analysis/passes/unreachable:go_tool_library", 17 | "@org_golang_x_tools//go/analysis/passes/unmarshal:go_tool_library", 18 | "@org_golang_x_tools//go/analysis/passes/tests:go_tool_library", 19 | "@org_golang_x_tools//go/analysis/passes/structtag:go_tool_library", 20 | "@org_golang_x_tools//go/analysis/passes/stdmethods:go_tool_library", 21 | "@org_golang_x_tools//go/analysis/passes/shift:go_tool_library", 22 | "@org_golang_x_tools//go/analysis/passes/printf:go_tool_library", 23 | "@org_golang_x_tools//go/analysis/passes/pkgfact:go_tool_library", 24 | "@org_golang_x_tools//go/analysis/passes/nilness:go_tool_library", 25 | "@org_golang_x_tools//go/analysis/passes/nilfunc:go_tool_library", 26 | "@org_golang_x_tools//go/analysis/passes/lostcancel:go_tool_library", 27 | "@org_golang_x_tools//go/analysis/passes/loopclosure:go_tool_library", 28 | "@org_golang_x_tools//go/analysis/passes/httpresponse:go_tool_library", 29 | "@org_golang_x_tools//go/analysis/passes/findcall:go_tool_library", 30 | "@org_golang_x_tools//go/analysis/passes/deepequalerrors:go_tool_library", 31 | "@org_golang_x_tools//go/analysis/passes/ctrlflow:go_tool_library", 32 | "@org_golang_x_tools//go/analysis/passes/copylock:go_tool_library", 33 | "@org_golang_x_tools//go/analysis/passes/cgocall:go_tool_library", 34 | "@org_golang_x_tools//go/analysis/passes/buildtag:go_tool_library", 35 | "@org_golang_x_tools//go/analysis/passes/buildssa:go_tool_library", 36 | "@org_golang_x_tools//go/analysis/passes/bools:go_tool_library", 37 | "@org_golang_x_tools//go/analysis/passes/atomicalign:go_tool_library", 38 | "@org_golang_x_tools//go/analysis/passes/atomic:go_tool_library", 39 | "@org_golang_x_tools//go/analysis/passes/assign:go_tool_library", 40 | "@org_golang_x_tools//go/analysis/passes/inspect:go_tool_library", 41 | "@org_golang_x_tools//go/analysis/passes/asmdecl:go_tool_library", 42 | ], 43 | visibility = ["//visibility:public"], 44 | config = "nogo_config.json", 45 | ) 46 | 47 | workspace_binary( 48 | name = "golint", 49 | cmd = "@com_github_golang_lint//golint", 50 | ) 51 | 52 | go_library( 53 | name = "go_default_library", 54 | srcs = [ 55 | "deep_equal.go", 56 | "doc.go", 57 | "proto.pb.go", 58 | "ssz.go", 59 | ], 60 | importpath = "github.com/prysmaticlabs/go-ssz", 61 | visibility = ["//visibility:public"], 62 | deps = [ 63 | "//types:go_default_library", 64 | "@com_github_ferranbt_fastssz//:go_default_library", 65 | "@com_github_pkg_errors//:go_default_library", 66 | "@com_github_prysmaticlabs_go_bitfield//:go_default_library", 67 | ], 68 | ) 69 | 70 | go_test( 71 | name = "go_default_test", 72 | srcs = [ 73 | "round_trip_test.go", 74 | "ssz_test.go", 75 | ], 76 | embed = [":go_default_library"], 77 | deps = [ 78 | "//types:go_default_library", 79 | "@com_github_pkg_errors//:go_default_library", 80 | "@com_github_prysmaticlabs_go_bitfield//:go_default_library", 81 | ], 82 | ) 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Prysmatic Labs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) 2 | [![Discord](https://user-images.githubusercontent.com/7288322/34471967-1df7808a-efbb-11e7-9088-ed0b04151291.png)](https://discord.gg/prysmaticlabs) 3 | 4 | # DEPRECATED 5 | 6 | ⛔️This project is no longer supported and actively discouraged! ⛔️ Please use [fastssz](https://github.com/ferranbt/fastssz) instead. This repository is known to have security issues, bugs, and be extremely inefficient. If you use this project, YOU RUN SERIOUS RISKS. 7 | 8 | # Simple Serialize (SSZ) 9 | 10 | Simple Serialize is the serialization algorithm standard for all data structures common across Ethereum 2.0 client implementations. It is outlined in the official [Ethereum 2.0 specification](https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md). 11 | 12 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "com_github_prysmaticlabs_go_ssz") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | 5 | http_archive( 6 | name = "io_bazel_rules_go", 7 | sha256 = "f04d2373bcaf8aa09bccb08a98a57e721306c8f6043a2a0ee610fd6853dcde3d", 8 | urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.18.6/rules_go-0.18.6.tar.gz"], 9 | ) 10 | 11 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 12 | 13 | go_rules_dependencies() 14 | 15 | go_register_toolchains(nogo = "@com_github_prysmaticlabs_go_ssz//:nogo") 16 | 17 | http_archive( 18 | name = "bazel_gazelle", 19 | sha256 = "3c681998538231a2d24d0c07ed5a7658cb72bfb5fd4bf9911157c0e9ac6a2687", 20 | urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.17.0/bazel-gazelle-0.17.0.tar.gz"], 21 | ) 22 | 23 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository") 24 | 25 | gazelle_dependencies() 26 | 27 | load("@com_github_prysmaticlabs_go_ssz//:deps.bzl", "go_ssz_dependencies") 28 | 29 | go_ssz_dependencies() 30 | 31 | http_archive( 32 | name = "eth2_spec_tests_minimal", 33 | build_file_content = """ 34 | filegroup( 35 | name = "test_data", 36 | srcs = glob([ 37 | "**/*.ssz", 38 | "**/*.yaml", 39 | ]), 40 | visibility = ["//visibility:public"], 41 | ) 42 | """, 43 | sha256 = "3b5f0168af4331d09da52bebc26609def9d11be3e6c784ce7c3df3596617808d", 44 | url = "https://github.com/ethereum/eth2.0-spec-tests/releases/download/v0.9.0/minimal.tar.gz", 45 | ) 46 | 47 | http_archive( 48 | name = "eth2_spec_tests_general", 49 | build_file_content = """ 50 | filegroup( 51 | name = "test_data", 52 | srcs = glob([ 53 | "**/*.ssz", 54 | "**/*.yaml", 55 | ]), 56 | visibility = ["//visibility:public"], 57 | ) 58 | """, 59 | sha256 = "5c5b65a961b5e7251435efc9548648b45142a07993ad3e100850c240cb76e9af", 60 | url = "https://github.com/ethereum/eth2.0-spec-tests/releases/download/v0.9.0/general.tar.gz", 61 | ) 62 | 63 | http_archive( 64 | name = "eth2_spec_tests_mainnet", 65 | build_file_content = """ 66 | filegroup( 67 | name = "test_data", 68 | srcs = glob([ 69 | "**/*.ssz", 70 | "**/*.yaml", 71 | ]), 72 | visibility = ["//visibility:public"], 73 | ) 74 | """, 75 | sha256 = "f3ff68508dfe9696f23506daf0ca895cda955e30398741e00cffa33a01b0565c", 76 | url = "https://github.com/ethereum/eth2.0-spec-tests/releases/download/v0.9.0/mainnet.tar.gz", 77 | ) 78 | 79 | http_archive( 80 | name = "io_kubernetes_build", 81 | sha256 = "dd02a62c2a458295f561e280411b04d2efbd97e4954986a401a9a1334cc32cc3", 82 | strip_prefix = "repo-infra-1b2ddaf3fb8775a5d0f4e28085cf846f915977a8", 83 | url = "https://github.com/kubernetes/repo-infra/archive/1b2ddaf3fb8775a5d0f4e28085cf846f915977a8.tar.gz", 84 | ) 85 | 86 | go_repository( 87 | name = "com_github_golang_lint", 88 | commit = "5b3e6a55c961c61f4836ae6868c17b070744c590", 89 | importpath = "github.com/golang/lint", 90 | ) 91 | 92 | go_repository( 93 | name = "org_golang_x_lint", 94 | commit = "5b3e6a55c961c61f4836ae6868c17b070744c590", 95 | importpath = "golang.org/x/lint", 96 | ) 97 | 98 | go_repository( 99 | name = "com_github_ghodss_yaml", 100 | commit = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7", # v1.0.0 101 | importpath = "github.com/ghodss/yaml", 102 | ) 103 | 104 | go_repository( 105 | name = "in_gopkg_yaml_v2", 106 | commit = "51d6538a90f86fe93ac480b35f37b2be17fef232", # v2.2.2 107 | importpath = "gopkg.in/yaml.v2", 108 | ) 109 | 110 | go_repository( 111 | name = "in_gopkg_d4l3k_messagediff_v1", 112 | commit = "29f32d820d112dbd66e58492a6ffb7cc3106312b", # v1.2.1 113 | importpath = "gopkg.in/d4l3k/messagediff.v1", 114 | ) 115 | 116 | # Do not add go dependencies here. They must be added in deps.bzl to provide 117 | # dependencies to downstream bazel projects. Only test related dependencies 118 | # are allowed in this WORKSPACE. 119 | -------------------------------------------------------------------------------- /deep_equal.go: -------------------------------------------------------------------------------- 1 | package ssz 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | // During deepValueEqual, must keep track of checks that are 9 | // in progress. The comparison algorithm assumes that all 10 | // checks in progress are true when it reencounters them. 11 | // Visited comparisons are stored in a map indexed by visit. 12 | type visit struct { 13 | a1 unsafe.Pointer 14 | a2 unsafe.Pointer 15 | typ reflect.Type 16 | } 17 | 18 | // Copyright 2009 The Go Authors. All rights reserved. 19 | // Use of this source code is governed by a BSD-style 20 | // license that can be found in the LICENSE file. 21 | // 22 | // This file extends Go's reflect.DeepEqual function into a ssz.DeepEqual 23 | // function that is compliant with the supported types of ssz and its 24 | // intricacies when determining equality of empty values. 25 | // 26 | // Tests for deep equality using reflected types. The map argument tracks 27 | // comparisons that have already been seen, which allows short circuiting on 28 | // recursive types. 29 | func deepValueEqual(v1, v2 reflect.Value, visited map[visit]bool, depth int) bool { 30 | if !v1.IsValid() || !v2.IsValid() { 31 | return v1.IsValid() == v2.IsValid() 32 | } 33 | if v1.Type() != v2.Type() { 34 | return false 35 | } 36 | 37 | // if depth > 10 { panic("deepValueEqual") } // for debugging 38 | 39 | // We want to avoid putting more in the visited map than we need to. 40 | // For any possible reference cycle that might be encountered, 41 | // hard(t) needs to return true for at least one of the types in the cycle. 42 | hard := func(k reflect.Kind) bool { 43 | switch k { 44 | case reflect.Slice, reflect.Ptr, reflect.Interface: 45 | return true 46 | } 47 | return false 48 | } 49 | 50 | if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) { 51 | addr1 := unsafe.Pointer(v1.UnsafeAddr()) 52 | addr2 := unsafe.Pointer(v2.UnsafeAddr()) 53 | if uintptr(addr1) > uintptr(addr2) { 54 | // Canonicalize order to reduce number of entries in visited. 55 | // Assumes non-moving garbage collector. 56 | addr1, addr2 = addr2, addr1 57 | } 58 | 59 | // Short circuit if references are already seen. 60 | typ := v1.Type() 61 | v := visit{addr1, addr2, typ} 62 | if visited[v] { 63 | return true 64 | } 65 | 66 | // Remember for later. 67 | visited[v] = true 68 | } 69 | 70 | switch v1.Kind() { 71 | case reflect.String: 72 | return v1.String() == v2.String() 73 | case reflect.Array: 74 | for i := 0; i < v1.Len(); i++ { 75 | if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) { 76 | return false 77 | } 78 | } 79 | return true 80 | case reflect.Slice: 81 | if v1.IsNil() && v2.Len() == 0 { 82 | return true 83 | } 84 | if v1.Len() == 0 && v2.IsNil() { 85 | return true 86 | } 87 | if v1.IsNil() && v2.IsNil() { 88 | return true 89 | } 90 | if v1.Len() != v2.Len() { 91 | return false 92 | } 93 | if v1.Pointer() == v2.Pointer() { 94 | return true 95 | } 96 | for i := 0; i < v1.Len(); i++ { 97 | if !deepValueEqual(v1.Index(i), v2.Index(i), visited, depth+1) { 98 | return false 99 | } 100 | } 101 | return true 102 | case reflect.Interface: 103 | if v1.IsNil() || v2.IsNil() { 104 | return v1.IsNil() == v2.IsNil() 105 | } 106 | return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1) 107 | case reflect.Ptr: 108 | if v1.Pointer() == v2.Pointer() { 109 | return true 110 | } 111 | return deepValueEqual(v1.Elem(), v2.Elem(), visited, depth+1) 112 | case reflect.Struct: 113 | for i, n := 0, v1.NumField(); i < n; i++ { 114 | if !deepValueEqual(v1.Field(i), v2.Field(i), visited, depth+1) { 115 | return false 116 | } 117 | } 118 | return true 119 | case reflect.Uint64: 120 | return v1.Interface().(uint64) == v2.Interface().(uint64) 121 | case reflect.Uint32: 122 | return v1.Interface().(uint32) == v2.Interface().(uint32) 123 | case reflect.Int32: 124 | return v1.Interface().(int32) == v2.Interface().(int32) 125 | case reflect.Uint16: 126 | return v1.Interface().(uint16) == v2.Interface().(uint16) 127 | case reflect.Uint8: 128 | return v1.Interface().(uint8) == v2.Interface().(uint8) 129 | case reflect.Bool: 130 | return v1.Interface().(bool) == v2.Interface().(bool) 131 | default: 132 | return false 133 | } 134 | } 135 | 136 | // DeepEqual reports whether two SSZ-able values x and y are ``deeply equal,'' defined as follows: 137 | // Two values of identical type are deeply equal if one of the following cases applies: 138 | // 139 | // Values of distinct types are never deeply equal. 140 | // 141 | // Array values are deeply equal when their corresponding elements are deeply equal. 142 | // 143 | // Struct values are deeply equal if their corresponding fields, 144 | // both exported and unexported, are deeply equal. 145 | // 146 | // Interface values are deeply equal if they hold deeply equal concrete values. 147 | // 148 | // Pointer values are deeply equal if they are equal using Go's == operator 149 | // or if they point to deeply equal values. 150 | // 151 | // Slice values are deeply equal when all of the following are true: 152 | // they are both nil, one is nil and the other is empty or vice-versa, 153 | // they have the same length, and either they point to the same initial entry of the same array 154 | // (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal. 155 | // 156 | // Other values - numbers, bools, strings, and channels - are deeply equal 157 | // if they are equal using Go's == operator. 158 | // 159 | // In general DeepEqual is a recursive relaxation of Go's == operator. 160 | // However, this idea is impossible to implement without some inconsistency. 161 | // Specifically, it is possible for a value to be unequal to itself, 162 | // either because it is of func type (uncomparable in general) 163 | // or because it is a floating-point NaN value (not equal to itself in floating-point comparison), 164 | // or because it is an array, struct, or interface containing 165 | // such a value. 166 | // 167 | // On the other hand, pointer values are always equal to themselves, 168 | // even if they point at or contain such problematic values, 169 | // because they compare equal using Go's == operator, and that 170 | // is a sufficient condition to be deeply equal, regardless of content. 171 | // DeepEqual has been defined so that the same short-cut applies 172 | // to slices and maps: if x and y are the same slice or the same map, 173 | // they are deeply equal regardless of content. 174 | // 175 | // As DeepEqual traverses the data values it may find a cycle. The 176 | // second and subsequent times that DeepEqual compares two pointer 177 | // values that have been compared before, it treats the values as 178 | // equal rather than examining the values to which they point. 179 | // This ensures that DeepEqual terminates. 180 | // 181 | // Credits go to the Go team as this is an extension of the official Go source code's 182 | // reflect.DeepEqual function to handle special SSZ edge cases. 183 | func DeepEqual(x, y interface{}) bool { 184 | if x == nil || y == nil { 185 | return x == y 186 | } 187 | v1 := reflect.ValueOf(x) 188 | v2 := reflect.ValueOf(y) 189 | if v1.Type() != v2.Type() { 190 | return false 191 | } 192 | return deepValueEqual(v1, v2, make(map[visit]bool), 0) 193 | } 194 | -------------------------------------------------------------------------------- /deps.bzl: -------------------------------------------------------------------------------- 1 | """ 2 | go-ssz dependencies. 3 | 4 | Add go_repository dependencies where with the _maybe function so that downstream 5 | bazel projects' go_repository take precidence over the commits specified here. 6 | 7 | Add the license comment to each dependency for quick analysis of the licensing 8 | requirements for this project. 9 | """ 10 | 11 | load("@bazel_gazelle//:deps.bzl", "go_repository") 12 | 13 | def go_ssz_dependencies(): 14 | 15 | _maybe( 16 | # Apache License 2.0 17 | go_repository, 18 | name = "com_github_prometheus_client_golang", 19 | commit = "662e8a9ffaaa74a4d050023c2cb26902cd9bab63", 20 | importpath = "github.com/prometheus/client_golang", 21 | ) 22 | 23 | _maybe( 24 | # Apache License 2.0 25 | go_repository, 26 | name = "com_github_prometheus_common", 27 | commit = "1ba88736f028e37bc17328369e94a537ae9e0234", 28 | importpath = "github.com/prometheus/common", 29 | ) 30 | 31 | _maybe( 32 | # Apache License 2.0 33 | go_repository, 34 | name = "com_github_prometheus_client_model", 35 | commit = "fd36f4220a901265f90734c3183c5f0c91daa0b8", 36 | importpath = "github.com/prometheus/client_model", 37 | ) 38 | 39 | _maybe( 40 | # Apache License 2.0 41 | go_repository, 42 | name = "com_github_prometheus_procfs", 43 | commit = "bbced9601137e764853b2fad7ec3e2dc4c504e02", 44 | importpath = "github.com/prometheus/procfs", 45 | ) 46 | 47 | _maybe( 48 | # Apache License 2.0 49 | go_repository, 50 | name = "com_github_matttproud_golang_protobuf_extensions", 51 | commit = "c12348ce28de40eed0136aa2b644d0ee0650e56c", 52 | importpath = "github.com/matttproud/golang_protobuf_extensions", 53 | ) 54 | 55 | _maybe( 56 | # MIT License 57 | go_repository, 58 | name = "com_github_beorn7_perks", 59 | commit = "4ded152d4a3e2847f17f185a27b2041ae7b63979", 60 | importpath = "github.com/beorn7/perks", 61 | ) 62 | 63 | _maybe( 64 | # GNU Lesser General Public License v3.0 65 | go_repository, 66 | name = "com_github_ethereum_go_ethereum", 67 | commit = "099afb3fd89784f9e3e594b7c2ed11335ca02a9b", 68 | importpath = "github.com/ethereum/go-ethereum", 69 | # Note: go-ethereum is not bazel-friendly with regards to cgo. We have a 70 | # a fork that has resolved these issues by disabling HID/USB support and 71 | # some manual fixes for c imports in the crypto package. This is forked 72 | # branch should be updated from time to time with the latest go-ethereum 73 | # code. 74 | remote = "https://github.com/prysmaticlabs/bazel-go-ethereum", 75 | vcs = "git", 76 | ) 77 | 78 | _maybe( 79 | # Permissive license 80 | # https://github.com/golang/crypto/blob/master/LICENSE 81 | go_repository, 82 | name = "org_golang_x_crypto", 83 | commit = "8dd112bcdc25174059e45e07517d9fc663123347", 84 | importpath = "golang.org/x/crypto", 85 | ) 86 | 87 | _maybe( 88 | # Apache License 2.0 89 | # https://github.com/prysmaticlabs/go-bitfield/blob/master/LICENSE 90 | go_repository, 91 | name = "com_github_prysmaticlabs_go_bitfield", 92 | commit = "ec88cc4d1d143cad98308da54b73d0cdb04254eb", 93 | importpath = "github.com/prysmaticlabs/go-bitfield", 94 | ) 95 | 96 | _maybe( 97 | go_repository, 98 | name = "com_github_minio_sha256_simd", 99 | commit = "05b4dd3047e5d6e86cb4e0477164b850cd896261", 100 | importpath = "github.com/minio/sha256-simd", 101 | ) 102 | 103 | _maybe( 104 | go_repository, 105 | name = "com_github_protolambda_zssz", 106 | commit = "632f11e5e281660402bd0ac58f76090f3503def0", 107 | importpath = "github.com/protolambda/zssz", 108 | ) 109 | 110 | _maybe( 111 | go_repository, 112 | name = "com_github_pkg_errors", 113 | commit = "27936f6d90f9c8e1145f11ed52ffffbfdb9e0af7", 114 | importpath = "github.com/pkg/errors", 115 | ) 116 | 117 | _maybe( 118 | go_repository, 119 | name = "com_github_minio_highwayhash", 120 | importpath = "github.com/minio/highwayhash", 121 | commit = "02ca4b43caa3297fbb615700d8800acc7933be98", 122 | ) 123 | 124 | _maybe( 125 | go_repository, 126 | name = "com_github_dgraph_io_ristretto", 127 | importpath = "github.com/dgraph-io/ristretto", 128 | commit = "99d1bbbf28e64530eb246be0568fc7709a35ebdd", 129 | ) 130 | 131 | _maybe( 132 | go_repository, 133 | name = "com_github_cespare_xxhash", 134 | commit = "d7df74196a9e781ede915320c11c378c1b2f3a1f", 135 | importpath = "github.com/cespare/xxhash", 136 | ) 137 | 138 | _maybe( 139 | go_repository, 140 | name = "com_github_ferranbt_fastssz", 141 | commit = "99fccaf9347271d8daad22406ae2c718e3be01c9", 142 | importpath = "github.com/ferranbt/fastssz", 143 | ) 144 | 145 | def _maybe(repo_rule, name, **kwargs): 146 | if name not in native.existing_rules(): 147 | repo_rule(name = name, **kwargs) 148 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package ssz implements the Simple Serialize algorithm specified at 3 | https://github.com/ethereum/eth2.0-specs/blob/master/specs/simple-serialize.md 4 | 5 | Currently directly supported types: 6 | 7 | bool 8 | uint8 9 | uint16 10 | uint32 11 | uint64 12 | bytes 13 | slice 14 | struct 15 | ptr 16 | */ 17 | package ssz 18 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package ssz 4 | 5 | func Fuzz(data []byte) int { 6 | type Base struct { 7 | F1 bool 8 | F2 uint8 9 | F3 uint16 10 | F4 uint32 11 | F5 uint64 12 | F6 []byte 13 | F7 []Base 14 | F8 *Base 15 | } 16 | 17 | type T struct { 18 | F9 Base 19 | } 20 | 21 | var v T 22 | if err := Unmarshal(data, &v); err != nil { 23 | return 0 24 | } 25 | return 1 26 | } 27 | -------------------------------------------------------------------------------- /nogo_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "assign": { 3 | "exclude_files": { 4 | "external/*": "Third party code" 5 | } 6 | }, 7 | "nilness": { 8 | "exclude_files": { 9 | "external/*": "Third party code" 10 | } 11 | }, 12 | "unreachable": { 13 | "exclude_files": { 14 | "external/*": "Third party code" 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /proto.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | package ssz 3 | 4 | type simpleProtoMessage struct { 5 | Foo []byte `protobuf:"bytes,1,opt,name=foo,proto3" json:"foo,omitempty"` 6 | Bar uint64 `protobuf:"varint,2,opt,name=bar,proto3" json:"bar,omitempty"` 7 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 8 | XXX_unrecognized []byte `json:"-"` 9 | XXX_sizecache int32 `json:"-"` 10 | } 11 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /round_trip_test.go: -------------------------------------------------------------------------------- 1 | package ssz_test 2 | 3 | import ( 4 | "math/rand" 5 | "reflect" 6 | "testing" 7 | 8 | ssz "github.com/prysmaticlabs/go-ssz" 9 | ) 10 | 11 | type fork struct { 12 | PreviousVersion [4]byte 13 | CurrentVersion [4]byte 14 | Epoch uint64 15 | } 16 | 17 | type nestedItem struct { 18 | Field1 []uint64 19 | Field2 *fork 20 | Field3 [3]byte 21 | } 22 | 23 | type varItem struct { 24 | Field2 []uint16 25 | Field3 []uint16 26 | } 27 | 28 | type nestedVarItem struct { 29 | Field1 []varItem 30 | Field2 uint64 31 | } 32 | 33 | var ( 34 | forkExample = fork{ 35 | PreviousVersion: [4]byte{1, 2, 3, 4}, 36 | CurrentVersion: [4]byte{5, 6, 7, 8}, 37 | Epoch: 5, 38 | } 39 | nestedItemExample = nestedItem{ 40 | Field1: []uint64{1, 2, 3, 4}, 41 | Field2: &forkExample, 42 | Field3: [3]byte{32, 33, 34}, 43 | } 44 | nestedVarItemExample = nestedVarItem{ 45 | Field1: []varItem{}, 46 | Field2: 5, 47 | } 48 | varItemExample = varItem{ 49 | Field2: []uint16{}, 50 | Field3: []uint16{2, 3}, 51 | } 52 | varItemAmbiguous = varItem{ 53 | Field3: []uint16{4, 5}, 54 | } 55 | ) 56 | 57 | func TestMarshalUnmarshal(t *testing.T) { 58 | tests := []struct { 59 | input interface{} 60 | ptr interface{} 61 | }{ 62 | // Bool test cases. 63 | {input: true, ptr: new(bool)}, 64 | {input: false, ptr: new(bool)}, 65 | // Uint8 test cases. 66 | {input: byte(1), ptr: new(byte)}, 67 | {input: byte(0), ptr: new(byte)}, 68 | // Uint16 test cases. 69 | {input: uint16(100), ptr: new(uint16)}, 70 | {input: uint16(232), ptr: new(uint16)}, 71 | // Int32 test cases. 72 | {input: int32(1), ptr: new(int32)}, 73 | {input: int32(1029391), ptr: new(int32)}, 74 | // Uint32 test cases. 75 | {input: uint32(1), ptr: new(uint32)}, 76 | {input: uint32(1029391), ptr: new(uint32)}, 77 | // Uint64 test cases. 78 | {input: uint64(5), ptr: new(uint64)}, 79 | {input: uint64(23929309), ptr: new(uint64)}, 80 | // Byte slice, byte array test cases. 81 | {input: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ptr: new([8]byte)}, 82 | {input: []byte{9, 8, 9, 8}, ptr: new([]byte)}, 83 | // Basic type array test cases. 84 | {input: [12]uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, ptr: new([12]uint64)}, 85 | {input: [100]bool{true, false, true, true}, ptr: new([100]bool)}, 86 | {input: [20]uint16{3, 4, 5}, ptr: new([20]uint16)}, 87 | {input: [20]uint32{4, 5}, ptr: new([20]uint32)}, 88 | {input: [20][2]uint32{{3, 4}, {5}, {8}, {9, 10}}, ptr: new([20][2]uint32)}, 89 | // Basic type slice test cases. 90 | {input: []uint64{1, 2, 3}, ptr: new([]uint64)}, 91 | {input: []bool{true, false, true, true, true}, ptr: new([]bool)}, 92 | {input: []uint32{0, 0, 0}, ptr: new([]uint32)}, 93 | {input: []uint32{92939, 232, 222}, ptr: new([]uint32)}, 94 | // String test cases. 95 | {input: "hello world", ptr: new(string)}, 96 | {input: nestedVarItemExample, ptr: new(nestedVarItem)}, 97 | {input: varItemExample, ptr: new(varItem)}, 98 | {input: varItemAmbiguous, ptr: new(varItem)}, 99 | // Non-basic type slice/array test cases. 100 | {input: []fork{forkExample, forkExample}, ptr: new([]fork)}, 101 | {input: [][]uint64{{4, 3, 2}, {1}, {0}}, ptr: new([][]uint64)}, 102 | {input: [][][]uint64{{{1, 2}, {3}}, {{4, 5}}, {{0}}}, ptr: new([][][]uint64)}, 103 | {input: [][3]uint64{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, ptr: new([][3]uint64)}, 104 | {input: [3][]uint64{{1, 2}, {4, 5, 6}, {7}}, ptr: new([3][]uint64)}, 105 | {input: [][4]fork{{forkExample, forkExample, forkExample}}, ptr: new([][4]fork)}, 106 | {input: [2]fork{forkExample, forkExample}, ptr: new([2]fork)}, 107 | // Pointer-type test cases. 108 | {input: &forkExample, ptr: new(fork)}, 109 | {input: &nestedItemExample, ptr: new(nestedItem)}, 110 | {input: []*fork{&forkExample, &forkExample}, ptr: new([]*fork)}, 111 | {input: []*nestedItem{&nestedItemExample, &nestedItemExample}, ptr: new([]*nestedItem)}, 112 | {input: [2]*nestedItem{&nestedItemExample, &nestedItemExample}, ptr: new([2]*nestedItem)}, 113 | {input: [2]*fork{&forkExample, &forkExample}, ptr: new([2]*fork)}, 114 | } 115 | for _, tt := range tests { 116 | if _, err := ssz.HashTreeRoot(tt.input); err != nil { 117 | t.Fatal(err) 118 | } 119 | serializedItem, err := ssz.Marshal(tt.input) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | if err := ssz.Unmarshal(serializedItem, tt.ptr); err != nil { 124 | t.Fatal(err) 125 | } 126 | output := reflect.ValueOf(tt.ptr) 127 | inputVal := reflect.ValueOf(tt.input) 128 | if inputVal.Kind() == reflect.Ptr { 129 | if !ssz.DeepEqual(output.Interface(), tt.input) { 130 | t.Errorf("Expected %v, received %v", tt.input, output.Interface()) 131 | } 132 | } else { 133 | got := output.Elem().Interface() 134 | want := tt.input 135 | if !ssz.DeepEqual(want, got) { 136 | t.Errorf("Did not unmarshal properly: wanted %v, received %v", tt.input, output.Elem().Interface()) 137 | } 138 | } 139 | } 140 | } 141 | 142 | type UInt64InStruct struct { 143 | UInt64 uint64 144 | } 145 | 146 | type allTypes struct { 147 | Bool bool 148 | UInt8 uint8 149 | UInt16 uint16 150 | UInt32 uint32 151 | UInt64 uint64 152 | Slice []byte 153 | Array [100]byte 154 | SubStruct UInt64InStruct 155 | Pointer *UInt64InStruct 156 | } 157 | 158 | func randBytes(count int) []byte { 159 | buf := make([]byte, count) 160 | rand.Read(buf) 161 | return buf 162 | } 163 | 164 | func generateData(count int) []allTypes { 165 | data := make([]allTypes, count) 166 | for i := 0; i < count; i++ { 167 | data[i] = allTypes{ 168 | Bool: rand.Intn(2) == 1, 169 | UInt8: uint8(rand.Intn(256)), 170 | UInt16: uint16(rand.Intn(256 * 256)), 171 | UInt32: rand.Uint32(), 172 | UInt64: rand.Uint64(), 173 | Slice: randBytes(rand.Intn(256)), 174 | SubStruct: UInt64InStruct{UInt64: rand.Uint64()}, 175 | Pointer: &UInt64InStruct{UInt64: rand.Uint64()}, 176 | } 177 | copy(data[i].Array[:], randBytes(len(data[i].Array))) 178 | } 179 | return data 180 | } 181 | 182 | func BenchmarkMarshal(b *testing.B) { 183 | data := generateData(b.N) 184 | b.ResetTimer() 185 | for i := 0; i < b.N; i++ { 186 | ssz.Marshal(data[i]) 187 | } 188 | } 189 | 190 | func BenchmarkUnmarshal(b *testing.B) { 191 | data := generateData(b.N) 192 | ser := make([][]byte, b.N) 193 | for i, item := range data { 194 | ser[i], _ = ssz.Marshal(item) 195 | } 196 | result := make([]allTypes, b.N) 197 | b.ResetTimer() 198 | for i, item := range ser { 199 | ssz.Unmarshal(item, &result[i]) 200 | } 201 | b.StopTimer() 202 | // check Marshal/Unmarshal 203 | for i := 0; i < b.N; i++ { 204 | if data[i].Bool != result[i].Bool || 205 | data[i].UInt8 != result[i].UInt8 || 206 | data[i].UInt16 != result[i].UInt16 || 207 | data[i].UInt32 != result[i].UInt32 || 208 | data[i].UInt64 != result[i].UInt64 || 209 | (!reflect.DeepEqual(data[i].Slice, result[i].Slice) && !(len(data[i].Slice) == 0 && len(result[i].Slice) == 0)) || 210 | data[i].SubStruct.UInt64 != result[i].SubStruct.UInt64 || 211 | data[i].Pointer.UInt64 != result[i].Pointer.UInt64 { 212 | b.Fatalf("incorrect marshal/unmarshal for\n%v\nser data:\n%v\nresult:\n%v\n(%v;%v)", data[i], ser[i], result[i], data[i].Slice, result[i].Slice) 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /spectests/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_test( 4 | name = "go_default_test", 5 | srcs = [ 6 | "bench_test.go", 7 | "generic_test.go", 8 | "mainnet_test.go", 9 | "minimal_test.go", 10 | ], 11 | data = glob(["*.yaml"]) + [ 12 | "@eth2_spec_tests_general//:test_data", 13 | "@eth2_spec_tests_minimal//:test_data", 14 | "@eth2_spec_tests_mainnet//:test_data", 15 | "yaml/ssz_single_block.yaml", 16 | "yaml/ssz_single_state.yaml", 17 | ], 18 | embed = [":go_default_library"], 19 | deps = [ 20 | "//:go_default_library", 21 | "@com_github_ghodss_yaml//:go_default_library", 22 | "@com_github_prysmaticlabs_go_bitfield//:go_default_library", 23 | "@io_bazel_rules_go//go/tools/bazel:go_default_library", 24 | ], 25 | ) 26 | 27 | go_library( 28 | name = "go_default_library", 29 | srcs = [ 30 | "generic_types.go", 31 | "mainnet_types.go", 32 | "minimal_types.go", 33 | ], 34 | importpath = "github.com/prysmaticlabs/go-ssz/spectests", 35 | visibility = ["//visibility:public"], 36 | deps = ["@com_github_prysmaticlabs_go_bitfield//:go_default_library"], 37 | ) 38 | -------------------------------------------------------------------------------- /spectests/bench_test.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/ghodss/yaml" 8 | "github.com/prysmaticlabs/go-ssz" 9 | ) 10 | 11 | type SszBenchmarkState struct { 12 | Value minimalBeaconState `json:"value"` 13 | Serialized []byte `json:"serialized"` 14 | Root []byte `json:"root" ssz:"size=32"` 15 | } 16 | 17 | type SszBenchmarkBlock struct { 18 | Value minimalBlock `json:"value"` 19 | Serialized []byte `json:"serialized"` 20 | Root []byte `json:"root" ssz:"size=32"` 21 | SigningRoot []byte `json:"signing_root" ssz:"size=96"` 22 | } 23 | 24 | func BenchmarkBeaconBlock_Marshal(b *testing.B) { 25 | b.StopTimer() 26 | s := &SszBenchmarkBlock{} 27 | populateStructFromYaml(b, "./yaml/ssz_single_block.yaml", s) 28 | b.StartTimer() 29 | for n := 0; n < b.N; n++ { 30 | if _, err := ssz.Marshal(s.Value); err != nil { 31 | b.Fatal(err) 32 | } 33 | } 34 | } 35 | 36 | func BenchmarkBeaconBlock_Unmarshal(b *testing.B) { 37 | b.StopTimer() 38 | s := &SszBenchmarkBlock{} 39 | populateStructFromYaml(b, "./yaml/ssz_single_block.yaml", s) 40 | encoded, err := ssz.Marshal(s.Value) 41 | if err != nil { 42 | b.Fatal(err) 43 | } 44 | var target minimalBlock 45 | b.StartTimer() 46 | for n := 0; n < b.N; n++ { 47 | if err := ssz.Unmarshal(encoded, &target); err != nil { 48 | b.Fatal(err) 49 | } 50 | } 51 | } 52 | 53 | func BenchmarkBeaconBlock_HashTreeRoot(b *testing.B) { 54 | b.StopTimer() 55 | s := &SszBenchmarkBlock{} 56 | populateStructFromYaml(b, "./yaml/ssz_single_block.yaml", s) 57 | b.StartTimer() 58 | for n := 0; n < b.N; n++ { 59 | if _, err := ssz.HashTreeRoot(s.Value); err != nil { 60 | b.Fatal(err) 61 | } 62 | } 63 | } 64 | 65 | func BenchmarkBeaconState_Marshal(b *testing.B) { 66 | b.StopTimer() 67 | s := &SszBenchmarkState{} 68 | populateStructFromYaml(b, "./yaml/ssz_single_state.yaml", s) 69 | b.StartTimer() 70 | for n := 0; n < b.N; n++ { 71 | if _, err := ssz.Marshal(s.Value); err != nil { 72 | b.Fatal(err) 73 | } 74 | } 75 | } 76 | 77 | func BenchmarkBeaconState_Unmarshal(b *testing.B) { 78 | b.StopTimer() 79 | s := &SszBenchmarkState{} 80 | populateStructFromYaml(b, "./yaml/ssz_single_state.yaml", s) 81 | encoded, err := ssz.Marshal(s.Value) 82 | if err != nil { 83 | b.Fatal(err) 84 | } 85 | var target minimalBeaconState 86 | b.StartTimer() 87 | for n := 0; n < b.N; n++ { 88 | if err := ssz.Unmarshal(encoded, &target); err != nil { 89 | b.Fatal(err) 90 | } 91 | } 92 | } 93 | 94 | func BenchmarkBeaconState_HashTreeRoot(b *testing.B) { 95 | b.StopTimer() 96 | s := &SszBenchmarkState{} 97 | populateStructFromYaml(b, "./yaml/ssz_single_state.yaml", s) 98 | b.StartTimer() 99 | for n := 0; n < b.N; n++ { 100 | if _, err := ssz.HashTreeRoot(s.Value); err != nil { 101 | b.Fatal(err) 102 | } 103 | } 104 | } 105 | 106 | func populateStructFromYaml(t testing.TB, fPath string, val interface{}) { 107 | yamlFile, err := ioutil.ReadFile(fPath) 108 | if err != nil { 109 | t.Fatal(err) 110 | } 111 | if err := yaml.Unmarshal(yamlFile, val); err != nil { 112 | t.Fatalf("Failed to unmarshal: %v", err) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /spectests/generic_test.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | "io/ioutil" 8 | "path" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/bazelbuild/rules_go/go/tools/bazel" 14 | "github.com/ghodss/yaml" 15 | "github.com/prysmaticlabs/go-bitfield" 16 | "github.com/prysmaticlabs/go-ssz" 17 | ) 18 | 19 | func TestSSZGeneric(t *testing.T) { 20 | fullPath := "eth2_spec_tests_general/tests/general/phase0/ssz_generic/" 21 | filepath, err := bazel.Runfile(fullPath) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | testFolders, err := ioutil.ReadDir(filepath) 26 | if err != nil { 27 | t.Fatalf("Failed to read file: %v", err) 28 | } 29 | 30 | for _, folder := range testFolders { 31 | dataType := folder.Name() 32 | t.Run(dataType, func(t *testing.T) { 33 | // Runs the valid spec tests. 34 | t.Run("valid", func(t *testing.T) { 35 | runSSZGenericTests(t, filepath, dataType, "valid") 36 | }) 37 | // Runs the invalid spec tests. 38 | //t.Run("invalid", func(t *testing.T) { 39 | // runSSZGenericTests(t, filepath, dataType, "invalid") 40 | //}) 41 | }) 42 | } 43 | } 44 | 45 | func runSSZGenericTests(t *testing.T, testPath string, dataType string, validityFolder string) { 46 | isValid := validityFolder == "valid" 47 | casesPath, err := bazel.Runfile(path.Join(testPath, dataType, validityFolder)) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | cases, err := ioutil.ReadDir(casesPath) 52 | if err != nil { 53 | t.Fatalf("Failed to read file: %v", err) 54 | } 55 | for _, cs := range cases { 56 | typeName := cs.Name() 57 | t.Run(typeName, func(t *testing.T) { 58 | serializedBytes, err := bazelFileBytes(path.Join(casesPath, typeName, "serialized.ssz")) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | yamlPath := path.Join(casesPath, typeName, "meta.yaml") 63 | switch dataType { 64 | case "basic_vector": 65 | runBasicVectorSSZTestCase(t, serializedBytes, yamlPath, typeName, isValid) 66 | case "bitlist": 67 | sizeStr := strings.Split(typeName, "_")[1] 68 | var size int 69 | if sizeStr != "no" { 70 | size, err = strconv.Atoi(sizeStr) 71 | if err != nil { 72 | t.Fatalf("could not get convert string to int: %v", err) 73 | } 74 | } else { 75 | size = 0 76 | } 77 | runBitlistSSZTestCase(t, serializedBytes, uint64(size), yamlPath, isValid) 78 | case "bitvector": 79 | runBitvectorSSZTestCase(t, serializedBytes, yamlPath, typeName, isValid) 80 | case "boolean": 81 | runBooleanSSZTestCase(t, serializedBytes, yamlPath, isValid) 82 | case "containers": 83 | runContainerSSZTestCase(t, serializedBytes, yamlPath, typeName, isValid) 84 | case "uints": 85 | runUintSSZTestCase(t, serializedBytes, yamlPath, typeName, isValid) 86 | default: 87 | t.Log("Not covered") 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func runBasicVectorSSZTestCase(t *testing.T, objBytes []byte, yamlPath string, testName string, valid bool) { 94 | switch { 95 | case strings.Contains(testName, "bool_0"): 96 | var result [0]bool 97 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 98 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 99 | } 100 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 101 | t.Fatalf("Could not perform bool array root check: %v", err) 102 | } 103 | case strings.Contains(testName, "bool_1_"): 104 | var result [1]bool 105 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 106 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 107 | } 108 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 109 | t.Fatalf("Could not perform bool array root check: %v", err) 110 | } 111 | case strings.Contains(testName, "bool_2_"): 112 | var result [2]bool 113 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 114 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 115 | } 116 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 117 | t.Fatalf("Could not perform bool array root check: %v", err) 118 | } 119 | case strings.Contains(testName, "bool_3_"): 120 | var result [3]bool 121 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 122 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 123 | } 124 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 125 | t.Fatalf("Could not perform bool array root check: %v", err) 126 | } 127 | case strings.Contains(testName, "bool_4_"): 128 | var result [4]bool 129 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 130 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 131 | } 132 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 133 | t.Fatalf("Could not perform bool array root check: %v", err) 134 | } 135 | case strings.Contains(testName, "bool_5_"): 136 | var result [5]bool 137 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 138 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 139 | } 140 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 141 | t.Fatalf("Could not perform bool array root check: %v", err) 142 | } 143 | case strings.Contains(testName, "bool_8_"): 144 | var result [8]bool 145 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 146 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 147 | } 148 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 149 | t.Fatalf("Could not perform bool array root check: %v", err) 150 | } 151 | case strings.Contains(testName, "bool_16_"): 152 | var result [16]bool 153 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 154 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 155 | } 156 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 157 | t.Fatalf("Could not perform bool array root check: %v", err) 158 | } 159 | case strings.Contains(testName, "bool_31_"): 160 | var result [31]bool 161 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 162 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 163 | } 164 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 165 | t.Fatalf("Could not perform bool array root check: %v", err) 166 | } 167 | case strings.Contains(testName, "bool_512_"): 168 | var result [512]bool 169 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 170 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 171 | } 172 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 173 | t.Fatalf("Could not perform bool array root check: %v", err) 174 | } 175 | case strings.Contains(testName, "bool_513_"): 176 | var result [513]bool 177 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 178 | t.Fatalf("could not perform bool array ssz check for case %s: %v", testName, err) 179 | } 180 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 181 | t.Fatalf("Could not perform bool array root check: %v", err) 182 | } 183 | case strings.Contains(testName, "uint8_0"): 184 | var result [0]uint8 185 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 186 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 187 | } 188 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 189 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 190 | } 191 | case strings.Contains(testName, "uint8_1_"): 192 | var result [1]uint8 193 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 194 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 195 | } 196 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 197 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 198 | } 199 | case strings.Contains(testName, "uint8_2_"): 200 | var result [2]uint8 201 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 202 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 203 | } 204 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 205 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 206 | } 207 | case strings.Contains(testName, "uint8_3_"): 208 | var result [3]uint8 209 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 210 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 211 | } 212 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 213 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 214 | } 215 | case strings.Contains(testName, "uint8_4_"): 216 | var result [4]uint8 217 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 218 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 219 | } 220 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 221 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 222 | } 223 | case strings.Contains(testName, "uint8_5_"): 224 | var result [5]uint8 225 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 226 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 227 | } 228 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 229 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 230 | } 231 | case strings.Contains(testName, "uint8_8_"): 232 | var result [8]uint8 233 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 234 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 235 | } 236 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 237 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 238 | } 239 | case strings.Contains(testName, "uint8_16_"): 240 | var result [16]uint8 241 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 242 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 243 | } 244 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 245 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 246 | } 247 | case strings.Contains(testName, "uint8_31_"): 248 | var result [31]uint8 249 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 250 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 251 | } 252 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 253 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 254 | } 255 | case strings.Contains(testName, "uint8_512_"): 256 | var result [512]uint8 257 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 258 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 259 | } 260 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 261 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 262 | } 263 | case strings.Contains(testName, "uint8_513_"): 264 | var result [513]uint8 265 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 266 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 267 | } 268 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 269 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 270 | } 271 | case strings.Contains(testName, "uint16_0"): 272 | var result [0]uint16 273 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 274 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 275 | } 276 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 277 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 278 | } 279 | case strings.Contains(testName, "uint16_1_"): 280 | var result [1]uint16 281 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 282 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 283 | } 284 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 285 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 286 | } 287 | case strings.Contains(testName, "uint16_2_"): 288 | var result [2]uint16 289 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 290 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 291 | } 292 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 293 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 294 | } 295 | case strings.Contains(testName, "uint16_3_"): 296 | var result [3]uint16 297 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 298 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 299 | } 300 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 301 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 302 | } 303 | case strings.Contains(testName, "uint16_4_"): 304 | var result [4]uint16 305 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 306 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 307 | } 308 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 309 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 310 | } 311 | case strings.Contains(testName, "uint16_5_"): 312 | var result [5]uint16 313 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 314 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 315 | } 316 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 317 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 318 | } 319 | case strings.Contains(testName, "uint16_8_"): 320 | var result [8]uint16 321 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 322 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 323 | } 324 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 325 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 326 | } 327 | case strings.Contains(testName, "uint16_16_"): 328 | var result [16]uint16 329 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 330 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 331 | } 332 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 333 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 334 | } 335 | case strings.Contains(testName, "uint16_31_"): 336 | var result [31]uint16 337 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 338 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 339 | } 340 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 341 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 342 | } 343 | case strings.Contains(testName, "uint16_512_"): 344 | var result [512]uint16 345 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 346 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 347 | } 348 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 349 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 350 | } 351 | case strings.Contains(testName, "uint16_513_"): 352 | var result [513]uint16 353 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 354 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 355 | } 356 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 357 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 358 | } 359 | case strings.Contains(testName, "uint32_0"): 360 | var result [0]uint32 361 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 362 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 363 | } 364 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 365 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 366 | } 367 | case strings.Contains(testName, "uint32_1_"): 368 | var result [1]uint32 369 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 370 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 371 | } 372 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 373 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 374 | } 375 | case strings.Contains(testName, "uint32_2_"): 376 | var result [2]uint32 377 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 378 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 379 | } 380 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 381 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 382 | } 383 | case strings.Contains(testName, "uint32_3_"): 384 | var result [3]uint32 385 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 386 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 387 | } 388 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 389 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 390 | } 391 | case strings.Contains(testName, "uint32_4_"): 392 | var result [4]uint32 393 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 394 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 395 | } 396 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 397 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 398 | } 399 | case strings.Contains(testName, "uint32_5_"): 400 | var result [5]uint32 401 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 402 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 403 | } 404 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 405 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 406 | } 407 | case strings.Contains(testName, "uint32_8_"): 408 | var result [8]uint32 409 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 410 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 411 | } 412 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 413 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 414 | } 415 | case strings.Contains(testName, "uint32_16_"): 416 | var result [16]uint32 417 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 418 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 419 | } 420 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 421 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 422 | } 423 | case strings.Contains(testName, "uint32_31_"): 424 | var result [31]uint32 425 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 426 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 427 | } 428 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 429 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 430 | } 431 | case strings.Contains(testName, "uint32_512_"): 432 | var result [512]uint32 433 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 434 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 435 | } 436 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 437 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 438 | } 439 | case strings.Contains(testName, "uint32_513_"): 440 | var result [513]uint32 441 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 442 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 443 | } 444 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 445 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 446 | } 447 | case strings.Contains(testName, "uint64_0"): 448 | var result [0]uint64 449 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 450 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 451 | } 452 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 453 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 454 | } 455 | case strings.Contains(testName, "uint64_1_"): 456 | var result [1]uint64 457 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 458 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 459 | } 460 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 461 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 462 | } 463 | case strings.Contains(testName, "uint64_2_"): 464 | var result [2]uint64 465 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 466 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 467 | } 468 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 469 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 470 | } 471 | case strings.Contains(testName, "uint64_3_"): 472 | var result [3]uint64 473 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 474 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 475 | } 476 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 477 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 478 | } 479 | case strings.Contains(testName, "uint64_4_"): 480 | var result [4]uint64 481 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 482 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 483 | } 484 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 485 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 486 | } 487 | case strings.Contains(testName, "uint64_5_"): 488 | var result [5]uint64 489 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 490 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 491 | } 492 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 493 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 494 | } 495 | case strings.Contains(testName, "uint64_8_"): 496 | var result [8]uint64 497 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 498 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 499 | } 500 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 501 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 502 | } 503 | case strings.Contains(testName, "uint64_16_"): 504 | var result [16]uint64 505 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 506 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 507 | } 508 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 509 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 510 | } 511 | case strings.Contains(testName, "uint64_31_"): 512 | var result [31]uint64 513 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 514 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 515 | } 516 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 517 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 518 | } 519 | case strings.Contains(testName, "uint64_512_"): 520 | var result [512]uint64 521 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 522 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 523 | } 524 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 525 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 526 | } 527 | case strings.Contains(testName, "uint64_513_"): 528 | var result [513]uint64 529 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 530 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 531 | } 532 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 533 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 534 | } 535 | case strings.Contains(testName, "uint128"): 536 | case strings.Contains(testName, "uint256"): 537 | default: 538 | t.Error("Case not covered") 539 | } 540 | } 541 | 542 | func runBitlistSSZTestCase(t *testing.T, objBytes []byte, size uint64, yamlPath string, valid bool) { 543 | if !valid { 544 | return 545 | } 546 | var result bitfield.Bitlist 547 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 548 | t.Fatalf("Could not perform bitlist ssz test case: %v", err) 549 | } 550 | if err := PerformBitfieldRootCheck(result, size, yamlPath, valid); err != nil { 551 | t.Fatalf("Could not perform bitlist root check: %v", err) 552 | } 553 | } 554 | 555 | func runBitvectorSSZTestCase(t *testing.T, objBytes []byte, yamlPath string, testName string, valid bool) { 556 | if !strings.Contains(testName, "bitvec_4") { 557 | t.Log("Only bitvector4 supported") 558 | return 559 | } 560 | if !valid { 561 | return 562 | } 563 | var result bitfield.Bitvector4 564 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 565 | t.Fatalf("Could not perform bitvector ssz test case: %v", err) 566 | } 567 | size := uint64(4) 568 | if err := PerformBitfieldRootCheck(result, size, yamlPath, valid); err != nil { 569 | t.Fatalf("Could not perform bool root check: %v", err) 570 | } 571 | } 572 | 573 | func runBooleanSSZTestCase(t *testing.T, objBytes []byte, yamlPath string, valid bool) { 574 | var result bool 575 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 576 | t.Fatalf("Could not perform bool ssz test case: %v", err) 577 | } 578 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 579 | t.Fatalf("Could not perform bool root check: %v", err) 580 | } 581 | } 582 | 583 | func runContainerSSZTestCase(t *testing.T, objBytes []byte, yamlPath string, testName string, valid bool) { 584 | switch { 585 | case strings.Contains(testName, "ComplexTestStruct"): 586 | if !valid { 587 | t.Log("Skipping invalid complex struct test") 588 | return 589 | } 590 | var container complexTestStruct 591 | if err := PerformSSZCheck(objBytes, &container, yamlPath, valid); err != nil { 592 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 593 | } 594 | if err := PerformRootCheck(container, yamlPath, valid); err != nil { 595 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 596 | } 597 | case strings.Contains(testName, "BitsStruct"): 598 | t.Log("Container has unsupported Bitvector5") 599 | case strings.Contains(testName, "FixedTestStruct"): 600 | var container fixedTestStruct 601 | if err := PerformSSZCheck(objBytes, &container, yamlPath, valid); err != nil { 602 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 603 | } 604 | if err := PerformRootCheck(container, yamlPath, valid); err != nil { 605 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 606 | } 607 | case strings.Contains(testName, "SingleFieldTestStruct"): 608 | var container singleFieldStruct 609 | if err := PerformSSZCheck(objBytes, &container, yamlPath, valid); err != nil { 610 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 611 | } 612 | if err := PerformRootCheck(container, yamlPath, valid); err != nil { 613 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 614 | } 615 | case strings.Contains(testName, "SmallTestStruct"): 616 | var container smallTestStruct 617 | if err := PerformSSZCheck(objBytes, &container, yamlPath, valid); err != nil { 618 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 619 | } 620 | if err := PerformRootCheck(container, yamlPath, valid); err != nil { 621 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 622 | } 623 | case strings.Contains(testName, "VarTestStruct"): 624 | var container varTestStruct 625 | if err := PerformSSZCheck(objBytes, &container, yamlPath, valid); err != nil { 626 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 627 | } 628 | if err := PerformRootCheck(container, yamlPath, valid); err != nil { 629 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 630 | } 631 | default: 632 | t.Error("Not covered") 633 | } 634 | } 635 | 636 | func runUintSSZTestCase(t *testing.T, objBytes []byte, yamlPath string, testName string, valid bool) { 637 | switch { 638 | case strings.Contains(testName, "uint_8"): 639 | var result uint8 640 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 641 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 642 | } 643 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 644 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 645 | } 646 | case strings.Contains(testName, "uint_16"): 647 | var result uint16 648 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 649 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 650 | } 651 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 652 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 653 | } 654 | case strings.Contains(testName, "uint_32"): 655 | var result uint32 656 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 657 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 658 | } 659 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 660 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 661 | } 662 | case strings.Contains(testName, "uint_64"): 663 | var result uint64 664 | if err := PerformSSZCheck(objBytes, &result, yamlPath, valid); err != nil { 665 | t.Fatalf("could not perform ssz check for case %s: %v", testName, err) 666 | } 667 | if err := PerformRootCheck(result, yamlPath, valid); err != nil { 668 | t.Fatalf("Could not perform root check for case %s: %v", testName, err) 669 | } 670 | case strings.Contains(testName, "uint_128"): 671 | case strings.Contains(testName, "uint_256"): 672 | default: 673 | t.Error("Case not covered") 674 | } 675 | } 676 | 677 | func PerformSSZCheck(objBytes []byte, val interface{}, yamlPath string, valid bool) error { 678 | err := ssz.Unmarshal(objBytes, val) 679 | if valid { 680 | if err != nil { 681 | return fmt.Errorf("test case should not have failed: %v", err) 682 | } 683 | encoded, err := ssz.Marshal(val) 684 | if err != nil { 685 | return fmt.Errorf("failed to marshal: %v", err) 686 | } 687 | if !bytes.Equal(objBytes, encoded) { 688 | return fmt.Errorf("encorrect encoding/decoding, expected %#x, received %#x", objBytes, encoded) 689 | } 690 | } else if err == nil { 691 | return fmt.Errorf("expected error, received nil: %v", val) 692 | } 693 | return nil 694 | } 695 | 696 | func PerformRootCheck(val interface{}, yamlPath string, valid bool) error { 697 | if !valid { 698 | return nil 699 | } 700 | yamlBytes, err := bazelFileBytes(yamlPath) 701 | if err != nil { 702 | return err 703 | } 704 | var roots sszRoots 705 | if err := yaml.Unmarshal(yamlBytes, &roots); err != nil { 706 | return fmt.Errorf("failed to unmarshal: %v", err) 707 | } 708 | correctRoot, err := hex.DecodeString(roots.Root[2:]) 709 | if err != nil { 710 | return fmt.Errorf("failed to decode: %v", err) 711 | } 712 | root, err := ssz.HashTreeRoot(val) 713 | if err != nil { 714 | return fmt.Errorf("failed to get hashtreeroot: %v", err) 715 | } 716 | if !bytes.Equal(correctRoot, root[:]) { 717 | return fmt.Errorf("failed hash tree root check, expected %#x, received %#x", correctRoot, root) 718 | } 719 | if roots.SigningRoot != "" { 720 | correctRoot, err = hex.DecodeString(roots.SigningRoot[2:]) 721 | if err != nil { 722 | return fmt.Errorf("failed to decode: %v", err) 723 | } 724 | root, err = ssz.SigningRoot(val) 725 | if err != nil { 726 | return fmt.Errorf("failed to get hashtreeroot: %v", err) 727 | } 728 | if !bytes.Equal(correctRoot, root[:]) { 729 | return fmt.Errorf("failed signing root check, expected %#x, received %#x", correctRoot, root) 730 | } 731 | } 732 | return nil 733 | } 734 | 735 | func PerformBitfieldRootCheck(bitlist bitfield.Bitfield, size uint64, yamlPath string, valid bool) error { 736 | yamlBytes, err := FileBytesOrDie(yamlPath) 737 | if err != nil { 738 | return err 739 | } 740 | 741 | var roots sszRoots 742 | if err := yaml.Unmarshal(yamlBytes, &roots); err != nil { 743 | return fmt.Errorf("failed to unmarshal: %v", err) 744 | } 745 | 746 | correctRoot, err := hex.DecodeString(roots.Root[2:]) 747 | if err != nil { 748 | return fmt.Errorf("failed to decode: %v", err) 749 | } 750 | 751 | root, err := ssz.HashTreeRootBitfield(bitlist, size) 752 | if err != nil { 753 | return fmt.Errorf("failed to get hashtreeroot: %v", err) 754 | } 755 | 756 | if !bytes.Equal(correctRoot, root[:]) { 757 | return fmt.Errorf("failed hash tree root check, expected %#x, received %#x", correctRoot, root) 758 | } 759 | return nil 760 | } 761 | 762 | func PerformListRootCheck(val interface{}, size uint64, yamlPath string, valid bool) error { 763 | if !valid { 764 | return nil 765 | } 766 | 767 | yamlBytes, err := FileBytesOrDie(yamlPath) 768 | if err != nil { 769 | return err 770 | } 771 | 772 | var roots sszRoots 773 | if err := yaml.Unmarshal(yamlBytes, &roots); err != nil { 774 | return fmt.Errorf("failed to unmarshal: %v", err) 775 | } 776 | 777 | correctRoot, err := hex.DecodeString(roots.Root[2:]) 778 | if err != nil { 779 | return fmt.Errorf("failed to decode: %v", err) 780 | } 781 | 782 | root, err := ssz.HashTreeRootWithCapacity(val, size) 783 | if err != nil { 784 | return fmt.Errorf("failed to get hashtreeroot: %v", err) 785 | } 786 | 787 | if !bytes.Equal(correctRoot, root[:]) { 788 | return fmt.Errorf("failed hash tree root check, expected %#x, received %#x", correctRoot, root) 789 | } 790 | 791 | if roots.SigningRoot != "" { 792 | correctRoot, err = hex.DecodeString(roots.SigningRoot[2:]) 793 | if err != nil { 794 | return fmt.Errorf("failed to decode: %v", err) 795 | } 796 | 797 | root, err = ssz.SigningRoot(val) 798 | if err != nil { 799 | return fmt.Errorf("failed to get hashtreeroot: %v", err) 800 | } 801 | 802 | if !bytes.Equal(correctRoot, root[:]) { 803 | return fmt.Errorf("failed signing root check, expected %#x, received %#x", correctRoot, root) 804 | } 805 | } 806 | return nil 807 | } 808 | 809 | func FileBytesOrDie(path string) ([]byte, error) { 810 | file, err := bazel.Runfile(path) 811 | if err != nil { 812 | return []byte{}, fmt.Errorf("failed to marshal: %v", err) 813 | } 814 | bytes, err := ioutil.ReadFile(file) 815 | if err != nil { 816 | return []byte{}, fmt.Errorf("failed to marshal: %v", err) 817 | } 818 | return bytes, nil 819 | } 820 | -------------------------------------------------------------------------------- /spectests/generic_types.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | type sszRoots struct { 4 | Root string `yaml:"root"` 5 | SigningRoot string `yaml:"signing_root"` 6 | } 7 | 8 | type singleFieldStruct struct { 9 | A byte 10 | } 11 | 12 | type smallTestStruct struct { 13 | A uint16 14 | B uint16 15 | } 16 | 17 | type fixedTestStruct struct { 18 | A uint8 19 | B uint64 20 | C uint32 21 | } 22 | 23 | type varTestStruct struct { 24 | A uint16 25 | B []uint16 `ssz-max:"1024"` 26 | C uint8 27 | } 28 | 29 | type complexTestStruct struct { 30 | A uint16 31 | B []uint16 `ssz-max:"128"` 32 | C uint8 33 | D []byte `ssz-max:"256"` 34 | E varTestStruct 35 | F []fixedTestStruct `ssz-size:"4"` 36 | G []varTestStruct `ssz-size:"2"` 37 | } 38 | -------------------------------------------------------------------------------- /spectests/mainnet_test.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "path" 8 | "testing" 9 | 10 | "github.com/bazelbuild/rules_go/go/tools/bazel" 11 | "github.com/prysmaticlabs/go-ssz" 12 | ) 13 | 14 | func TestSSZStatic_Mainnet(t *testing.T) { 15 | testFolders, testsFolderPath := testFolders(t, "mainnet", "ssz_static") 16 | for _, folder := range testFolders { 17 | eth2TypeName := folder.Name() 18 | t.Run(eth2TypeName, func(t *testing.T) { 19 | sszCases, err := bazel.Runfile(path.Join(testsFolderPath, eth2TypeName)) 20 | if err != nil { 21 | t.Fatalf("Failed to read file: %v", err) 22 | } 23 | innerSSZFolders, err := ioutil.ReadDir(sszCases) 24 | if err != nil { 25 | t.Fatalf("Failed to read file: %v", err) 26 | } 27 | for _, innerFolder := range innerSSZFolders { 28 | innerPath := path.Join(sszCases, innerFolder.Name()) 29 | testCases, err := ioutil.ReadDir(innerPath) 30 | if err != nil { 31 | t.Fatalf("Failed to read file: %v", err) 32 | } 33 | for _, cs := range testCases { 34 | serialized, err := bazelFileBytes(path.Join(innerPath, cs.Name(), "serialized.ssz")) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | rootPath := path.Join(innerPath, cs.Name(), "roots.yaml") 39 | cont := &rootContainer{} 40 | populateStructFromYaml(t, rootPath, cont) 41 | switch folder.Name() { 42 | case "AggregateAndProof": 43 | dec := &mainnetAggregateAndProof{} 44 | if err := ssz.Unmarshal(serialized, dec); err != nil { 45 | t.Fatal(err) 46 | } 47 | enc, err := ssz.Marshal(dec) 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if !bytes.Equal(serialized, enc) { 52 | t.Errorf("Wanted %v, received %v", serialized, enc) 53 | } 54 | rt, err := ssz.HashTreeRoot(dec) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if fmt.Sprintf("%#x", rt) != cont.Root { 59 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 60 | } 61 | case "Attestation": 62 | dec := &mainnetAttestation{} 63 | if err := ssz.Unmarshal(serialized, dec); err != nil { 64 | t.Fatal(err) 65 | } 66 | enc, err := ssz.Marshal(dec) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | if !bytes.Equal(serialized, enc) { 71 | t.Errorf("Wanted %v, received %v", serialized, enc) 72 | } 73 | rt, err := ssz.HashTreeRoot(dec) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | if fmt.Sprintf("%#x", rt) != cont.Root { 78 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 79 | } 80 | case "AttestationData": 81 | dec := &mainnetAttestationData{} 82 | if err := ssz.Unmarshal(serialized, dec); err != nil { 83 | t.Fatal(err) 84 | } 85 | enc, err := ssz.Marshal(dec) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | if !bytes.Equal(serialized, enc) { 90 | t.Errorf("Wanted %v, received %v", serialized, enc) 91 | } 92 | rt, err := ssz.HashTreeRoot(dec) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | if fmt.Sprintf("%#x", rt) != cont.Root { 97 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 98 | } 99 | case "AttestationDataAndCustodyBit": 100 | dec := &mainnetAttestationAndCustodyBit{} 101 | if err := ssz.Unmarshal(serialized, dec); err != nil { 102 | t.Fatal(err) 103 | } 104 | enc, err := ssz.Marshal(dec) 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | if !bytes.Equal(serialized, enc) { 109 | t.Errorf("Wanted %v, received %v", serialized, enc) 110 | } 111 | rt, err := ssz.HashTreeRoot(dec) 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if fmt.Sprintf("%#x", rt) != cont.Root { 116 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 117 | } 118 | case "AttesterSlashing": 119 | dec := &mainnetAttesterSlashing{} 120 | if err := ssz.Unmarshal(serialized, dec); err != nil { 121 | t.Fatal(err) 122 | } 123 | enc, err := ssz.Marshal(dec) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | if !bytes.Equal(serialized, enc) { 128 | t.Errorf("Wanted %v, received %v", serialized, enc) 129 | } 130 | rt, err := ssz.HashTreeRoot(dec) 131 | if err != nil { 132 | t.Fatal(err) 133 | } 134 | if fmt.Sprintf("%#x", rt) != cont.Root { 135 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 136 | } 137 | case "BeaconBlock": 138 | dec := &mainnetBlock{} 139 | if err := ssz.Unmarshal(serialized, dec); err != nil { 140 | t.Fatal(err) 141 | } 142 | enc, err := ssz.Marshal(dec) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | if !bytes.Equal(serialized, enc) { 147 | t.Errorf("Wanted %v, received %v", serialized, enc) 148 | } 149 | rt, err := ssz.HashTreeRoot(dec) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if fmt.Sprintf("%#x", rt) != cont.Root { 154 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 155 | } 156 | case "BeaconBlockBody": 157 | dec := &mainnetBlockBody{} 158 | if err := ssz.Unmarshal(serialized, dec); err != nil { 159 | t.Fatal(err) 160 | } 161 | enc, err := ssz.Marshal(dec) 162 | if err != nil { 163 | t.Fatal(err) 164 | } 165 | if !bytes.Equal(serialized, enc) { 166 | t.Errorf("Wanted %v, received %v", serialized, enc) 167 | } 168 | rt, err := ssz.HashTreeRoot(dec) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | if fmt.Sprintf("%#x", rt) != cont.Root { 173 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 174 | } 175 | case "BeaconBlockHeader": 176 | dec := &MainnetBlockHeader{} 177 | if err := ssz.Unmarshal(serialized, dec); err != nil { 178 | t.Fatal(err) 179 | } 180 | enc, err := ssz.Marshal(dec) 181 | if err != nil { 182 | t.Fatal(err) 183 | } 184 | if !bytes.Equal(serialized, enc) { 185 | t.Errorf("Wanted %v, received %v", serialized, enc) 186 | } 187 | rt, err := ssz.HashTreeRoot(dec) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | if fmt.Sprintf("%#x", rt) != cont.Root { 192 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 193 | } 194 | case "BeaconState": 195 | dec := &mainnetBeaconState{} 196 | if err := ssz.Unmarshal(serialized, dec); err != nil { 197 | t.Fatal(err) 198 | } 199 | enc, err := ssz.Marshal(dec) 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | if !bytes.Equal(serialized, enc) { 204 | t.Errorf("Wanted %v, received %v", serialized, enc) 205 | } 206 | rt, err := ssz.HashTreeRoot(dec) 207 | if err != nil { 208 | t.Fatal(err) 209 | } 210 | if fmt.Sprintf("%#x", rt) != cont.Root { 211 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 212 | } 213 | case "Checkpoint": 214 | dec := &mainnetCheckpoint{} 215 | if err := ssz.Unmarshal(serialized, dec); err != nil { 216 | t.Fatal(err) 217 | } 218 | enc, err := ssz.Marshal(dec) 219 | if err != nil { 220 | t.Fatal(err) 221 | } 222 | if !bytes.Equal(serialized, enc) { 223 | t.Errorf("Wanted %v, received %v", serialized, enc) 224 | } 225 | rt, err := ssz.HashTreeRoot(dec) 226 | if err != nil { 227 | t.Fatal(err) 228 | } 229 | if fmt.Sprintf("%#x", rt) != cont.Root { 230 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 231 | } 232 | case "Deposit": 233 | dec := &mainnetDeposit{} 234 | if err := ssz.Unmarshal(serialized, dec); err != nil { 235 | t.Fatal(err) 236 | } 237 | enc, err := ssz.Marshal(dec) 238 | if err != nil { 239 | t.Fatal(err) 240 | } 241 | if !bytes.Equal(serialized, enc) { 242 | t.Errorf("Wanted %v, received %v", serialized, enc) 243 | } 244 | rt, err := ssz.HashTreeRoot(dec) 245 | if err != nil { 246 | t.Fatal(err) 247 | } 248 | if fmt.Sprintf("%#x", rt) != cont.Root { 249 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 250 | } 251 | case "DepositData": 252 | dec := &mainnetDepositData{} 253 | if err := ssz.Unmarshal(serialized, dec); err != nil { 254 | t.Fatal(err) 255 | } 256 | enc, err := ssz.Marshal(dec) 257 | if err != nil { 258 | t.Fatal(err) 259 | } 260 | if !bytes.Equal(serialized, enc) { 261 | t.Errorf("Wanted %v, received %v", serialized, enc) 262 | } 263 | rt, err := ssz.HashTreeRoot(dec) 264 | if err != nil { 265 | t.Fatal(err) 266 | } 267 | if fmt.Sprintf("%#x", rt) != cont.Root { 268 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 269 | } 270 | case "Eth1Data": 271 | dec := &mainnetEth1Data{} 272 | if err := ssz.Unmarshal(serialized, dec); err != nil { 273 | t.Fatal(err) 274 | } 275 | enc, err := ssz.Marshal(dec) 276 | if err != nil { 277 | t.Fatal(err) 278 | } 279 | if !bytes.Equal(serialized, enc) { 280 | t.Errorf("Wanted %v, received %v", serialized, enc) 281 | } 282 | rt, err := ssz.HashTreeRoot(dec) 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | if fmt.Sprintf("%#x", rt) != cont.Root { 287 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 288 | } 289 | case "Fork": 290 | dec := &mainnetFork{} 291 | if err := ssz.Unmarshal(serialized, dec); err != nil { 292 | t.Fatal(err) 293 | } 294 | enc, err := ssz.Marshal(dec) 295 | if err != nil { 296 | t.Fatal(err) 297 | } 298 | if !bytes.Equal(serialized, enc) { 299 | t.Errorf("Wanted %v, received %v", serialized, enc) 300 | } 301 | rt, err := ssz.HashTreeRoot(dec) 302 | if err != nil { 303 | t.Fatal(err) 304 | } 305 | if fmt.Sprintf("%#x", rt) != cont.Root { 306 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 307 | } 308 | case "HistoricalBatch": 309 | dec := &mainnetHistoricalBatch{} 310 | if err := ssz.Unmarshal(serialized, dec); err != nil { 311 | t.Fatal(err) 312 | } 313 | enc, err := ssz.Marshal(dec) 314 | if err != nil { 315 | t.Fatal(err) 316 | } 317 | if !bytes.Equal(serialized, enc) { 318 | t.Errorf("Wanted %v, received %v", serialized, enc) 319 | } 320 | rt, err := ssz.HashTreeRoot(dec) 321 | if err != nil { 322 | t.Fatal(err) 323 | } 324 | if fmt.Sprintf("%#x", rt) != cont.Root { 325 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 326 | } 327 | case "IndexedAttestation": 328 | dec := &mainnetIndexedAttestation{} 329 | if err := ssz.Unmarshal(serialized, dec); err != nil { 330 | t.Fatal(err) 331 | } 332 | enc, err := ssz.Marshal(dec) 333 | if err != nil { 334 | t.Fatal(err) 335 | } 336 | if !bytes.Equal(serialized, enc) { 337 | t.Errorf("Wanted %v, received %v", serialized, enc) 338 | } 339 | rt, err := ssz.HashTreeRoot(dec) 340 | if err != nil { 341 | t.Fatal(err) 342 | } 343 | if fmt.Sprintf("%#x", rt) != cont.Root { 344 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 345 | } 346 | case "PendingAttestation": 347 | dec := &mainnetPendingAttestation{} 348 | if err := ssz.Unmarshal(serialized, dec); err != nil { 349 | t.Fatal(err) 350 | } 351 | enc, err := ssz.Marshal(dec) 352 | if err != nil { 353 | t.Fatal(err) 354 | } 355 | if !bytes.Equal(serialized, enc) { 356 | t.Errorf("Wanted %v, received %v", serialized, enc) 357 | } 358 | rt, err := ssz.HashTreeRoot(dec) 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | if fmt.Sprintf("%#x", rt) != cont.Root { 363 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 364 | } 365 | case "ProposerSlashing": 366 | dec := &mainnetProposerSlashing{} 367 | if err := ssz.Unmarshal(serialized, dec); err != nil { 368 | t.Fatal(err) 369 | } 370 | enc, err := ssz.Marshal(dec) 371 | if err != nil { 372 | t.Fatal(err) 373 | } 374 | if !bytes.Equal(serialized, enc) { 375 | t.Errorf("Wanted %v, received %v", serialized, enc) 376 | } 377 | rt, err := ssz.HashTreeRoot(dec) 378 | if err != nil { 379 | t.Fatal(err) 380 | } 381 | if fmt.Sprintf("%#x", rt) != cont.Root { 382 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 383 | } 384 | case "Validator": 385 | dec := &mainnetValidator{} 386 | if err := ssz.Unmarshal(serialized, dec); err != nil { 387 | t.Fatal(err) 388 | } 389 | enc, err := ssz.Marshal(dec) 390 | if err != nil { 391 | t.Fatal(err) 392 | } 393 | if !bytes.Equal(serialized, enc) { 394 | t.Errorf("Wanted %v, received %v", serialized, enc) 395 | } 396 | rt, err := ssz.HashTreeRoot(dec) 397 | if err != nil { 398 | t.Fatal(err) 399 | } 400 | if fmt.Sprintf("%#x", rt) != cont.Root { 401 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 402 | } 403 | case "VoluntaryExit": 404 | dec := &mainnetVoluntaryExit{} 405 | if err := ssz.Unmarshal(serialized, dec); err != nil { 406 | t.Fatal(err) 407 | } 408 | enc, err := ssz.Marshal(dec) 409 | if err != nil { 410 | t.Fatal(err) 411 | } 412 | if !bytes.Equal(serialized, enc) { 413 | t.Errorf("Wanted %v, received %v", serialized, enc) 414 | } 415 | rt, err := ssz.HashTreeRoot(dec) 416 | if err != nil { 417 | t.Fatal(err) 418 | } 419 | if fmt.Sprintf("%#x", rt) != cont.Root { 420 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 421 | } 422 | default: 423 | t.Error("Case not covered") 424 | } 425 | } 426 | } 427 | }) 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /spectests/mainnet_types.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "github.com/prysmaticlabs/go-bitfield" 5 | ) 6 | 7 | type mainnetFork struct { 8 | PreviousVersion []byte `json:"previous_version" ssz-size:"4"` 9 | CurrentVersion []byte `json:"current_version" ssz-size:"4"` 10 | Epoch uint64 `json:"epoch"` 11 | } 12 | 13 | type mainnetCheckpoint struct { 14 | Epoch uint64 `json:"epoch"` 15 | Root []byte `json:"root" ssz-size:"32"` 16 | } 17 | 18 | type mainnetValidator struct { 19 | Pubkey []byte `json:"pubkey" ssz-size:"48"` 20 | WithdrawalCredentials []byte `json:"withdrawal_credentials" ssz-size:"32"` 21 | EffectiveBalance uint64 `json:"effective_balance"` 22 | Slashed bool `json:"slashed"` 23 | ActivationEligibilityEpoch uint64 `json:"activation_eligibility_epoch"` 24 | ActivationEpoch uint64 `json:"activation_epoch"` 25 | ExitEpoch uint64 `json:"exit_epoch"` 26 | WithdrawableEpoch uint64 `json:"withdrawable_epoch"` 27 | } 28 | 29 | type mainnetAttestationData struct { 30 | Slot uint64 31 | Index uint64 32 | BeaconBlockRoot []byte `json:"beacon_block_root" ssz-size:"32"` 33 | Source mainnetCheckpoint `json:"source"` 34 | Target mainnetCheckpoint `json:"target"` 35 | } 36 | 37 | type mainnetAttestationAndCustodyBit struct { 38 | Data mainnetAttestationData `json:"data"` 39 | CustodyBit bool `json:"custody_bit"` 40 | } 41 | 42 | type mainnetIndexedAttestation struct { 43 | CustodyBit0Indices []uint64 `json:"custody_bit_0_indices" ssz-max:"2048"` 44 | CustodyBit1Indices []uint64 `json:"custody_bit_1_indices" ssz-max:"2048"` 45 | Data mainnetAttestationData `json:"data"` 46 | Signature []byte `json:"signature" ssz-size:"96"` 47 | } 48 | 49 | type mainnetPendingAttestation struct { 50 | AggregationBits bitfield.Bitlist `json:"aggregation_bits" ssz-max:"2048"` 51 | Data mainnetAttestationData `json:"data"` 52 | InclusionDelay uint64 `json:"inclusion_delay"` 53 | ProposerIndex uint64 `json:"proposer_index"` 54 | } 55 | 56 | type mainnetEth1Data struct { 57 | DepositRoot []byte `json:"deposit_root" ssz-size:"32"` 58 | DepositCount uint64 `json:"deposit_count"` 59 | BlockHash []byte `json:"block_hash" ssz-size:"32"` 60 | } 61 | 62 | type mainnetHistoricalBatch struct { 63 | BlockRoots [][]byte `json:"block_roots" ssz-size:"8192,32"` 64 | StateRoots [][]byte `json:"state_roots" ssz-size:"8192,32"` 65 | } 66 | 67 | type mainnetDepositData struct { 68 | Pubkey []byte `json:"pubkey" ssz-size:"48"` 69 | WithdrawalCredentials []byte `json:"withdrawal_credentials" ssz-size:"32"` 70 | Amount uint64 `json:"amount"` 71 | Signature []byte `json:"signature" ssz-size:"96"` 72 | } 73 | 74 | // MainnetBlockHeader -- 75 | type MainnetBlockHeader struct { 76 | Slot uint64 `json:"slot"` 77 | ParentRoot []byte `json:"parent_root" ssz-size:"32"` 78 | StateRoot []byte `json:"state_root" ssz-size:"32"` 79 | BodyRoot []byte `json:"body_root" ssz-size:"32"` 80 | Signature []byte `json:"signature" ssz-size:"96"` 81 | } 82 | 83 | type mainnetProposerSlashing struct { 84 | ProposerIndex uint64 `json:"proposer_index"` 85 | Header1 MainnetBlockHeader `json:"header_1"` 86 | Header2 MainnetBlockHeader `json:"header_2"` 87 | } 88 | 89 | type mainnetAttesterSlashing struct { 90 | Attestation1 mainnetIndexedAttestation `json:"attestation_1"` 91 | Attestation2 mainnetIndexedAttestation `json:"attestation_2"` 92 | } 93 | 94 | type mainnetAttestation struct { 95 | AggregationBits bitfield.Bitlist `json:"aggregation_bits" ssz-max:"2048"` 96 | Data mainnetAttestationData `json:"data"` 97 | CustodyBits bitfield.Bitlist `json:"custody_bits" ssz-max:"2048"` 98 | Signature []byte `json:"signature" ssz-size:"96"` 99 | } 100 | 101 | type mainnetDeposit struct { 102 | Proof [][]byte `json:"proof" ssz-size:"33,32"` 103 | Data mainnetDepositData `json:"data"` 104 | } 105 | 106 | type mainnetVoluntaryExit struct { 107 | Epoch uint64 `json:"epoch"` 108 | ValidatorIndex uint64 `json:"validator_index"` 109 | Signature []byte `json:"signature" ssz-size:"96"` 110 | } 111 | 112 | type mainnetBlockBody struct { 113 | RandaoReveal []byte `json:"randao_reveal" ssz-size:"96"` 114 | Eth1Data mainnetEth1Data `json:"eth1_data"` 115 | Graffiti []byte `json:"graffiti" ssz-size:"32"` 116 | ProposerSlashings []mainnetProposerSlashing `json:"proposer_slashings" ssz-max:"16"` 117 | AttesterSlashings []mainnetAttesterSlashing `json:"attester_slashings" ssz-max:"1"` 118 | Attestations []mainnetAttestation `json:"attestations" ssz-max:"128"` 119 | Deposits []mainnetDeposit `json:"deposits" ssz-max:"16"` 120 | VoluntaryExits []mainnetVoluntaryExit `json:"voluntary_exits" ssz-max:"16"` 121 | } 122 | 123 | type mainnetBlock struct { 124 | Slot uint64 `json:"slot"` 125 | ParentRoot []byte `json:"parent_root" ssz-size:"32"` 126 | StateRoot []byte `json:"state_root" ssz-size:"32"` 127 | Body mainnetBlockBody `json:"body"` 128 | Signature []byte `json:"signature" ssz-size:"96"` 129 | } 130 | 131 | type mainnetAggregateAndProof struct { 132 | Index uint64 133 | SelectionProof []byte `ssz-size:"96"` 134 | Aggregate mainnetAttestation 135 | } 136 | 137 | type mainnetBeaconState struct { 138 | GenesisTime uint64 `json:"genesis_time"` 139 | Slot uint64 `json:"slot"` 140 | Fork mainnetFork `json:"fork"` 141 | LatestBlockHeader MainnetBlockHeader `json:"latest_block_header"` 142 | BlockRoots [][]byte `json:"block_roots" ssz-size:"8192,32"` 143 | StateRoots [][]byte `json:"state_roots" ssz-size:"8192,32"` 144 | HistoricalRoots [][]byte `json:"historical_roots" ssz-size:"?,32" ssz-max:"16777216"` 145 | Eth1Data mainnetEth1Data `json:"eth1_data"` 146 | Eth1DataVotes []mainnetEth1Data `json:"eth1_data_votes" ssz-max:"1024"` 147 | Eth1DepositIndex uint64 `json:"eth1_deposit_index"` 148 | Validators []mainnetValidator `json:"validators" ssz-max:"1099511627776"` 149 | Balances []uint64 `json:"balances" ssz-max:"1099511627776"` 150 | RandaoMixes [][]byte `json:"randao_mixes" ssz-size:"65536,32"` 151 | Slashings []uint64 `json:"slashings" ssz-size:"8192"` 152 | 153 | PreviousEpochAttestations []mainnetPendingAttestation `json:"previous_epoch_attestations" ssz-max:"4096"` 154 | CurrentEpochAttestations []mainnetPendingAttestation `json:"current_epoch_attestations" ssz-max:"4096"` 155 | JustificationBits bitfield.Bitvector4 `json:"justification_bits" ssz-size:"1"` 156 | 157 | PreviousJustifiedCheckpoint mainnetCheckpoint `json:"previous_justified_checkpoint"` 158 | CurrentJustifiedCheckpoint mainnetCheckpoint `json:"current_justified_checkpoint"` 159 | FinalizedCheckpoint mainnetCheckpoint `json:"finalized_checkpoint"` 160 | } 161 | -------------------------------------------------------------------------------- /spectests/minimal_test.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "path" 9 | "testing" 10 | 11 | "github.com/bazelbuild/rules_go/go/tools/bazel" 12 | 13 | "github.com/prysmaticlabs/go-ssz" 14 | ) 15 | 16 | type rootContainer struct { 17 | Root string 18 | } 19 | 20 | func TestSSZStatic_Minimal(t *testing.T) { 21 | testFolders, testsFolderPath := testFolders(t, "minimal", "ssz_static") 22 | for _, folder := range testFolders { 23 | eth2TypeName := folder.Name() 24 | t.Run(eth2TypeName, func(t *testing.T) { 25 | sszCases, err := bazel.Runfile(path.Join(testsFolderPath, eth2TypeName)) 26 | if err != nil { 27 | t.Fatalf("Failed to read file: %v", err) 28 | } 29 | innerSSZFolders, err := ioutil.ReadDir(sszCases) 30 | if err != nil { 31 | t.Fatalf("Failed to read file: %v", err) 32 | } 33 | for _, innerFolder := range innerSSZFolders { 34 | innerPath := path.Join(sszCases, innerFolder.Name()) 35 | cases, err := ioutil.ReadDir(innerPath) 36 | if err != nil { 37 | t.Fatalf("Failed to read file: %v", err) 38 | } 39 | for _, cs := range cases { 40 | serializedPath := path.Join(innerPath, cs.Name(), "serialized.ssz") 41 | serialized, err := bazelFileBytes(serializedPath) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | rootPath := path.Join(innerPath, cs.Name(), "roots.yaml") 46 | cont := &rootContainer{} 47 | populateStructFromYaml(t, rootPath, cont) 48 | switch folder.Name() { 49 | case "AggregateAndProof": 50 | dec := &minimalAggregateAndProof{} 51 | if err := ssz.Unmarshal(serialized, dec); err != nil { 52 | t.Fatal(err) 53 | } 54 | enc, err := ssz.Marshal(dec) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | if !bytes.Equal(serialized, enc) { 59 | t.Errorf("Wanted %v, received %v", serialized, enc) 60 | } 61 | rt, err := ssz.HashTreeRoot(dec) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | if fmt.Sprintf("%#x", rt) != cont.Root { 66 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 67 | } 68 | case "Attestation": 69 | dec := &minimalAttestation{} 70 | if err := ssz.Unmarshal(serialized, dec); err != nil { 71 | t.Fatal(err) 72 | } 73 | enc, err := ssz.Marshal(dec) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | if !bytes.Equal(serialized, enc) { 78 | t.Errorf("Wanted %v, received %v", serialized, enc) 79 | } 80 | rt, err := ssz.HashTreeRoot(dec) 81 | if err != nil { 82 | t.Fatal(err) 83 | } 84 | if fmt.Sprintf("%#x", rt) != cont.Root { 85 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 86 | } 87 | case "AttestationData": 88 | dec := &minimalAttestationData{} 89 | if err := ssz.Unmarshal(serialized, dec); err != nil { 90 | t.Fatal(err) 91 | } 92 | enc, err := ssz.Marshal(dec) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | if !bytes.Equal(serialized, enc) { 97 | t.Errorf("Wanted %v, received %v", serialized, enc) 98 | } 99 | rt, err := ssz.HashTreeRoot(dec) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | if fmt.Sprintf("%#x", rt) != cont.Root { 104 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 105 | } 106 | case "AttestationDataAndCustodyBit": 107 | dec := &minimalAttestationAndCustodyBit{} 108 | if err := ssz.Unmarshal(serialized, dec); err != nil { 109 | t.Fatal(err) 110 | } 111 | enc, err := ssz.Marshal(dec) 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | if !bytes.Equal(serialized, enc) { 116 | t.Errorf("Wanted %v, received %v", serialized, enc) 117 | } 118 | rt, err := ssz.HashTreeRoot(dec) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | if fmt.Sprintf("%#x", rt) != cont.Root { 123 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 124 | } 125 | case "AttesterSlashing": 126 | dec := &minimalAttesterSlashing{} 127 | if err := ssz.Unmarshal(serialized, dec); err != nil { 128 | t.Fatal(err) 129 | } 130 | enc, err := ssz.Marshal(dec) 131 | if err != nil { 132 | t.Fatal(err) 133 | } 134 | if !bytes.Equal(serialized, enc) { 135 | t.Errorf("Wanted %v, received %v", serialized, enc) 136 | } 137 | rt, err := ssz.HashTreeRoot(dec) 138 | if err != nil { 139 | t.Fatal(err) 140 | } 141 | if fmt.Sprintf("%#x", rt) != cont.Root { 142 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 143 | } 144 | case "BeaconBlock": 145 | dec := &minimalBlock{} 146 | if err := ssz.Unmarshal(serialized, dec); err != nil { 147 | t.Fatal(err) 148 | } 149 | enc, err := ssz.Marshal(dec) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if !bytes.Equal(serialized, enc) { 154 | t.Errorf("Wanted %v, received %v", serialized, enc) 155 | } 156 | rt, err := ssz.HashTreeRoot(dec) 157 | if err != nil { 158 | t.Fatal(err) 159 | } 160 | if fmt.Sprintf("%#x", rt) != cont.Root { 161 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 162 | } 163 | case "BeaconBlockBody": 164 | dec := &minimalBlockBody{} 165 | if err := ssz.Unmarshal(serialized, dec); err != nil { 166 | t.Fatal(err) 167 | } 168 | enc, err := ssz.Marshal(dec) 169 | if err != nil { 170 | t.Fatal(err) 171 | } 172 | if !bytes.Equal(serialized, enc) { 173 | t.Errorf("Wanted %v, received %v", serialized, enc) 174 | } 175 | rt, err := ssz.HashTreeRoot(dec) 176 | if err != nil { 177 | t.Fatal(err) 178 | } 179 | if fmt.Sprintf("%#x", rt) != cont.Root { 180 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 181 | } 182 | case "BeaconBlockHeader": 183 | dec := &minimalBlockHeader{} 184 | if err := ssz.Unmarshal(serialized, dec); err != nil { 185 | t.Fatal(err) 186 | } 187 | enc, err := ssz.Marshal(dec) 188 | if err != nil { 189 | t.Fatal(err) 190 | } 191 | if !bytes.Equal(serialized, enc) { 192 | t.Errorf("Wanted %v, received %v", serialized, enc) 193 | } 194 | rt, err := ssz.HashTreeRoot(dec) 195 | if err != nil { 196 | t.Fatal(err) 197 | } 198 | if fmt.Sprintf("%#x", rt) != cont.Root { 199 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 200 | } 201 | case "BeaconState": 202 | dec := &minimalBeaconState{} 203 | if err := ssz.Unmarshal(serialized, dec); err != nil { 204 | t.Fatal(err) 205 | } 206 | enc, err := ssz.Marshal(dec) 207 | if err != nil { 208 | t.Fatal(err) 209 | } 210 | if !bytes.Equal(serialized, enc) { 211 | t.Errorf("Wanted %v, received %v", serialized, enc) 212 | } 213 | rt, err := ssz.HashTreeRoot(dec) 214 | if err != nil { 215 | t.Fatal(err) 216 | } 217 | if fmt.Sprintf("%#x", rt) != cont.Root { 218 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 219 | } 220 | case "Checkpoint": 221 | dec := &minimalCheckpoint{} 222 | if err := ssz.Unmarshal(serialized, dec); err != nil { 223 | t.Fatal(err) 224 | } 225 | enc, err := ssz.Marshal(dec) 226 | if err != nil { 227 | t.Fatal(err) 228 | } 229 | if !bytes.Equal(serialized, enc) { 230 | t.Errorf("Wanted %v, received %v", serialized, enc) 231 | } 232 | rt, err := ssz.HashTreeRoot(dec) 233 | if err != nil { 234 | t.Fatal(err) 235 | } 236 | if fmt.Sprintf("%#x", rt) != cont.Root { 237 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 238 | } 239 | case "Deposit": 240 | dec := &minimalDeposit{} 241 | if err := ssz.Unmarshal(serialized, dec); err != nil { 242 | t.Fatal(err) 243 | } 244 | enc, err := ssz.Marshal(dec) 245 | if err != nil { 246 | t.Fatal(err) 247 | } 248 | if !bytes.Equal(serialized, enc) { 249 | t.Errorf("Wanted %v, received %v", serialized, enc) 250 | } 251 | rt, err := ssz.HashTreeRoot(dec) 252 | if err != nil { 253 | t.Fatal(err) 254 | } 255 | if fmt.Sprintf("%#x", rt) != cont.Root { 256 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 257 | } 258 | case "DepositData": 259 | dec := &minimalDepositData{} 260 | if err := ssz.Unmarshal(serialized, dec); err != nil { 261 | t.Fatal(err) 262 | } 263 | enc, err := ssz.Marshal(dec) 264 | if err != nil { 265 | t.Fatal(err) 266 | } 267 | if !bytes.Equal(serialized, enc) { 268 | t.Errorf("Wanted %v, received %v", serialized, enc) 269 | } 270 | rt, err := ssz.HashTreeRoot(dec) 271 | if err != nil { 272 | t.Fatal(err) 273 | } 274 | if fmt.Sprintf("%#x", rt) != cont.Root { 275 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 276 | } 277 | case "Eth1Data": 278 | dec := &minimalEth1Data{} 279 | if err := ssz.Unmarshal(serialized, dec); err != nil { 280 | t.Fatal(err) 281 | } 282 | enc, err := ssz.Marshal(dec) 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | if !bytes.Equal(serialized, enc) { 287 | t.Errorf("Wanted %v, received %v", serialized, enc) 288 | } 289 | rt, err := ssz.HashTreeRoot(dec) 290 | if err != nil { 291 | t.Fatal(err) 292 | } 293 | if fmt.Sprintf("%#x", rt) != cont.Root { 294 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 295 | } 296 | case "Fork": 297 | dec := &minimalFork{} 298 | if err := ssz.Unmarshal(serialized, dec); err != nil { 299 | t.Fatal(err) 300 | } 301 | enc, err := ssz.Marshal(dec) 302 | if err != nil { 303 | t.Fatal(err) 304 | } 305 | if !bytes.Equal(serialized, enc) { 306 | t.Errorf("Wanted %v, received %v", serialized, enc) 307 | } 308 | rt, err := ssz.HashTreeRoot(dec) 309 | if err != nil { 310 | t.Fatal(err) 311 | } 312 | if fmt.Sprintf("%#x", rt) != cont.Root { 313 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 314 | } 315 | case "HistoricalBatch": 316 | dec := &minimalHistoricalBatch{} 317 | if err := ssz.Unmarshal(serialized, dec); err != nil { 318 | t.Fatal(err) 319 | } 320 | enc, err := ssz.Marshal(dec) 321 | if err != nil { 322 | t.Fatal(err) 323 | } 324 | if !bytes.Equal(serialized, enc) { 325 | t.Errorf("Wanted %v, received %v", serialized, enc) 326 | } 327 | rt, err := ssz.HashTreeRoot(dec) 328 | if err != nil { 329 | t.Fatal(err) 330 | } 331 | if fmt.Sprintf("%#x", rt) != cont.Root { 332 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 333 | } 334 | case "IndexedAttestation": 335 | dec := &minimalIndexedAttestation{} 336 | if err := ssz.Unmarshal(serialized, dec); err != nil { 337 | t.Fatal(err) 338 | } 339 | enc, err := ssz.Marshal(dec) 340 | if err != nil { 341 | t.Fatal(err) 342 | } 343 | if !bytes.Equal(serialized, enc) { 344 | t.Errorf("Wanted %v, received %v", serialized, enc) 345 | } 346 | rt, err := ssz.HashTreeRoot(dec) 347 | if err != nil { 348 | t.Fatal(err) 349 | } 350 | if fmt.Sprintf("%#x", rt) != cont.Root { 351 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 352 | } 353 | case "PendingAttestation": 354 | dec := &minimalPendingAttestation{} 355 | if err := ssz.Unmarshal(serialized, dec); err != nil { 356 | t.Fatal(err) 357 | } 358 | enc, err := ssz.Marshal(dec) 359 | if err != nil { 360 | t.Fatal(err) 361 | } 362 | if !bytes.Equal(serialized, enc) { 363 | t.Errorf("Wanted %v, received %v", serialized, enc) 364 | } 365 | rt, err := ssz.HashTreeRoot(dec) 366 | if err != nil { 367 | t.Fatal(err) 368 | } 369 | if fmt.Sprintf("%#x", rt) != cont.Root { 370 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 371 | } 372 | case "ProposerSlashing": 373 | dec := &minimalProposerSlashing{} 374 | if err := ssz.Unmarshal(serialized, dec); err != nil { 375 | t.Fatal(err) 376 | } 377 | enc, err := ssz.Marshal(dec) 378 | if err != nil { 379 | t.Fatal(err) 380 | } 381 | if !bytes.Equal(serialized, enc) { 382 | t.Errorf("Wanted %v, received %v", serialized, enc) 383 | } 384 | rt, err := ssz.HashTreeRoot(dec) 385 | if err != nil { 386 | t.Fatal(err) 387 | } 388 | if fmt.Sprintf("%#x", rt) != cont.Root { 389 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 390 | } 391 | case "Validator": 392 | dec := &minimalValidator{} 393 | if err := ssz.Unmarshal(serialized, dec); err != nil { 394 | t.Fatal(err) 395 | } 396 | enc, err := ssz.Marshal(dec) 397 | if err != nil { 398 | t.Fatal(err) 399 | } 400 | if !bytes.Equal(serialized, enc) { 401 | t.Errorf("Wanted %v, received %v", serialized, enc) 402 | } 403 | rt, err := ssz.HashTreeRoot(dec) 404 | if err != nil { 405 | t.Fatal(err) 406 | } 407 | if fmt.Sprintf("%#x", rt) != cont.Root { 408 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 409 | } 410 | case "VoluntaryExit": 411 | dec := &minimalVoluntaryExit{} 412 | if err := ssz.Unmarshal(serialized, dec); err != nil { 413 | t.Fatal(err) 414 | } 415 | enc, err := ssz.Marshal(dec) 416 | if err != nil { 417 | t.Fatal(err) 418 | } 419 | if !bytes.Equal(serialized, enc) { 420 | t.Errorf("Wanted %v, received %v", serialized, enc) 421 | } 422 | rt, err := ssz.HashTreeRoot(dec) 423 | if err != nil { 424 | t.Fatal(err) 425 | } 426 | if fmt.Sprintf("%#x", rt) != cont.Root { 427 | t.Errorf("Wanted %#x, received %#x", cont.Root, rt) 428 | } 429 | default: 430 | t.Error("Case not covered") 431 | } 432 | } 433 | } 434 | }) 435 | } 436 | } 437 | 438 | func testFolders(t *testing.T, config string, folderPath string) ([]os.FileInfo, string) { 439 | testsFolderPath := path.Join("eth2_spec_tests_"+config, "tests", config, "phase0", folderPath) 440 | filepath, err := bazel.Runfile(testsFolderPath) 441 | if err != nil { 442 | t.Fatal(err) 443 | } 444 | testFolders, err := ioutil.ReadDir(filepath) 445 | if err != nil { 446 | t.Fatalf("Failed to read file: %v", err) 447 | } 448 | return testFolders, testsFolderPath 449 | } 450 | 451 | func bazelFileBytes(filePaths ...string) ([]byte, error) { 452 | filepath, err := bazel.Runfile(path.Join(filePaths...)) 453 | if err != nil { 454 | return nil, err 455 | } 456 | fileBytes, err := ioutil.ReadFile(filepath) 457 | if err != nil { 458 | return nil, err 459 | } 460 | return fileBytes, nil 461 | } 462 | -------------------------------------------------------------------------------- /spectests/minimal_types.go: -------------------------------------------------------------------------------- 1 | package spectests 2 | 3 | import ( 4 | "github.com/prysmaticlabs/go-bitfield" 5 | ) 6 | 7 | type minimalFork struct { 8 | PreviousVersion []byte `json:"previous_version" ssz-size:"4"` 9 | CurrentVersion []byte `json:"current_version" ssz-size:"4"` 10 | Epoch uint64 `json:"epoch"` 11 | } 12 | 13 | type minimalCheckpoint struct { 14 | Epoch uint64 `json:"epoch"` 15 | Root []byte `json:"root" ssz-size:"32"` 16 | } 17 | 18 | type minimalValidator struct { 19 | Pubkey []byte `json:"pubkey" ssz-size:"48"` 20 | WithdrawalCredentials []byte `json:"withdrawal_credentials" ssz-size:"32"` 21 | EffectiveBalance uint64 `json:"effective_balance"` 22 | Slashed bool `json:"slashed"` 23 | ActivationEligibilityEpoch uint64 `json:"activation_eligibility_epoch"` 24 | ActivationEpoch uint64 `json:"activation_epoch"` 25 | ExitEpoch uint64 `json:"exit_epoch"` 26 | WithdrawableEpoch uint64 `json:"withdrawable_epoch"` 27 | } 28 | 29 | type minimalAttestationData struct { 30 | Slot uint64 31 | Index uint64 32 | BeaconBlockRoot []byte `json:"beacon_block_root" ssz-size:"32"` 33 | Source minimalCheckpoint `json:"source"` 34 | Target minimalCheckpoint `json:"target"` 35 | } 36 | 37 | type minimalAttestationAndCustodyBit struct { 38 | Data minimalAttestationData `json:"data"` 39 | CustodyBit bool `json:"custody_bit"` 40 | } 41 | 42 | type minimalIndexedAttestation struct { 43 | CustodyBit0Indices []uint64 `json:"custody_bit_0_indices" ssz-max:"2048"` 44 | CustodyBit1Indices []uint64 `json:"custody_bit_1_indices" ssz-max:"2048"` 45 | Data minimalAttestationData `json:"data"` 46 | Signature []byte `json:"signature" ssz-size:"96"` 47 | } 48 | 49 | type minimalPendingAttestation struct { 50 | AggregationBits bitfield.Bitlist `json:"aggregation_bits" ssz-max:"2048"` 51 | Data minimalAttestationData `json:"data"` 52 | InclusionDelay uint64 `json:"inclusion_delay"` 53 | ProposerIndex uint64 `json:"proposer_index"` 54 | } 55 | 56 | type minimalEth1Data struct { 57 | DepositRoot []byte `json:"deposit_root" ssz-size:"32"` 58 | DepositCount uint64 `json:"deposit_count"` 59 | BlockHash []byte `json:"block_hash" ssz-size:"32"` 60 | } 61 | 62 | type minimalHistoricalBatch struct { 63 | BlockRoots [][]byte `json:"block_roots" ssz-size:"64,32"` 64 | StateRoots [][]byte `json:"state_roots" ssz-size:"64,32"` 65 | } 66 | 67 | type minimalDepositData struct { 68 | Pubkey []byte `json:"pubkey" ssz-size:"48"` 69 | WithdrawalCredentials []byte `json:"withdrawal_credentials" ssz-size:"32"` 70 | Amount uint64 `json:"amount"` 71 | Signature []byte `json:"signature" ssz-size:"96"` 72 | } 73 | 74 | type minimalBlockHeader struct { 75 | Slot uint64 `json:"slot"` 76 | ParentRoot []byte `json:"parent_root" ssz-size:"32"` 77 | StateRoot []byte `json:"state_root" ssz-size:"32"` 78 | BodyRoot []byte `json:"body_root" ssz-size:"32"` 79 | Signature []byte `json:"signature" ssz-size:"96"` 80 | } 81 | 82 | type minimalProposerSlashing struct { 83 | ProposerIndex uint64 `json:"proposer_index"` 84 | Header1 minimalBlockHeader `json:"header_1"` 85 | Header2 minimalBlockHeader `json:"header_2"` 86 | } 87 | 88 | type minimalAttesterSlashing struct { 89 | Attestation1 minimalIndexedAttestation `json:"attestation_1"` 90 | Attestation2 minimalIndexedAttestation `json:"attestation_2"` 91 | } 92 | 93 | type minimalAttestation struct { 94 | AggregationBits bitfield.Bitlist `json:"aggregation_bits" ssz-max:"2048"` 95 | Data minimalAttestationData `json:"data"` 96 | CustodyBits bitfield.Bitlist `json:"custody_bits" ssz-max:"2048"` 97 | Signature []byte `json:"signature" ssz-size:"96"` 98 | } 99 | 100 | type minimalDeposit struct { 101 | Proof [][]byte `json:"proof" ssz-size:"33,32"` 102 | Data minimalDepositData `json:"data"` 103 | } 104 | 105 | type minimalVoluntaryExit struct { 106 | Epoch uint64 `json:"epoch"` 107 | ValidatorIndex uint64 `json:"validator_index"` 108 | Signature []byte `json:"signature" ssz-size:"96"` 109 | } 110 | 111 | type minimalBlockBody struct { 112 | RandaoReveal []byte `json:"randao_reveal" ssz-size:"96"` 113 | Eth1Data minimalEth1Data `json:"eth1_data"` 114 | Graffiti []byte `json:"graffiti" ssz-size:"32"` 115 | ProposerSlashings []minimalProposerSlashing `json:"proposer_slashings" ssz-max:"16"` 116 | AttesterSlashings []minimalAttesterSlashing `json:"attester_slashings" ssz-max:"1"` 117 | Attestations []minimalAttestation `json:"attestations" ssz-max:"128"` 118 | Deposits []minimalDeposit `json:"deposits" ssz-max:"16"` 119 | VoluntaryExits []minimalVoluntaryExit `json:"voluntary_exits" ssz-max:"16"` 120 | } 121 | 122 | type minimalBlock struct { 123 | Slot uint64 `json:"slot"` 124 | ParentRoot []byte `json:"parent_root" ssz-size:"32"` 125 | StateRoot []byte `json:"state_root" ssz-size:"32"` 126 | Body minimalBlockBody `json:"body"` 127 | Signature []byte `json:"signature" ssz-size:"96"` 128 | } 129 | 130 | type minimalAggregateAndProof struct { 131 | Index uint64 132 | SelectionProof []byte `ssz-size:"96"` 133 | Aggregate minimalAttestation 134 | } 135 | 136 | type minimalBeaconState struct { 137 | GenesisTime uint64 `json:"genesis_time"` 138 | Slot uint64 `json:"slot"` 139 | Fork minimalFork `json:"fork"` 140 | LatestBlockHeader minimalBlockHeader `json:"latest_block_header"` 141 | BlockRoots [][]byte `json:"block_roots" ssz-size:"64,32"` 142 | StateRoots [][]byte `json:"state_roots" ssz-size:"64,32"` 143 | HistoricalRoots [][]byte `json:"historical_roots" ssz-size:"?,32" ssz-max:"16777216"` 144 | Eth1Data minimalEth1Data `json:"eth1_data"` 145 | Eth1DataVotes []minimalEth1Data `json:"eth1_data_votes" ssz-max:"16"` 146 | Eth1DepositIndex uint64 `json:"eth1_deposit_index"` 147 | Validators []minimalValidator `json:"validators" ssz-max:"1099511627776"` 148 | Balances []uint64 `json:"balances" ssz-max:"1099511627776"` 149 | RandaoMixes [][]byte `json:"randao_mixes" ssz-size:"64,32"` 150 | Slashings []uint64 `json:"slashings" ssz-size:"64"` 151 | 152 | PreviousEpochAttestations []minimalPendingAttestation `json:"previous_epoch_attestations" ssz-max:"1024"` 153 | CurrentEpochAttestations []minimalPendingAttestation `json:"current_epoch_attestations" ssz-max:"1024"` 154 | JustificationBits bitfield.Bitvector4 `json:"justification_bits" ssz-size:"1"` 155 | 156 | PreviousJustifiedCheckpoint minimalCheckpoint `json:"previous_justified_checkpoint"` 157 | CurrentJustifiedCheckpoint minimalCheckpoint `json:"current_justified_checkpoint"` 158 | FinalizedCheckpoint minimalCheckpoint `json:"finalized_checkpoint"` 159 | } 160 | -------------------------------------------------------------------------------- /spectests/yaml/ssz_single_block.yaml: -------------------------------------------------------------------------------- 1 | value: 2 | slot: 6981174820758748693 3 | parent_root: Av4z5i9b5pZHdmj/x+4FDjNN1OH/1j9lrw9Y1wFxWqA= 4 | state_root: wDiWF2TBTrNnqAJjf5TNTWgC0d4LOnCfckwgnd2++P4= 5 | body: 6 | randao_reveal: k1JLbA/qVShDysEFvn3tKmXl1EwrNONzYlRl4UD7msUtyC6YLcWi7PnLuNkM6W+koz2HlEvrSu0SaZ37K9rmAA05JSTpMtFbLjE7g+xW9dxSsJUYsMYJ10D4p+qzJOvQ 7 | eth1_data: {deposit_root: QL9elyLNptb40boLC5utruJ2iyaUTKhyfXAUyw2R2HU=, 8 | deposit_count: 1739304199635837596, block_hash: 2NPQk4XyJfr+1RYlabxv5rnrixR/PxV5GaDiqDGjWD4=} 9 | graffiti: bWrNAHGuSN6qIXwajjfb7QxMfYtDCqHu7u7Wd5yKlc8= 10 | proposer_slashings: 11 | - proposer_index: 10491711507982893100 12 | header_1: {slot: 11745773551599411766, parent_root: 7qlgGYq1fA62GDFsvSZ9tc5NucO6f29QHTYN4/xZDGU=, 13 | state_root: /GSlEiZDobpc1jb55yIGYrAmc3pth6RHTkwrzLs8qFg=, 14 | body_root: Bc7GYiBm4GfieBuCYI+q3iUs2mFTaH/I94hOuttmJ9I=, 15 | signature: MMW+ro+MbLQ9duxuUXpEACDsazbjLXFLjq/OaGjgq/Grc3UBbvtejnJtuWqnv0iodOnFOSWkB1D/WSMeRRsAn/Kpl6dSAcQHXp6aNmhQCDy1CPBoUpvErpwMT+FVvfZR} 16 | header_2: {slot: 1771141903768822944, parent_root: XdHtfhB7HdSzRNXs1bhZ9fX0I89XOIaBDmKdEG0l1s0=, 17 | state_root: 1JFLVXEK72L8ayhnEgI/jDKyZZT4UA6bUtfY8TdifrQ=, 18 | body_root: xzvskpgMxHHaZuAqpqGBfa6yEVkz4Xk6w7f9+AJoCEc=, 19 | signature: y37k0ved9Fim2HHF3zwLST8CPYZSsZ8Y9nHyY/ZpwsM6YSqWj2ZD7XQsmATqA8StT3rXyvK1Nx/w39GOVPwQGRLcJCVpui3aNvo97MgWJghRpGrBwQeXLha01ww2PRyx} 20 | attester_slashings: 21 | - attestation_1: 22 | custody_bit_0_indices: [4522934148937007460] 23 | custody_bit_1_indices: [3284793873573324765] 24 | data: 25 | beacon_block_root: ubYrgaN0xmoGf4jz8fuKcpli3r1AcOUVxZJcFvIwgGw= 26 | source: {epoch: 3076719062453694009, root: ukk7IJKVvLI2AvmahtZT6RIt3sCwVThEFLJvM31h20U=} 27 | target: {epoch: 3688887332436095263, root: TLA9y7vWR0jASgoikWWVfzTOOSiYxVR6tTUyxmlcLIc=} 28 | crosslink: {shard: 15361810764659897227, parent_root: VZUWemD81Z5SOSUSSXZWrYCV1SymBvMQCHsMZ5AaZnI=, 29 | start_epoch: 2600108251232260882, end_epoch: 5057779758418103775, 30 | data_root: VszBmj/seosLXJ+1qqQUXpQsCwUeKVzmfBG3mQ9xsys=} 31 | signature: yaelwvArUvBXkV83JEwAnEMgDkbCHFrIycEPTDCfoAE2vz1RjWamDrJwITs1Dbvl9wYHPkak8TXh6IkTjXJDE4EMS4xGwqX4vb+sOlxIUHtficPQWgRRK58x5ouyHamh 32 | attestation_2: 33 | custody_bit_0_indices: [7698010879847963740] 34 | custody_bit_1_indices: [16269162465403092080] 35 | data: 36 | beacon_block_root: uHYcBwO/N/S+5XVBP7n9mb3ifrX8Gs2wVefQKHsGhLc= 37 | source: {epoch: 16512270676997886659, root: ckGhEIWewtQqN4gqnymZrjhNtvdWfg3Mf64QnTePDHM=} 38 | target: {epoch: 4360385981513172958, root: m8hgzPi+ZzFWjhTnwFptvGKwb7qrHIrRjrXyq4rUjeg=} 39 | crosslink: {shard: 16880642434757401023, parent_root: lzWJW5/rQ83eSN4iGuCo1jnuUjODA64c34Z0AvQShdQ=, 40 | start_epoch: 6086886729897112077, end_epoch: 5153955649621857687, 41 | data_root: tjFtYs6riR7iVe7Gu/h+zcL85P0ptaIOEB2fvx2woDw=} 42 | signature: 2AfhuPWUafJc15NZI0FTQ0AJwSyYW+wa2q8q/Cn2eKx9Yz3OfOSM7L15SiqCJfai5L+nh+mguX+xLXjJysw3Op+HxX9GSg4KUI7W2HsRIpq1R5Y7oMfFs9gltaKYGBaZ 43 | attestations: 44 | - aggregation_bits: Aw== 45 | data: 46 | beacon_block_root: R71e89XX55/oj1vO3tgwFb1HWmKEJ8dgKDS6OOZjyBk= 47 | source: {epoch: 13721795736203734179, root: hwWw190XaWMLkecDmAvuFWAlyAo7KjDlX0dtQ4XaBuw=} 48 | target: {epoch: 1105260563491864537, root: YaKMeUZnIXBFuGuzCSsuSvMZOPRJdjqla50LYdPLoso=} 49 | crosslink: {shard: 669857773712077917, parent_root: uOgcZGyvQ8hc/uZmo308CN9bwrz9oF/yMMckyz7DDxw=, 50 | start_epoch: 3947066976140479750, end_epoch: 7325654853870846113, data_root: qxxeVfbP+zYtaD/hoGtKIFtFSm5AtRMrY6oxil1zvLo=} 51 | custody_bits: Ag== 52 | signature: KXhNRfDxSk0IK8Kjl17k8HsGkEuikqGl2za8dYu4uhTeHoQ76MQIH6JVX7qnD0sNKgBl79fx3zn9ADOVU1tZs/zHVzr3B8jl7DsClD3NKRlkdyp0sAFs7DsCGOUOSNwY 53 | deposits: 54 | - proof: [FDvjDrkixWuL0VFs8FCYGSzZy+4uuA6w5O5j8gqBkc0=, 55 | UTizDbQitYvR12/qtWW/ruNrCi/Sv8OzLjjV7E6SQOA=, 56 | JuRv6k458N5q/QONDCFukBOqs9tsttKx76BDmdKxsYs=, 57 | JFhVpMo/s3VWwTYFQSmuu8Luqto/DRPkOcsm4qo4ew8=, 58 | ufClWiOqkoc3t9G2QPEpbPcfgpLAOTe7IQ8MHNNcF9I=, 59 | FeF4j1BcXafXf9mM+M6pvB8Id3avQSM9SF0LBGJKTnk=, 60 | keEn1Pq3uOg1/LSrxxK13hGG1FggjOKyNMFKSELVpKk=, 61 | Jhwer7UyYCGZkAFEoI2WfHPzZ72QvgdXQUAGuZA80Wc=, 62 | sefl1/Z9xuRxyO5FZ3KWcSIT3nGaWvhskwyPGYJ+mlI=, 63 | ad++2Wadd2WDrveLbEjS+oYa0Iaok7GK62RzbNzjGOs=, 64 | +NWaLGFUin4uSvJrvJDJDHjDy0XnRJAuAaJ2pNe63+4=, 65 | 469Ri9g+hfT1ux91I2pt+hXUFP4Nb4Zdvm5RlB6d8Qo=, 66 | fjYw4MW9x/PUQC5p9tmhE4UdR/Hu4ss+ZxfV2eSNSUI=, 67 | 8Bc2bXisB5hF4pZNV6pJamIGqZAtisUlqY35ITWCtp4=, 68 | fPExmozgH8e2mGClaaohGGaVK3Yjva2uhMQ7Lul2u8Y=, 69 | 9TJJYuCk3AbO7C9A14gVNmHz+RvLjpFR4LJQzxeGDog=, 70 | 8u/z4u0SXtxoOLbLXzxQtOSZhUPPbtdvRbdClp8bVMs=, 71 | 70YhpOK5hMAcQGX1wFW4Tcc31rSlDBA4QkC7TpBtc9o=, 72 | iufC+0mqs5MsrTKEoL9Q+ITGyXj99cc01X0CFGQSf6M=, 73 | Lr96IgDw4qU3az245XCqk10UaZAW8TAtS15NXy9uRlE=, 74 | 0sSdFgWkQEBGROXlmP9ZeAJZAt/jCEqYpwdYAAqbXvU=, 75 | 1ka9UHfOhhnxapboX6MIXtaM1CbG8GLKtF9LW/CCuK8=, 76 | +GPGiIYDp5O/aYOfDy+JGVKV8ovCvPA/mRkWLm59Nwo=, 77 | nyx4sR1lxbc9BUqe8KoW596yPjD7vWw0St4BEBDNWbM=, 78 | u1DJ3qtbZnj7YbRYM+3EytYlyD7KEr9Nb56F3CJwXzw=, 79 | nFL6p3M89ulUKFdfbMSgfpV/GloBA/jtppZKpwhH4s0=, 80 | 8pBSZWSsAJ7RH/UaHOoPVXpZOTEGuRG5RF0JqqnRm4E=, 81 | +CFRL44WTVQLDrt1OIvikuv4DtnusvbrvrVXgc2r/WQ=, 82 | dpe2pNfJOBCH73mtSBt7BZGK3DtG9Sl1y7bymZ6/1Ts=, 83 | XUydqLUWjUCz66JY5M5Z7Iu4rcTORh4bjIdgDiPWZqo=, 84 | e1Yx9IucBShlOE3xGMAgqHBkuoBG83idmsBTk20U7EA=, 85 | S+QiHLXu8R++MMHUMn/BQa3mOPnad/mY+JXjn+9m2Po=, 86 | 8gRKFx5uHtfXO2vktd9yqNTHxgBTjgeYwyBJRqBo+Lw=] 87 | data: {pubkey: 1NHh6h58YlXlpRYHl5sbv5CLAtzOD+yHXwRLY1rCZQ4h3HztUhbQ7mpXw0IyJerE, 88 | withdrawal_credentials: tAXfe1vN6nXCTtmPssAvdiU/0s7X8CHdnMJHMK6338s=, 89 | amount: 5303574477318665474, signature: ySTEsEh274H4gYpZgiu8XaakDrRyu/U5j88UX0VokqopLpzY0Zigz4DsEo12sgG/pb7iGgoyxNTxLsPJk+55nL8SHGmyW0wyiHeDN2F/SVTj0x7w1G345hhsCceweoRq} 90 | voluntary_exits: 91 | - {epoch: 18061400715763591556, validator_index: 456063649398159015, signature: XxS+aV7QDRqFfwng+CFUP8USLclR0oNv+vpdI8sNBvCvAY6rEqziWKrXO0qhFo1jzAvX1dSIlGnGFmcx84cdzjFaLOdmlIBccqqRiA0HB0LusVJw3Wd2dA3+6N/Xtr9l} 92 | transfers: [] 93 | signature: N6kvsED1oU677nbrALmxvSwKjYcZ5jO79FCc86r5CMVZ+gwDLOlHaXKIejdU3mPop5kio5vves0YqmWMST0TdPds6IZJMzazwO2JhjAi/8f+r5bOcFMr9Ff0QERU1VC3 94 | serialized: Ff45+oId4mAC/jPmL1vmlkd2aP/H7gUOM03U4f/WP2WvD1jXAXFaoMA4lhdkwU6zZ6gCY3+UzU1oAtHeCzpwn3JMIJ3dvvj+rAAAADepL7BA9aFOu+526wC5sb0sCo2HGeYzu/RQnPOq+QjFWfoMAyzpR2lyiHo3VN5j6KeZIqOb73rNGKpljEk9E3T3bOiGSTM2s8DtiYYwIv/H/q+WznBTK/RX9EBEVNVQt5NSS2wP6lUoQ8rBBb597Spl5dRMKzTjc2JUZeFA+5rFLcgumC3Fouz5y7jZDOlvpKM9h5RL60rtEmmd+yva5gANOSUk6TLRWy4xO4PsVvXcUrCVGLDGCddA+KfqsyTr0EC/XpcizabW+NG6Cwubra7idosmlEyocn1wFMsNkdh1nKKjT/Q/IxjY09CThfIl+v7VFiVpvG/mueuLFH8/FXkZoOKoMaNYPm1qzQBxrkjeqiF8Go432+0MTH2LQwqh7u7u1necipXP4AAAAHgCAAAEBQAAOgYAABILAACCCwAALMAybwcMmpE24kI70F4Bo+6pYBmKtXwOthgxbL0mfbXOTbnDun9vUB02DeP8WQxl/GSlEiZDobpc1jb55yIGYrAmc3pth6RHTkwrzLs8qFgFzsZiIGbgZ+J4G4Jgj6reJSzaYVNof8j3iE6622Yn0jDFvq6PjGy0PXbsblF6RAAg7Gs24y1xS46vzmho4Kvxq3N1AW77Xo5ybblqp79IqHTpxTklpAdQ/1kjHkUbAJ/yqZenUgHEB16emjZoUAg8tQjwaFKbxK6cDE/hVb32UaAk83QtXJQYXdHtfhB7HdSzRNXs1bhZ9fX0I89XOIaBDmKdEG0l1s3UkUtVcQrvYvxrKGcSAj+MMrJllPhQDptS19jxN2J+tMc77JKYDMRx2mbgKqahgX2ushFZM+F5OsO3/fgCaAhHy37k0ved9Fim2HHF3zwLST8CPYZSsZ8Y9nHyY/ZpwsM6YSqWj2ZD7XQsmATqA8StT3rXyvK1Nx/w39GOVPwQGRLcJCVpui3aNvo97MgWJghRpGrBwQeXLha01ww2PRyxBAAAAAgAAABIAQAAMAEAADgBAAC5tiuBo3TGagZ/iPPx+4pymWLevUBw5RXFklwW8jCAbDmuOaevs7Iqukk7IJKVvLI2AvmahtZT6RIt3sCwVThEFLJvM31h20UfsUr6e48xM0ywPcu71kdIwEoKIpFllX80zjkomMVUerU1MsZpXCyHizNTBoAdMNVVlRZ6YPzVnlI5JRJJdlatgJXVLKYG8xAIewxnkBpmchLTQrStcBUk3w2snOTXMEZWzMGaP+x6iwtcn7WqpBRelCwLBR4pXOZ8EbeZD3GzK8mnpcLwK1LwV5FfNyRMAJxDIA5GwhxayMnBD0wwn6ABNr89UY1mpg6ycCE7NQ275fcGBz5GpPE14eiJE41yQxOBDEuMRsKl+L2/rDpcSFB7X4nD0FoEUSufMeaLsh2poWTB3xmlsMQ+3ae32p/ulS0wAQAAOAEAALh2HAcDvzf0vuV1QT+5/Zm94n61/BrNsFXn0Ch7BoS3w9KO6bFeJ+VyQaEQhZ7C1Co3iCqfKZmuOE2291Z+Dcx/rhCdN48Mc96Hr6fyM4M8m8hgzPi+ZzFWjhTnwFptvGKwb7qrHIrRjrXyq4rUjei/mRlg2xZE6pc1iVuf60PN3kjeIhrgqNY57lIzgwOuHN+GdAL0EoXUDXKOKzv3eFSXFdRDW4eGR7YxbWLOq4ke4lXuxrv4fs3C/OT9KbWiDhAdn78dsKA82AfhuPWUafJc15NZI0FTQ0AJwSyYW+wa2q8q/Cn2eKx9Yz3OfOSM7L15SiqCJfai5L+nh+mguX+xLXjJysw3Op+HxX9GSg4KUI7W2HsRIpq1R5Y7oMfFs9gltaKYGBaZXBxuAhzU1GpwPHiHEK3H4QQAAAAwAQAAR71e89XX55/oj1vO3tgwFb1HWmKEJ8dgKDS6OOZjyBmjrORYhZxtvocFsNfdF2ljC5HnA5gL7hVgJcgKOyow5V9HbUOF2gbs2T8uZ6CsVg9hoox5RmchcEW4a7MJKy5K8xk49El2OqVrnQth08uiyl10JJwY0EsJuOgcZGyvQ8hc/uZmo308CN9bwrz9oF/yMMckyz7DDxwGTYeZgMzGNqGAFtBG9KllqxxeVfbP+zYtaD/hoGtKIFtFSm5AtRMrY6oxil1zvLoxAQAAKXhNRfDxSk0IK8Kjl17k8HsGkEuikqGl2za8dYu4uhTeHoQ76MQIH6JVX7qnD0sNKgBl79fx3zn9ADOVU1tZs/zHVzr3B8jl7DsClD3NKRlkdyp0sAFs7DsCGOUOSNwYAwIUO+MOuSLFa4vRUWzwUJgZLNnL7i64DrDk7mPyCoGRzVE4sw20IrWL0ddv6rVlv67jawov0r/Dsy441exOkkDgJuRv6k458N5q/QONDCFukBOqs9tsttKx76BDmdKxsYskWFWkyj+zdVbBNgVBKa67wu6q2j8NE+Q5yybiqjh7D7nwpVojqpKHN7fRtkDxKWz3H4KSwDk3uyEPDBzTXBfSFeF4j1BcXafXf9mM+M6pvB8Id3avQSM9SF0LBGJKTnmR4SfU+re46DX8tKvHErXeEYbUWCCM4rI0wUpIQtWkqSYcHq+1MmAhmZABRKCNlnxz82e9kL4HV0FABrmQPNFnsefl1/Z9xuRxyO5FZ3KWcSIT3nGaWvhskwyPGYJ+mlJp377ZZp13ZYOu94tsSNL6hhrQhqiTsYrrZHNs3OMY6/jVmixhVIp+Lkrya7yQyQx4w8tF50SQLgGidqTXut/u469Ri9g+hfT1ux91I2pt+hXUFP4Nb4Zdvm5RlB6d8Qp+NjDgxb3H89RALmn22aEThR1H8e7iyz5nF9XZ5I1JQvAXNm14rAeYReKWTVeqSWpiBqmQLYrFJamN+SE1graefPExmozgH8e2mGClaaohGGaVK3Yjva2uhMQ7Lul2u8b1Mkli4KTcBs7sL0DXiBU2YfP5G8uOkVHgslDPF4YOiPLv8+LtEl7caDi2y188ULTkmYVDz27Xb0W3QpafG1TL70YhpOK5hMAcQGX1wFW4Tcc31rSlDBA4QkC7TpBtc9qK58L7SaqzkyytMoSgv1D4hMbJeP31xzTVfQIUZBJ/oy6/eiIA8OKlN2s9uOVwqpNdFGmQFvEwLUteTV8vbkZR0sSdFgWkQEBGROXlmP9ZeAJZAt/jCEqYpwdYAAqbXvXWRr1Qd86GGfFqluhfowhe1ozUJsbwYsq0X0tb8IK4r/hjxoiGA6eTv2mDnw8viRlSlfKLwrzwP5kZFi5ufTcKnyx4sR1lxbc9BUqe8KoW596yPjD7vWw0St4BEBDNWbO7UMneq1tmePthtFgz7cTK1iXIPsoSv01vnoXcInBfPJxS+qdzPPbpVChXX2zEoH6VfxpaAQP47aaWSqcIR+LN8pBSZWSsAJ7RH/UaHOoPVXpZOTEGuRG5RF0JqqnRm4H4IVEvjhZNVAsOu3U4i+KS6/gO2e6y9uu+tVeBzav9ZHaXtqTXyTgQh+95rUgbewWRitw7RvUpdcu28pmev9U7XUydqLUWjUCz66JY5M5Z7Iu4rcTORh4bjIdgDiPWZqp7VjH0i5wFKGU4TfEYwCCocGS6gEbzeJ2awFOTbRTsQEvkIhy17vEfvjDB1DJ/wUGt5jj52nf5mPiV45/vZtj68gRKFx5uHtfXO2vktd9yqNTHxgBTjgeYwyBJRqBo+LzU0eHqHnxiVeWlFgeXmxu/kIsC3M4P7IdfBEtjWsJlDiHcfO1SFtDualfDQjIl6sS0Bd97W83qdcJO2Y+ywC92JT/SztfwId2cwkcwrrffywI1aDjjFJpJySTEsEh274H4gYpZgiu8XaakDrRyu/U5j88UX0VokqopLpzY0Zigz4DsEo12sgG/pb7iGgoyxNTxLsPJk+55nL8SHGmyW0wyiHeDN2F/SVTj0x7w1G345hhsCceweoRqhCkT8UH8pvqndlgJeUNUBl8Uvmle0A0ahX8J4PghVD/FEi3JUdKDb/r6XSPLDQbwrwGOqxKs4liq1ztKoRaNY8wL19XUiJRpxhZnMfOHHc4xWiznZpSAXHKqkYgNBwdC7rFScN1ndnQN/ujf17a/ZQ== 95 | root: y9uDHuyMFEP/XTRErqPd0oWJxbcFtIun7yNZ0ggctmo= 96 | signing_root: gMgwT19nvV06RITnHQW7fxCmPWv+rLrTrEcvceqfLjA= -------------------------------------------------------------------------------- /ssz.go: -------------------------------------------------------------------------------- 1 | package ssz 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | fssz "github.com/ferranbt/fastssz" 9 | "github.com/pkg/errors" 10 | "github.com/prysmaticlabs/go-bitfield" 11 | "github.com/prysmaticlabs/go-ssz/types" 12 | ) 13 | 14 | // Marshal a value and output the result into a byte slice. 15 | // Given a struct with the following fields, one can marshal it as follows: 16 | // type exampleStruct struct { 17 | // Field1 uint8 18 | // Field2 []byte 19 | // } 20 | // 21 | // ex := exampleStruct{ 22 | // Field1: 10, 23 | // Field2: []byte{1, 2, 3, 4}, 24 | // } 25 | // encoded, err := Marshal(ex) 26 | // if err != nil { 27 | // return fmt.Errorf("failed to marshal: %v", err) 28 | // } 29 | // 30 | // One can also specify the specific size of a struct's field by using 31 | // ssz-specific field tags as follows: 32 | // 33 | // type exampleStruct struct { 34 | // Field1 uint8 35 | // Field2 []byte `ssz:"size=32"` 36 | // } 37 | // 38 | // This will treat `Field2` as as [32]byte array when marshaling. For unbounded 39 | // fields or multidimensional slices, ssz size tags can also be used as follows: 40 | // 41 | // type exampleStruct struct { 42 | // Field1 uint8 43 | // Field2 [][]byte `ssz:"size=?,32"` 44 | // } 45 | // 46 | // This will treat `Field2` as type [][32]byte when marshaling a 47 | // struct of that type. 48 | func Marshal(val interface{}) ([]byte, error) { 49 | if val == nil { 50 | return nil, errors.New("untyped-value nil cannot be marshaled") 51 | } 52 | 53 | if v, ok := val.(fssz.Marshaler); ok { 54 | return v.MarshalSSZ() 55 | } 56 | 57 | rval := reflect.ValueOf(val) 58 | 59 | // We pre-allocate a buffer-size depending on the value's calculated total byte size. 60 | buf := make([]byte, types.DetermineSize(rval)) 61 | factory, err := types.SSZFactory(rval, rval.Type()) 62 | if err != nil { 63 | return nil, err 64 | } 65 | if rval.Type().Kind() == reflect.Ptr { 66 | if rval.IsNil() { 67 | return buf, nil 68 | } 69 | if _, err := factory.Marshal(rval.Elem(), rval.Type().Elem(), buf, 0 /* start offset */); err != nil { 70 | return nil, errors.Wrapf(err, "failed to marshal for type: %v", rval.Type().Elem()) 71 | } 72 | return buf, nil 73 | } 74 | if _, err := factory.Marshal(rval, rval.Type(), buf, 0 /* start offset */); err != nil { 75 | return nil, errors.Wrapf(err, "failed to marshal for type: %v", rval.Type()) 76 | } 77 | return buf, nil 78 | } 79 | 80 | // Unmarshal SSZ encoded data and output it into the object pointed by pointer val. 81 | // Given a struct with the following fields, and some encoded bytes of type []byte, 82 | // one can then unmarshal the bytes into a pointer of the struct as follows: 83 | // type exampleStruct1 struct { 84 | // Field1 uint8 85 | // Field2 []byte 86 | // } 87 | // 88 | // var targetStruct exampleStruct1 89 | // if err := Unmarshal(encodedBytes, &targetStruct); err != nil { 90 | // return fmt.Errorf("failed to unmarshal: %v", err) 91 | // } 92 | func Unmarshal(input []byte, val interface{}) error { 93 | if val == nil { 94 | return errors.New("cannot unmarshal into untyped, nil value") 95 | } 96 | if v, ok := val.(fssz.Unmarshaler); ok { 97 | return v.UnmarshalSSZ(input) 98 | } 99 | if len(input) == 0 { 100 | return errors.New("no data to unmarshal from, input is an empty byte slice []byte{}") 101 | } 102 | rval := reflect.ValueOf(val) 103 | rtyp := rval.Type() 104 | // val must be a pointer, otherwise we refuse to unmarshal 105 | if rtyp.Kind() != reflect.Ptr { 106 | return errors.New("can only unmarshal into a pointer target") 107 | } 108 | if rval.IsNil() { 109 | return errors.New("cannot output to pointer of nil value") 110 | } 111 | factory, err := types.SSZFactory(rval.Elem(), rtyp.Elem()) 112 | if err != nil { 113 | return err 114 | } 115 | if _, err := factory.Unmarshal(rval.Elem(), rval.Elem().Type(), input, 0); err != nil { 116 | return errors.Wrapf(err, "could not unmarshal input into type: %v", rval.Elem().Type()) 117 | } 118 | 119 | fixedSize := types.DetermineSize(rval) 120 | totalLength := uint64(len(input)) 121 | if totalLength != fixedSize { 122 | return fmt.Errorf( 123 | "unexpected amount of data, expected: %d, received: %d", 124 | fixedSize, 125 | totalLength, 126 | ) 127 | } 128 | return nil 129 | } 130 | 131 | // HashTreeRoot determines the root hash using SSZ's Merkleization. 132 | // Given a struct with the following fields, one can tree hash it as follows: 133 | // type exampleStruct struct { 134 | // Field1 uint8 135 | // Field2 []byte 136 | // } 137 | // 138 | // ex := exampleStruct{ 139 | // Field1: 10, 140 | // Field2: []byte{1, 2, 3, 4}, 141 | // } 142 | // root, err := HashTreeRoot(ex) 143 | // if err != nil { 144 | // return errors.Wrap(err, "failed to compute root") 145 | // } 146 | func HashTreeRoot(val interface{}) ([32]byte, error) { 147 | if val == nil { 148 | return [32]byte{}, errors.New("untyped nil is not supported") 149 | } 150 | rval := reflect.ValueOf(val) 151 | factory, err := types.SSZFactory(rval, rval.Type()) 152 | if err != nil { 153 | return [32]byte{}, errors.Wrapf(err, "could not generate tree hasher for type: %v", rval.Type()) 154 | } 155 | return factory.Root(rval, rval.Type(), "", 0) 156 | } 157 | 158 | // HashTreeRootBitfield determines the root hash of a bitfield type using SSZ's Merkleization. 159 | func HashTreeRootBitfield(bfield bitfield.Bitfield, maxCapacity uint64) ([32]byte, error) { 160 | if b, ok := bfield.(bitfield.Bitvector4); ok { 161 | return types.Bitvector4Root(b, 4) 162 | } 163 | return types.BitlistRoot(bfield, maxCapacity) 164 | } 165 | 166 | // HashTreeRootWithCapacity determines the root hash of a dynamic list 167 | // using SSZ's Merkleization and applies a max capacity value when computing the root. 168 | // If the input is not a slice, the function returns an error. 169 | // 170 | // accountBalances := []uint64{1, 2, 3, 4} 171 | // root, err := HashTreeRootWithCapacity(accountBalances, 100) // Max 100 accounts. 172 | // if err != nil { 173 | // return errors.Wrap(err, "failed to compute root") 174 | // } 175 | func HashTreeRootWithCapacity(val interface{}, maxCapacity uint64) ([32]byte, error) { 176 | if val == nil { 177 | return [32]byte{}, errors.New("untyped nil is not supported") 178 | } 179 | rval := reflect.ValueOf(val) 180 | if rval.Kind() != reflect.Slice { 181 | return [32]byte{}, fmt.Errorf("expected slice-kind input, received %v", rval.Kind()) 182 | } 183 | factory, err := types.SSZFactory(rval, rval.Type()) 184 | if err != nil { 185 | return [32]byte{}, errors.Wrapf(err, "could not generate tree hasher for type: %v", rval.Type()) 186 | } 187 | return factory.Root(rval, rval.Type(), "", maxCapacity) 188 | } 189 | 190 | // SigningRoot truncates the last property of the struct passed in 191 | // and returns its tree hash. This is done because the last property 192 | // usually contains the signature that which this data is the root for. 193 | // 194 | // Deprecated: Prefer signed container objects rather than using signing root. 195 | func SigningRoot(val interface{}) ([32]byte, error) { 196 | if val == nil { 197 | return [32]byte{}, errors.New("value cannot be nil") 198 | } 199 | valObj := reflect.ValueOf(val) 200 | if valObj.Type().Kind() == reflect.Ptr { 201 | if valObj.IsNil() { 202 | return [32]byte{}, errors.New("nil pointer given") 203 | } 204 | elem := valObj.Elem() 205 | elemType := valObj.Elem().Type() 206 | totalFields := 0 207 | for i := 0; i < elemType.NumField(); i++ { 208 | // We skip protobuf related metadata fields. 209 | if strings.Contains(elemType.Field(i).Name, "XXX_") { 210 | continue 211 | } 212 | totalFields++ 213 | } 214 | return types.StructFactory.FieldsHasher(elem, elemType, totalFields-1) 215 | } 216 | totalFields := 0 217 | for i := 0; i < valObj.Type().NumField(); i++ { 218 | // We skip protobuf related metadata fields. 219 | if strings.Contains(valObj.Type().Field(i).Name, "XXX_") { 220 | continue 221 | } 222 | totalFields++ 223 | } 224 | return types.StructFactory.FieldsHasher(valObj, valObj.Type(), totalFields-1) 225 | } 226 | -------------------------------------------------------------------------------- /ssz_test.go: -------------------------------------------------------------------------------- 1 | package ssz 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "reflect" 7 | "strconv" 8 | "sync" 9 | "testing" 10 | 11 | "github.com/pkg/errors" 12 | "github.com/prysmaticlabs/go-bitfield" 13 | "github.com/prysmaticlabs/go-ssz/types" 14 | ) 15 | 16 | type beaconState struct { 17 | BlockRoots [][]byte `ssz-size:"65536,32"` 18 | } 19 | 20 | type fork struct { 21 | PreviousVersion [4]byte 22 | CurrentVersion [4]byte 23 | Epoch uint64 24 | } 25 | 26 | type truncateSignatureCase struct { 27 | Slot uint64 28 | PreviousBlockRoot []byte 29 | Signature []byte 30 | } 31 | 32 | type simpleNonProtoMessage struct { 33 | Foo []byte 34 | Bar uint64 35 | } 36 | 37 | func TestNilElementMarshal(t *testing.T) { 38 | type ex struct{} 39 | var item *ex 40 | buf, err := Marshal(item) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | if !bytes.Equal(buf, []byte{}) { 45 | t.Errorf("Wanted empty byte slice, got %v", buf) 46 | } 47 | } 48 | 49 | func TestNilElementDetermineSize(t *testing.T) { 50 | type ex struct{} 51 | var item *ex 52 | size := types.DetermineSize(reflect.ValueOf(item)) 53 | if size != 0 { 54 | t.Errorf("Wanted size 0, received %d", size) 55 | } 56 | } 57 | 58 | // This test verifies if a nil pseudo-array is treated the same as an instantiated, 59 | // zero-valued array when running hash tree root computations. 60 | func TestEmptyArrayInstantiation(t *testing.T) { 61 | type data struct { 62 | DepositRoot []byte `ssz-size:"32"` 63 | DepositCount uint64 64 | BlockHash []byte `ssz-size:"32"` 65 | } 66 | type example struct { 67 | Randao []byte `ssz-size:"96"` 68 | Data *data 69 | Graffiti []byte `ssz-size:"32"` 70 | } 71 | empty := &example{ 72 | Randao: make([]byte, 96), 73 | Data: &data{ 74 | DepositRoot: make([]byte, 32), 75 | DepositCount: 0, 76 | BlockHash: make([]byte, 32), 77 | }, 78 | } 79 | withInstantiatedArray := &example{ 80 | Randao: make([]byte, 96), 81 | Data: &data{ 82 | DepositRoot: make([]byte, 32), 83 | DepositCount: 0, 84 | BlockHash: make([]byte, 32), 85 | }, 86 | Graffiti: make([]byte, 32), 87 | } 88 | r1, err := HashTreeRoot(empty) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | r2, err := HashTreeRoot(withInstantiatedArray) 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | if r1 != r2 { 97 | t.Errorf("Wanted nil_array_field = %#x, instiantiated_empty_array_field = %#x", r1, r2) 98 | } 99 | } 100 | 101 | func TestMarshalNilArray(t *testing.T) { 102 | type ex struct { 103 | Slot uint64 104 | Graffiti []byte `ssz-size:"32"` 105 | DepositIndex uint64 106 | } 107 | b1 := &ex{ 108 | Slot: 5, 109 | Graffiti: nil, 110 | DepositIndex: 64, 111 | } 112 | b2 := &ex{ 113 | Slot: 5, 114 | Graffiti: make([]byte, 32), 115 | DepositIndex: 64, 116 | } 117 | enc1, err := Marshal(b1) 118 | if err != nil { 119 | t.Fatal(err) 120 | } 121 | enc2, err := Marshal(b2) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | if !bytes.Equal(enc1, enc2) { 126 | t.Errorf("First item %v != second item %v", enc1, enc2) 127 | } 128 | } 129 | 130 | func TestPartialDataMarshalUnmarshal(t *testing.T) { 131 | type block struct { 132 | Slot uint64 133 | Transfers []*simpleProtoMessage 134 | } 135 | b := &block{ 136 | Slot: 5, 137 | } 138 | enc, err := Marshal(b) 139 | if err != nil { 140 | t.Fatal(err) 141 | } 142 | dec := &block{} 143 | if err := Unmarshal(enc, dec); err != nil { 144 | t.Fatal(err) 145 | } 146 | } 147 | 148 | func TestMarshal(t *testing.T) { 149 | tests := []struct { 150 | name string 151 | input interface{} 152 | output []byte 153 | err error 154 | }{ 155 | { 156 | name: "Nil", 157 | err: errors.New("untyped-value nil cannot be marshaled"), 158 | }, 159 | { 160 | name: "Unsupported", 161 | input: complex(1, 1), 162 | err: errors.New("unsupported kind: complex128"), 163 | }, 164 | { 165 | name: "UnsupportedPointer", 166 | input: &[]complex128{complex(1, 1), complex(1, 1)}, 167 | err: errors.New("failed to marshal for type: []complex128: unsupported kind: complex128"), 168 | }, 169 | { 170 | name: "UnsupportedStructElement", 171 | input: struct{ Foo complex128 }{complex(1, 1)}, 172 | err: errors.New("failed to marshal for type: struct { Foo complex128 }: unsupported kind: complex128"), 173 | }, 174 | { 175 | name: "Simple", 176 | input: struct{ Foo uint32 }{12345}, 177 | output: []byte{0x39, 0x30, 0x00, 0x00}, 178 | }, 179 | } 180 | 181 | for _, test := range tests { 182 | t.Run(test.name, func(t *testing.T) { 183 | output, err := Marshal(test.input) 184 | if test.err == nil { 185 | if err != nil { 186 | t.Fatalf("unexpected error %v", err) 187 | } 188 | if bytes.Compare(test.output, output) != 0 { 189 | t.Errorf("incorrect output: expected %v; received %v", test.output, output) 190 | } 191 | } else { 192 | if err == nil { 193 | t.Fatalf("missing expected error %v", test.err) 194 | } 195 | if test.err.Error() != err.Error() { 196 | t.Errorf("incorrect error: expected %v; received %v", test.err, err) 197 | } 198 | } 199 | }) 200 | } 201 | } 202 | 203 | func TestUnmarshal(t *testing.T) { 204 | tests := []struct { 205 | name string 206 | input []byte 207 | output interface{} 208 | err error 209 | }{ 210 | { 211 | name: "Nil", 212 | err: errors.New("cannot unmarshal into untyped, nil value"), 213 | }, 214 | { 215 | name: "NotPointer", 216 | input: []byte{0x00, 0x00, 0x00, 0x00}, 217 | output: "", 218 | err: errors.New("can only unmarshal into a pointer target"), 219 | }, 220 | { 221 | name: "OutputNotSupported", 222 | input: []byte{0x00, 0x00, 0x00, 0x00}, 223 | output: &struct{ Foo complex128 }{complex(1, 1)}, 224 | err: errors.New("could not unmarshal input into type: struct { Foo complex128 }: unsupported kind: complex128"), 225 | }, 226 | } 227 | 228 | for _, test := range tests { 229 | t.Run(test.name, func(t *testing.T) { 230 | err := Unmarshal(test.input, test.output) 231 | if test.err == nil { 232 | if err != nil { 233 | t.Errorf("unexpected error %v", err) 234 | } 235 | } else { 236 | if err == nil { 237 | t.Fatalf("missing expected error %v", test.err) 238 | } 239 | if test.err.Error() != err.Error() { 240 | t.Errorf("unexpected error value %v (expected %v)", err, test.err) 241 | } 242 | } 243 | }) 244 | } 245 | } 246 | 247 | func TestHashTreeRoot(t *testing.T) { 248 | tests := []struct { 249 | name string 250 | input interface{} 251 | output [32]byte 252 | err error 253 | }{ 254 | { 255 | name: "Nil", 256 | err: errors.New("untyped nil is not supported"), 257 | }, 258 | { 259 | name: "UnsupportedKind", 260 | input: complex(1, 1), 261 | err: errors.New("could not generate tree hasher for type: complex128: unsupported kind: complex128"), 262 | }, 263 | { 264 | name: "NoInput", 265 | input: &struct{ Foo complex128 }{}, 266 | err: errors.New("unsupported kind: complex128"), 267 | }, 268 | { 269 | name: "Valid", 270 | input: fork{ 271 | PreviousVersion: [4]byte{0x9f, 0x41, 0xbd, 0x5b}, 272 | CurrentVersion: [4]byte{0xcb, 0xb0, 0xf1, 0xd7}, 273 | Epoch: 11971467576204192310, 274 | }, 275 | output: [32]byte{0x3a, 0xd1, 0x26, 0x4c, 0x33, 0xbc, 0x66, 0xb4, 0x3a, 0x49, 0xb1, 0x25, 0x8b, 0x88, 0xf3, 0x4b, 0x8d, 0xbf, 0xa1, 0x64, 0x9f, 0x17, 0xe6, 0xdf, 0x55, 0x0f, 0x58, 0x96, 0x50, 0xd3, 0x49, 0x92}, 276 | }, 277 | } 278 | 279 | for _, test := range tests { 280 | t.Run(test.name, func(t *testing.T) { 281 | output, err := HashTreeRoot(test.input) 282 | if test.err == nil { 283 | if err != nil { 284 | t.Errorf("unexpected error %v", err) 285 | } 286 | if bytes.Compare(test.output[:], output[:]) != 0 { 287 | t.Errorf("incorrect output: expected %v; received %v", test.output, output) 288 | } 289 | } else { 290 | if err == nil { 291 | t.Fatalf("missing expected error %v", test.err) 292 | } 293 | if test.err.Error() != err.Error() { 294 | t.Errorf("incorrect error: expected %v; received %v", test.err, err) 295 | } 296 | } 297 | }) 298 | } 299 | } 300 | 301 | func TestHashTreeRootBitlist(t *testing.T) { 302 | tests := []struct { 303 | name string 304 | input bitfield.Bitlist 305 | maxCapacity uint64 306 | output []byte 307 | err error 308 | }{ 309 | { 310 | name: "Nil", 311 | input: nil, 312 | maxCapacity: 0, 313 | // Hash([]byte{}) 314 | output: hexDecodeOrDie(t, "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"), 315 | err: nil, 316 | }, 317 | { 318 | name: "SampleBitlist", 319 | input: bitfield.Bitlist{1, 2, 3}, 320 | maxCapacity: 4, 321 | // Known output hash. 322 | output: hexDecodeOrDie(t, "835e878350f244651619cbac69de3002251be60225ba0d6ac999b5becb469281"), 323 | err: nil, 324 | }, 325 | } 326 | for _, test := range tests { 327 | t.Run(test.name, func(t *testing.T) { 328 | output, err := HashTreeRootBitfield(test.input, test.maxCapacity) 329 | if test.err == nil { 330 | if err != nil { 331 | t.Fatalf("unexpected error %v", err) 332 | } 333 | if bytes.Compare(test.output[:], output[:]) != 0 { 334 | t.Errorf("incorrect output: expected %#x; received %#x", test.output, output) 335 | } 336 | } else { 337 | if err == nil { 338 | t.Fatalf("missing expected error %v", test.err) 339 | } 340 | if test.err.Error() != err.Error() { 341 | t.Errorf("incorrect error: expected %#x; received %#x", test.err, err) 342 | } 343 | } 344 | }) 345 | } 346 | } 347 | 348 | func TestHashTreeRootWithCapacity(t *testing.T) { 349 | tests := []struct { 350 | name string 351 | input interface{} 352 | maxCapacity uint64 353 | output [32]byte 354 | err error 355 | }{ 356 | { 357 | name: "Nil", 358 | err: errors.New("untyped nil is not supported"), 359 | }, 360 | { 361 | name: "NotSlice", 362 | input: "foo", 363 | err: errors.New("expected slice-kind input, received string"), 364 | }, 365 | { 366 | name: "InvalidSlice1", 367 | input: []complex128{complex(1, 1)}, 368 | err: errors.New("unsupported kind: complex128"), 369 | }, 370 | { 371 | name: "InvalidSlice2", 372 | input: []struct{ Foo complex128 }{{Foo: complex(1, 1)}}, 373 | err: errors.New("unsupported kind: complex128"), 374 | }, 375 | { 376 | name: "NoInput", 377 | input: []uint32{}, 378 | output: [32]byte{0xf5, 0xa5, 0xfd, 0x42, 0xd1, 0x6a, 0x20, 0x30, 0x27, 0x98, 0xef, 0x6e, 0xd3, 0x09, 0x97, 0x9b, 0x43, 0x00, 0x3d, 0x23, 0x20, 0xd9, 0xf0, 0xe8, 0xea, 0x98, 0x31, 0xa9, 0x27, 0x59, 0xfb, 0x4b}, 379 | }, 380 | { 381 | name: "Valid", 382 | input: []fork{{ 383 | PreviousVersion: [4]byte{0x9f, 0x41, 0xbd, 0x5b}, 384 | CurrentVersion: [4]byte{0xcb, 0xb0, 0xf1, 0xd7}, 385 | Epoch: 11971467576204192310, 386 | }}, 387 | maxCapacity: 100, 388 | output: [32]byte{0x5c, 0xa4, 0xd8, 0xbf, 0x17, 0xb9, 0x53, 0x6d, 0x69, 0x56, 0xee, 0x48, 0xfa, 0x3d, 0xc6, 0x91, 0xe3, 0x52, 0x48, 0xbd, 0x09, 0xb2, 0x9b, 0x1b, 0x5b, 0xa4, 0x5a, 0x0e, 0xd5, 0xda, 0xe0, 0xd9}, 389 | }, 390 | } 391 | 392 | for _, test := range tests { 393 | t.Run(test.name, func(t *testing.T) { 394 | output, err := HashTreeRootWithCapacity(test.input, test.maxCapacity) 395 | if test.err == nil { 396 | if err != nil { 397 | t.Fatalf("unexpected error %v", err) 398 | } 399 | if bytes.Compare(test.output[:], output[:]) != 0 { 400 | t.Errorf("incorrect output: expected %v; received %v", test.output, output) 401 | } 402 | } else { 403 | if err == nil { 404 | t.Fatalf("missing expected error %v", test.err) 405 | } 406 | if test.err.Error() != err.Error() { 407 | t.Errorf("incorrect error: expected %v; received %v", test.err, err) 408 | } 409 | } 410 | }) 411 | } 412 | } 413 | 414 | func TestProtobufSSZFieldsIgnored(t *testing.T) { 415 | withProto := &simpleProtoMessage{ 416 | Foo: []byte("foo"), 417 | Bar: 9001, 418 | } 419 | noProto := &simpleNonProtoMessage{ 420 | Foo: []byte("foo"), 421 | Bar: 9001, 422 | } 423 | enc, err := Marshal(withProto) 424 | if err != nil { 425 | t.Fatal(err) 426 | } 427 | enc2, err := Marshal(noProto) 428 | if err != nil { 429 | t.Fatal(err) 430 | } 431 | if !bytes.Equal(enc, enc2) { 432 | t.Errorf("Wanted %v, received %v", enc, enc2) 433 | } 434 | withProtoDecoded := &simpleProtoMessage{} 435 | if err := Unmarshal(enc, withProtoDecoded); err != nil { 436 | t.Fatal(err) 437 | } 438 | noProtoDecoded := &simpleNonProtoMessage{} 439 | if err := Unmarshal(enc2, noProtoDecoded); err != nil { 440 | t.Fatal(err) 441 | } 442 | if !reflect.DeepEqual(noProto, noProtoDecoded) { 443 | t.Errorf("Wanted %v, received %v", noProto, noProtoDecoded) 444 | } 445 | if !reflect.DeepEqual(withProto, withProtoDecoded) { 446 | t.Errorf("Wanted %v, received %v", withProto, withProtoDecoded) 447 | } 448 | } 449 | 450 | func TestNilPointerHashTreeRoot(t *testing.T) { 451 | type nilItem struct { 452 | Field1 []*fork 453 | Field2 uint64 454 | } 455 | i := &nilItem{ 456 | Field1: []*fork{nil}, 457 | Field2: 10, 458 | } 459 | if _, err := HashTreeRoot(i); err != nil { 460 | t.Fatal(err) 461 | } 462 | } 463 | 464 | func TestNilInstantiationMarshalEquality(t *testing.T) { 465 | type exampleBody struct { 466 | Epoch uint64 467 | } 468 | type example struct { 469 | Slot uint64 470 | Root [32]byte 471 | Body *exampleBody 472 | } 473 | root := [32]byte{1, 2, 3, 4} 474 | item := &example{ 475 | Slot: 5, 476 | Root: root, 477 | Body: nil, 478 | } 479 | item2 := &example{ 480 | Slot: 5, 481 | Root: root, 482 | Body: &exampleBody{}, 483 | } 484 | enc, err := Marshal(item) 485 | if err != nil { 486 | t.Fatal(err) 487 | } 488 | enc2, err := Marshal(item2) 489 | if err != nil { 490 | t.Fatal(err) 491 | } 492 | dec := &example{} 493 | if err := Unmarshal(enc, dec); err != nil { 494 | t.Fatal(err) 495 | } 496 | dec2 := &example{} 497 | if err := Unmarshal(enc2, dec2); err != nil { 498 | t.Fatal(err) 499 | } 500 | if !bytes.Equal(enc, enc2) { 501 | t.Errorf("Unequal marshalings %v != %v", enc, enc2) 502 | } 503 | } 504 | 505 | func TestEmptyDataUnmarshal(t *testing.T) { 506 | msg := &simpleProtoMessage{} 507 | if err := Unmarshal([]byte{}, msg); err == nil { 508 | t.Error("Expected unmarshal to fail when attempting to unmarshal from an empty byte slice") 509 | } 510 | } 511 | 512 | func TestHashTreeRootWithCapacity_FailsWithNonSliceType(t *testing.T) { 513 | forkItem := fork{ 514 | Epoch: 11971467576204192310, 515 | } 516 | capacity := uint64(100) 517 | if _, err := HashTreeRootWithCapacity(forkItem, capacity); err == nil { 518 | t.Error("Expected hash tree root to fail with non-slice type") 519 | } 520 | } 521 | 522 | func TestHashTreeRootWithCapacity_HashesCorrectly(t *testing.T) { 523 | capacity := uint64(1099511627776) 524 | balances := make([]uint64, 512) 525 | for i := 0; i < len(balances); i++ { 526 | balances[i] = 32000000000 527 | } 528 | root, err := HashTreeRootWithCapacity(balances, capacity) 529 | if err != nil { 530 | t.Fatal(err) 531 | } 532 | // Test case taken from validator balances of the state value in: 533 | // https://github.com/ethereum/eth2.0-spec-tests/blob/v0.8.0/tests/sanity/slots/sanity_slots_mainnet.yaml. 534 | want, err := hex.DecodeString("21a67313b0c6f988aac4fb6dd68686e1329243f7f6af21b722f6b83ca8fed9a8") 535 | if err != nil { 536 | t.Fatal(err) 537 | } 538 | if !bytes.Equal(root[:], want) { 539 | t.Errorf("Mismatched roots, wanted %#x == %#x", root, want) 540 | } 541 | } 542 | 543 | // Regression test for https://github.com/prysmaticlabs/go-ssz/issues/46. 544 | func TestHashTreeRoot_EncodeSliceLengthCorrectly(t *testing.T) { 545 | type accountBalances struct { 546 | Balances []uint64 `ssz-max:"1099511627776"` // Large uint64 capacity. 547 | } 548 | acct := accountBalances{ 549 | Balances: make([]uint64, 512), 550 | } 551 | for i := 0; i < len(acct.Balances); i++ { 552 | acct.Balances[i] = 32000000000 553 | } 554 | root, err := HashTreeRoot(acct) 555 | if err != nil { 556 | t.Fatal(err) 557 | } 558 | // Test case taken from validator balances of the state value in: 559 | // https://github.com/ethereum/eth2.0-spec-tests/blob/v0.8.0/tests/sanity/slots/sanity_slots_mainnet.yaml. 560 | want, err := hex.DecodeString("21a67313b0c6f988aac4fb6dd68686e1329243f7f6af21b722f6b83ca8fed9a8") 561 | if err != nil { 562 | t.Fatal(err) 563 | } 564 | if !bytes.Equal(root[:], want) { 565 | t.Errorf("Mismatched roots, wanted %#x == %#x", root, want) 566 | } 567 | } 568 | 569 | func TestHashTreeRoot_ConcurrentAccess(t *testing.T) { 570 | item := &truncateSignatureCase{ 571 | Slot: 10, 572 | PreviousBlockRoot: []byte{'a', 'b'}, 573 | Signature: []byte("TESTING23"), 574 | } 575 | var wg sync.WaitGroup 576 | // We ensure the hash tree root function can be computed in a thread-safe manner. 577 | // No panic from this test is a successful run. 578 | wg.Add(100) 579 | for i := 0; i < 100; i++ { 580 | go func(tt *testing.T, w *sync.WaitGroup) { 581 | if _, err := HashTreeRoot(item); err != nil { 582 | tt.Fatal(err) 583 | } 584 | w.Done() 585 | }(t, &wg) 586 | } 587 | wg.Wait() 588 | } 589 | 590 | func TestSigningRoot(t *testing.T) { 591 | type signingRootTest struct { 592 | Val1 interface{} 593 | Val2 interface{} 594 | Err error 595 | } 596 | type truncateLastCase struct { 597 | Slot uint64 598 | StateRoot []byte 599 | TruncatedField []byte 600 | } 601 | var signingRootTests = []signingRootTest{ 602 | { 603 | Val1: &truncateSignatureCase{Slot: 20, Signature: []byte{'A', 'B'}}, 604 | Val2: &truncateSignatureCase{Slot: 20, Signature: []byte("TESTING")}, 605 | }, 606 | { 607 | Val1: &truncateSignatureCase{ 608 | Slot: 10, 609 | PreviousBlockRoot: []byte{'a', 'b'}, 610 | Signature: []byte("TESTINGDIFF")}, 611 | Val2: &truncateSignatureCase{ 612 | Slot: 10, 613 | PreviousBlockRoot: []byte{'a', 'b'}, 614 | Signature: []byte("TESTING23")}, 615 | }, 616 | { 617 | Val1: truncateSignatureCase{Slot: 50, Signature: []byte("THIS")}, 618 | Val2: truncateSignatureCase{Slot: 50, Signature: []byte("DOESNT")}, 619 | }, 620 | { 621 | Val1: truncateSignatureCase{Signature: []byte("MATTER")}, 622 | Val2: truncateSignatureCase{Signature: []byte("TESTING")}, 623 | }, 624 | { 625 | Val1: truncateLastCase{ 626 | Slot: 5, 627 | StateRoot: []byte("MATTERS"), 628 | TruncatedField: []byte("DOESNT MATTER"), 629 | }, 630 | Val2: truncateLastCase{ 631 | Slot: 5, 632 | StateRoot: []byte("MATTERS"), 633 | TruncatedField: []byte("SHOULDNT MATTER"), 634 | }, 635 | }, 636 | { 637 | Val1: truncateLastCase{ 638 | Slot: 550, 639 | StateRoot: []byte("SHOULD"), 640 | TruncatedField: []byte("DOESNT"), 641 | }, 642 | Val2: truncateLastCase{ 643 | Slot: 550, 644 | StateRoot: []byte("SHOULD"), 645 | TruncatedField: []byte("SHOULDNT"), 646 | }, 647 | }, 648 | { 649 | Val1: nil, 650 | Err: errors.New("value cannot be nil"), 651 | Val2: nil, 652 | }, 653 | } 654 | 655 | for i, test := range signingRootTests { 656 | output1, err := SigningRoot(test.Val1) 657 | if test.Err != nil { 658 | if err == nil { 659 | t.Fatalf("missing expected error of test %d value 1", i) 660 | } 661 | if test.Err.Error() != err.Error() { 662 | t.Fatalf("incorrect error at test %d value 1 %v", i, err) 663 | } 664 | } else { 665 | if err != nil { 666 | t.Fatalf("could not get the signing root of test %d, value 1 %v", i, err) 667 | } 668 | 669 | output2, err := SigningRoot(test.Val2) 670 | if err != nil { 671 | t.Errorf("could not get the signing root of test %d, value 2 %v", i, err) 672 | } 673 | // Check values have same result hash 674 | if !bytes.Equal(output1[:], output2[:]) { 675 | t.Errorf("test %d: hash mismatch: %X\n != %X", i, output1, output2) 676 | } 677 | } 678 | } 679 | } 680 | 681 | func TestSigningRoot_ConcurrentAccess(t *testing.T) { 682 | item := &truncateSignatureCase{ 683 | Slot: 10, 684 | PreviousBlockRoot: []byte{'a', 'b'}, 685 | Signature: []byte("TESTING23"), 686 | } 687 | var wg sync.WaitGroup 688 | // We ensure the signing root function can be computed in a thread-safe manner. 689 | // No panic from this test is a successful run. 690 | wg.Add(100) 691 | for i := 0; i < 100; i++ { 692 | go func(tt *testing.T, w *sync.WaitGroup) { 693 | if _, err := SigningRoot(item); err != nil { 694 | tt.Fatal(err) 695 | } 696 | w.Done() 697 | }(t, &wg) 698 | } 699 | wg.Wait() 700 | } 701 | 702 | func BenchmarkSSZ_NoCache(b *testing.B) { 703 | b.StopTimer() 704 | bs := &beaconState{ 705 | BlockRoots: make([][]byte, 65536), 706 | } 707 | for i := 0; i < len(bs.BlockRoots); i++ { 708 | newItem := [32]byte{1, 2, 3} 709 | bs.BlockRoots[i] = newItem[:] 710 | } 711 | b.StartTimer() 712 | for i := 0; i < b.N; i++ { 713 | if _, err := HashTreeRoot(bs); err != nil { 714 | b.Fatal(err) 715 | } 716 | } 717 | types.ToggleCache(true) 718 | } 719 | 720 | func BenchmarkSSZ_WithCache(b *testing.B) { 721 | b.StopTimer() 722 | types.ToggleCache(true) 723 | bs := &beaconState{ 724 | BlockRoots: make([][]byte, 65536), 725 | } 726 | for i := 0; i < len(bs.BlockRoots); i++ { 727 | newItem := [32]byte{1, 2, 3} 728 | bs.BlockRoots[i] = newItem[:] 729 | } 730 | b.StartTimer() 731 | for i := 0; i < b.N; i++ { 732 | if _, err := HashTreeRoot(bs); err != nil { 733 | b.Fatal(err) 734 | } 735 | } 736 | types.ToggleCache(false) 737 | } 738 | 739 | func BenchmarkSSZ_SingleElementChanged(b *testing.B) { 740 | b.StopTimer() 741 | types.ToggleCache(true) 742 | bs := &beaconState{ 743 | BlockRoots: make([][]byte, 65536), 744 | } 745 | for i := 0; i < len(bs.BlockRoots); i++ { 746 | newItem := [32]byte{1, 2, 3} 747 | bs.BlockRoots[i] = newItem[:] 748 | } 749 | if _, err := HashTreeRoot(bs); err != nil { 750 | b.Fatal(err) 751 | } 752 | b.StartTimer() 753 | for i := 0; i < b.N; i++ { 754 | newItem := []byte(strconv.Itoa(i)) 755 | newRoot := toBytes32(newItem) 756 | bs.BlockRoots[i%len(bs.BlockRoots)] = newRoot[:] 757 | if _, err := HashTreeRoot(bs); err != nil { 758 | b.Fatal(err) 759 | } 760 | } 761 | types.ToggleCache(false) 762 | } 763 | 764 | func toBytes32(x []byte) [32]byte { 765 | var y [32]byte 766 | copy(y[:], x) 767 | return y 768 | } 769 | 770 | func TestBoolArray_MissingByte(t *testing.T) { 771 | objBytes := hexDecodeOrDie(t, "010101010101010101010101010101") 772 | var result [16]bool 773 | if err := Unmarshal(objBytes, &result); err == nil { 774 | t.Error("Expected message with missing byte to fail marshalling") 775 | } 776 | } 777 | 778 | func TestBoolArray_ExtraByte(t *testing.T) { 779 | objBytes := hexDecodeOrDie(t, "01010101010101010101010101010101ff") 780 | var result [16]bool 781 | if err := Unmarshal(objBytes, &result); err == nil { 782 | t.Error("Expected message with extra byte to fail marshalling") 783 | } 784 | } 785 | 786 | func TestBoolArray_Correct(t *testing.T) { 787 | objBytes := hexDecodeOrDie(t, "01010101010101010101010101010101") 788 | var result [16]bool 789 | if err := Unmarshal(objBytes, &result); err != nil { 790 | t.Errorf("Unexpected error: %v", err) 791 | } 792 | } 793 | 794 | func hexDecodeOrDie(t *testing.T, s string) []byte { 795 | res, err := hex.DecodeString(s) 796 | if err != nil { 797 | t.Fatal(err) 798 | } 799 | return res 800 | } 801 | -------------------------------------------------------------------------------- /types/BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") 2 | 3 | go_library( 4 | name = "go_default_library", 5 | srcs = [ 6 | "array_basic.go", 7 | "array_composite.go", 8 | "array_roots.go", 9 | "basic.go", 10 | "bitlist.go", 11 | "determine_size.go", 12 | "factory.go", 13 | "helpers.go", 14 | "slice_basic.go", 15 | "slice_composite.go", 16 | "string.go", 17 | "struct.go", 18 | ], 19 | importpath = "github.com/prysmaticlabs/go-ssz/types", 20 | visibility = ["//visibility:public"], 21 | deps = [ 22 | "@com_github_dgraph_io_ristretto//:go_default_library", 23 | "@com_github_minio_highwayhash//:go_default_library", 24 | "@com_github_minio_sha256_simd//:go_default_library", 25 | "@com_github_pkg_errors//:go_default_library", 26 | "@com_github_protolambda_zssz//htr:go_default_library", 27 | "@com_github_protolambda_zssz//merkle:go_default_library", 28 | "@com_github_prysmaticlabs_go_bitfield//:go_default_library", 29 | ], 30 | ) 31 | 32 | go_test( 33 | name = "go_default_test", 34 | srcs = [ 35 | "array_roots_test.go", 36 | "helpers_test.go", 37 | "struct_test.go", 38 | ], 39 | embed = [":go_default_library"], 40 | ) 41 | -------------------------------------------------------------------------------- /types/array_basic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | 7 | "github.com/dgraph-io/ristretto" 8 | "github.com/minio/highwayhash" 9 | ) 10 | 11 | // BasicArraySizeCache for HashTreeRoot. 12 | const BasicArraySizeCache = 100000 13 | 14 | var fastSumHashKey = toBytes32([]byte("hash_fast_sum64_key")) 15 | 16 | type basicArraySSZ struct { 17 | hashCache *ristretto.Cache 18 | lock sync.Mutex 19 | } 20 | 21 | func newBasicArraySSZ() *basicArraySSZ { 22 | cache, _ := ristretto.NewCache(&ristretto.Config{ 23 | NumCounters: BasicArraySizeCache, // number of keys to track frequency of (1M). 24 | MaxCost: 1 << 22, // maximum cost of cache (3MB). 25 | // 100,000 roots will take up approximately 3 MB in memory. 26 | BufferItems: 64, // number of keys per Get buffer. 27 | }) 28 | return &basicArraySSZ{ 29 | hashCache: cache, 30 | } 31 | } 32 | 33 | func (b *basicArraySSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 34 | numItems := val.Len() 35 | hashKeyElements := make([]byte, BytesPerChunk*numItems) 36 | emptyKey := highwayhash.Sum(hashKeyElements, fastSumHashKey[:]) 37 | leaves := make([][]byte, numItems) 38 | offset := 0 39 | var factory SSZAble 40 | var err error 41 | if numItems > 0 { 42 | factory, err = SSZFactory(val.Index(0), typ.Elem()) 43 | if err != nil { 44 | return [32]byte{}, err 45 | } 46 | } 47 | for i := 0; i < numItems; i++ { 48 | r, err := factory.Root(val.Index(i), typ.Elem(), "", 0) 49 | if err != nil { 50 | return [32]byte{}, err 51 | } 52 | leaves[i] = r[:] 53 | copy(hashKeyElements[offset:offset+32], r[:]) 54 | offset += 32 55 | } 56 | hashKey := highwayhash.Sum(hashKeyElements, fastSumHashKey[:]) 57 | if enableCache && hashKey != emptyKey { 58 | res, ok := b.hashCache.Get(string(hashKey[:])) 59 | if res != nil && ok { 60 | return res.([32]byte), nil 61 | } 62 | } 63 | chunks, err := pack(leaves) 64 | if err != nil { 65 | return [32]byte{}, err 66 | } 67 | root, err := bitwiseMerkleize(chunks, uint64(len(chunks)), uint64(len(chunks))) 68 | if err != nil { 69 | return [32]byte{}, err 70 | } 71 | if enableCache && hashKey != emptyKey { 72 | b.hashCache.Set(string(hashKey[:]), root, 32) 73 | } 74 | return root, nil 75 | } 76 | 77 | func (b *basicArraySSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 78 | index := startOffset 79 | var err error 80 | if val.Len() == 0 { 81 | return index, nil 82 | } 83 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 84 | if err != nil { 85 | return 0, err 86 | } 87 | for i := 0; i < val.Len(); i++ { 88 | index, err = factory.Marshal(val.Index(i), typ.Elem(), buf, index) 89 | if err != nil { 90 | return 0, err 91 | } 92 | } 93 | return index, nil 94 | } 95 | 96 | func (b *basicArraySSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 97 | i := 0 98 | index := startOffset 99 | size := val.Len() 100 | var err error 101 | var factory SSZAble 102 | for i < size { 103 | if val.Index(i).Kind() == reflect.Ptr { 104 | instantiateConcreteTypeForElement(val.Index(i), typ.Elem().Elem()) 105 | factory, err = SSZFactory(val.Index(i), typ.Elem().Elem()) 106 | if err != nil { 107 | return 0, err 108 | } 109 | } else { 110 | factory, err = SSZFactory(val.Index(i), typ.Elem()) 111 | if err != nil { 112 | return 0, err 113 | } 114 | } 115 | index, err = factory.Unmarshal(val.Index(i), typ.Elem(), input, index) 116 | if err != nil { 117 | return 0, err 118 | } 119 | i++ 120 | } 121 | return index, nil 122 | } 123 | -------------------------------------------------------------------------------- /types/array_composite.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | ) 7 | 8 | type compositeArraySSZ struct{} 9 | 10 | func newCompositeArraySSZ() *compositeArraySSZ { 11 | return &compositeArraySSZ{} 12 | } 13 | 14 | func (b *compositeArraySSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 15 | var factory SSZAble 16 | var err error 17 | numItems := val.Len() 18 | if numItems > 0 { 19 | factory, err = SSZFactory(val.Index(0), typ.Elem()) 20 | if err != nil { 21 | return [32]byte{}, err 22 | } 23 | } 24 | roots := make([][]byte, numItems) 25 | elemSize := uint64(0) 26 | if isBasicType(typ.Elem().Kind()) { 27 | elemSize = determineFixedSize(val, typ.Elem()) 28 | } else { 29 | elemSize = 32 30 | } 31 | limit := (uint64(val.Len())*elemSize + 31) / 32 32 | for i := 0; i < val.Len(); i++ { 33 | r, err := factory.Root(val.Index(i), typ.Elem(), "", 0) 34 | if err != nil { 35 | return [32]byte{}, err 36 | } 37 | roots[i] = r[:] 38 | } 39 | chunks, err := pack(roots) 40 | if err != nil { 41 | return [32]byte{}, err 42 | } 43 | if val.Len() == 0 { 44 | chunks = [][]byte{} 45 | } 46 | root, err := bitwiseMerkleize(chunks, uint64(len(chunks)), limit) 47 | if err != nil { 48 | return [32]byte{}, err 49 | } 50 | return root, nil 51 | } 52 | 53 | func (b *compositeArraySSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 54 | index := startOffset 55 | if val.Len() == 0 { 56 | return index, nil 57 | } 58 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 59 | if err != nil { 60 | return 0, err 61 | } 62 | if !isVariableSizeType(typ.Elem()) { 63 | for i := 0; i < val.Len(); i++ { 64 | // If each element is not variable size, we simply encode sequentially and write 65 | // into the buffer at the last index we wrote at. 66 | index, err = factory.Marshal(val.Index(i), typ.Elem(), buf, index) 67 | if err != nil { 68 | return 0, err 69 | } 70 | } 71 | return index, nil 72 | } 73 | fixedIndex := index 74 | currentOffsetIndex := startOffset + uint64(val.Len())*BytesPerLengthOffset 75 | nextOffsetIndex := currentOffsetIndex 76 | // If the elements are variable size, we need to include offset indices 77 | // in the serialized output list. 78 | for i := 0; i < val.Len(); i++ { 79 | nextOffsetIndex, err = factory.Marshal(val.Index(i), typ.Elem(), buf, currentOffsetIndex) 80 | if err != nil { 81 | return 0, err 82 | } 83 | // Write the offset. 84 | offsetBuf := make([]byte, BytesPerLengthOffset) 85 | binary.LittleEndian.PutUint32(offsetBuf, uint32(currentOffsetIndex-startOffset)) 86 | copy(buf[fixedIndex:fixedIndex+BytesPerLengthOffset], offsetBuf) 87 | 88 | // We increase the offset indices accordingly. 89 | currentOffsetIndex = nextOffsetIndex 90 | fixedIndex += BytesPerLengthOffset 91 | } 92 | index = currentOffsetIndex 93 | return index, nil 94 | } 95 | 96 | func (b *compositeArraySSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 97 | currentIndex := startOffset 98 | nextIndex := currentIndex 99 | offsetVal := input[startOffset : startOffset+BytesPerLengthOffset] 100 | firstOffset := startOffset + uint64(binary.LittleEndian.Uint32(offsetVal)) 101 | currentOffset := firstOffset 102 | nextOffset := currentOffset 103 | endOffset := uint64(len(input)) 104 | i := 0 105 | if val.Kind() == reflect.Slice { 106 | instantiatedArray := reflect.MakeSlice(val.Type(), typ.Len(), typ.Len()) 107 | val.Set(instantiatedArray) 108 | } 109 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 110 | if err != nil { 111 | return 0, err 112 | } 113 | for currentIndex < firstOffset { 114 | nextIndex = currentIndex + BytesPerLengthOffset 115 | if nextIndex == firstOffset { 116 | nextOffset = endOffset 117 | } else { 118 | nextOffsetVal := input[nextIndex : nextIndex+BytesPerLengthOffset] 119 | nextOffset = startOffset + uint64(binary.LittleEndian.Uint32(nextOffsetVal)) 120 | } 121 | if val.Index(i).Kind() == reflect.Ptr { 122 | instantiateConcreteTypeForElement(val.Index(i), typ.Elem().Elem()) 123 | } 124 | if _, err := factory.Unmarshal(val.Index(i), typ.Elem(), input[currentOffset:nextOffset], 0); err != nil { 125 | return 0, err 126 | } 127 | i++ 128 | currentIndex = nextIndex 129 | currentOffset = nextOffset 130 | } 131 | return currentIndex, nil 132 | } 133 | -------------------------------------------------------------------------------- /types/array_roots.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | 9 | "github.com/dgraph-io/ristretto" 10 | "github.com/minio/highwayhash" 11 | "github.com/protolambda/zssz/merkle" 12 | ) 13 | 14 | // RootsArraySizeCache for hash tree root. 15 | const RootsArraySizeCache = 100000 16 | 17 | type rootsArraySSZ struct { 18 | hashCache *ristretto.Cache 19 | lock sync.Mutex 20 | cachedLeaves map[string][][]byte 21 | layers map[string][][][]byte 22 | } 23 | 24 | func newRootsArraySSZ() *rootsArraySSZ { 25 | cache, _ := ristretto.NewCache(&ristretto.Config{ 26 | NumCounters: RootsArraySizeCache, // number of keys to track frequency of (100000). 27 | MaxCost: 1 << 23, // maximum cost of cache (3MB). 28 | // 100,000 roots will take up approximately 3 MB in memory. 29 | BufferItems: 64, // number of keys per Get buffer. 30 | }) 31 | return &rootsArraySSZ{ 32 | hashCache: cache, 33 | cachedLeaves: make(map[string][][]byte), 34 | layers: make(map[string][][][]byte), 35 | } 36 | } 37 | 38 | func (a *rootsArraySSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 39 | numItems := val.Len() 40 | // We make sure to look into the cache only if a field name is provided, that is, 41 | // if this function is called when calling HashTreeRoot on a struct type that has 42 | // a field which is an array of roots. An example is: 43 | // 44 | // type BeaconState struct { 45 | // BlockRoots [2048][32]byte 46 | // } 47 | // 48 | // which would allow us to look into the cache by the field "BlockRoots". 49 | if enableCache && fieldName != "" { 50 | if _, ok := a.layers[fieldName]; !ok { 51 | depth := merkle.GetDepth(uint64(numItems)) 52 | a.layers[fieldName] = make([][][]byte, depth+1) 53 | } 54 | } 55 | hashKeyElements := make([]byte, BytesPerChunk*numItems) 56 | emptyKey := highwayhash.Sum(hashKeyElements, fastSumHashKey[:]) 57 | offset := 0 58 | leaves := make([][]byte, numItems) 59 | changedIndices := make([]int, 0) 60 | for i := 0; i < numItems; i++ { 61 | var item [32]byte 62 | if res, ok := val.Index(i).Interface().([]byte); ok { 63 | item = toBytes32(res) 64 | } else if res, ok := val.Index(i).Interface().([32]byte); ok { 65 | item = res 66 | } else { 67 | return [32]byte{}, fmt.Errorf("expected array or slice of len 32, received %v", val.Index(i)) 68 | } 69 | leaves[i] = item[:] 70 | copy(hashKeyElements[offset:offset+32], leaves[i]) 71 | offset += 32 72 | if enableCache && fieldName != "" { 73 | if _, ok := a.cachedLeaves[fieldName]; ok { 74 | if !bytes.Equal(leaves[i], a.cachedLeaves[fieldName][i]) { 75 | changedIndices = append(changedIndices, i) 76 | } 77 | } 78 | } 79 | } 80 | chunks := leaves 81 | // Recompute the root from the modified branches from the previous call 82 | // to this function. 83 | if len(changedIndices) > 0 { 84 | var rt [32]byte 85 | for i := 0; i < len(changedIndices); i++ { 86 | rt = a.recomputeRoot(changedIndices[i], chunks, fieldName) 87 | } 88 | return rt, nil 89 | } 90 | hashKey := highwayhash.Sum(hashKeyElements, fastSumHashKey[:]) 91 | if enableCache && hashKey != emptyKey { 92 | res, ok := a.hashCache.Get(string(hashKey[:])) 93 | if res != nil && ok { 94 | return res.([32]byte), nil 95 | } 96 | } 97 | root := a.merkleize(chunks, fieldName) 98 | if enableCache && fieldName != "" { 99 | a.cachedLeaves[fieldName] = leaves 100 | } 101 | if enableCache && hashKey != emptyKey { 102 | a.hashCache.Set(string(hashKey[:]), root, 32) 103 | } 104 | return root, nil 105 | } 106 | 107 | func (a *rootsArraySSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 108 | index := startOffset 109 | if val.Len() == 0 { 110 | return index, nil 111 | } 112 | for i := 0; i < val.Len(); i++ { 113 | var item [32]byte 114 | if res, ok := val.Index(i).Interface().([32]byte); ok { 115 | item = res 116 | } else if res, ok := val.Index(i).Interface().([]byte); ok { 117 | item = toBytes32(res) 118 | } else { 119 | return 0, fmt.Errorf("expected array or slice of len 32, received %v", val.Index(i)) 120 | } 121 | copy(buf[index:index+uint64(len(item))], item[:]) 122 | index += uint64(len(item)) 123 | } 124 | return index, nil 125 | } 126 | 127 | func (a *rootsArraySSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 128 | i := 0 129 | index := startOffset 130 | for i < val.Len() { 131 | val.Index(i).SetBytes(input[index : index+uint64(32)]) 132 | index += uint64(32) 133 | i++ 134 | } 135 | return index, nil 136 | } 137 | 138 | func (a *rootsArraySSZ) recomputeRoot(idx int, chunks [][]byte, fieldName string) [32]byte { 139 | root := chunks[idx] 140 | for i := 0; i < len(a.layers[fieldName])-1; i++ { 141 | subIndex := (uint64(idx) / (1 << uint64(i))) ^ 1 142 | isLeft := uint64(idx) / (1 << uint64(i)) 143 | parentIdx := uint64(idx) / (1 << uint64(i+1)) 144 | item := a.layers[fieldName][i][subIndex] 145 | if isLeft%2 != 0 { 146 | parentHash := hash(append(item, root...)) 147 | root = parentHash[:] 148 | } else { 149 | parentHash := hash(append(root, item...)) 150 | root = parentHash[:] 151 | } 152 | // Update the cached layers at the parent index. 153 | a.layers[fieldName][i+1][parentIdx] = root 154 | } 155 | return toBytes32(root) 156 | } 157 | 158 | func (a *rootsArraySSZ) merkleize(chunks [][]byte, fieldName string) [32]byte { 159 | if len(chunks) == 1 { 160 | var root [32]byte 161 | copy(root[:], chunks[0]) 162 | return root 163 | } 164 | for !isPowerOf2(len(chunks)) { 165 | chunks = append(chunks, make([]byte, BytesPerChunk)) 166 | } 167 | hashLayer := chunks 168 | if enableCache && fieldName != "" { 169 | a.layers[fieldName][0] = hashLayer 170 | } 171 | // We keep track of the hash layers of a Merkle trie until we reach 172 | // the top layer of length 1, which contains the single root element. 173 | // [Root] -> Top layer has length 1. 174 | // [E] [F] -> This layer has length 2. 175 | // [A] [B] [C] [D] -> The bottom layer has length 4 (needs to be a power of two). 176 | i := 1 177 | for len(hashLayer) > 1 { 178 | layer := [][]byte{} 179 | for i := 0; i < len(hashLayer); i += 2 { 180 | hashedChunk := hash(append(hashLayer[i], hashLayer[i+1]...)) 181 | layer = append(layer, hashedChunk[:]) 182 | } 183 | hashLayer = layer 184 | if enableCache && fieldName != "" { 185 | a.layers[fieldName][i] = hashLayer 186 | } 187 | i++ 188 | } 189 | var root [32]byte 190 | copy(root[:], hashLayer[0]) 191 | return root 192 | } 193 | 194 | func isPowerOf2(n int) bool { 195 | return n != 0 && (n&(n-1)) == 0 196 | } 197 | -------------------------------------------------------------------------------- /types/array_roots_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type beaconState struct { 9 | BlockRoots [65536][32]byte 10 | } 11 | 12 | func BenchmarkRootsArray_Root_WithCache(b *testing.B) { 13 | b.StopTimer() 14 | bs := beaconState{ 15 | BlockRoots: [65536][32]byte{}, 16 | } 17 | for i := 0; i < len(bs.BlockRoots); i++ { 18 | bs.BlockRoots[i] = [32]byte{1, 2, 3} 19 | } 20 | ss := newRootsArraySSZ() 21 | v := reflect.ValueOf(bs.BlockRoots) 22 | typ := v.Type() 23 | b.StartTimer() 24 | for i := 0; i < b.N; i++ { 25 | if _, err := ss.Root(v, typ, "BlockRoots", 0); err != nil { 26 | b.Fatal(err) 27 | } 28 | } 29 | } 30 | 31 | func BenchmarkRootsArray_Root_MinimalChanges(b *testing.B) { 32 | b.StopTimer() 33 | bs := beaconState{ 34 | BlockRoots: [65536][32]byte{}, 35 | } 36 | for i := 0; i < len(bs.BlockRoots); i++ { 37 | bs.BlockRoots[i] = [32]byte{1, 2, 3} 38 | } 39 | ss := newRootsArraySSZ() 40 | v := reflect.ValueOf(bs.BlockRoots) 41 | typ := v.Type() 42 | b.StartTimer() 43 | for i := 0; i < b.N; i++ { 44 | bs.BlockRoots[i%len(bs.BlockRoots)] = [32]byte{4, 5, 6} 45 | if _, err := ss.Root(v, typ, "BlockRoots", 0); err != nil { 46 | b.Fatal(err) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /types/basic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "reflect" 7 | "sync" 8 | 9 | "github.com/dgraph-io/ristretto" 10 | ) 11 | 12 | // BasicTypeCacheSize for HashTreeRoot. 13 | const BasicTypeCacheSize = 100000 14 | 15 | type basicSSZ struct { 16 | hashCache *ristretto.Cache 17 | lock sync.Mutex 18 | } 19 | 20 | func newBasicSSZ() *basicSSZ { 21 | cache, _ := ristretto.NewCache(&ristretto.Config{ 22 | NumCounters: BasicTypeCacheSize, // number of keys to track frequency of (100K). 23 | MaxCost: 1 << 23, // maximum cost of cache (3MB). 24 | // 100,000 roots will take up approximately 3 MB in memory. 25 | BufferItems: 64, // number of keys per Get buffer. 26 | }) 27 | return &basicSSZ{ 28 | hashCache: cache, 29 | } 30 | } 31 | 32 | func (b *basicSSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 33 | kind := typ.Kind() 34 | switch { 35 | case kind == reflect.Bool: 36 | return marshalBool(val, buf, startOffset) 37 | case kind == reflect.Uint8: 38 | return marshalUint8(val, buf, startOffset) 39 | case kind == reflect.Uint16: 40 | return marshalUint16(val, buf, startOffset) 41 | case kind == reflect.Int32: 42 | return marshalInt32(val, buf, startOffset) 43 | case kind == reflect.Uint32: 44 | return marshalUint32(val, buf, startOffset) 45 | case kind == reflect.Uint64: 46 | return marshalUint64(val, buf, startOffset) 47 | case kind == reflect.Array && typ.Elem().Kind() == reflect.Uint8: 48 | return marshalByteArray(val, typ, buf, startOffset) 49 | case kind == reflect.Array && isBasicType(typ.Elem().Kind()): 50 | return b.marshalBasicArray(val, typ, buf, startOffset) 51 | default: 52 | return 0, fmt.Errorf("type %v is not serializable", val.Type()) 53 | } 54 | } 55 | 56 | func (b *basicSSZ) Unmarshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 57 | if startOffset >= uint64(len(buf)) { 58 | return 0, fmt.Errorf("startOffset %d is greater than length of input %d", startOffset, len(buf)) 59 | } 60 | 61 | kind := typ.Kind() 62 | switch { 63 | case kind == reflect.Bool: 64 | return unmarshalBool(val, typ, buf, startOffset) 65 | case kind == reflect.Uint8: 66 | return unmarshalUint8(val, typ, buf, startOffset) 67 | case kind == reflect.Uint16: 68 | return unmarshalUint16(val, typ, buf, startOffset) 69 | case kind == reflect.Int32: 70 | return unmarshalInt32(val, typ, buf, startOffset) 71 | case kind == reflect.Uint32: 72 | return unmarshalUint32(val, typ, buf, startOffset) 73 | case kind == reflect.Uint64: 74 | return unmarshalUint64(val, typ, buf, startOffset) 75 | case kind == reflect.Slice && typ.Elem().Kind() == reflect.Uint8: 76 | return unmarshalByteArray(val, typ, buf, startOffset) 77 | case kind == reflect.Array && isBasicType(typ.Elem().Kind()): 78 | return basicArrayFactory.Unmarshal(val, typ, buf, startOffset) 79 | default: 80 | return 0, fmt.Errorf("type %v is not serializable", val.Type()) 81 | } 82 | } 83 | 84 | func (b *basicSSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 85 | var chunks [][]byte 86 | var err error 87 | var hashKey string 88 | newVal := reflect.New(val.Type()).Elem() 89 | newVal.Set(val) 90 | if val.Type().Kind() == reflect.Slice && val.IsNil() { 91 | newVal.Set(reflect.MakeSlice(val.Type(), typ.Len(), typ.Len())) 92 | } 93 | buf := make([]byte, DetermineSize(newVal)) 94 | if _, err := b.Marshal(newVal, typ, buf, 0); err != nil { 95 | return [32]byte{}, err 96 | } 97 | hashKey = string(buf) 98 | res, ok := b.hashCache.Get(string(hashKey)) 99 | if res != nil && ok { 100 | return res.([32]byte), nil 101 | } 102 | 103 | // In order to find the root of a basic type, we simply marshal it, 104 | // split the marshaling into chunks, and compute the most simple 105 | // Merkleization over the chunks. 106 | chunks, err = pack([][]byte{buf}) 107 | if err != nil { 108 | return [32]byte{}, err 109 | } 110 | root, err := bitwiseMerkleize(chunks, uint64(len(chunks)), uint64(len(chunks))) 111 | if err != nil { 112 | return [32]byte{}, err 113 | } 114 | b.hashCache.Set(string(hashKey), root, 32) 115 | return root, nil 116 | } 117 | 118 | func (b *basicSSZ) marshalBasicArray(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 119 | index := startOffset 120 | var err error 121 | for i := 0; i < val.Len(); i++ { 122 | index, err = b.Marshal(val.Index(i), typ.Elem(), buf, index) 123 | if err != nil { 124 | return 0, err 125 | } 126 | } 127 | return index, nil 128 | } 129 | 130 | func marshalByteArray(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 131 | if val.Kind() == reflect.Array { 132 | for i := 0; i < val.Len(); i++ { 133 | buf[int(startOffset)+i] = uint8(val.Index(i).Uint()) 134 | } 135 | return startOffset + uint64(val.Len()), nil 136 | } 137 | if val.IsNil() { 138 | item := make([]byte, typ.Len()) 139 | copy(buf[startOffset:], item) 140 | return startOffset + uint64(typ.Len()), nil 141 | } 142 | copy(buf[startOffset:], val.Bytes()) 143 | return startOffset + uint64(val.Len()), nil 144 | } 145 | 146 | func unmarshalByteArray(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 147 | offset := startOffset + uint64(len(input)) 148 | val.SetBytes(input[startOffset:offset]) 149 | return offset, nil 150 | } 151 | 152 | func marshalBool(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 153 | if val.Interface().(bool) { 154 | buf[startOffset] = uint8(1) 155 | } else { 156 | buf[startOffset] = uint8(0) 157 | } 158 | return startOffset + 1, nil 159 | } 160 | 161 | func unmarshalBool(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 162 | v := input[startOffset] 163 | if v == 0 { 164 | val.SetBool(false) 165 | } else if v == 1 { 166 | val.SetBool(true) 167 | } else { 168 | return 0, fmt.Errorf("expected 0 or 1 but received %d", v) 169 | } 170 | return startOffset + 1, nil 171 | } 172 | 173 | func marshalUint8(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 174 | buf[startOffset] = val.Interface().(uint8) 175 | return startOffset + 1, nil 176 | } 177 | 178 | func unmarshalUint8(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 179 | val.SetUint(uint64(input[startOffset])) 180 | return startOffset + 1, nil 181 | } 182 | 183 | func marshalUint16(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 184 | binary.LittleEndian.PutUint16(buf[startOffset:], val.Interface().(uint16)) 185 | return startOffset + 2, nil 186 | } 187 | 188 | func unmarshalUint16(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 189 | offset := startOffset + 2 190 | buf := make([]byte, 2) 191 | copy(buf, input[startOffset:offset]) 192 | val.SetUint(uint64(binary.LittleEndian.Uint16(buf))) 193 | return offset, nil 194 | } 195 | 196 | func marshalInt32(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 197 | binary.LittleEndian.PutUint32(buf[startOffset:], uint32(val.Interface().(int32))) 198 | return startOffset + 4, nil 199 | } 200 | 201 | func unmarshalInt32(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 202 | offset := startOffset + 4 203 | buf := make([]byte, 4) 204 | copy(buf, input[startOffset:offset]) 205 | val.SetInt(int64(binary.LittleEndian.Uint32(buf))) 206 | return offset, nil 207 | } 208 | 209 | func marshalUint32(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 210 | binary.LittleEndian.PutUint32(buf[startOffset:], val.Interface().(uint32)) 211 | return startOffset + 4, nil 212 | } 213 | 214 | func unmarshalUint32(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 215 | offset := startOffset + 4 216 | buf := make([]byte, 4) 217 | copy(buf, input[startOffset:offset]) 218 | val.SetUint(uint64(binary.LittleEndian.Uint32(buf))) 219 | return offset, nil 220 | } 221 | 222 | func marshalUint64(val reflect.Value, buf []byte, startOffset uint64) (uint64, error) { 223 | binary.LittleEndian.PutUint64(buf[startOffset:], val.Interface().(uint64)) 224 | return startOffset + 8, nil 225 | } 226 | 227 | func unmarshalUint64(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 228 | offset := startOffset + 8 229 | buf := make([]byte, 8) 230 | copy(buf, input[startOffset:offset]) 231 | val.SetUint(binary.LittleEndian.Uint64(buf)) 232 | return offset, nil 233 | } 234 | -------------------------------------------------------------------------------- /types/bitlist.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | 7 | "github.com/prysmaticlabs/go-bitfield" 8 | ) 9 | 10 | // BitlistRoot computes the hash tree root of a bitlist type as outlined in the 11 | // Simple Serialize official specification document. 12 | func BitlistRoot(bfield bitfield.Bitfield, maxCapacity uint64) ([32]byte, error) { 13 | limit := (maxCapacity + 255) / 256 14 | if bfield == nil || bfield.Len() == 0 { 15 | length := make([]byte, 32) 16 | root, err := bitwiseMerkleize([][]byte{}, 0, limit) 17 | if err != nil { 18 | return [32]byte{}, err 19 | } 20 | return mixInLength(root, length), nil 21 | } 22 | chunks, err := pack([][]byte{bfield.Bytes()}) 23 | if err != nil { 24 | return [32]byte{}, err 25 | } 26 | buf := new(bytes.Buffer) 27 | if err := binary.Write(buf, binary.LittleEndian, bfield.Len()); err != nil { 28 | return [32]byte{}, err 29 | } 30 | output := make([]byte, 32) 31 | copy(output, buf.Bytes()) 32 | root, err := bitwiseMerkleize(chunks, uint64(len(chunks)), limit) 33 | if err != nil { 34 | return [32]byte{}, err 35 | } 36 | return mixInLength(root, output), nil 37 | } 38 | 39 | // Bitvector4Root computes the hash tree root of a bitvector4 type as outlined in the 40 | // Simple Serialize official specification document. 41 | func Bitvector4Root(bfield bitfield.Bitfield, maxCapacity uint64) ([32]byte, error) { 42 | limit := (maxCapacity + 255) / 256 43 | if bfield == nil { 44 | return bitwiseMerkleize([][]byte{}, 0, limit) 45 | } 46 | chunks, err := pack([][]byte{bfield.Bytes()}) 47 | if err != nil { 48 | return [32]byte{}, err 49 | } 50 | return bitwiseMerkleize(chunks, uint64(len(chunks)), limit) 51 | } 52 | -------------------------------------------------------------------------------- /types/determine_size.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | // DetermineSize returns the required byte size of a buffer for 9 | // using SSZ to marshal an object. 10 | func DetermineSize(val reflect.Value) uint64 { 11 | if val.Kind() == reflect.Ptr { 12 | if val.IsNil() { 13 | return DetermineSize(reflect.New(val.Type().Elem()).Elem()) 14 | } 15 | return DetermineSize(val.Elem()) 16 | } 17 | if isVariableSizeType(val.Type()) { 18 | return determineVariableSize(val, val.Type()) 19 | } 20 | return determineFixedSize(val, val.Type()) 21 | } 22 | 23 | func isBasicType(kind reflect.Kind) bool { 24 | return kind == reflect.Bool || 25 | kind == reflect.Int32 || 26 | kind == reflect.Uint8 || 27 | kind == reflect.Uint16 || 28 | kind == reflect.Uint32 || 29 | kind == reflect.Uint64 30 | } 31 | 32 | func isBasicTypeArray(typ reflect.Type, kind reflect.Kind) bool { 33 | return kind == reflect.Array && isBasicType(typ.Elem().Kind()) 34 | } 35 | 36 | func isRootsArray(val reflect.Value, typ reflect.Type) bool { 37 | elemTyp := typ.Elem() 38 | elemKind := elemTyp.Kind() 39 | isByteArray := elemKind == reflect.Array && elemTyp.Elem().Kind() == reflect.Uint8 40 | return isByteArray && elemTyp.Len() == 32 41 | } 42 | 43 | func isVariableSizeType(typ reflect.Type) bool { 44 | kind := typ.Kind() 45 | switch { 46 | case isBasicType(kind): 47 | return false 48 | case isBasicTypeArray(typ, kind): 49 | return false 50 | case kind == reflect.Slice: 51 | return true 52 | case kind == reflect.String: 53 | return true 54 | case kind == reflect.Array: 55 | return isVariableSizeType(typ.Elem()) 56 | case kind == reflect.Struct: 57 | for i := 0; i < typ.NumField(); i++ { 58 | if strings.Contains(typ.Field(i).Name, "XXX_") { 59 | continue 60 | } 61 | f := typ.Field(i) 62 | fType, err := determineFieldType(f) 63 | if err != nil { 64 | return false 65 | } 66 | if isVariableSizeType(fType) { 67 | return true 68 | } 69 | } 70 | return false 71 | case kind == reflect.Ptr: 72 | return isVariableSizeType(typ.Elem()) 73 | } 74 | return false 75 | } 76 | 77 | func determineFixedSize(val reflect.Value, typ reflect.Type) uint64 { 78 | kind := typ.Kind() 79 | switch { 80 | case kind == reflect.Bool: 81 | return 1 82 | case kind == reflect.Uint8: 83 | return 1 84 | case kind == reflect.Uint16: 85 | return 2 86 | case kind == reflect.Uint32 || kind == reflect.Int32: 87 | return 4 88 | case kind == reflect.Uint64: 89 | return 8 90 | case kind == reflect.Array && typ.Elem().Kind() == reflect.Uint8: 91 | return uint64(typ.Len()) 92 | case kind == reflect.Slice && typ.Elem().Kind() == reflect.Uint8: 93 | return uint64(val.Len()) 94 | case kind == reflect.Array || kind == reflect.Slice: 95 | var num uint64 96 | for i := 0; i < val.Len(); i++ { 97 | num += determineFixedSize(val.Index(i), typ.Elem()) 98 | } 99 | return num 100 | case kind == reflect.Struct: 101 | totalSize := uint64(0) 102 | for i := 0; i < typ.NumField(); i++ { 103 | if strings.Contains(typ.Field(i).Name, "XXX_") { 104 | continue 105 | } 106 | f := typ.Field(i) 107 | fType, err := determineFieldType(f) 108 | if err != nil { 109 | return 0 110 | } 111 | totalSize += determineFixedSize(val.Field(i), fType) 112 | } 113 | return totalSize 114 | case kind == reflect.Ptr: 115 | if val.IsNil() { 116 | newElem := reflect.New(typ.Elem()).Elem() 117 | return determineVariableSize(newElem, newElem.Type()) 118 | } 119 | return determineFixedSize(val.Elem(), typ.Elem()) 120 | default: 121 | return 0 122 | } 123 | } 124 | 125 | func determineVariableSize(val reflect.Value, typ reflect.Type) uint64 { 126 | kind := typ.Kind() 127 | switch { 128 | case kind == reflect.Slice && typ.Elem().Kind() == reflect.Uint8: 129 | return uint64(val.Len()) 130 | case kind == reflect.String: 131 | return uint64(val.Len()) 132 | case kind == reflect.Slice || kind == reflect.Array: 133 | totalSize := uint64(0) 134 | for i := 0; i < val.Len(); i++ { 135 | varSize := DetermineSize(val.Index(i)) 136 | if isVariableSizeType(typ.Elem()) { 137 | totalSize += varSize + BytesPerLengthOffset 138 | } else { 139 | totalSize += varSize 140 | } 141 | } 142 | return totalSize 143 | case kind == reflect.Struct: 144 | totalSize := uint64(0) 145 | for i := 0; i < typ.NumField(); i++ { 146 | if strings.Contains(typ.Field(i).Name, "XXX_") { 147 | continue 148 | } 149 | f := typ.Field(i) 150 | fType, err := determineFieldType(f) 151 | if err != nil { 152 | return 0 153 | } 154 | if isVariableSizeType(fType) { 155 | varSize := determineVariableSize(val.Field(i), fType) 156 | totalSize += varSize + BytesPerLengthOffset 157 | } else { 158 | varSize := determineFixedSize(val.Field(i), fType) 159 | totalSize += varSize 160 | } 161 | } 162 | return totalSize 163 | case kind == reflect.Ptr: 164 | if val.IsNil() { 165 | newElem := reflect.New(typ.Elem()).Elem() 166 | return determineVariableSize(newElem, newElem.Type()) 167 | } 168 | return determineVariableSize(val.Elem(), val.Elem().Type()) 169 | default: 170 | return 0 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /types/factory.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | var enableCache = false 9 | 10 | // ToggleCache enables caching of ssz hash tree root. It is disabled by default. 11 | func ToggleCache(val bool) { 12 | enableCache = val 13 | } 14 | 15 | // StructFactory exports an implementation of a interface 16 | // containing helpers for marshaling/unmarshaling, and determining 17 | // the hash tree root of struct values. 18 | var StructFactory = newStructSSZ() 19 | var basicFactory = newBasicSSZ() 20 | var basicArrayFactory = newBasicArraySSZ() 21 | var rootsArrayFactory = newRootsArraySSZ() 22 | var compositeArrayFactory = newCompositeArraySSZ() 23 | var basicSliceFactory = newBasicSliceSSZ() 24 | var stringFactory = newStringSSZ() 25 | var compositeSliceFactory = newCompositeSliceSSZ() 26 | 27 | // SSZAble defines a type which can marshal/unmarshal and compute its 28 | // hash tree root according to the Simple Serialize specification. 29 | // See: https://github.com/ethereum/eth2.0-specs/blob/v0.8.2/specs/simple-serialize.md. 30 | type SSZAble interface { 31 | Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) 32 | Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) 33 | Unmarshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) 34 | } 35 | 36 | // SSZFactory recursively walks down a type and determines which SSZ-able 37 | // core type it belongs to, and then returns and implementation of 38 | // SSZ-able that contains marshal, unmarshal, and hash tree root related 39 | // functions for use. 40 | func SSZFactory(val reflect.Value, typ reflect.Type) (SSZAble, error) { 41 | kind := typ.Kind() 42 | switch { 43 | case isBasicType(kind) || isBasicTypeArray(typ, typ.Kind()): 44 | return basicFactory, nil 45 | case kind == reflect.String: 46 | return stringFactory, nil 47 | case kind == reflect.Slice: 48 | switch { 49 | case isBasicType(typ.Elem().Kind()): 50 | return basicSliceFactory, nil 51 | case !isVariableSizeType(typ.Elem()): 52 | return basicSliceFactory, nil 53 | default: 54 | return compositeSliceFactory, nil 55 | } 56 | case kind == reflect.Array: 57 | switch { 58 | case isRootsArray(val, typ): 59 | return rootsArrayFactory, nil 60 | case isBasicTypeArray(typ.Elem(), typ.Elem().Kind()): 61 | return basicArrayFactory, nil 62 | case !isVariableSizeType(typ.Elem()): 63 | return basicArrayFactory, nil 64 | default: 65 | return compositeArrayFactory, nil 66 | } 67 | case kind == reflect.Struct: 68 | return StructFactory, nil 69 | case kind == reflect.Ptr: 70 | return SSZFactory(val.Elem(), typ.Elem()) 71 | default: 72 | return nil, fmt.Errorf("unsupported kind: %v", kind) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /types/helpers.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "reflect" 7 | 8 | "github.com/minio/sha256-simd" 9 | "github.com/protolambda/zssz/htr" 10 | "github.com/protolambda/zssz/merkle" 11 | ) 12 | 13 | var ( 14 | // BytesPerChunk for an SSZ serialized object. 15 | BytesPerChunk = 32 16 | // BytesPerLengthOffset defines a constant for off-setting serialized chunks. 17 | BytesPerLengthOffset = uint64(4) 18 | zeroHashes = make([][32]byte, 100) 19 | ) 20 | 21 | func init() { 22 | for i := 1; i < 100; i++ { 23 | leaf := append(zeroHashes[i-1][:], zeroHashes[i-1][:]...) 24 | result := hash(leaf) 25 | copy(zeroHashes[i][:], result[:]) 26 | } 27 | } 28 | 29 | // Given ordered BYTES_PER_CHUNK-byte chunks, if necessary utilize zero chunks so that the 30 | // number of chunks is a power of two, Merkleize the chunks, and return the root. 31 | // Note that merkleize on a single chunk is simply that chunk, i.e. the identity 32 | // when the number of chunks is one. 33 | func bitwiseMerkleize(chunks [][]byte, count uint64, limit uint64) ([32]byte, error) { 34 | if count > limit { 35 | return [32]byte{}, errors.New("merkleizing list that is too large, over limit") 36 | } 37 | hasher := htr.HashFn(hash) 38 | leafIndexer := func(i uint64) []byte { 39 | return chunks[i] 40 | } 41 | return merkle.Merkleize(hasher, count, limit, leafIndexer), nil 42 | } 43 | 44 | // Given ordered objects of the same basic type, serialize them, pack them into BYTES_PER_CHUNK-byte 45 | // chunks, right-pad the last chunk with zero bytes, and return the chunks. 46 | // Basic types are either bool, or uintN where N = {8, 16, 32, 64, 128, 256}. 47 | // 48 | // Important: due to limitations in Go generics, we will assume the input is already 49 | // a list of SSZ-encoded objects of the same type. 50 | func pack(serializedItems [][]byte) ([][]byte, error) { 51 | areAllEmpty := true 52 | for _, item := range serializedItems { 53 | if !bytes.Equal(item, []byte{}) { 54 | areAllEmpty = false 55 | break 56 | } 57 | } 58 | // If there are no items, we return an empty chunk. 59 | if len(serializedItems) == 0 || areAllEmpty { 60 | emptyChunk := make([]byte, BytesPerChunk) 61 | return [][]byte{emptyChunk}, nil 62 | } else if len(serializedItems[0]) == BytesPerChunk { 63 | // If each item has exactly BYTES_PER_CHUNK length, we return the list of serialized items. 64 | return serializedItems, nil 65 | } 66 | // We flatten the list in order to pack its items into byte chunks correctly. 67 | orderedItems := []byte{} 68 | for _, item := range serializedItems { 69 | orderedItems = append(orderedItems, item...) 70 | } 71 | numItems := len(orderedItems) 72 | chunks := [][]byte{} 73 | for i := 0; i < numItems; i += BytesPerChunk { 74 | j := i + BytesPerChunk 75 | // We create our upper bound index of the chunk, if it is greater than numItems, 76 | // we set it as numItems itself. 77 | if j > numItems { 78 | j = numItems 79 | } 80 | // We create chunks from the list of items based on the 81 | // indices determined above. 82 | chunks = append(chunks, orderedItems[i:j]) 83 | } 84 | // Right-pad the last chunk with zero bytes if it does not 85 | // have length BytesPerChunk. 86 | lastChunk := chunks[len(chunks)-1] 87 | for len(lastChunk) < BytesPerChunk { 88 | lastChunk = append(lastChunk, 0) 89 | } 90 | chunks[len(chunks)-1] = lastChunk 91 | return chunks, nil 92 | } 93 | 94 | // Given a Merkle root root and a length length ("uint256" little-endian serialization) 95 | // return hash(root + length). 96 | func mixInLength(root [32]byte, length []byte) [32]byte { 97 | var hash [32]byte 98 | h := sha256.New() 99 | h.Write(root[:]) 100 | h.Write(length) 101 | // The hash interface never returns an error, for that reason 102 | // we are not handling the error below. For reference, it is 103 | // stated here https://golang.org/pkg/hash/#Hash 104 | // #nosec G104 105 | h.Sum(hash[:0]) 106 | return hash 107 | } 108 | 109 | // Instantiates a reflect value which may not have a concrete type to have a concrete type 110 | // for unmarshaling. For example, we cannot unmarshal into a nil value - instead, it must have 111 | // a concrete type even if all of its values are zero values. 112 | func instantiateConcreteTypeForElement(val reflect.Value, typ reflect.Type) { 113 | val.Set(reflect.New(typ)) 114 | } 115 | 116 | // Grows a slice to a new length and instantiates the element at length-1 with a concrete type 117 | // accordingly if it is set to a pointer. 118 | func growConcreteSliceType(val reflect.Value, typ reflect.Type, length int) { 119 | newVal := reflect.MakeSlice(typ, length, length) 120 | reflect.Copy(newVal, val) 121 | val.Set(newVal) 122 | if val.Index(length-1).Kind() == reflect.Ptr { 123 | instantiateConcreteTypeForElement(val.Index(length-1), typ.Elem().Elem()) 124 | } 125 | } 126 | 127 | // hash defines a function that returns the sha256 hash of the data passed in. 128 | func hash(data []byte) [32]byte { 129 | return sha256.Sum256(data) 130 | } 131 | 132 | func growSliceFromSizeTags(val reflect.Value, sizes []uint64) reflect.Value { 133 | if len(sizes) == 0 { 134 | return val 135 | } 136 | finalValue := reflect.MakeSlice(val.Type(), int(sizes[0]), int(sizes[0])) 137 | for i := 0; i < int(sizes[0]); i++ { 138 | intermediate := growSliceFromSizeTags(finalValue.Index(i), sizes[1:]) 139 | finalValue.Index(i).Set(intermediate) 140 | } 141 | return finalValue 142 | } 143 | 144 | func toBytes32(x []byte) [32]byte { 145 | var y [32]byte 146 | copy(y[:], x) 147 | return y 148 | } 149 | -------------------------------------------------------------------------------- /types/helpers_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestPack_NoItems(t *testing.T) { 9 | output, err := pack([][]byte{}) 10 | if err != nil { 11 | t.Fatalf("pack() error = %v", err) 12 | } 13 | if len(output[0]) != BytesPerChunk { 14 | t.Errorf("Expected empty input to return an empty chunk, received %v", output) 15 | } 16 | } 17 | 18 | func TestPack_ExactBytePerChunkLength(t *testing.T) { 19 | input := [][]byte{} 20 | for i := 0; i < 10; i++ { 21 | item := make([]byte, BytesPerChunk) 22 | input = append(input, item) 23 | } 24 | output, err := pack(input) 25 | if err != nil { 26 | t.Fatalf("pack() error = %v", err) 27 | } 28 | if len(output) != 10 { 29 | t.Errorf("Expected empty input to return an empty chunk, received %v", output) 30 | } 31 | if !reflect.DeepEqual(output, input) { 32 | t.Errorf("pack() = %v, want %v", output, input) 33 | } 34 | } 35 | 36 | func TestPack_OK(t *testing.T) { 37 | tests := []struct { 38 | name string 39 | input [][]byte 40 | output [][]byte 41 | }{ 42 | { 43 | name: "an item having less than BytesPerChunk should return a padded chunk", 44 | input: [][]byte{make([]byte, BytesPerChunk-4)}, 45 | output: [][]byte{make([]byte, BytesPerChunk)}, 46 | }, 47 | { 48 | name: "two items having less than BytesPerChunk should return two chunks", 49 | input: [][]byte{make([]byte, BytesPerChunk-5), make([]byte, BytesPerChunk-5)}, 50 | output: [][]byte{make([]byte, BytesPerChunk), make([]byte, BytesPerChunk)}, 51 | }, 52 | { 53 | name: "two items with length BytesPerChunk/2 should return one chunk", 54 | input: [][]byte{make([]byte, BytesPerChunk/2), make([]byte, BytesPerChunk/2)}, 55 | output: [][]byte{make([]byte, BytesPerChunk)}, 56 | }, 57 | { 58 | name: "an item with length BytesPerChunk*2 should return two chunks", 59 | input: [][]byte{make([]byte, BytesPerChunk*2)}, 60 | output: [][]byte{make([]byte, BytesPerChunk), make([]byte, BytesPerChunk)}, 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | got, err := pack(tt.input) 66 | if err != nil { 67 | t.Fatalf("pack() error = %v", err) 68 | } 69 | if !reflect.DeepEqual(got, tt.output) { 70 | t.Errorf("pack() = %v, want %v", got, tt.output) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestMerkleize_Identity(t *testing.T) { 77 | want := make([]byte, BytesPerChunk) 78 | output, err := bitwiseMerkleize([][]byte{}, 0, 1) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | if !reflect.DeepEqual(output[:], want) { 83 | t.Errorf("merkleize() = %v, want %v", output, want) 84 | } 85 | } 86 | 87 | func TestMerkleize_OK(t *testing.T) { 88 | chunk := make([]byte, BytesPerChunk) 89 | secondLayerRoot := hash(append(chunk, chunk...)) 90 | thirdLayerRoot := hash(append(secondLayerRoot[:], secondLayerRoot[:]...)) 91 | tests := []struct { 92 | name string 93 | input [][]byte 94 | output [32]byte 95 | }{ 96 | { 97 | name: "two elements should return the hash of their concatenation", 98 | input: [][]byte{make([]byte, BytesPerChunk), make([]byte, BytesPerChunk)}, 99 | output: hash(make([]byte, BytesPerChunk*2)), 100 | }, 101 | { 102 | name: "four chunks should return the Merkle root of a three layer trie", 103 | input: [][]byte{chunk, chunk, chunk, chunk}, 104 | output: thirdLayerRoot, 105 | }, 106 | { 107 | name: "three chunks should pad until there are four chunks", 108 | input: [][]byte{chunk, chunk, chunk}, 109 | output: thirdLayerRoot, 110 | }, 111 | } 112 | for _, tt := range tests { 113 | t.Run(tt.name, func(t *testing.T) { 114 | got, err := bitwiseMerkleize(tt.input, uint64(len(tt.input)), uint64(len(tt.input))) 115 | if err != nil { 116 | t.Fatal(err) 117 | } 118 | if !reflect.DeepEqual(got, tt.output) { 119 | t.Errorf("merkleize() = %v, want %v", got, tt.output) 120 | } 121 | }) 122 | } 123 | } 124 | func BenchmarkPack(b *testing.B) { 125 | input := [][]byte{make([]byte, BytesPerChunk*8000)} 126 | for n := 0; n < b.N; n++ { 127 | if _, err := pack(input); err != nil { 128 | b.Fatal(err) 129 | } 130 | } 131 | } 132 | 133 | func BenchmarkMerkleize(b *testing.B) { 134 | input := make([][]byte, 8000) 135 | for i := 0; i < len(input); i++ { 136 | input[i] = make([]byte, BytesPerChunk) 137 | } 138 | for n := 0; n < b.N; n++ { 139 | if _, err := bitwiseMerkleize(input, uint64(len(input)), 1); err != nil { 140 | b.Fatal(err) 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /types/slice_basic.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "reflect" 7 | ) 8 | 9 | type basicSliceSSZ struct{} 10 | 11 | func newBasicSliceSSZ() *basicSliceSSZ { 12 | return &basicSliceSSZ{} 13 | } 14 | 15 | func (b *basicSliceSSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 16 | var factory SSZAble 17 | var limit uint64 18 | var elemSize uint64 19 | var err error 20 | numItems := val.Len() 21 | if numItems > 0 { 22 | factory, err = SSZFactory(val.Index(0), typ.Elem()) 23 | if err != nil { 24 | return [32]byte{}, err 25 | } 26 | } 27 | 28 | if isBasicType(typ.Elem().Kind()) { 29 | elemSize = determineFixedSize(val, typ.Elem()) 30 | } else { 31 | elemSize = 32 32 | } 33 | limit = (maxCapacity*elemSize + 31) / 32 34 | if limit == 0 { 35 | if numItems == 0 { 36 | limit = 1 37 | } else { 38 | limit = uint64(numItems) 39 | } 40 | } 41 | leaves := make([][]byte, numItems) 42 | for i := 0; i < numItems; i++ { 43 | if isBasicType(val.Index(i).Kind()) { 44 | innerBuf := make([]byte, elemSize) 45 | if _, err = factory.Marshal(val.Index(i), typ.Elem(), innerBuf, 0); err != nil { 46 | return [32]byte{}, err 47 | } 48 | leaves[i] = innerBuf 49 | } else { 50 | r, err := factory.Root(val.Index(i), typ.Elem(), fieldName, 0) 51 | if err != nil { 52 | return [32]byte{}, err 53 | } 54 | leaves[i] = r[:] 55 | } 56 | } 57 | chunks, err := pack(leaves) 58 | if err != nil { 59 | return [32]byte{}, err 60 | } 61 | buf := new(bytes.Buffer) 62 | if err := binary.Write(buf, binary.LittleEndian, uint64(val.Len())); err != nil { 63 | return [32]byte{}, err 64 | } 65 | output := make([]byte, 32) 66 | copy(output, buf.Bytes()) 67 | merkleRoot, err := bitwiseMerkleize(chunks, uint64(len(chunks)), limit) 68 | if err != nil { 69 | return [32]byte{}, err 70 | } 71 | return mixInLength(merkleRoot, output), nil 72 | } 73 | 74 | func (b *basicSliceSSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 75 | index := startOffset 76 | var err error 77 | if val.Len() == 0 { 78 | return index, nil 79 | } 80 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 81 | if err != nil { 82 | return 0, err 83 | } 84 | for i := 0; i < val.Len(); i++ { 85 | index, err = factory.Marshal(val.Index(i), typ.Elem(), buf, index) 86 | if err != nil { 87 | return 0, err 88 | } 89 | } 90 | return index, nil 91 | } 92 | 93 | func (b *basicSliceSSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 94 | if len(input) == 0 { 95 | newVal := reflect.MakeSlice(val.Type(), 0, 0) 96 | val.Set(newVal) 97 | return 0, nil 98 | } 99 | // If there are struct tags that specify a different type, we handle accordingly. 100 | if val.Type() != typ { 101 | sizes := []uint64{1} 102 | innerElement := typ.Elem() 103 | for { 104 | if innerElement.Kind() == reflect.Slice { 105 | sizes = append(sizes, 0) 106 | innerElement = innerElement.Elem() 107 | } else if innerElement.Kind() == reflect.Array { 108 | sizes = append(sizes, uint64(innerElement.Len())) 109 | innerElement = innerElement.Elem() 110 | } else { 111 | break 112 | } 113 | } 114 | // If the item is a slice, we grow it accordingly based on the size tags. 115 | result := growSliceFromSizeTags(val, sizes) 116 | reflect.Copy(result, val) 117 | val.Set(result) 118 | } else { 119 | growConcreteSliceType(val, val.Type(), 1) 120 | } 121 | 122 | var err error 123 | index := startOffset 124 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 125 | if err != nil { 126 | return 0, err 127 | } 128 | index, err = factory.Unmarshal(val.Index(0), typ.Elem(), input, index) 129 | if err != nil { 130 | return 0, err 131 | } 132 | 133 | elementSize := index - startOffset 134 | endOffset := uint64(len(input)) / elementSize 135 | if val.Type() != typ { 136 | sizes := []uint64{endOffset} 137 | innerElement := typ.Elem() 138 | for { 139 | if innerElement.Kind() == reflect.Slice { 140 | sizes = append(sizes, 0) 141 | innerElement = innerElement.Elem() 142 | } else if innerElement.Kind() == reflect.Array { 143 | sizes = append(sizes, uint64(innerElement.Len())) 144 | innerElement = innerElement.Elem() 145 | } else { 146 | break 147 | } 148 | } 149 | // If the item is a slice, we grow it accordingly based on the size tags. 150 | result := growSliceFromSizeTags(val, sizes) 151 | reflect.Copy(result, val) 152 | val.Set(result) 153 | } 154 | i := uint64(1) 155 | for i < endOffset { 156 | if val.Type() == typ { 157 | growConcreteSliceType(val, val.Type(), int(i)+1) 158 | } 159 | index, err = factory.Unmarshal(val.Index(int(i)), typ.Elem(), input, index) 160 | if err != nil { 161 | return 0, err 162 | } 163 | i++ 164 | } 165 | return index, nil 166 | } 167 | -------------------------------------------------------------------------------- /types/slice_composite.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "reflect" 7 | ) 8 | 9 | type compositeSliceSSZ struct{} 10 | 11 | func newCompositeSliceSSZ() *compositeSliceSSZ { 12 | return &compositeSliceSSZ{} 13 | } 14 | 15 | func (b *compositeSliceSSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 16 | output := make([]byte, 32) 17 | if val.Len() == 0 && maxCapacity == 0 { 18 | root, err := bitwiseMerkleize([][]byte{}, 0, 0) 19 | if err != nil { 20 | return [32]byte{}, err 21 | } 22 | return mixInLength(root, output), nil 23 | } 24 | numItems := val.Len() 25 | var factory SSZAble 26 | var err error 27 | if numItems > 0 { 28 | factory, err = SSZFactory(val.Index(0), typ.Elem()) 29 | if err != nil { 30 | return [32]byte{}, err 31 | } 32 | } 33 | roots := make([][]byte, numItems) 34 | for i := 0; i < numItems; i++ { 35 | r, err := factory.Root(val.Index(i), typ.Elem(), fieldName, 0) 36 | if err != nil { 37 | return [32]byte{}, err 38 | } 39 | roots[i] = r[:] 40 | } 41 | chunks, err := pack(roots) 42 | if err != nil { 43 | return [32]byte{}, err 44 | } 45 | buf := new(bytes.Buffer) 46 | if err := binary.Write(buf, binary.LittleEndian, uint64(val.Len())); err != nil { 47 | return [32]byte{}, err 48 | } 49 | copy(output, buf.Bytes()) 50 | objLen := maxCapacity 51 | if maxCapacity == 0 { 52 | objLen = uint64(val.Len()) 53 | } 54 | root, err := bitwiseMerkleize(chunks, uint64(len(chunks)), objLen) 55 | if err != nil { 56 | return [32]byte{}, err 57 | } 58 | return mixInLength(root, output), nil 59 | } 60 | 61 | func (b *compositeSliceSSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 62 | index := startOffset 63 | if val.Len() == 0 { 64 | return index, nil 65 | } 66 | factory, err := SSZFactory(val.Index(0), typ.Elem()) 67 | if err != nil { 68 | return 0, err 69 | } 70 | if !isVariableSizeType(typ.Elem()) { 71 | for i := 0; i < val.Len(); i++ { 72 | // If each element is not variable size, we simply encode sequentially and write 73 | // into the buffer at the last index we wrote at. 74 | index, err = factory.Marshal(val.Index(i), typ.Elem(), buf, index) 75 | if err != nil { 76 | return 0, err 77 | } 78 | } 79 | return index, nil 80 | } 81 | fixedIndex := index 82 | currentOffsetIndex := startOffset + uint64(val.Len())*BytesPerLengthOffset 83 | nextOffsetIndex := currentOffsetIndex 84 | // If the elements are variable size, we need to include offset indices 85 | // in the serialized output list. 86 | for i := 0; i < val.Len(); i++ { 87 | nextOffsetIndex, err = factory.Marshal(val.Index(i), typ.Elem(), buf, currentOffsetIndex) 88 | if err != nil { 89 | return 0, err 90 | } 91 | // Write the offset. 92 | offsetBuf := make([]byte, BytesPerLengthOffset) 93 | binary.LittleEndian.PutUint32(offsetBuf, uint32(currentOffsetIndex-startOffset)) 94 | copy(buf[fixedIndex:fixedIndex+BytesPerLengthOffset], offsetBuf) 95 | 96 | // We increase the offset indices accordingly. 97 | currentOffsetIndex = nextOffsetIndex 98 | fixedIndex += BytesPerLengthOffset 99 | } 100 | index = currentOffsetIndex 101 | return index, nil 102 | } 103 | 104 | func (b *compositeSliceSSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 105 | if len(input) == 0 { 106 | newVal := reflect.MakeSlice(val.Type(), 0, 0) 107 | val.Set(newVal) 108 | return 0, nil 109 | } 110 | growConcreteSliceType(val, typ, 1) 111 | endOffset := uint64(len(input)) 112 | 113 | currentIndex := startOffset 114 | nextIndex := currentIndex 115 | offsetVal := input[startOffset : startOffset+BytesPerLengthOffset] 116 | firstOffset := startOffset + uint64(binary.LittleEndian.Uint32(offsetVal)) 117 | currentOffset := firstOffset 118 | nextOffset := currentOffset 119 | i := 0 120 | for currentIndex < firstOffset { 121 | nextIndex = currentIndex + BytesPerLengthOffset 122 | if nextIndex == firstOffset { 123 | nextOffset = endOffset 124 | } else { 125 | nextOffsetVal := input[nextIndex : nextIndex+BytesPerLengthOffset] 126 | nextOffset = startOffset + uint64(binary.LittleEndian.Uint32(nextOffsetVal)) 127 | } 128 | if nextOffset < currentOffset { 129 | break 130 | } 131 | // We grow the slice's size to accommodate a new element being unmarshaled. 132 | growConcreteSliceType(val, typ, i+1) 133 | factory, err := SSZFactory(val.Index(i), typ.Elem()) 134 | if err != nil { 135 | return 0, err 136 | } 137 | if _, err := factory.Unmarshal(val.Index(i), typ.Elem(), input[currentOffset:nextOffset], 0); err != nil { 138 | return 0, err 139 | } 140 | i++ 141 | currentIndex = nextIndex 142 | currentOffset = nextOffset 143 | } 144 | return currentIndex, nil 145 | } 146 | -------------------------------------------------------------------------------- /types/string.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "reflect" 7 | ) 8 | 9 | type stringSSZ struct{} 10 | 11 | func newStringSSZ() *stringSSZ { 12 | return &stringSSZ{} 13 | } 14 | 15 | func (b *stringSSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 16 | var err error 17 | numItems := val.Len() 18 | elemSize := uint64(1) 19 | limit := (maxCapacity*elemSize + 31) / 32 20 | if limit == 0 { 21 | limit = 1 22 | } 23 | leaves := make([][]byte, numItems) 24 | for i := 0; i < numItems; i++ { 25 | innerBuf := make([]byte, elemSize) 26 | if _, err = marshalUint8(val.Index(i), innerBuf, 0); err != nil { 27 | return [32]byte{}, err 28 | } 29 | leaves[i] = innerBuf 30 | } 31 | chunks, err := pack(leaves) 32 | if err != nil { 33 | return [32]byte{}, err 34 | } 35 | buf := new(bytes.Buffer) 36 | if err := binary.Write(buf, binary.LittleEndian, uint64(numItems)); err != nil { 37 | return [32]byte{}, err 38 | } 39 | output := make([]byte, 32) 40 | copy(output, buf.Bytes()) 41 | merkleRoot, err := bitwiseMerkleize(chunks, uint64(len(chunks)), limit) 42 | if err != nil { 43 | return [32]byte{}, err 44 | } 45 | return mixInLength(merkleRoot, output), nil 46 | } 47 | 48 | func (b *stringSSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 49 | for i := 0; i < val.Len(); i++ { 50 | buf[int(startOffset)+i] = uint8(val.Index(i).Uint()) 51 | } 52 | return startOffset + uint64(val.Len()), nil 53 | } 54 | 55 | func (b *stringSSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 56 | offset := startOffset + uint64(len(input)) 57 | val.SetString(string(input[startOffset:offset])) 58 | return offset, nil 59 | } 60 | -------------------------------------------------------------------------------- /types/struct.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "reflect" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/pkg/errors" 11 | "github.com/prysmaticlabs/go-bitfield" 12 | ) 13 | 14 | // UnboundedSSZFieldSizeMarker is the character used to specify a ssz field should have 15 | // unbounded size, which is useful when describing slices of arrays such as [][32]byte. 16 | // The ssz struct tag for such field type would be `ssz:"size=?,32"`. A question mark 17 | // is chosen as the default value given its simplicity to represent unbounded size. 18 | var UnboundedSSZFieldSizeMarker = "?" 19 | 20 | type structSSZ struct{} 21 | 22 | func newStructSSZ() *structSSZ { 23 | return &structSSZ{} 24 | } 25 | 26 | func (b *structSSZ) Root(val reflect.Value, typ reflect.Type, fieldName string, maxCapacity uint64) ([32]byte, error) { 27 | if typ.Kind() == reflect.Ptr { 28 | if val.IsNil() { 29 | instance := reflect.New(typ.Elem()).Elem() 30 | return b.Root(instance, instance.Type(), fieldName, maxCapacity) 31 | } 32 | return b.Root(val.Elem(), typ.Elem(), fieldName, maxCapacity) 33 | } 34 | numFields := typ.NumField() 35 | return b.FieldsHasher(val, typ, numFields) 36 | } 37 | 38 | func (b *structSSZ) FieldsHasher(val reflect.Value, typ reflect.Type, numFields int) ([32]byte, error) { 39 | roots := make([][]byte, numFields) 40 | var err error 41 | totalCountedFields := uint64(0) 42 | structName := typ.Name() 43 | for i := 0; i < numFields; i++ { 44 | // We skip protobuf related metadata fields. 45 | if strings.HasPrefix(typ.Field(i).Name, "XXX_") { 46 | continue 47 | } 48 | totalCountedFields++ 49 | fCapacity := determineFieldCapacity(typ.Field(i)) 50 | if b, ok := val.Field(i).Interface().(bitfield.Bitlist); ok { 51 | r, err := BitlistRoot(b, fCapacity) 52 | if err != nil { 53 | return [32]byte{}, nil 54 | } 55 | roots[i] = r[:] 56 | continue 57 | } 58 | fType, err := determineFieldType(typ.Field(i)) 59 | if err != nil { 60 | return [32]byte{}, err 61 | } 62 | factory, err := SSZFactory(val.Field(i), fType) 63 | if err != nil { 64 | return [32]byte{}, err 65 | } 66 | r, err := factory.Root(val.Field(i), fType, structName+"."+typ.Field(i).Name, fCapacity) 67 | if err != nil { 68 | return [32]byte{}, err 69 | } 70 | roots[i] = r[:] 71 | } 72 | root, err := bitwiseMerkleize(roots, totalCountedFields, totalCountedFields) 73 | if err != nil { 74 | return [32]byte{}, err 75 | } 76 | return root, nil 77 | } 78 | 79 | func (b *structSSZ) Marshal(val reflect.Value, typ reflect.Type, buf []byte, startOffset uint64) (uint64, error) { 80 | if typ.Kind() == reflect.Ptr { 81 | if val.IsNil() { 82 | newVal := reflect.New(typ.Elem()).Elem() 83 | return b.Marshal(newVal, newVal.Type(), buf, startOffset) 84 | } 85 | return b.Marshal(val.Elem(), typ.Elem(), buf, startOffset) 86 | } 87 | fixedIndex := startOffset 88 | fixedLength := uint64(0) 89 | // For every field, we add up the total length of the items depending if they 90 | // are variable or fixed-size fields. 91 | for i := 0; i < typ.NumField(); i++ { 92 | // We skip protobuf related metadata fields. 93 | if strings.Contains(typ.Field(i).Name, "XXX_") { 94 | continue 95 | } 96 | fType, err := determineFieldType(typ.Field(i)) 97 | if err != nil { 98 | return 0, err 99 | } 100 | if isVariableSizeType(fType) { 101 | fixedLength += BytesPerLengthOffset 102 | } else { 103 | if val.Type().Kind() == reflect.Ptr && val.IsNil() { 104 | elem := reflect.New(val.Type().Elem()).Elem() 105 | fixedLength += determineFixedSize(elem, fType) 106 | } else { 107 | fixedLength += determineFixedSize(val.Field(i), fType) 108 | } 109 | } 110 | } 111 | currentOffsetIndex := startOffset + fixedLength 112 | nextOffsetIndex := currentOffsetIndex 113 | for i := 0; i < typ.NumField(); i++ { 114 | // We skip protobuf related metadata fields. 115 | if strings.Contains(typ.Field(i).Name, "XXX_") { 116 | continue 117 | } 118 | fType, err := determineFieldType(typ.Field(i)) 119 | if err != nil { 120 | return 0, err 121 | } 122 | factory, err := SSZFactory(val.Field(i), fType) 123 | if err != nil { 124 | return 0, err 125 | } 126 | if !isVariableSizeType(fType) { 127 | fixedIndex, err = factory.Marshal(val.Field(i), fType, buf, fixedIndex) 128 | if err != nil { 129 | return 0, err 130 | } 131 | } else { 132 | nextOffsetIndex, err = factory.Marshal(val.Field(i), fType, buf, currentOffsetIndex) 133 | if err != nil { 134 | return 0, err 135 | } 136 | // Write the offset. 137 | offsetBuf := make([]byte, BytesPerLengthOffset) 138 | binary.LittleEndian.PutUint32(offsetBuf, uint32(currentOffsetIndex-startOffset)) 139 | copy(buf[fixedIndex:fixedIndex+BytesPerLengthOffset], offsetBuf) 140 | 141 | // We increase the offset indices accordingly. 142 | currentOffsetIndex = nextOffsetIndex 143 | fixedIndex += BytesPerLengthOffset 144 | } 145 | } 146 | return currentOffsetIndex, nil 147 | } 148 | 149 | func (b *structSSZ) Unmarshal(val reflect.Value, typ reflect.Type, input []byte, startOffset uint64) (uint64, error) { 150 | if typ.Kind() == reflect.Ptr { 151 | if val.IsNil() { 152 | return startOffset, nil 153 | } 154 | return b.Unmarshal(val.Elem(), typ.Elem(), input, startOffset) 155 | } 156 | endOffset := uint64(len(input)) 157 | currentIndex := startOffset 158 | nextIndex := currentIndex 159 | numFields := 0 160 | 161 | for i := 0; i < typ.NumField(); i++ { 162 | // We skip protobuf related metadata fields. 163 | if strings.Contains(typ.Field(i).Name, "XXX_") { 164 | continue 165 | } 166 | numFields++ 167 | } 168 | 169 | fixedSizes := make(map[int]uint64) 170 | for i := 0; i < numFields; i++ { 171 | fType, err := determineFieldType(typ.Field(i)) 172 | if err != nil { 173 | return 0, err 174 | } 175 | if isVariableSizeType(fType) { 176 | continue 177 | } 178 | if val.Field(i).Kind() == reflect.Ptr { 179 | instantiateConcreteTypeForElement(val.Field(i), fType.Elem()) 180 | } 181 | concreteVal := val.Field(i) 182 | sszSizeTags, hasTags, err := parseSSZFieldTags(typ.Field(i)) 183 | if err != nil { 184 | return 0, err 185 | } 186 | if hasTags { 187 | concreteType := inferFieldTypeFromSizeTags(typ.Field(i), sszSizeTags) 188 | concreteVal = reflect.New(concreteType).Elem() 189 | // If the item is a slice, we grow it accordingly based on the size tags. 190 | if val.Field(i).Kind() == reflect.Slice { 191 | result := growSliceFromSizeTags(val.Field(i), sszSizeTags) 192 | val.Field(i).Set(result) 193 | } 194 | } 195 | fixedSz := determineFixedSize(concreteVal, fType) 196 | fixedSizes[i] = fixedSz 197 | } 198 | 199 | offsets := make([]uint64, 0) 200 | offsetIndexCounter := startOffset 201 | for i := 0; i < numFields; i++ { 202 | if item, ok := fixedSizes[i]; ok { 203 | offsetIndexCounter += item 204 | } else { 205 | if offsetIndexCounter+BytesPerLengthOffset > uint64(len(input)) { 206 | offsetIndexCounter += BytesPerLengthOffset 207 | continue 208 | } 209 | offsetVal := input[offsetIndexCounter : offsetIndexCounter+BytesPerLengthOffset] 210 | offsets = append(offsets, startOffset+uint64(binary.LittleEndian.Uint32(offsetVal))) 211 | offsetIndexCounter += BytesPerLengthOffset 212 | } 213 | } 214 | offsets = append(offsets, endOffset) 215 | offsetIndex := uint64(0) 216 | for i := 0; i < numFields; i++ { 217 | fType, err := determineFieldType(typ.Field(i)) 218 | if err != nil { 219 | return 0, err 220 | } 221 | if val.Field(i).Kind() == reflect.Ptr { 222 | instantiateConcreteTypeForElement(val.Field(i), fType.Elem()) 223 | } 224 | factory, err := SSZFactory(val.Field(i), fType) 225 | if err != nil { 226 | return 0, err 227 | } 228 | if item, ok := fixedSizes[i]; ok { 229 | if item == 0 { 230 | continue 231 | } 232 | nextIndex = currentIndex + item 233 | if _, err := factory.Unmarshal(val.Field(i), fType, input[currentIndex:nextIndex], 0); err != nil { 234 | return 0, err 235 | } 236 | currentIndex = nextIndex 237 | } else { 238 | firstOff := offsets[offsetIndex] 239 | if firstOff == uint64(len(input)) { 240 | currentIndex += BytesPerLengthOffset 241 | continue 242 | } 243 | nextOff := offsets[offsetIndex+1] 244 | if nextOff > uint64(len(input)) { 245 | return 0, fmt.Errorf("slice bounds out of range [%d:%d]", firstOff, nextOff) 246 | } 247 | if _, err := factory.Unmarshal(val.Field(i), fType, input[firstOff:nextOff], 0); err != nil { 248 | return 0, err 249 | } 250 | offsetIndex++ 251 | currentIndex += BytesPerLengthOffset 252 | } 253 | } 254 | return currentIndex, nil 255 | } 256 | 257 | func determineFieldType(field reflect.StructField) (reflect.Type, error) { 258 | fieldSizeTags, exists, err := parseSSZFieldTags(field) 259 | if err != nil { 260 | return nil, errors.Wrap(err, "could not parse ssz struct field tags") 261 | } 262 | if exists { 263 | // If the field does indeed specify ssz struct tags, we infer the field's type. 264 | return inferFieldTypeFromSizeTags(field, fieldSizeTags), nil 265 | } 266 | return field.Type, nil 267 | } 268 | 269 | func determineFieldCapacity(field reflect.StructField) uint64 { 270 | tag, exists := field.Tag.Lookup("ssz-max") 271 | if !exists { 272 | return 0 273 | } 274 | val, err := strconv.ParseUint(tag, 10, 64) 275 | if err != nil { 276 | return 0 277 | } 278 | return val 279 | } 280 | 281 | func parseSSZFieldTags(field reflect.StructField) ([]uint64, bool, error) { 282 | tag, exists := field.Tag.Lookup("ssz-size") 283 | if !exists { 284 | return nil, false, nil 285 | } 286 | items := strings.Split(tag, ",") 287 | sizes := make([]uint64, len(items)) 288 | var err error 289 | for i := 0; i < len(items); i++ { 290 | // If a field is unbounded, we mark it with a size of 0. 291 | if items[i] == UnboundedSSZFieldSizeMarker { 292 | sizes[i] = 0 293 | continue 294 | } 295 | sizes[i], err = strconv.ParseUint(items[i], 10, 64) 296 | if err != nil { 297 | return nil, false, err 298 | } 299 | } 300 | return sizes, true, nil 301 | } 302 | 303 | func inferFieldTypeFromSizeTags(field reflect.StructField, sizes []uint64) reflect.Type { 304 | innerElement := field.Type.Elem() 305 | for i := 1; i < len(sizes); i++ { 306 | innerElement = innerElement.Elem() 307 | } 308 | currentType := innerElement 309 | for i := len(sizes) - 1; i >= 0; i-- { 310 | if sizes[i] == 0 { 311 | currentType = reflect.SliceOf(currentType) 312 | } else { 313 | currentType = reflect.ArrayOf(int(sizes[i]), currentType) 314 | } 315 | } 316 | return currentType 317 | } 318 | -------------------------------------------------------------------------------- /types/struct_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | type structWithTags struct { 9 | NestedItem [][][][]byte `ssz-size:"4,1,4,1"` 10 | UnboundedItem [][]byte `ssz-size:"?,4"` 11 | } 12 | 13 | func TestInferTypeFromStructTags(t *testing.T) { 14 | structExample := structWithTags{ 15 | NestedItem: [][][][]byte{{{{4}}}, {{{3}}}}, 16 | UnboundedItem: [][]byte{{2, 3, 4, 5}, {1, 2, 3, 4}}, 17 | } 18 | // We then verify that highly nested items can have their types 19 | // inferred via SSZ field tags. 20 | typ := reflect.TypeOf(structExample) 21 | sizes, exists, err := parseSSZFieldTags(typ.Field(0)) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | if !exists { 26 | t.Fatal("Expected struct tags to exist") 27 | } 28 | fType := inferFieldTypeFromSizeTags(typ.Field(0), sizes) 29 | expectedField := [4][1][4][1]byte{} 30 | expectedFieldType := reflect.TypeOf(expectedField) 31 | 32 | if expectedFieldType != fType { 33 | t.Errorf("Expected inferred field: %v, received %v", expectedFieldType, fType) 34 | } 35 | 36 | // We then verify that unbounded items can be formed via SSZ field tags 37 | // and their type can be correctly inferred. 38 | sizes, exists, err = parseSSZFieldTags(typ.Field(1)) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | if !exists { 43 | t.Fatal("Expected struct tags to exist") 44 | } 45 | fType = inferFieldTypeFromSizeTags(typ.Field(1), sizes) 46 | unboundedExpectedField := [][4]byte{} 47 | unboundedExpectedFieldType := reflect.TypeOf(unboundedExpectedField) 48 | 49 | if unboundedExpectedFieldType != fType { 50 | t.Errorf("Expected inferred field: %v, received %v", expectedFieldType, fType) 51 | } 52 | } 53 | 54 | // Regression test for https://github.com/prysmaticlabs/go-ssz/issues/44. 55 | func TestDetermineFieldCapacity_HandlesOverflow(t *testing.T) { 56 | input := struct { 57 | Data string `ssz-max:"18446744073709551615"` // max uint64 58 | }{} 59 | 60 | result := determineFieldCapacity(reflect.TypeOf(input).Field(0)) 61 | want := uint64(18446744073709551615) 62 | if result != want { 63 | t.Errorf("got: %d, wanted %d", result, want) 64 | } 65 | } 66 | 67 | // Regression test for https://github.com/prysmaticlabs/go-ssz/issues/44. 68 | func TestParseSSZFieldTags_HandlesOverflow(t *testing.T) { 69 | input := struct { 70 | Data string `ssz-size:"18446744073709551615"` // max uint64 71 | }{} 72 | 73 | result, _, err := parseSSZFieldTags(reflect.TypeOf(input).Field(0)) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | want := uint64(18446744073709551615) 78 | if result[0] != want { 79 | t.Errorf("got: %d, wanted %d", result, want) 80 | } 81 | } 82 | --------------------------------------------------------------------------------