diff --git a/Cargo.lock b/Cargo.lock index 9397492c..4ef54e9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -47,6 +47,11 @@ dependencies = [ "memchr", ] +[[package]] +name = "amcl" +version = "0.3.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -67,9 +72,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" [[package]] name = "approx" @@ -106,9 +111,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-trait" -version = "0.1.59" +version = "0.1.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" dependencies = [ "proc-macro2", "quote", @@ -123,13 +128,13 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", - "cfg-if 1.0.0", + "cfg-if", "libc", "miniz_oxide", "object", @@ -156,9 +161,67 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "beacon" +version = "0.1.0" +dependencies = [ + "bls", + "bytes", + "derivative", + "eth2_hashing", + "eth2_serde_utils", + "eth2_ssz", + "eth2_ssz_derive", + "eth2_ssz_types", + "ethereum-types", + "hex", + "hex-literal", + "itertools", + "lazy_static", + "maplit", + "parity-scale-codec", + "scale-info", + "serde", + "serde-big-array", + "serde_json", + "serde_with", + "serde_yaml", + "superstruct", + "tempfile", + "tree_hash", + "tree_hash_derive", +] + +[[package]] +name = "beacon-light-client" +version = "0.1.0" +dependencies = [ + "beacon", + "bls", + "bridge-types", + "eth2_ssz_types", + "ethereum-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex-literal", + "pallet-timestamp", + "parity-scale-codec", + "rlp", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", + "tree_hash", +] [[package]] name = "beef" @@ -269,9 +332,9 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest 0.10.6", ] @@ -299,9 +362,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array 0.14.6", ] @@ -315,6 +378,39 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "blst", + "eth2_hashing", + "eth2_serde_utils", + "eth2_ssz", + "ethereum-types", + "hex", + "milagro_bls", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "serde", + "tree_hash", + "zeroize", +] + +[[package]] +name = "blst" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a30d0edd9dd1c60ddb42b80341c7852f6f985279a5c1a83659dcb65899dec99" +dependencies = [ + "cc", + "glob", + "threadpool", + "which", + "zeroize", +] + [[package]] name = "bridge-common" version = "0.1.0" @@ -339,21 +435,17 @@ name = "bridge-types" version = "0.1.0" dependencies = [ "beefy-primitives", - "enum-iterator", "ethabi", - "ethash", "ethbloom", + "ethereum-primitives", "ethereum-types", "frame-support", "frame-system", - "getrandom 0.2.8", - "hex-literal", + "hex", "libsecp256k1", - "parity-bytes", "parity-scale-codec", "rand 0.7.3", "rlp", - "rustc-hex", "scale-info", "serde", "serde_json", @@ -361,24 +453,24 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", - "wasm-bindgen-test", "xcm", ] [[package]] name = "bstr" -version = "0.2.17" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byte-slice-cast" @@ -400,15 +492,15 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.0.77" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-expr" @@ -419,12 +511,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -440,18 +526,10 @@ dependencies = [ "iana-time-zone", "num-integer", "num-traits", + "serde", "winapi", ] -[[package]] -name = "ckb-merkle-mountain-range" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f061f97d64fd1822664bdfb722f7ae5469a97b77567390f7442be5b5dc82a5b" -dependencies = [ - "cfg-if 0.1.10", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -468,7 +546,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen", ] @@ -569,9 +647,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72" dependencies = [ "cc", "cxxbridge-flags", @@ -581,9 +659,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613" dependencies = [ "cc", "codespan-reporting", @@ -596,18 +674,88 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", "proc-macro2", "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core 0.13.4", + "quote", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", "syn", ] @@ -666,7 +814,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", "crypto-common", "subtle", ] @@ -719,9 +867,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" @@ -751,9 +899,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -774,30 +922,88 @@ dependencies = [ ] [[package]] -name = "enum-iterator" -version = "0.6.0" +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + +[[package]] +name = "errno" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79a6321a1197d7730510c7e3f6cb80432dfefecb32426de8cea0aa19b4bb8d7" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ - "enum-iterator-derive", + "errno-dragonfly", + "libc", + "winapi", ] [[package]] -name = "enum-iterator-derive" -version = "0.6.0" +name = "errno-dragonfly" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e94aa31f7c0dc764f57896dc615ddd76fc13b0d5dca7eb6cc5e018a5a09ec06" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "eth2_hashing" +version = "0.3.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "lazy_static", + "sha2 0.10.6", +] + +[[package]] +name = "eth2_serde_utils" +version = "0.1.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "ethereum-types", + "hex", + "serde", + "serde_json", +] + +[[package]] +name = "eth2_ssz" +version = "0.4.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" dependencies = [ + "ethereum-types", + "itertools", + "smallvec", +] + +[[package]] +name = "eth2_ssz_derive" +version = "0.3.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "darling 0.13.4", "proc-macro2", "quote", "syn", ] [[package]] -name = "environmental" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" +name = "eth2_ssz_types" +version = "0.2.2" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "derivative", + "eth2_serde_utils", + "eth2_ssz", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "tree_hash", + "typenum", +] [[package]] name = "ethabi" @@ -837,6 +1043,33 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "ethereum-primitives" +version = "0.1.0" +dependencies = [ + "beacon", + "ethabi", + "ethash", + "ethbloom", + "ethereum-types", + "frame-support", + "hex", + "hex-literal", + "libsecp256k1", + "parity-bytes", + "parity-scale-codec", + "rand 0.7.3", + "rlp", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "wasm-bindgen-test", +] + [[package]] name = "ethereum-types" version = "0.14.1" @@ -859,6 +1092,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "ff" version = "0.11.1" @@ -916,7 +1158,7 @@ version = "15.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "parity-scale-codec", "scale-info", "serde", @@ -1016,9 +1258,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", @@ -1031,9 +1273,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" dependencies = [ "futures-core", "futures-sink", @@ -1041,15 +1283,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" dependencies = [ "futures-core", "futures-task", @@ -1059,15 +1301,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote", @@ -1076,15 +1318,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" [[package]] name = "futures-timer" @@ -1094,9 +1336,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" dependencies = [ "futures-channel", "futures-core", @@ -1135,7 +1377,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -1148,24 +1390,28 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if 1.0.0", - "js-sys", + "cfg-if", "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ "aho-corasick", "bstr", @@ -1187,9 +1433,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" dependencies = [ "bytes", "fnv", @@ -1230,15 +1476,15 @@ dependencies = [ [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" dependencies = [ "libc", ] @@ -1288,9 +1534,9 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", @@ -1322,9 +1568,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" dependencies = [ "bytes", "futures-channel", @@ -1368,6 +1614,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "impl-codec" version = "0.6.0" @@ -1416,6 +1668,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "integer-sqrt" version = "0.1.5" @@ -1425,6 +1686,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "io-lifetimes" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3" +dependencies = [ + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1436,15 +1707,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" dependencies = [ "wasm-bindgen", ] @@ -1562,7 +1833,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "ecdsa", "elliptic-curve", "sec1", @@ -1592,12 +1863,14 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "leaf-provider" version = "0.1.1" dependencies = [ - "beefy-merkle-tree", "beefy-primitives", "bridge-types", "ethabi", @@ -1605,8 +1878,6 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-beefy-mmr", - "pallet-mmr", "parity-scale-codec", "scale-info", "serde", @@ -1650,9 +1921,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.138" +version = "0.2.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" [[package]] name = "libm" @@ -1710,9 +1981,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -1727,6 +1998,12 @@ dependencies = [ "statrs", ] +[[package]] +name = "linux-raw-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" + [[package]] name = "lock_api" version = "0.4.9" @@ -1743,7 +2020,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -1755,6 +2032,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "matchers" version = "0.0.1" @@ -1808,25 +2091,35 @@ dependencies = [ "zeroize", ] +[[package]] +name = "milagro_bls" +version = "1.5.0" +source = "git+https://github.com/Snowfork/milagro_bls?rev=bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095#bc2b5b5e8d48b7e2e1bfaa56dc2d93e13cb32095" +dependencies = [ + "amcl", + "rand 0.8.5", + "zeroize", +] + [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -1877,9 +2170,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] @@ -1928,9 +2221,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ "hermit-abi", "libc", @@ -1938,18 +2231,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "opaque-debug" @@ -2042,84 +2335,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-beefy" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "beefy-primitives", - "frame-support", - "frame-system", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-beefy-mmr" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "array-bytes", - "beefy-merkle-tree", - "beefy-primitives", - "frame-support", - "frame-system", - "log", - "pallet-beefy", - "pallet-mmr", - "pallet-session", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-mmr" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "ckb-merkle-mountain-range", - "frame-benchmarking", - "frame-support", - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-mmr-primitives", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-session" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "frame-support", - "frame-system", - "impl-trait-for-tuples", - "log", - "pallet-timestamp", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-session", - "sp-staking", - "sp-std", - "sp-trie", -] - [[package]] name = "pallet-timestamp" version = "4.0.0-dev" @@ -2146,9 +2361,9 @@ checksum = "16b56e3a2420138bdb970f84dfb9c774aea80fa0e7371549eedec0d80c209c67" [[package]] name = "parity-scale-codec" -version = "3.2.1" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -2161,9 +2376,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.3" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2177,7 +2392,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d32c34f4f5ca7f9196001c0aba5a1f9a5a12382c8944b8b0f90233282d1e8f8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "hashbrown", "impl-trait-for-tuples", "parity-util-mem-derive", @@ -2215,22 +2430,22 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] name = "paste" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -2315,13 +2530,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "thiserror", - "toml", + "toml_edit", ] [[package]] @@ -2350,18 +2564,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -2480,18 +2694,18 @@ dependencies = [ [[package]] name = "ref-cast" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" +checksum = "a9af2cf09ef80e610097515e80095b7f76660a92743c4185aff5406cd5ce3dd5" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.13" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" +checksum = "9c501201393982e275433bc55de7d6ae6f00e7699cd5572c5b57581cd69c881b" dependencies = [ "proc-macro2", "quote", @@ -2500,9 +2714,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ "aho-corasick", "memchr", @@ -2563,26 +2777,40 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustix" +version = "0.36.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.45.0", +] + [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "scale-info" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" +checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" dependencies = [ "bitvec", - "cfg-if 1.0.0", + "cfg-if", "derive_more", "parity-scale-codec", "scale-info-derive", @@ -2591,9 +2819,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" +checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -2633,9 +2861,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sec1" @@ -2652,9 +2880,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "secp256k1-sys", ] @@ -2679,18 +2907,27 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" +checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" -version = "1.0.149" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" +checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217" dependencies = [ "proc-macro2", "quote", @@ -2699,13 +2936,53 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea48c9627169d206b35905699f513f513c303ab9d964a59b44fdcf66c1d1ab7" +dependencies = [ + "base64", + "chrono", + "hex", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6b7e52858f9f06c25e1c566bbb4ab428200cb3b30053ea09dc50837de7538b" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_yaml" +version = "0.9.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10" dependencies = [ + "indexmap", "itoa", "ryu", "serde", + "unsafe-libyaml", ] [[package]] @@ -2715,7 +2992,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -2740,7 +3017,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -2752,7 +3029,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "digest 0.10.6", ] @@ -2800,9 +3077,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] @@ -2815,9 +3092,9 @@ checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -3200,20 +3477,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sp-session" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.31#7a4e5163091384c4c10b6d76f5cb80dac0834f38" -dependencies = [ - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-core", - "sp-runtime", - "sp-staking", - "sp-std", -] - [[package]] name = "sp-staking" version = "4.0.0-dev" @@ -3372,6 +3635,12 @@ dependencies = [ "sp-std", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.5.4" @@ -3384,9 +3653,9 @@ dependencies = [ [[package]] name = "ss58-registry" -version = "1.36.0" +version = "1.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d92659e7d18d82b803824a9ba5a6022cff101c3491d027c1c1d8d30e749284" +checksum = "ecf0bd63593ef78eca595a7fc25e9a443ca46fe69fd472f8f09f5245cdcd769d" dependencies = [ "Inflector", "num-format", @@ -3416,6 +3685,12 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strum" version = "0.24.1" @@ -3533,11 +3808,25 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "superstruct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f4e1f478a7728f8855d7e620e9a152cf8932c6614f86564c886f9b8141f3201" +dependencies = [ + "darling 0.13.4", + "itertools", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + [[package]] name = "syn" -version = "1.0.105" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", @@ -3562,11 +3851,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.42.0", +] + [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] @@ -3586,7 +3888,7 @@ version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e45b7bf6e19353ddd832745c8fcf77a17a93171df7151187f26623f2b75b5b26" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "proc-macro-error", "proc-macro2", "quote", @@ -3595,18 +3897,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e" dependencies = [ "proc-macro2", "quote", @@ -3615,13 +3917,39 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -3661,15 +3989,15 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.23.0" +version = "1.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64" dependencies = [ "autocfg", "bytes", @@ -3680,7 +4008,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.45.0", ] [[package]] @@ -3696,9 +4024,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -3707,9 +4035,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" dependencies = [ "bytes", "futures-core", @@ -3721,12 +4049,20 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.5.9" +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" dependencies = [ - "serde", + "indexmap", + "toml_datetime", + "winnow", ] [[package]] @@ -3741,7 +4077,7 @@ version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3821,6 +4157,26 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "tree_hash" +version = "0.4.1" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "eth2_hashing", + "ethereum-types", + "smallvec", +] + +[[package]] +name = "tree_hash_derive" +version = "0.4.0" +source = "git+https://github.com/vovac12/lighthouse-ssz.git#ee51b854d42080794ef6fafd0a41bcee61f13900" +dependencies = [ + "darling 0.13.4", + "quote", + "syn", +] + [[package]] name = "trie-db" version = "0.24.0" @@ -3845,15 +4201,15 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tt-call" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" [[package]] name = "twox-hash" @@ -3861,7 +4217,7 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "digest 0.10.6", "rand 0.8.5", "static_assertions", @@ -3896,9 +4252,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" [[package]] name = "unicode-normalization" @@ -3921,6 +4277,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unsafe-libyaml" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" + [[package]] name = "valuable" version = "0.1.0" @@ -3957,19 +4319,19 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" dependencies = [ "bumpalo", "log", @@ -3982,11 +4344,11 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -3994,9 +4356,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4004,9 +4366,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ "proc-macro2", "quote", @@ -4017,15 +4379,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" [[package]] name = "wasm-bindgen-test" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d2fff962180c3fadf677438054b1db62bee4aa32af26a45388af07d1287e1d" +checksum = "6db36fc0f9fb209e88fb3642590ae0205bb5a56216dabd963ba15879fe53a30b" dependencies = [ "console_error_panic_hook", "js-sys", @@ -4037,9 +4399,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4683da3dfc016f704c9f82cf401520c4f1cb3ee440f7f52b3d6ac29506a49ca7" +checksum = "0734759ae6b3b1717d661fe4f016efcfb9828f5edb4520c18eaee05af3b43be9" dependencies = [ "proc-macro2", "quote", @@ -4080,14 +4442,25 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4134,47 +4507,80 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winnow" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f" +dependencies = [ + "memchr", +] [[package]] name = "wyz" diff --git a/Cargo.toml b/Cargo.toml index 872ae1d3..5cb54b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,7 @@ members = [ "pallets/substrate-channel/rpc", "pallets/beefy-light-client/runtime-api", "pallets/beefy-light-client/rpc", + "primitives/*", ] + +resolver = "2" diff --git a/pallets/beacon-light-client/Cargo.toml b/pallets/beacon-light-client/Cargo.toml new file mode 100644 index 00000000..da6d40f8 --- /dev/null +++ b/pallets/beacon-light-client/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "beacon-light-client" +authors = ["Anonymous"] +description = "" +version = "0.1.0" +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +license = "Unlicense" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.137", optional = true } +codec = { version = "3.1.5", package = "parity-scale-codec", default-features = false, features = [ "derive" ] } +scale-info = { version = "2.2.0", default-features = false, features = [ "derive" ] } +rlp = { version = "0.5", default-features = false } +hex-literal = { version = "0.3.4", optional = true } + +frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +beacon = { path = "../../primitives/beacon", default-features = false } +ethereum-primitives = { path = "../../primitives/ethereum", default-features = false } +bridge-types = { path = "../types", default-features = false } +tree_hash = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +eth2_ssz_types = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.2", default-features = false } +bls = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.0", default-features = false, features = ["milagro"] } + +[dev-dependencies] +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31" } +serde_json = "1.0.68" +hex-literal = { version = "0.3.4" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + 'frame-benchmarking?/std', + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "pallet-timestamp/std", + "beacon/std", + "ethereum-primitives/std", + "tree_hash/std", + "bridge-types/std", + "eth2_ssz_types/std", + "bls/std" +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal" +] + +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/beacon-light-client/src/lib.rs b/pallets/beacon-light-client/src/lib.rs new file mode 100644 index 00000000..8cd69158 --- /dev/null +++ b/pallets/beacon-light-client/src/lib.rs @@ -0,0 +1,518 @@ +//! # Ethereum Beacon Client +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod light_client; +pub mod weights; + +use core::marker::PhantomData; + +use beacon::{ + light_client_bootstrap::LightClientBootstrap, light_client_update::LightClientUpdate, EthSpec, + EthSpecId, GnosisEthSpec, LightClientHeader, MainnetEthSpec, MinimalEthSpec, SyncCommittee, +}; +use bridge_types::EVMChainId; +use codec::{Decode, Encode, MaxEncodedLen}; +use light_client::LightClientStore; +use scale_info::TypeInfo; +pub use weights::WeightInfo; + +use frame_support::{dispatch::DispatchResult, traits::UnixTime, RuntimeDebug}; +use frame_system::ensure_signed; +use sp_core::H256; +use sp_std::prelude::*; + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericBootstrap { + Minimal(LightClientBootstrap), + Mainnet(LightClientBootstrap), + Gnosis(LightClientBootstrap), +} + +impl GenericBootstrap { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericUpdate { + Minimal(LightClientUpdate), + Mainnet(LightClientUpdate), + Gnosis(LightClientUpdate), +} + +impl GenericUpdate { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericHeader { + Minimal(LightClientHeader), + Mainnet(LightClientHeader), + Gnosis(LightClientHeader), +} + +impl GenericHeader { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Minimal(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Mainnet(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for LightClientHeader { + type Error = (); + fn try_from(value: GenericHeader) -> Result { + match value { + GenericHeader::Gnosis(x) => Ok(x), + _ => Err(()), + } + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Minimal(value) + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Mainnet(value) + } +} + +impl From> for GenericHeader { + fn from(value: LightClientHeader) -> Self { + Self::Gnosis(value) + } +} + +#[derive(Decode, Encode, MaxEncodedLen, TypeInfo, RuntimeDebug, Clone, PartialEq)] +pub enum GenericSyncCommittee { + Minimal(SyncCommittee), + Mainnet(SyncCommittee), + Gnosis(SyncCommittee), +} + +impl GenericSyncCommittee { + pub fn spec_id(&self) -> EthSpecId { + match self { + Self::Minimal(_) => EthSpecId::Minimal, + Self::Mainnet(_) => EthSpecId::Mainnet, + Self::Gnosis(_) => EthSpecId::Gnosis, + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Minimal(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Mainnet(x) => Ok(x), + _ => Err(()), + } + } +} + +impl TryFrom for SyncCommittee { + type Error = (); + fn try_from(value: GenericSyncCommittee) -> Result { + match value { + GenericSyncCommittee::Gnosis(x) => Ok(x), + _ => Err(()), + } + } +} + +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Minimal(value) + } +} + +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Mainnet(value) + } +} + +impl From> for GenericSyncCommittee { + fn from(value: SyncCommittee) -> Self { + Self::Gnosis(value) + } +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + use beacon::{ConsensusConfig, Slot}; + use frame_support::{ + pallet_prelude::{ValueQuery, *}, + BoundedVec, + }; + use frame_system::pallet_prelude::*; + use sp_runtime::DispatchError; + + use crate::light_client::BeaconLightClient; + use bridge_types::network_config::NetworkConfig; + use bridge_types::{network_config::Consensus, types::Message}; + use bridge_types::{traits::Verifier, EVMChainId}; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type TimeProvider: UnixTime; + type WeightInfo: WeightInfo; + type WeakSubjectivityPeriodSeconds: Get; + type MaxFinalizedStateRootArray: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + BeaconHeaderImported { block_hash: H256, slot: Slot }, + ExecutionHeaderImported { block_hash: H256, block_number: u64 }, + SyncCommitteeUpdated { period: u64 }, + } + + #[pallet::error] + pub enum Error { + InvalidMerkleBranch, + SignatureVerificationFailed, + DuplicateSyncCommitteeUpdate, + InvalidUpdate, + ArithError, + NetworkNotInitialized, + InvalidPublicKeyBytes, + InvalidConsensus, + InvalidSpecId, + NetworkAlreadyRegistered, + ZeroParticipants, + NotEnoughParticipants, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::storage] + pub(super) type FinalizedLightClientHeader = + StorageMap<_, Identity, EVMChainId, GenericHeader, OptionQuery>; + + #[pallet::storage] + pub(super) type OptimisticLightClientHeader = + StorageMap<_, Identity, EVMChainId, GenericHeader, OptionQuery>; + + #[pallet::storage] + pub(super) type CurrentSyncCommittee = + StorageMap<_, Identity, EVMChainId, GenericSyncCommittee, OptionQuery>; + + #[pallet::storage] + pub(super) type NextSyncCommittee = + StorageMap<_, Identity, EVMChainId, GenericSyncCommittee, OptionQuery>; + + #[pallet::storage] + pub(super) type LatestFinalizedExecutionStateRoots = StorageMap< + _, + Identity, + EVMChainId, + BoundedVec, + ValueQuery, + >; + + #[pallet::storage] + pub(super) type NetworkConfigs = + StorageMap<_, Identity, EVMChainId, NetworkConfig, OptionQuery>; + + #[pallet::storage] + pub(super) type LatestSyncCommitteeUpdate = + StorageMap<_, Identity, EVMChainId, u64, OptionQuery>; + + #[pallet::call] + impl Pallet { + #[pallet::weight(T::WeightInfo::initialize())] + pub fn initialize( + origin: OriginFor, + chain_id: EVMChainId, + network_config: NetworkConfig, + bootstrap: GenericBootstrap, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + !CurrentSyncCommittee::::contains_key(chain_id), + Error::::NetworkAlreadyRegistered + ); + + let consensus = match network_config.consensus() { + Consensus::Beacon(consensus) => consensus, + _ => return Err(Error::::InvalidConsensus.into()), + }; + + ensure!( + consensus.spec_id == bootstrap.spec_id(), + Error::::InvalidSpecId + ); + + match bootstrap { + GenericBootstrap::Minimal(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, + GenericBootstrap::Mainnet(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, + GenericBootstrap::Gnosis(bootstrap) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .initialize(bootstrap) + .map_err(Error::::from)?, + } + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::import_update())] + pub fn import_update( + origin: OriginFor, + chain_id: EVMChainId, + update: GenericUpdate, + ) -> DispatchResult { + // TODO: Weak subjectivity check + // TODO: State root update + let _sender = ensure_signed(origin)?; + + let consensus = Pallet::::consensus_config(chain_id)?; + + ensure!( + consensus.spec_id == update.spec_id(), + Error::::InvalidSpecId + ); + + match update { + GenericUpdate::Minimal(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, + GenericUpdate::Mainnet(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, + GenericUpdate::Gnosis(update) => BeaconLightClient::new( + consensus.fork_schedule, + consensus.genesis_validators_root, + PalletLightClientStore::::new(chain_id), + ) + .import_update(update) + .map_err(Error::::from)?, + } + + Ok(()) + } + } + + impl Pallet { + pub fn consensus_config(chain_id: EVMChainId) -> Result { + let network_config = + NetworkConfigs::::get(chain_id).ok_or(Error::::NetworkNotInitialized)?; + match network_config.consensus() { + Consensus::Beacon(consensus) => Ok(consensus), + _ => Err(Error::::InvalidConsensus.into()), + } + } + } + + impl Verifier for Pallet { + type Result = (H256, u64); + /// Verify a message by verifying the existence of the corresponding + /// Ethereum log in a block. Returns the log if successful. + fn verify(_chain_id: EVMChainId, _message: &Message) -> Result<(H256, u64), DispatchError> { + // TODO: Implement storage verification + todo!(); + } + } +} + +impl From for Error { + fn from(value: light_client::Error) -> Self { + use light_client::Error as E; + match value { + E::InvalidMerkleBranch => Error::InvalidMerkleBranch, + E::ArithError => Error::ArithError, + E::ZeroParticipants => Error::ZeroParticipants, + E::NotEnoughParticipants => Error::NotEnoughParticipants, + E::InvalidUpdate => Error::InvalidUpdate, + E::InvalidPublicKeyBytes => Error::InvalidPublicKeyBytes, + E::SignatureVerificationFailed => Error::SignatureVerificationFailed, + E::DuplicateSyncCommitteeUpdate => Error::DuplicateSyncCommitteeUpdate, + E::InvalidSpecId => Error::InvalidSpecId, + E::StoreNotInitialized => Error::NetworkNotInitialized, + } + } +} + +#[derive(Default)] +pub struct PalletLightClientStore { + chain_id: EVMChainId, + _phantom: PhantomData<(T, E)>, +} + +impl PalletLightClientStore { + pub fn new(chain_id: EVMChainId) -> Self { + Self { + chain_id, + _phantom: Default::default(), + } + } +} + +impl LightClientStore for PalletLightClientStore +where + T: Config, + E: EthSpec, + SyncCommittee: TryFrom + Into, + LightClientHeader: TryFrom + Into, +{ + fn get_current_sync_committee(&self) -> Result, light_client::Error> { + let sync_committee = CurrentSyncCommittee::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let sync_committee = sync_committee + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(sync_committee) + } + + fn set_current_sync_committee( + &self, + sync_committee: SyncCommittee, + ) -> Result<(), light_client::Error> { + let sync_committee: GenericSyncCommittee = sync_committee.into(); + CurrentSyncCommittee::::insert(self.chain_id, sync_committee); + Ok(()) + } + + fn get_next_sync_committee(&self) -> Result>, light_client::Error> { + let sync_committee = NextSyncCommittee::::get(self.chain_id); + if let Some(sync_committee) = sync_committee { + let sync_committee = sync_committee + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(Some(sync_committee)) + } else { + Ok(None) + } + } + + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), light_client::Error> { + if let Some(sync_committee) = sync_committee { + let sync_committee: GenericSyncCommittee = sync_committee.into(); + NextSyncCommittee::::insert(self.chain_id, sync_committee); + } else { + NextSyncCommittee::::set(self.chain_id, None); + } + Ok(()) + } + + fn get_finalized_header(&self) -> Result, light_client::Error> { + let header = FinalizedLightClientHeader::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let header = header + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(header) + } + + fn set_finalized_header( + &self, + header: LightClientHeader, + ) -> Result<(), light_client::Error> { + let header: GenericHeader = header.into(); + FinalizedLightClientHeader::::insert(self.chain_id, header); + Ok(()) + } + + fn get_optimistic_header(&self) -> Result, light_client::Error> { + let header = OptimisticLightClientHeader::::get(self.chain_id) + .ok_or(light_client::Error::StoreNotInitialized)?; + let header = header + .try_into() + .map_err(|_| light_client::Error::InvalidSpecId)?; + Ok(header) + } + + fn set_optimistic_header( + &self, + header: LightClientHeader, + ) -> Result<(), light_client::Error> { + let header: GenericHeader = header.into(); + OptimisticLightClientHeader::::insert(self.chain_id, header); + Ok(()) + } +} diff --git a/pallets/beacon-light-client/src/light_client.rs b/pallets/beacon-light-client/src/light_client.rs new file mode 100644 index 00000000..3dba84d7 --- /dev/null +++ b/pallets/beacon-light-client/src/light_client.rs @@ -0,0 +1,477 @@ +use core::cell::RefCell; +use core::marker::PhantomData; + +use beacon::{ + light_client_bootstrap::LightClientBootstrap, + light_client_header::LightClientHeaderRef, + light_client_update::{ + LightClientUpdate, CURRENT_SYNC_COMMITTEE_INDEX, CURRENT_SYNC_COMMITTEE_PROOF_LEN, + EXECUTION_PAYLOAD_INDEX, EXECUTION_PAYLOAD_PROOF_LEN, FINALIZED_ROOT_INDEX, + FINALIZED_ROOT_PROOF_LEN, NEXT_SYNC_COMMITTEE_INDEX, NEXT_SYNC_COMMITTEE_PROOF_LEN, + }, + BeaconBlockHeader, Epoch, EthSpec, ForkVersion, LightClientHeader, Slot, SyncAggregate, + SyncCommittee, +}; +use beacon::{ForkSchedule, Unsigned}; +use bridge_types::H256; +use frame_support::log; +use sp_io::hashing::sha2_256; +use tree_hash::TreeHash; + +pub const DOMAIN_SYNC_COMMITTEE: u32 = 7; +pub const MIN_SYNC_COMMITTEE_PARTICIPANTS: usize = 1; + +pub enum Error { + InvalidMerkleBranch, + ArithError, + ZeroParticipants, + NotEnoughParticipants, + InvalidUpdate, + InvalidPublicKeyBytes, + SignatureVerificationFailed, + DuplicateSyncCommitteeUpdate, + InvalidSpecId, + StoreNotInitialized, +} + +impl From for Error { + fn from(_value: beacon::safe_arith::ArithError) -> Self { + Error::ArithError + } +} + +pub struct MemoryLightClientStore { + pub current_sync_committee: RefCell>>, + pub next_sync_committee: RefCell>>, + pub finalized_header: RefCell>>, + pub optimistic_header: RefCell>>, +} + +impl LightClientStore for MemoryLightClientStore { + fn get_current_sync_committee(&self) -> Result, Error> { + self.current_sync_committee + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_current_sync_committee(&self, sync_committee: SyncCommittee) -> Result<(), Error> { + log::debug!("Current sync committee updated"); + let mut value = self.current_sync_committee.borrow_mut(); + *value = Some(sync_committee); + Ok(()) + } + + fn get_next_sync_committee(&self) -> Result>, Error> { + Ok(self + .next_sync_committee + .borrow() + .as_ref() + .map(|x| x.clone())) + } + + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), Error> { + log::debug!("Next sync committee updated"); + let mut value = self.next_sync_committee.borrow_mut(); + *value = sync_committee; + Ok(()) + } + + fn get_finalized_header(&self) -> Result, Error> { + self.finalized_header + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_finalized_header(&self, header: LightClientHeader) -> Result<(), Error> { + log::debug!("Finalized header updated"); + let mut value = self.finalized_header.borrow_mut(); + *value = Some(header); + Ok(()) + } + + fn get_optimistic_header(&self) -> Result, Error> { + self.optimistic_header + .borrow() + .as_ref() + .map(|x| x.clone()) + .ok_or(Error::StoreNotInitialized) + } + + fn set_optimistic_header(&self, header: LightClientHeader) -> Result<(), Error> { + log::debug!("Optimistic header updated"); + let mut value = self.optimistic_header.borrow_mut(); + *value = Some(header); + Ok(()) + } +} + +pub trait LightClientStore { + fn get_current_sync_committee(&self) -> Result, Error>; + fn set_current_sync_committee(&self, sync_committee: SyncCommittee) -> Result<(), Error>; + fn get_next_sync_committee(&self) -> Result>, Error>; + fn set_next_sync_committee( + &self, + sync_committee: Option>, + ) -> Result<(), Error>; + fn get_finalized_header(&self) -> Result, Error>; + fn set_finalized_header(&self, header: LightClientHeader) -> Result<(), Error>; + fn get_optimistic_header(&self) -> Result, Error>; + fn set_optimistic_header(&self, header: LightClientHeader) -> Result<(), Error>; +} + +pub struct BeaconLightClient { + fork_schedule: ForkSchedule, + validators_root: H256, + store: S, + _phantom: PhantomData, +} + +impl BeaconLightClient +where + E: EthSpec, + S: LightClientStore, +{ + pub fn new(fork_schedule: ForkSchedule, validators_root: H256, store: S) -> Self { + Self { + fork_schedule, + validators_root, + store, + _phantom: Default::default(), + } + } + + pub fn initialize(&self, bootstrap: LightClientBootstrap) -> Result<(), Error> { + self.is_valid_header(bootstrap.header())?; + Self::is_valid_merkle_branch( + bootstrap.current_sync_committee().tree_hash_root(), + bootstrap.current_sync_committee_branch(), + CURRENT_SYNC_COMMITTEE_PROOF_LEN, + CURRENT_SYNC_COMMITTEE_INDEX, + bootstrap.header().beacon().state_root, + )?; + self.store + .set_current_sync_committee(bootstrap.current_sync_committee().clone())?; + self.store + .set_finalized_header(bootstrap.header().owned())?; + self.store + .set_optimistic_header(bootstrap.header().owned())?; + Ok(()) + } + + pub fn import_update(&self, update: LightClientUpdate) -> Result<(), Error> { + self.validate_update(&update)?; + let store_next_sync_committee = self.store.get_next_sync_committee()?; + let sync_committee_bits = update.sync_aggregate().sync_committee_bits.num_set_bits(); + let sync_committee_size = E::SyncCommitteeSize::to_usize(); + + let store_optimistic_slot = self.store.get_optimistic_header()?.beacon().slot; + let update_attested_slot = update.attested_header().beacon().slot; + let update_attested_period = update_attested_slot.sync_committee_period_with_spec::(); + let update_finalized_slot = if let Some(header) = update.finalized_header() { + header.beacon().slot + } else { + Default::default() + }; + let update_finalized_period = update_finalized_slot.sync_committee_period_with_spec::(); + let store_finalized_slot = self.store.get_finalized_header()?.beacon().slot; + let store_finalized_period = store_finalized_slot.sync_committee_period_with_spec::(); + + // Update optimistic header + // Requires at least 1/3 signatures + if sync_committee_bits * 3 >= sync_committee_size + && update_attested_slot > store_optimistic_slot + { + self.store + .set_optimistic_header(update.attested_header().owned())?; + } + + let update_has_finalized_next_sync_committee = store_next_sync_committee.is_none() + && update.is_sync_committee_update() + && update.is_finality_update() + && update_attested_period == update_finalized_period; + + if sync_committee_bits * 3 >= sync_committee_size * 2 + && (update_finalized_slot > store_finalized_slot + || update_has_finalized_next_sync_committee) + { + if let Some(next_sync_committee) = store_next_sync_committee { + if update_finalized_period == store_finalized_period + 1 { + self.store.set_current_sync_committee(next_sync_committee)?; + self.store + .set_next_sync_committee(update.next_sync_committee().clone())?; + } + } else { + if update_finalized_period != store_finalized_period { + return Err(Error::InvalidUpdate); + } + self.store + .set_next_sync_committee(update.next_sync_committee().clone())?; + } + if update_finalized_slot > store_finalized_slot { + if let Some(finalized_header) = update.finalized_header() { + self.store.set_finalized_header(finalized_header.owned())?; + } + } + } + + Ok(()) + } + + pub fn validate_update(&self, update: &LightClientUpdate) -> Result<(), Error> { + if update.sync_aggregate().sync_committee_bits.num_set_bits() + < MIN_SYNC_COMMITTEE_PARTICIPANTS + { + return Err(Error::ZeroParticipants); + } + + self.is_valid_header(update.attested_header())?; + + let update_finalized_slot = if let Some(header) = update.finalized_header() { + header.beacon().slot + } else { + Default::default() + }; + let update_attested_slot = update.attested_header().beacon().slot; + + // Sanity check + if update.signature_slot() <= update_attested_slot + || update_attested_slot < update_finalized_slot + { + return Err(Error::InvalidUpdate); + } + + let store_finalized_slot = self.store.get_finalized_header()?.beacon().slot; + let store_period = store_finalized_slot.sync_committee_period_with_spec::(); + let update_signature_period = update + .signature_slot() + .sync_committee_period_with_spec::(); + let next_sync_committee = self.store.get_next_sync_committee()?; + if next_sync_committee.is_some() { + if ![store_period, store_period + 1].contains(&update_signature_period) { + return Err(Error::InvalidUpdate); + } + } else { + if store_period != update_signature_period { + return Err(Error::InvalidUpdate); + } + } + + let update_attested_period = update + .attested_header() + .beacon() + .slot + .sync_committee_period_with_spec::(); + let update_has_next_sync_committee = next_sync_committee.is_none() + && update.next_sync_committee().is_some() + && update.next_sync_committee_branch().is_some() + && update_attested_period == store_period; + + if !update_has_next_sync_committee && update_attested_slot <= store_finalized_slot { + return Err(Error::InvalidUpdate); + } + + match (update.finalized_header(), update.finality_branch()) { + (Some(header), Some(branch)) => { + self.is_valid_header(header)?; + Self::is_valid_merkle_branch( + header.beacon().tree_hash_root(), + branch, + FINALIZED_ROOT_PROOF_LEN, + FINALIZED_ROOT_INDEX, + update.attested_header().beacon().state_root, + )?; + } + (None, None) => {} + _ => return Err(Error::InvalidUpdate), + } + + match ( + update.next_sync_committee(), + update.next_sync_committee_branch(), + ) { + (Some(sync_committee), Some(branch)) => { + if let Some(next_sync_committee) = &next_sync_committee { + if update_attested_period == store_period + && next_sync_committee != sync_committee + { + return Err(Error::InvalidUpdate); + } + } + Self::is_valid_merkle_branch( + sync_committee.tree_hash_root(), + branch, + NEXT_SYNC_COMMITTEE_PROOF_LEN, + NEXT_SYNC_COMMITTEE_INDEX, + update.attested_header().beacon().state_root, + )?; + } + (None, None) => {} + _ => return Err(Error::InvalidUpdate), + } + + // Verify sync committee aggregate signature + let sync_committee = if update_signature_period == store_period { + self.store.get_current_sync_committee()? + } else { + next_sync_committee.ok_or(Error::InvalidUpdate)? + }; + self.validate_signed_header( + update.sync_aggregate(), + &sync_committee, + update.attested_header().beacon(), + update.signature_slot(), + )?; + + Ok(()) + } + + pub fn validate_signed_header( + &self, + sync_aggregate: &SyncAggregate, + sync_committee: &SyncCommittee, + header: &BeaconBlockHeader, + signature_slot: Slot, + ) -> Result<(), Error> { + let mut participant_pubkeys: Vec = Vec::new(); + // Gathers all the pubkeys of the sync committee members that participated in signing + // the header. + for (bit, pubkey) in sync_aggregate + .sync_committee_bits + .iter() + .zip(sync_committee.pubkeys.iter()) + { + if bit { + participant_pubkeys.push( + pubkey + .decompress() + .map_err(|_| Error::InvalidPublicKeyBytes)?, + ); + } + } + + let fork = + self.compute_fork_version((signature_slot.saturating_sub(1u64)).epoch_with_spec::()); + + // Domains are used for for seeds, for signatures, and for selecting aggregators. + let domain = + Self::compute_domain_with_constant(DOMAIN_SYNC_COMMITTEE, fork, self.validators_root); + // Hash tree root of SigningData - object root + domain + let signing_root = Self::compute_signing_root(header, domain); + + // Verify sync committee aggregate signature. + if !sync_aggregate + .sync_committee_signature + .fast_aggregate_verify( + signing_root, + // TODO: Optimize + &participant_pubkeys.iter().collect::>(), + ) + { + return Err(Error::SignatureVerificationFailed.into()); + } + Ok(()) + } + + pub fn is_valid_header(&self, header: LightClientHeaderRef) -> Result<(), Error> { + if let Ok(execution) = header.execution() { + Self::is_valid_merkle_branch( + execution.tree_hash_root(), + header + .execution_branch() + .expect("execution branch exists if execution payload exists"), + EXECUTION_PAYLOAD_PROOF_LEN, + EXECUTION_PAYLOAD_INDEX, + header.beacon().body_root, + )?; + } + // Execution branch is not presented before Capella fork + Ok(()) + } + + pub fn is_valid_merkle_branch( + leaf: H256, + branch: &[H256], + depth: usize, + index: usize, + root: H256, + ) -> Result<(), Error> { + if branch.len() != depth { + log::error!(target: "ethereum-beacon-client", "Merkle proof branch length doesn't match depth."); + + return Err(Error::InvalidMerkleBranch); + } + let mut value = leaf; + for (i, item) in branch.iter().enumerate() { + if (index / 2usize.pow(i as u32) % 2) == 0 { + // left node + let mut data = [0u8; 64]; + data[0..32].copy_from_slice(&(value.0)); + data[32..64].copy_from_slice(&(item.0)); + value = sha2_256(&data).into(); + } else { + // right node + let mut data = [0u8; 64]; + data[0..32].copy_from_slice(&(item.0)); + data[32..64].copy_from_slice(&(value.0)); + value = sha2_256(&data).into(); + } + } + + if value != root { + return Err(Error::InvalidMerkleBranch); + } + Ok(()) + } + + pub fn compute_signing_root(beacon_header: &BeaconBlockHeader, domain: H256) -> H256 { + let beacon_header_root = beacon_header.tree_hash_root(); + let hash_root = beacon::SigningData { + domain, + object_root: beacon_header_root, + } + .tree_hash_root(); + hash_root + } + + pub fn compute_fork_version(&self, epoch: Epoch) -> ForkVersion { + self.fork_schedule.fork_version(epoch.as_u64()) + } + + /// Return the 32-byte fork data root for the `current_version` and `genesis_validators_root`. + /// + /// This is used primarily in signature domains to avoid collisions across forks/chains. + /// + /// Spec v0.12.1 + pub fn compute_fork_data_root(current_version: [u8; 4], genesis_validators_root: H256) -> H256 { + beacon::ForkData { + current_version, + genesis_validators_root, + } + .tree_hash_root() + } + + /// Compute a domain by applying the given `fork_version`. + pub fn compute_domain_with_constant( + domain_constant: u32, + fork_version: [u8; 4], + genesis_validators_root: H256, + ) -> H256 { + let mut domain = [0; 32]; + domain[0..4].copy_from_slice(&beacon::int_to_bytes::int_to_bytes4(domain_constant)); + domain[4..].copy_from_slice( + Self::compute_fork_data_root(fork_version, genesis_validators_root) + .as_bytes() + .get(..28) + .expect("fork has is 32 bytes so first 28 bytes should exist"), + ); + + H256::from(domain) + } +} diff --git a/pallets/beacon-light-client/src/weights.rs b/pallets/beacon-light-client/src/weights.rs new file mode 100644 index 00000000..84d4cfb6 --- /dev/null +++ b/pallets/beacon-light-client/src/weights.rs @@ -0,0 +1,32 @@ +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for ethereum_beacon_client. +pub trait WeightInfo { + fn initialize() -> Weight; + fn import_update() -> Weight; +} + +pub struct PalletWeight(PhantomData); +impl WeightInfo for PalletWeight { + fn initialize() -> Weight { + Default::default() + } + fn import_update() -> Weight { + Default::default() + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn initialize() -> Weight { + Default::default() + } + fn import_update() -> Weight { + Default::default() + } +} diff --git a/pallets/leaf-provider/Cargo.toml b/pallets/leaf-provider/Cargo.toml index 137a3b4f..a0b8894f 100644 --- a/pallets/leaf-provider/Cargo.toml +++ b/pallets/leaf-provider/Cargo.toml @@ -25,9 +25,6 @@ sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkado sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } sp-mmr-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -pallet-mmr = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -pallet-beefy-mmr = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } -beefy-merkle-tree = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } beefy-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } bridge-types = { path = "../types", default-features = false } @@ -51,8 +48,6 @@ std = [ "sp-std/std", "bridge-types/std", "sp-mmr-primitives/std", - "pallet-mmr/std", - "pallet-beefy-mmr/std", ] runtime-benchmarks = [ "bridge-types/runtime-benchmarks", diff --git a/pallets/types/Cargo.toml b/pallets/types/Cargo.toml index 9b997367..0be0f9f8 100644 --- a/pallets/types/Cargo.toml +++ b/pallets/types/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Snowfork "] edition = "2021" [dependencies] -enum-iterator = "0.6.0" codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive", ] } @@ -19,13 +18,10 @@ ethereum-types = { version = "0.14.1", default-features = false, features = [ "rlp", "serialize", ] } -hex = { package = "rustc-hex", version = "2.1.0", default-features = false } -hex-literal = { version = "0.3.1", default-features = false } +hex = { version = "0.4", default-features = false } libsecp256k1 = { version = "0.7", default-features = false } -parity-bytes = { version = "0.1.2", default-features = false } rlp = { version = "0.5", default-features = false } -serde = { version = "1.0.101", optional = true } -getrandom = { version = "0.2.1", features = ["js"] } +serde = { version = "1.0.101", optional = true, features = ["derive"] } frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } @@ -36,12 +32,12 @@ sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "po beefy-primitives = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0", default-features = false } -ethash = { git = "https://github.com/sora-xor/ethash.git", branch = "sora-v1.6.0", default-features = false } serde_json = { version = "1.0", optional = true } xcm = { git = "https://github.com/paritytech/polkadot.git", branch = "release-v0.9.31", default-features = false } +ethereum-primitives = { path = "../../primitives/ethereum", default-features = false } + [dev-dependencies] -wasm-bindgen-test = "0.3.19" rand = "0.7.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" @@ -59,13 +55,15 @@ std = [ "ethereum-types/std", "hex/std", "libsecp256k1/std", - "parity-bytes/std", "rlp/std", + "frame-support/std", + "frame-system/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", "beefy-primitives/std", + "ethereum-primitives/std", "xcm/std", ] diff --git a/pallets/types/src/lib.rs b/pallets/types/src/lib.rs index 98499fe0..7c2c735d 100644 --- a/pallets/types/src/lib.rs +++ b/pallets/types/src/lib.rs @@ -31,30 +31,16 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod channel_abi; -pub mod difficulty; -pub mod ethashdata; -pub mod ethashproof; -pub mod header; -pub mod log; -mod mpt; -pub mod network_config; -pub mod receipt; pub mod substrate; pub mod traits; pub mod types; -#[cfg(any(feature = "test", test))] -pub mod test_utils; - +#[cfg(feature = "std")] +use ::serde::{Deserialize, Serialize}; use codec::{Decode, Encode}; -pub use ethereum_types::{Address, H128, H160, H256, H512, H64, U256}; use frame_support::RuntimeDebug; -use sp_std::vec; -use sp_std::vec::Vec; -pub use header::{Header, HeaderId}; -pub use log::Log; -pub use receipt::Receipt; +pub use ethereum_primitives::*; #[derive(Debug)] pub enum DecodeError { @@ -91,7 +77,7 @@ pub type EVMChainId = U256; scale_info::TypeInfo, codec::MaxEncodedLen, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum SubNetworkId { Mainnet, Kusama, @@ -111,7 +97,7 @@ pub enum SubNetworkId { scale_info::TypeInfo, codec::MaxEncodedLen, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum GenericNetworkId { EVM(EVMChainId), Sub(SubNetworkId), @@ -139,14 +125,3 @@ pub enum GenericAccount { } pub const CHANNEL_INDEXING_PREFIX: &[u8] = b"commitment"; - -pub fn import_digest(network_id: &EVMChainId, header: &Header) -> Vec -where - EVMChainId: Encode, - Header: Encode, -{ - let mut digest = vec![]; - network_id.encode_to(&mut digest); - header.encode_to(&mut digest); - digest -} diff --git a/pallets/types/src/types.rs b/pallets/types/src/types.rs index 98356372..87a3befe 100644 --- a/pallets/types/src/types.rs +++ b/pallets/types/src/types.rs @@ -89,7 +89,7 @@ pub struct Message { /// A message relayed from Parachain. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ParachainMessage { pub payload: Vec, pub nonce: MessageNonce, @@ -132,7 +132,7 @@ impl From for AuxiliaryDigest { /// Auxiliary [`DigestItem`] to include in header digest. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum AuxiliaryDigestItem { /// A batch of messages has been committed. Commitment(GenericNetworkId, H256), diff --git a/primitives/beacon/Cargo.toml b/primitives/beacon/Cargo.toml new file mode 100644 index 00000000..09ec3c83 --- /dev/null +++ b/primitives/beacon/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "beacon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +eth2_ssz = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +eth2_ssz_derive = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.3.1" } +superstruct = "0.7.0" +tree_hash = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.1", default-features = false } +tree_hash_derive = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.4.0" } +codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2", default-features = false, features = ["derive"] } +bls = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.0", default-features = false, features = ["milagro"] } +eth2_serde_utils = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.1.1", default-features = false } +serde = { version = "1.0.152", features = ["derive"], default-features = false } +ethereum-types = { version = "0.14.1", default-features = false, features = [ + "codec", + "rlp", + "serialize", +] } +derivative = { version = "2.2.0", features = ["use_core"] } +bytes = { version = "1.4.0", default-features = false } +eth2_ssz_types = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.2.2", default-features = false } +serde_json = { version = "1.0.94", optional = true } +serde-big-array = "0.5.1" +serde_with = { version = "2.2.0", default-features = false, features = ["alloc", "macros"] } +eth2_hashing = { git = "https://github.com/vovac12/lighthouse-ssz.git", version = "0.3.0", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +lazy_static = { version = "1.4.0", features = ["spin_no_std"] } +maplit = "1.0.2" +hex-literal = { version = "0.3.1", default-features = false } + +[dev-dependencies] +itertools = "0.10.5" +serde_yaml = "0.9.19" +tempfile = "3.4.0" + +[features] +default = ["std"] +std = ["eth2_ssz/std", "tree_hash/std", "codec/std", "scale-info/std", "bls/std", "eth2_serde_utils/std", "ethereum-types/std", "bytes/std", "eth2_ssz_types/std", "serde_json", "eth2_hashing/std"] diff --git a/primitives/beacon/src/attestation.rs b/primitives/beacon/src/attestation.rs new file mode 100644 index 00000000..87934012 --- /dev/null +++ b/primitives/beacon/src/attestation.rs @@ -0,0 +1,95 @@ +use crate::prelude::*; +use crate::safe_arith::ArithError; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +use crate::slot_data::SlotData; +use crate::Slot; + +use super::{AggregateSignature, AttestationData, BitList, EthSpec, Signature}; + +#[derive(Debug, PartialEq)] +pub enum Error { + SszTypesError(ssz_types::Error), + AlreadySigned(usize), + SubnetCountIsZero(ArithError), +} + +/// Details an attestation that can be slashable. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct Attestation { + pub aggregation_bits: BitList, + pub data: AttestationData, + pub signature: AggregateSignature, +} + +impl Attestation { + /// Are the aggregation bitfields of these attestations disjoint? + pub fn signers_disjoint_from(&self, other: &Self) -> bool { + self.aggregation_bits + .intersection(&other.aggregation_bits) + .is_zero() + } + + /// Aggregate another Attestation into this one. + /// + /// The aggregation bitfields must be disjoint, and the data must be the same. + pub fn aggregate(&mut self, other: &Self) { + debug_assert_eq!(self.data, other.data); + debug_assert!(self.signers_disjoint_from(other)); + + self.aggregation_bits = self.aggregation_bits.union(&other.aggregation_bits); + self.signature.add_assign_aggregate(&other.signature); + } + + /// Adds `signature` to `self` and sets the `committee_position`'th bit of `aggregation_bits` to `true`. + /// + /// Returns an `AlreadySigned` error if the `committee_position`'th bit is already `true`. + pub fn add_signature( + &mut self, + signature: &Signature, + committee_position: usize, + ) -> Result<(), Error> { + if self + .aggregation_bits + .get(committee_position) + .map_err(Error::SszTypesError)? + { + Err(Error::AlreadySigned(committee_position)) + } else { + self.aggregation_bits + .set(committee_position, true) + .map_err(Error::SszTypesError)?; + + self.signature.add_assign(signature); + + Ok(()) + } + } +} + +impl SlotData for Attestation { + fn get_slot(&self) -> Slot { + self.data.slot + } +} diff --git a/primitives/beacon/src/attestation_data.rs b/primitives/beacon/src/attestation_data.rs new file mode 100644 index 00000000..3d0e9cda --- /dev/null +++ b/primitives/beacon/src/attestation_data.rs @@ -0,0 +1,48 @@ +use crate::{Checkpoint, Hash256, SignedRoot, Slot}; + +use crate::prelude::*; +use crate::slot_data::SlotData; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data upon which an attestation is based. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Serialize, + Deserialize, + Hash, + Encode, + Decode, + TreeHash, + Default, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct AttestationData { + pub slot: Slot, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub index: u64, + + // LMD GHOST vote + pub beacon_block_root: Hash256, + + // FFG Vote + pub source: Checkpoint, + pub target: Checkpoint, +} + +impl SignedRoot for AttestationData {} + +impl SlotData for AttestationData { + fn get_slot(&self) -> Slot { + self.slot + } +} diff --git a/primitives/beacon/src/attester_slashing.rs b/primitives/beacon/src/attester_slashing.rs new file mode 100644 index 00000000..4bac6be9 --- /dev/null +++ b/primitives/beacon/src/attester_slashing.rs @@ -0,0 +1,32 @@ +use crate::{EthSpec, IndexedAttestation}; + +use crate::prelude::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Two conflicting attestations. +/// +/// Spec v0.12.1 +#[derive( + Derivative, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Eq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct AttesterSlashing { + pub attestation_1: IndexedAttestation, + pub attestation_2: IndexedAttestation, +} diff --git a/primitives/beacon/src/beacon_block.rs b/primitives/beacon/src/beacon_block.rs new file mode 100644 index 00000000..6c651031 --- /dev/null +++ b/primitives/beacon/src/beacon_block.rs @@ -0,0 +1,361 @@ +use crate::beacon_block_body::{ + BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyMerge, BeaconBlockBodyRef, + BeaconBlockBodyRefMut, +}; +use crate::prelude::*; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::Decode; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +/// A block of the `BeaconChain`. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: AbstractExecPayload")), + serde( + bound = "T: EthSpec, Payload: AbstractExecPayload", + deny_unknown_fields + ), + scale_info(skip_type_params(T)) + ), + ref_attributes( + derive(Debug, PartialEq, TreeHash), + tree_hash(enum_behaviour = "transparent") + ), + map_ref_into(BeaconBlockBodyRef, BeaconBlock), + map_ref_mut_into(BeaconBlockBodyRefMut) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct BeaconBlock = FullPayload> { + #[superstruct(getter(copy))] + pub slot: Slot, + #[superstruct(getter(copy))] + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_index: u64, + #[superstruct(getter(copy))] + pub parent_root: Hash256, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(only(Base), partial_getter(rename = "body_base"))] + pub body: BeaconBlockBodyBase, + #[superstruct(only(Altair), partial_getter(rename = "body_altair"))] + pub body: BeaconBlockBodyAltair, + #[superstruct(only(Merge), partial_getter(rename = "body_merge"))] + pub body: BeaconBlockBodyMerge, + #[superstruct(only(Capella), partial_getter(rename = "body_capella"))] + pub body: BeaconBlockBodyCapella, +} + +pub type BlindedBeaconBlock = BeaconBlock>; + +impl> SignedRoot for BeaconBlock {} +impl<'a, T: EthSpec, Payload: AbstractExecPayload> SignedRoot + for BeaconBlockRef<'a, T, Payload> +{ +} + +impl> BeaconBlock { + /// Try decoding each beacon block variant in sequence. + /// + /// This is *not* recommended unless you really have no idea what variant the block should be. + /// Usually it's better to prefer `from_ssz_bytes` which will decode the correct variant based + /// on the fork slot. + pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { + BeaconBlockCapella::from_ssz_bytes(bytes) + .map(BeaconBlock::Capella) + .or_else(|_| BeaconBlockMerge::from_ssz_bytes(bytes).map(BeaconBlock::Merge)) + .or_else(|_| BeaconBlockAltair::from_ssz_bytes(bytes).map(BeaconBlock::Altair)) + .or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base)) + } + + /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. + pub fn body(&self) -> BeaconBlockBodyRef<'_, T, Payload> { + self.to_ref().body() + } + + /// Convenience accessor for the `body` as a `BeaconBlockBodyRefMut`. + pub fn body_mut(&mut self) -> BeaconBlockBodyRefMut<'_, T, Payload> { + self.to_mut().body_mut() + } + + /// Returns the epoch corresponding to `self.slot()`. + pub fn epoch(&self) -> Epoch { + self.slot().epoch(T::slots_per_epoch()) + } + + /// Returns the `tree_hash_root` of the block. + pub fn canonical_root(&self) -> Hash256 { + self.tree_hash_root() + } + + /// Returns a full `BeaconBlockHeader` of this block. + /// + /// Note: This method is used instead of an `Into` impl to avoid a `Clone` of an entire block + /// when you want to have the block _and_ the header. + /// + /// Note: performs a full tree-hash of `self.body`. + pub fn block_header(&self) -> BeaconBlockHeader { + self.to_ref().block_header() + } + + /// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`. + pub fn temporary_block_header(&self) -> BeaconBlockHeader { + self.to_ref().temporary_block_header() + } + + /// Return the tree hash root of the block's body. + pub fn body_root(&self) -> Hash256 { + self.to_ref().body_root() + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRef<'a, T, Payload> { + /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. + pub fn body(&self) -> BeaconBlockBodyRef<'a, T, Payload> { + map_beacon_block_ref_into_beacon_block_body_ref!(&'a _, *self, |block, cons| cons( + &block.body + )) + } + + /// Return the tree hash root of the block's body. + pub fn body_root(&self) -> Hash256 { + map_beacon_block_ref!(&'a _, *self, |block, cons| { + let _: Self = cons(block); + block.body.tree_hash_root() + }) + } + + /// Returns the epoch corresponding to `self.slot()`. + pub fn epoch(&self) -> Epoch { + self.slot().epoch(T::slots_per_epoch()) + } + + /// Returns a full `BeaconBlockHeader` of this block. + pub fn block_header(&self) -> BeaconBlockHeader { + BeaconBlockHeader { + slot: self.slot(), + proposer_index: self.proposer_index(), + parent_root: self.parent_root(), + state_root: self.state_root(), + body_root: self.body_root(), + } + } + + /// Returns a "temporary" header, where the `state_root` is `Hash256::zero()`. + pub fn temporary_block_header(self) -> BeaconBlockHeader { + BeaconBlockHeader { + state_root: Hash256::zero(), + ..self.block_header() + } + } + + /// Extracts a reference to an execution payload from a block, returning an error if the block + /// is pre-merge. + pub fn execution_payload(&self) -> Result, Error> { + self.body().execution_payload() + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockRefMut<'a, T, Payload> { + /// Convert a mutable reference to a beacon block to a mutable ref to its body. + pub fn body_mut(self) -> BeaconBlockBodyRefMut<'a, T, Payload> { + map_beacon_block_ref_mut_into_beacon_block_body_ref_mut!(&'a _, self, |block, cons| cons( + &mut block.body + )) + } +} + +// We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. +impl From>> + for BeaconBlockBase> +{ + fn from(block: BeaconBlockBase>) -> Self { + let BeaconBlockBase { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockBase { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + +impl From>> + for BeaconBlockAltair> +{ + fn from(block: BeaconBlockAltair>) -> Self { + let BeaconBlockAltair { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockAltair { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + +// We can convert blocks with payloads to blocks without payloads, and an optional payload. +macro_rules! impl_from { + ($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>, $body_expr:expr) => { + impl From<$ty_name<$($from_params),*>> + for ($ty_name<$($to_params),*>, Option>) + { + #[allow(clippy::redundant_closure_call)] + fn from(block: $ty_name<$($from_params),*>) -> Self { + let $ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + let (body, payload) = ($body_expr)(body); + + ($ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + }, payload.map(Into::into)) + } + } + } +} + +impl_from!(BeaconBlockBase, >, >, |body: BeaconBlockBodyBase<_, _>| body.into()); +impl_from!(BeaconBlockAltair, >, >, |body: BeaconBlockBodyAltair<_, _>| body.into()); +impl_from!(BeaconBlockMerge, >, >, |body: BeaconBlockBodyMerge<_, _>| body.into()); +impl_from!(BeaconBlockCapella, >, >, |body: BeaconBlockBodyCapella<_, _>| body.into()); + +// We can clone blocks with payloads to blocks without payloads, without cloning the payload. +macro_rules! impl_clone_as_blinded { + ($ty_name:ident, <$($from_params:ty),*>, <$($to_params:ty),*>) => { + impl $ty_name<$($from_params),*> + { + pub fn clone_as_blinded(&self) -> $ty_name<$($to_params),*> { + let $ty_name { + slot, + proposer_index, + parent_root, + state_root, + body, + } = self; + + $ty_name { + slot: *slot, + proposer_index: *proposer_index, + parent_root: *parent_root, + state_root: *state_root, + body: body.clone_as_blinded(), + } + } + } + } +} + +impl_clone_as_blinded!(BeaconBlockBase, >, >); +impl_clone_as_blinded!(BeaconBlockAltair, >, >); +impl_clone_as_blinded!(BeaconBlockMerge, >, >); +impl_clone_as_blinded!(BeaconBlockCapella, >, >); + +// A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the +// execution payload. +impl<'a, E: EthSpec> From>> + for BeaconBlock> +{ + fn from( + full_block: BeaconBlockRef<'a, E, FullPayload>, + ) -> BeaconBlock> { + map_beacon_block_ref_into_beacon_block!(&'a _, full_block, |inner, cons| { + cons(inner.clone_as_blinded()) + }) + } +} + +impl From>> + for ( + BeaconBlock>, + Option>, + ) +{ + fn from(block: BeaconBlock>) -> Self { + map_beacon_block!(block, |inner, cons| { + let (block, payload) = inner.into(); + (cons(block), payload) + }) + } +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for BeaconBlock +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + Ok(map_fork_name!( + fork_name, + Self, + serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!( + "BeaconBlock failed to deserialize: {:?}", + e + )))? + )) + } +} diff --git a/primitives/beacon/src/beacon_block_body.rs b/primitives/beacon/src/beacon_block_body.rs new file mode 100644 index 00000000..32628916 --- /dev/null +++ b/primitives/beacon/src/beacon_block_body.rs @@ -0,0 +1,427 @@ +use crate::prelude::*; +use crate::*; +use core::marker::PhantomData; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::VariableList; +use superstruct::superstruct; +use tree_hash_derive::TreeHash; + +/// The body of a `BeaconChain` block, containing operations. +/// +/// This *superstruct* abstracts over the hard-fork. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec, Payload: AbstractExecPayload")), + serde( + bound = "T: EthSpec, Payload: AbstractExecPayload", + deny_unknown_fields + ), + scale_info(skip_type_params(T)) + ), + cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "T: EthSpec, Payload: AbstractExecPayload")] +#[scale_info(skip_type_params(T))] +pub struct BeaconBlockBody = FullPayload> { + pub randao_reveal: Signature, + pub eth1_data: Eth1Data, + pub graffiti: Graffiti, + pub proposer_slashings: VariableList, + pub attester_slashings: VariableList, T::MaxAttesterSlashings>, + pub attestations: VariableList, T::MaxAttestations>, + pub deposits: VariableList, + pub voluntary_exits: VariableList, + #[superstruct(only(Altair, Merge, Capella))] + pub sync_aggregate: SyncAggregate, + // We flatten the execution payload so that serde can use the name of the inner type, + // either `execution_payload` for full payloads, or `execution_payload_header` for blinded + // payloads. + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + #[serde(flatten)] + pub execution_payload: Payload::Merge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + #[serde(flatten)] + pub execution_payload: Payload::Capella, + #[superstruct(only(Capella))] + pub bls_to_execution_changes: + VariableList, + #[superstruct(only(Base, Altair))] + #[ssz(skip_serializing, skip_deserializing)] + #[tree_hash(skip_hashing)] + #[serde(skip)] + pub _phantom: PhantomData, +} + +impl> BeaconBlockBody { + pub fn execution_payload(&self) -> Result, Error> { + self.to_ref().execution_payload() + } +} + +impl<'a, T: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, T, Payload> { + pub fn execution_payload(&self) -> Result, Error> { + match self { + Self::Base(_) | Self::Altair(_) => Err(Error::IncorrectStateVariant), + Self::Merge(body) => Ok(Payload::Ref::from(&body.execution_payload)), + Self::Capella(body) => Ok(Payload::Ref::from(&body.execution_payload)), + } + } +} + +impl<'a, T: EthSpec> BeaconBlockBodyRef<'a, T> { + /// Get the fork_name of this object + pub fn fork_name(self) -> ForkName { + match self { + BeaconBlockBodyRef::Base { .. } => ForkName::Base, + BeaconBlockBodyRef::Altair { .. } => ForkName::Altair, + BeaconBlockBodyRef::Merge { .. } => ForkName::Merge, + BeaconBlockBodyRef::Capella { .. } => ForkName::Capella, + } + } +} + +// We can convert pre-Bellatrix block bodies without payloads into block bodies "with" payloads. +impl From>> + for BeaconBlockBodyBase> +{ + fn from(body: BeaconBlockBodyBase>) -> Self { + let BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom, + } = body; + + BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom: PhantomData, + } + } +} + +impl From>> + for BeaconBlockBodyAltair> +{ + fn from(body: BeaconBlockBodyAltair>) -> Self { + let BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom, + } = body; + + BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom: PhantomData, + } + } +} + +// Likewise bodies with payloads can be transformed into bodies without. +impl From>> + for ( + BeaconBlockBodyBase>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyBase>) -> Self { + let BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom, + } = body; + + ( + BeaconBlockBodyBase { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + _phantom: PhantomData, + }, + None, + ) + } +} + +impl From>> + for ( + BeaconBlockBodyAltair>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyAltair>) -> Self { + let BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom, + } = body; + + ( + BeaconBlockBodyAltair { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + _phantom: PhantomData, + }, + None, + ) + } +} + +impl From>> + for ( + BeaconBlockBodyMerge>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyMerge>) -> Self { + let BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + } = body; + + ( + BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadMerge { + execution_payload_header: From::from(&execution_payload), + }, + }, + Some(execution_payload), + ) + } +} + +impl From>> + for ( + BeaconBlockBodyCapella>, + Option>, + ) +{ + fn from(body: BeaconBlockBodyCapella>) -> Self { + let BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + } = body; + + ( + BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadCapella { + execution_payload_header: From::from(&execution_payload), + }, + bls_to_execution_changes, + }, + Some(execution_payload), + ) + } +} + +// We can clone a full block into a blinded block, without cloning the payload. +impl BeaconBlockBodyBase> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase> { + let (block_body, _payload) = self.clone().into(); + block_body + } +} + +impl BeaconBlockBodyAltair> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyAltair> { + let (block_body, _payload) = self.clone().into(); + block_body + } +} + +impl BeaconBlockBodyMerge> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyMerge> { + let BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + } = self; + + BeaconBlockBodyMerge { + randao_reveal: randao_reveal.clone(), + eth1_data: eth1_data.clone(), + graffiti: *graffiti, + proposer_slashings: proposer_slashings.clone(), + attester_slashings: attester_slashings.clone(), + attestations: attestations.clone(), + deposits: deposits.clone(), + voluntary_exits: voluntary_exits.clone(), + sync_aggregate: sync_aggregate.clone(), + execution_payload: BlindedPayloadMerge { + execution_payload_header: execution_payload.into(), + }, + } + } +} + +impl BeaconBlockBodyCapella> { + pub fn clone_as_blinded(&self) -> BeaconBlockBodyCapella> { + let BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + } = self; + + BeaconBlockBodyCapella { + randao_reveal: randao_reveal.clone(), + eth1_data: eth1_data.clone(), + graffiti: *graffiti, + proposer_slashings: proposer_slashings.clone(), + attester_slashings: attester_slashings.clone(), + attestations: attestations.clone(), + deposits: deposits.clone(), + voluntary_exits: voluntary_exits.clone(), + sync_aggregate: sync_aggregate.clone(), + execution_payload: BlindedPayloadCapella { + execution_payload_header: execution_payload.into(), + }, + bls_to_execution_changes: bls_to_execution_changes.clone(), + } + } +} + +impl From>> + for ( + BeaconBlockBody>, + Option>, + ) +{ + fn from(body: BeaconBlockBody>) -> Self { + map_beacon_block_body!(body, |inner, cons| { + let (block, payload) = inner.into(); + (cons(block), payload.map(Into::into)) + }) + } +} diff --git a/primitives/beacon/src/beacon_block_header.rs b/primitives/beacon/src/beacon_block_header.rs new file mode 100644 index 00000000..17e7cbaf --- /dev/null +++ b/primitives/beacon/src/beacon_block_header.rs @@ -0,0 +1,46 @@ +use crate::*; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +/// A header of a `BeaconBlock`. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct BeaconBlockHeader { + pub slot: Slot, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub proposer_index: u64, + pub parent_root: Hash256, + pub state_root: Hash256, + pub body_root: Hash256, +} + +impl SignedRoot for BeaconBlockHeader {} + +impl BeaconBlockHeader { + /// Returns the `tree_hash_root` of the header. + /// + /// Spec v0.12.1 + pub fn canonical_root(&self) -> Hash256 { + Hash256::from_slice(&self.tree_hash_root()[..]) + } +} diff --git a/primitives/beacon/src/beacon_config.rs b/primitives/beacon/src/beacon_config.rs new file mode 100644 index 00000000..0ebd503e --- /dev/null +++ b/primitives/beacon/src/beacon_config.rs @@ -0,0 +1,203 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use ethereum_types::H256; +use hex_literal::hex; +use scale_info::TypeInfo; + +use crate::{EthSpecId, ForkVersion}; + +#[derive( + Copy, + Clone, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ForkInfo { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + version: ForkVersion, + epoch: u64, +} + +#[derive( + Copy, + Clone, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ForkSchedule { + pub phase0: ForkInfo, + pub altair: ForkInfo, + pub bellatrix: ForkInfo, + pub capella: ForkInfo, + // Disabled forks + // sharding: BeaconFork, +} + +impl ForkSchedule { + pub fn fork_version(&self, epoch: u64) -> ForkVersion { + if epoch >= self.capella.epoch { + self.capella.version + } else if epoch >= self.bellatrix.epoch { + self.bellatrix.version + } else if epoch >= self.altair.epoch { + self.altair.version + } else { + self.phase0.version + } + } + + // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/sepolia.ts + pub fn sepolia() -> Self { + Self { + phase0: ForkInfo { + version: hex!("90000069"), + epoch: 0, + }, + altair: ForkInfo { + version: hex!("90000070"), + epoch: 50, + }, + bellatrix: ForkInfo { + version: hex!("90000071"), + epoch: 100, + }, + capella: ForkInfo { + version: hex!("90000072"), + epoch: 56832, + }, + } + } + + // https://github.com/ChainSafe/lodestar/blob/aa4349cee2b5bbefdf4e7c0bd58df36aaebff6de/packages/config/src/chainConfig/networks/goerli.ts + pub fn goerli() -> Self { + Self { + phase0: ForkInfo { + version: hex!("00001020"), + epoch: 0, + }, + altair: ForkInfo { + version: hex!("01001020"), + epoch: 36660, + }, + bellatrix: ForkInfo { + version: hex!("02001020"), + epoch: 112260, + }, + capella: ForkInfo { + version: hex!("03001020"), + epoch: 162304, + }, + } + } + + // https://github.com/eth-clients/merge-testnets/blob/302fe27afdc7a9d15b1766a0c0a9d64319140255/mainnet-shadow-fork-13/config.yaml + pub fn mainnet() -> Self { + Self { + phase0: ForkInfo { + version: hex!("00000000"), + epoch: 0, + }, + altair: ForkInfo { + version: hex!("01000000"), + epoch: 74240, + }, + bellatrix: ForkInfo { + version: hex!("02000000"), + epoch: 144896, + }, + capella: ForkInfo { + version: hex!("04000000"), + epoch: u64::MAX, + }, + } + } + + pub fn local() -> Self { + Self { + phase0: ForkInfo { + version: hex!("00000001"), + epoch: 0, + }, + altair: ForkInfo { + version: hex!("01000001"), + epoch: 0, + }, + bellatrix: ForkInfo { + version: hex!("02000001"), + epoch: 0, + }, + capella: ForkInfo { + version: hex!("03000001"), + epoch: u64::MAX, + }, + } + } +} + +#[derive( + Clone, + Copy, + Encode, + Decode, + PartialEq, + Eq, + Debug, + TypeInfo, + MaxEncodedLen, + serde::Serialize, + serde::Deserialize, +)] +pub struct ConsensusConfig { + pub spec_id: EthSpecId, + pub fork_schedule: ForkSchedule, + pub genesis_validators_root: H256, +} + +impl ConsensusConfig { + pub fn mainnet() -> Self { + Self { + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::mainnet(), + genesis_validators_root: H256(hex!( + "4b363db94e286120d76eb905340fdd4e54bfe9f06bf33ff6cf5ad27f511bfe95" + )), + } + } + + pub fn goerli() -> Self { + Self { + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::goerli(), + genesis_validators_root: H256(hex!( + "043db0d9a83813551ee2f33450d23797757d430911a9320530ad8a0eabc43efb" + )), + } + } + + pub fn sepolia() -> Self { + Self { + spec_id: EthSpecId::Mainnet, + fork_schedule: ForkSchedule::sepolia(), + genesis_validators_root: H256(hex!( + "d8ea171f3c94aea21ebc42a1ed61052acf3f9209c00e4efbaaddac09ed9b8078" + )), + } + } + + pub fn fork_version_from_epoch(&self, epoch: u64) -> ForkVersion { + self.fork_schedule.fork_version(epoch) + } +} diff --git a/primitives/beacon/src/beacon_state.rs b/primitives/beacon/src/beacon_state.rs new file mode 100644 index 00000000..a054524f --- /dev/null +++ b/primitives/beacon/src/beacon_state.rs @@ -0,0 +1,134 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::*; + +use core::fmt; +use core::hash::Hash; + +// #[macro_use] +// mod committee_cache; +// mod clone_config; +// mod exit_cache; +// mod iter; +// mod pubkey_cache; +// mod tests; +// mod tree_hash_cache; + +pub const CACHED_EPOCHS: usize = 3; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + /// A state for a different hard-fork was required -- a severe logic error. + IncorrectStateVariant, + EpochOutOfBounds, + SlotOutOfBounds, + UnknownValidator(usize), + UnableToDetermineProducer, + InvalidBitfield, + ValidatorIsWithdrawable, + ValidatorIsInactive { + val_index: usize, + }, + UnableToShuffle, + ShuffleIndexOutOfBounds(usize), + IsAggregatorOutOfBounds, + BlockRootsOutOfBounds(usize), + StateRootsOutOfBounds(usize), + SlashingsOutOfBounds(usize), + BalancesOutOfBounds(usize), + RandaoMixesOutOfBounds(usize), + CommitteeCachesOutOfBounds(usize), + ParticipationOutOfBounds(usize), + InactivityScoresOutOfBounds(usize), + TooManyValidators, + InsufficientValidators, + InsufficientRandaoMixes, + InsufficientBlockRoots, + InsufficientIndexRoots, + InsufficientAttestations, + InsufficientCommittees, + InsufficientStateRoots, + NoCommittee { + slot: Slot, + index: CommitteeIndex, + }, + ZeroSlotsPerEpoch, + PubkeyCacheInconsistent, + PubkeyCacheIncomplete { + cache_len: usize, + registry_len: usize, + }, + PreviousCommitteeCacheUninitialized, + CurrentCommitteeCacheUninitialized, + TotalActiveBalanceCacheUninitialized, + TotalActiveBalanceCacheInconsistent { + initialized_epoch: Epoch, + current_epoch: Epoch, + }, + ExitCacheUninitialized, + SyncCommitteeCacheUninitialized, + BlsError(bls::Error), + SszTypesError(ssz_types::Error), + TreeHashCacheNotInitialized, + NonLinearTreeHashCacheHistory, + TreeHashCacheSkippedSlot { + cache: Slot, + state: Slot, + }, + TreeHashError(tree_hash::Error), + InvalidValidatorPubkey(ssz::DecodeError), + ValidatorRegistryShrunk, + TreeHashCacheInconsistent, + InvalidDepositState { + deposit_count: u64, + deposit_index: u64, + }, + /// Attestation slipped through block processing with a non-matching source. + IncorrectAttestationSource, + /// An arithmetic operation occurred which would have overflowed or divided by 0. + /// + /// This represents a serious bug in either the spec or Lighthouse! + ArithError(ArithError), + MissingBeaconBlock(SignedBeaconBlockHash), + MissingBeaconState(BeaconStateHash), + PayloadConversionLogicFlaw, + SyncCommitteeNotKnown { + current_epoch: Epoch, + epoch: Epoch, + }, + IndexNotSupported(usize), +} + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub struct BeaconStateHash(Hash256); + +impl fmt::Debug for BeaconStateHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BeaconStateHash({:?})", self.0) + } +} + +impl fmt::Display for BeaconStateHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for BeaconStateHash { + fn from(hash: Hash256) -> BeaconStateHash { + BeaconStateHash(hash) + } +} + +impl From for Hash256 { + fn from(beacon_state_hash: BeaconStateHash) -> Hash256 { + beacon_state_hash.0 + } +} + +impl From for Error { + fn from(value: ArithError) -> Self { + Self::ArithError(value) + } +} diff --git a/primitives/beacon/src/bls_to_execution_change.rs b/primitives/beacon/src/bls_to_execution_change.rs new file mode 100644 index 00000000..761b06c9 --- /dev/null +++ b/primitives/beacon/src/bls_to_execution_change.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::*; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct BlsToExecutionChange { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + pub from_bls_pubkey: PublicKeyBytes, + pub to_execution_address: Address, +} + +impl SignedRoot for BlsToExecutionChange {} diff --git a/primitives/beacon/src/checkpoint.rs b/primitives/beacon/src/checkpoint.rs new file mode 100644 index 00000000..ec062cef --- /dev/null +++ b/primitives/beacon/src/checkpoint.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::{Epoch, Hash256}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Casper FFG checkpoint, used in attestations. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Eq, + Default, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Checkpoint { + pub epoch: Epoch, + pub root: Hash256, +} diff --git a/primitives/beacon/src/consts.rs b/primitives/beacon/src/consts.rs new file mode 100644 index 00000000..a9377bc3 --- /dev/null +++ b/primitives/beacon/src/consts.rs @@ -0,0 +1,24 @@ +pub mod altair { + pub const TIMELY_SOURCE_FLAG_INDEX: usize = 0; + pub const TIMELY_TARGET_FLAG_INDEX: usize = 1; + pub const TIMELY_HEAD_FLAG_INDEX: usize = 2; + pub const TIMELY_SOURCE_WEIGHT: u64 = 14; + pub const TIMELY_TARGET_WEIGHT: u64 = 26; + pub const TIMELY_HEAD_WEIGHT: u64 = 14; + pub const SYNC_REWARD_WEIGHT: u64 = 2; + pub const PROPOSER_WEIGHT: u64 = 8; + pub const WEIGHT_DENOMINATOR: u64 = 64; + pub const SYNC_COMMITTEE_SUBNET_COUNT: u64 = 4; + pub const TARGET_AGGREGATORS_PER_SYNC_SUBCOMMITTEE: u64 = 16; + + pub const PARTICIPATION_FLAG_WEIGHTS: [u64; NUM_FLAG_INDICES] = [ + TIMELY_SOURCE_WEIGHT, + TIMELY_TARGET_WEIGHT, + TIMELY_HEAD_WEIGHT, + ]; + + pub const NUM_FLAG_INDICES: usize = 3; +} +pub mod merge { + pub const INTERVALS_PER_SLOT: u64 = 3; +} diff --git a/primitives/beacon/src/deposit.rs b/primitives/beacon/src/deposit.rs new file mode 100644 index 00000000..9c3c9f39 --- /dev/null +++ b/primitives/beacon/src/deposit.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use ssz_types::typenum::U33; +use tree_hash_derive::TreeHash; + +pub const DEPOSIT_TREE_DEPTH: usize = 32; + +/// A deposit to potentially become a beacon chain validator. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Deposit { + pub proof: FixedVector, + pub data: DepositData, +} diff --git a/primitives/beacon/src/deposit_data.rs b/primitives/beacon/src/deposit_data.rs new file mode 100644 index 00000000..b4b06fde --- /dev/null +++ b/primitives/beacon/src/deposit_data.rs @@ -0,0 +1,33 @@ +use crate::*; + +use crate::prelude::*; +use bls::{PublicKeyBytes, SignatureBytes}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// The data supplied by the user to the deposit contract. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct DepositData { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub amount: u64, + pub signature: SignatureBytes, +} diff --git a/primitives/beacon/src/eth1_data.rs b/primitives/beacon/src/eth1_data.rs new file mode 100644 index 00000000..927a7140 --- /dev/null +++ b/primitives/beacon/src/eth1_data.rs @@ -0,0 +1,32 @@ +use super::Hash256; +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Contains data obtained from the Eth1 chain. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Clone, + Default, + Eq, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Eth1Data { + pub deposit_root: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub deposit_count: u64, + pub block_hash: Hash256, +} diff --git a/primitives/beacon/src/eth_spec.rs b/primitives/beacon/src/eth_spec.rs new file mode 100644 index 00000000..89cd82bf --- /dev/null +++ b/primitives/beacon/src/eth_spec.rs @@ -0,0 +1,369 @@ +use crate::*; + +use crate::prelude::*; +use core::fmt::{self, Debug}; +use core::str::FromStr; +use safe_arith::SafeArith; +use serde::{Deserialize, Serialize}; +use ssz_types::typenum::{ + bit::B0, UInt, Unsigned, U0, U1024, U1048576, U1073741824, U1099511627776, U128, U16, + U16777216, U2, U2048, U256, U32, U4, U4096, U512, U625, U64, U65536, U8, U8192, +}; + +pub type U5000 = UInt, B0>, B0>; // 625 * 8 = 5000 + +const MAINNET: &str = "mainnet"; +const MINIMAL: &str = "minimal"; +pub const GNOSIS: &str = "gnosis"; + +/// Used to identify one of the `EthSpec` instances defined here. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + Eq, + TypeInfo, + MaxEncodedLen, +)] +#[serde(rename_all = "lowercase")] +pub enum EthSpecId { + Mainnet, + Minimal, + Gnosis, +} + +impl FromStr for EthSpecId { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + MAINNET => Ok(EthSpecId::Mainnet), + MINIMAL => Ok(EthSpecId::Minimal), + GNOSIS => Ok(EthSpecId::Gnosis), + _ => Err(format!("Unknown eth spec: {}", s)), + } + } +} + +impl fmt::Display for EthSpecId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s = match self { + EthSpecId::Mainnet => MAINNET, + EthSpecId::Minimal => MINIMAL, + EthSpecId::Gnosis => GNOSIS, + }; + write!(f, "{}", s) + } +} + +pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Eq { + /* + * Constants + */ + type GenesisEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type JustificationBitsLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default; + type SubnetBitfieldLength: Unsigned + Clone + Sync + Send + Debug + PartialEq + Default; + /* + * Misc + */ + type MaxValidatorsPerCommittee: Unsigned + Clone + Sync + Send + Debug + PartialEq + Eq; + /* + * Time parameters + */ + type SlotsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type SlotsPerHistoricalRoot: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * State list lengths + */ + type EpochsPerHistoricalVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * Max operations per block + */ + type MaxProposerSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxAttesterSlashings: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxDeposits: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxVoluntaryExits: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Altair + */ + type SyncCommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The number of `sync_committee` subnets. + type SyncCommitteeSubnetCount: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type EpochsPerSyncCommitteePeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Merge + */ + type MaxBytesPerTransaction: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxTransactionsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BytesPerLogsBloom: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type GasLimitDenominator: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MinGasLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxExtraDataBytes: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Capella + */ + type MaxBlsToExecutionChanges: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxWithdrawalsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * Derived values (set these CAREFULLY) + */ + /// The length of the `{previous,current}_epoch_attestations` lists. + /// + /// Must be set to `MaxAttestations * SlotsPerEpoch` + // NOTE: we could safely instantiate these by using type-level arithmetic, but doing + // so adds ~25s to the time required to type-check this crate + type MaxPendingAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The length of `eth1_data_votes`. + /// + /// Must be set to `EpochsPerEth1VotingPeriod * SlotsPerEpoch` + type SlotsPerEth1VotingPeriod: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /// The size of `sync_subcommittees`. + /// + /// Must be set to `SyncCommitteeSize / SyncCommitteeSubnetCount`. + type SyncSubcommitteeSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + + fn spec_name() -> EthSpecId; + + fn genesis_epoch() -> Epoch { + Epoch::new(Self::GenesisEpoch::to_u64()) + } + + fn get_committee_count_per_slot_with( + active_validator_count: usize, + max_committees_per_slot: usize, + target_committee_size: usize, + ) -> Result { + let slots_per_epoch = Self::SlotsPerEpoch::to_usize(); + + Ok(core::cmp::max( + 1, + core::cmp::min( + max_committees_per_slot, + active_validator_count + .safe_div(slots_per_epoch)? + .safe_div(target_committee_size)?, + ), + )) + } + + /// Returns the minimum number of validators required for this spec. + /// + /// This is the _absolute_ minimum, the number required to make the chain operate in the most + /// basic sense. This count is not required to provide any security guarantees regarding + /// decentralization, entropy, etc. + fn minimum_validator_count() -> usize { + Self::SlotsPerEpoch::to_usize() + } + + /// Returns the `SLOTS_PER_EPOCH` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_epoch() -> u64 { + Self::SlotsPerEpoch::to_u64() + } + + /// Returns the `SLOTS_PER_HISTORICAL_ROOT` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_historical_root() -> usize { + Self::SlotsPerHistoricalRoot::to_usize() + } + + /// Returns the `EPOCHS_PER_HISTORICAL_VECTOR` constant for this specification. + /// + /// Spec v0.12.1 + fn epochs_per_historical_vector() -> usize { + Self::EpochsPerHistoricalVector::to_usize() + } + + /// Returns the `SLOTS_PER_ETH1_VOTING_PERIOD` constant for this specification. + /// + /// Spec v0.12.1 + fn slots_per_eth1_voting_period() -> usize { + Self::SlotsPerEth1VotingPeriod::to_usize() + } + + /// Returns the `SYNC_COMMITTEE_SIZE` constant for this specification. + fn sync_committee_size() -> usize { + Self::SyncCommitteeSize::to_usize() + } + + /// Returns the `SYNC_COMMITTEE_SIZE / SyncCommitteeSubnetCount`. + fn sync_subcommittee_size() -> usize { + Self::SyncSubcommitteeSize::to_usize() + } + + /// Returns the `MAX_BYTES_PER_TRANSACTION` constant for this specification. + fn max_bytes_per_transaction() -> usize { + Self::MaxBytesPerTransaction::to_usize() + } + + /// Returns the `MAX_TRANSACTIONS_PER_PAYLOAD` constant for this specification. + fn max_transactions_per_payload() -> usize { + Self::MaxTransactionsPerPayload::to_usize() + } + + /// Returns the `MAX_EXTRA_DATA_BYTES` constant for this specification. + fn max_extra_data_bytes() -> usize { + Self::MaxExtraDataBytes::to_usize() + } + + /// Returns the `BYTES_PER_LOGS_BLOOM` constant for this specification. + fn bytes_per_logs_bloom() -> usize { + Self::BytesPerLogsBloom::to_usize() + } + + /// Returns the `MAX_BLS_TO_EXECUTION_CHANGES` constant for this specification. + fn max_bls_to_execution_changes() -> usize { + Self::MaxBlsToExecutionChanges::to_usize() + } + + /// Returns the `MAX_WITHDRAWALS_PER_PAYLOAD` constant for this specification. + fn max_withdrawals_per_payload() -> usize { + Self::MaxWithdrawalsPerPayload::to_usize() + } +} + +/// Macro to inherit some type values from another EthSpec. +#[macro_export] +macro_rules! params_from_eth_spec { + ($spec_ty:ty { $($ty_name:ident),+ }) => { + $(type $ty_name = <$spec_ty as EthSpec>::$ty_name;)+ + } +} + +/// Ethereum Foundation specifications. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct MainnetEthSpec; + +impl EthSpec for MainnetEthSpec { + type JustificationBitsLength = U4; + type SubnetBitfieldLength = U64; + type MaxValidatorsPerCommittee = U2048; + type GenesisEpoch = U0; + type SlotsPerEpoch = U32; + type EpochsPerEth1VotingPeriod = U64; + type SlotsPerHistoricalRoot = U8192; + type EpochsPerHistoricalVector = U65536; + type EpochsPerSlashingsVector = U8192; + type HistoricalRootsLimit = U16777216; + type ValidatorRegistryLimit = U1099511627776; + type MaxProposerSlashings = U16; + type MaxAttesterSlashings = U2; + type MaxAttestations = U128; + type MaxDeposits = U16; + type MaxVoluntaryExits = U16; + type SyncCommitteeSize = U512; + type SyncCommitteeSubnetCount = U4; + type MaxBytesPerTransaction = U1073741824; // 1,073,741,824 + type MaxTransactionsPerPayload = U1048576; // 1,048,576 + type BytesPerLogsBloom = U256; + type GasLimitDenominator = U1024; + type MinGasLimit = U5000; + type MaxExtraDataBytes = U32; + type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch + type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch + type MaxBlsToExecutionChanges = U16; + type MaxWithdrawalsPerPayload = U16; + type EpochsPerSyncCommitteePeriod = U256; + + fn spec_name() -> EthSpecId { + EthSpecId::Mainnet + } +} + +/// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct MinimalEthSpec; + +impl EthSpec for MinimalEthSpec { + type SlotsPerEpoch = U8; + type EpochsPerEth1VotingPeriod = U4; + type SlotsPerHistoricalRoot = U64; + type EpochsPerHistoricalVector = U64; + type EpochsPerSlashingsVector = U64; + type SyncCommitteeSize = U32; + type SyncSubcommitteeSize = U8; // 32 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch + type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch + type MaxWithdrawalsPerPayload = U4; + type EpochsPerSyncCommitteePeriod = U8; + + params_from_eth_spec!(MainnetEthSpec { + JustificationBitsLength, + SubnetBitfieldLength, + SyncCommitteeSubnetCount, + MaxValidatorsPerCommittee, + GenesisEpoch, + HistoricalRootsLimit, + ValidatorRegistryLimit, + MaxProposerSlashings, + MaxAttesterSlashings, + MaxAttestations, + MaxDeposits, + MaxVoluntaryExits, + MaxBytesPerTransaction, + MaxTransactionsPerPayload, + BytesPerLogsBloom, + GasLimitDenominator, + MinGasLimit, + MaxExtraDataBytes, + MaxBlsToExecutionChanges + }); + + fn spec_name() -> EthSpecId { + EthSpecId::Minimal + } +} + +/// Gnosis Beacon Chain specifications. +#[derive(Clone, PartialEq, Eq, Debug, Default, Serialize, Deserialize)] +pub struct GnosisEthSpec; + +impl EthSpec for GnosisEthSpec { + type JustificationBitsLength = U4; + type SubnetBitfieldLength = U64; + type MaxValidatorsPerCommittee = U2048; + type GenesisEpoch = U0; + type SlotsPerEpoch = U16; + type EpochsPerEth1VotingPeriod = U64; + type SlotsPerHistoricalRoot = U8192; + type EpochsPerHistoricalVector = U65536; + type EpochsPerSlashingsVector = U8192; + type HistoricalRootsLimit = U16777216; + type ValidatorRegistryLimit = U1099511627776; + type MaxProposerSlashings = U16; + type MaxAttesterSlashings = U2; + type MaxAttestations = U128; + type MaxDeposits = U16; + type MaxVoluntaryExits = U16; + type SyncCommitteeSize = U512; + type SyncCommitteeSubnetCount = U4; + type MaxBytesPerTransaction = U1073741824; // 1,073,741,824 + type MaxTransactionsPerPayload = U1048576; // 1,048,576 + type BytesPerLogsBloom = U256; + type GasLimitDenominator = U1024; + type MinGasLimit = U5000; + type MaxExtraDataBytes = U32; + type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count + type MaxPendingAttestations = U2048; // 128 max attestations * 16 slots per epoch + type SlotsPerEth1VotingPeriod = U1024; // 64 epochs * 16 slots per epoch + type MaxBlsToExecutionChanges = U16; + type MaxWithdrawalsPerPayload = U16; + type EpochsPerSyncCommitteePeriod = U512; + + fn spec_name() -> EthSpecId { + EthSpecId::Gnosis + } +} diff --git a/primitives/beacon/src/execution_block_hash.rs b/primitives/beacon/src/execution_block_hash.rs new file mode 100644 index 00000000..e105265d --- /dev/null +++ b/primitives/beacon/src/execution_block_hash.rs @@ -0,0 +1,109 @@ +use crate::prelude::*; +use crate::Hash256; +use core::fmt; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; + +#[derive( + Default, + Clone, + Copy, + Serialize, + Deserialize, + Eq, + PartialEq, + Hash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(Debug = "transparent")] +#[serde(transparent)] +pub struct ExecutionBlockHash(Hash256); + +impl ExecutionBlockHash { + pub fn zero() -> Self { + Self(Hash256::zero()) + } + + pub fn repeat_byte(b: u8) -> Self { + Self(Hash256::repeat_byte(b)) + } + + pub fn from_root(root: Hash256) -> Self { + Self(root) + } + + pub fn into_root(self) -> Hash256 { + self.0 + } +} + +impl Encode for ExecutionBlockHash { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for ExecutionBlockHash { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Hash256::from_ssz_bytes(bytes).map(Self) + } +} + +impl tree_hash::TreeHash for ExecutionBlockHash { + fn tree_hash_type() -> tree_hash::TreeHashType { + Hash256::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + Hash256::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} + +impl core::str::FromStr for ExecutionBlockHash { + type Err = String; + + fn from_str(s: &str) -> Result { + Hash256::from_str(s) + .map(Self) + .map_err(|e| format!("{:?}", e)) + } +} + +impl fmt::Display for ExecutionBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/primitives/beacon/src/execution_payload.rs b/primitives/beacon/src/execution_payload.rs new file mode 100644 index 00000000..91fa4116 --- /dev/null +++ b/primitives/beacon/src/execution_payload.rs @@ -0,0 +1,167 @@ +use crate::prelude::*; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +pub type Transaction = VariableList; +pub type Transactions = VariableList< + Transaction<::MaxBytesPerTransaction>, + ::MaxTransactionsPerPayload, +>; + +pub type Withdrawals = VariableList::MaxWithdrawalsPerPayload>; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Default, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + map_into(FullPayload, BlindedPayload), + map_ref_into(ExecutionPayloadHeader) +)] +#[derive( + Debug, + Clone, + Serialize, + Encode, + Deserialize, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct ExecutionPayload { + #[superstruct(getter(copy))] + pub parent_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub fee_recipient: Address, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(getter(copy))] + pub receipts_root: Hash256, + #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] + pub logs_bloom: FixedVector, + #[superstruct(getter(copy))] + pub prev_randao: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub block_number: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_used: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + #[serde(with = "eth2_serde_utils::quoted_u256")] + #[superstruct(getter(copy))] + pub base_fee_per_gas: Uint256, + #[superstruct(getter(copy))] + pub block_hash: ExecutionBlockHash, + #[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")] + pub transactions: Transactions, + #[superstruct(only(Capella))] + pub withdrawals: Withdrawals, +} + +impl<'a, T: EthSpec> ExecutionPayloadRef<'a, T> { + // this emulates clone on a normal reference type + pub fn clone_from_ref(&self) -> ExecutionPayload { + map_execution_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecutionPayload { + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayload: {fork_name}", + ))), + ForkName::Merge => ExecutionPayloadMerge::from_ssz_bytes(bytes).map(Self::Merge), + ForkName::Capella => ExecutionPayloadCapella::from_ssz_bytes(bytes).map(Self::Capella), + } + } + + #[allow(clippy::integer_arithmetic)] + /// Returns the maximum size of an execution payload. + pub fn max_execution_payload_merge_size() -> usize { + // Fixed part + ExecutionPayloadMerge::::default().as_ssz_bytes().len() + // Max size of variable length `extra_data` field + + (T::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) + } + + #[allow(clippy::integer_arithmetic)] + /// Returns the maximum size of an execution payload. + pub fn max_execution_payload_capella_size() -> usize { + // Fixed part + ExecutionPayloadCapella::::default().as_ssz_bytes().len() + // Max size of variable length `extra_data` field + + (T::max_extra_data_bytes() * ::ssz_fixed_len()) + // Max size of variable length `transactions` field + + (T::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + T::max_bytes_per_transaction())) + // Max size of variable length `withdrawals` field + + (T::max_withdrawals_per_payload() * ::ssz_fixed_len()) + } +} + +#[cfg(feature = "std")] +impl ForkVersionDeserialize for ExecutionPayload { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!("ExecutionPayload failed to deserialize: {:?}", e)) + }; + + Ok(match fork_name { + ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Base | ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayload failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/execution_payload_header.rs b/primitives/beacon/src/execution_payload_header.rs new file mode 100644 index 00000000..7652d265 --- /dev/null +++ b/primitives/beacon/src/execution_payload_header.rs @@ -0,0 +1,254 @@ +use crate::prelude::*; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::Decode; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; +use BeaconStateError; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Default, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq, TreeHash), tree_hash(enum_behaviour = "transparent")), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct ExecutionPayloadHeader { + #[superstruct(getter(copy))] + pub parent_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub fee_recipient: Address, + #[superstruct(getter(copy))] + pub state_root: Hash256, + #[superstruct(getter(copy))] + pub receipts_root: Hash256, + #[serde(with = "ssz_types::serde_utils::hex_fixed_vec")] + pub logs_bloom: FixedVector, + #[superstruct(getter(copy))] + pub prev_randao: Hash256, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub block_number: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_limit: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub gas_used: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub timestamp: u64, + #[serde(with = "ssz_types::serde_utils::hex_var_list")] + pub extra_data: VariableList, + #[serde(with = "eth2_serde_utils::quoted_u256")] + #[superstruct(getter(copy))] + pub base_fee_per_gas: Uint256, + #[superstruct(getter(copy))] + pub block_hash: ExecutionBlockHash, + #[superstruct(getter(copy))] + pub transactions_root: Hash256, + #[superstruct(only(Capella))] + #[superstruct(getter(copy))] + pub withdrawals_root: Hash256, +} + +impl ExecutionPayloadHeader { + pub fn transactions(&self) -> Option<&Transactions> { + None + } + + pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayloadHeader: {fork_name}", + ))), + ForkName::Merge => ExecutionPayloadHeaderMerge::from_ssz_bytes(bytes).map(Self::Merge), + ForkName::Capella => { + ExecutionPayloadHeaderCapella::from_ssz_bytes(bytes).map(Self::Capella) + } + } + } +} + +impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> { + pub fn is_default_with_zero_roots(self) -> bool { + map_execution_payload_header_ref!(&'a _, self, |inner, cons| { + cons(inner); + *inner == Default::default() + }) + } +} + +impl ExecutionPayloadHeaderMerge { + pub fn upgrade_to_capella(&self) -> ExecutionPayloadHeaderCapella { + ExecutionPayloadHeaderCapella { + parent_hash: self.parent_hash, + fee_recipient: self.fee_recipient, + state_root: self.state_root, + receipts_root: self.receipts_root, + logs_bloom: self.logs_bloom.clone(), + prev_randao: self.prev_randao, + block_number: self.block_number, + gas_limit: self.gas_limit, + gas_used: self.gas_used, + timestamp: self.timestamp, + extra_data: self.extra_data.clone(), + base_fee_per_gas: self.base_fee_per_gas, + block_hash: self.block_hash, + transactions_root: self.transactions_root, + withdrawals_root: Hash256::zero(), + } + } +} + +impl<'a, T: EthSpec> From<&'a ExecutionPayloadMerge> for ExecutionPayloadHeaderMerge { + fn from(payload: &'a ExecutionPayloadMerge) -> Self { + Self { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom.clone(), + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + } + } +} +impl<'a, T: EthSpec> From<&'a ExecutionPayloadCapella> for ExecutionPayloadHeaderCapella { + fn from(payload: &'a ExecutionPayloadCapella) -> Self { + Self { + parent_hash: payload.parent_hash, + fee_recipient: payload.fee_recipient, + state_root: payload.state_root, + receipts_root: payload.receipts_root, + logs_bloom: payload.logs_bloom.clone(), + prev_randao: payload.prev_randao, + block_number: payload.block_number, + gas_limit: payload.gas_limit, + gas_used: payload.gas_used, + timestamp: payload.timestamp, + extra_data: payload.extra_data.clone(), + base_fee_per_gas: payload.base_fee_per_gas, + block_hash: payload.block_hash, + transactions_root: payload.transactions.tree_hash_root(), + withdrawals_root: payload.withdrawals.tree_hash_root(), + } + } +} + +// These impls are required to work around an inelegance in `to_execution_payload_header`. +// They only clone headers so they should be relatively cheap. +impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderMerge { + fn from(payload: &'a Self) -> Self { + payload.clone() + } +} + +impl<'a, T: EthSpec> From<&'a Self> for ExecutionPayloadHeaderCapella { + fn from(payload: &'a Self) -> Self { + payload.clone() + } +} + +impl<'a, T: EthSpec> From> for ExecutionPayloadHeader { + fn from(payload: ExecutionPayloadRef<'a, T>) -> Self { + map_execution_payload_ref_into_execution_payload_header!( + &'a _, + payload, + |inner, cons| cons(inner.into()) + ) + } +} + +impl TryFrom> for ExecutionPayloadHeaderMerge { + type Error = BeaconStateError; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::Merge(execution_payload_header) => Ok(execution_payload_header), + _ => Err(BeaconStateError::IncorrectStateVariant), + } + } +} +impl TryFrom> for ExecutionPayloadHeaderCapella { + type Error = BeaconStateError; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::Capella(execution_payload_header) => { + Ok(execution_payload_header) + } + _ => Err(BeaconStateError::IncorrectStateVariant), + } + } +} + +#[cfg(feature = "std")] +impl ForkVersionDeserialize for ExecutionPayloadHeader { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + ForkName::Merge => Self::Merge(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?), + ForkName::Base | ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/fork.rs b/primitives/beacon/src/fork.rs new file mode 100644 index 00000000..779d8e33 --- /dev/null +++ b/primitives/beacon/src/fork.rs @@ -0,0 +1,45 @@ +use crate::Epoch; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Specifies a fork of the `BeaconChain`, to prevent replay attacks. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + Copy, + PartialEq, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Fork { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub previous_version: [u8; 4], + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub current_version: [u8; 4], + pub epoch: Epoch, +} + +impl Fork { + /// Return the fork version of the given ``epoch``. + /// + /// Spec v0.12.1 + pub fn get_fork_version(&self, epoch: Epoch) -> [u8; 4] { + if epoch < self.epoch { + return self.previous_version; + } + self.current_version + } +} diff --git a/primitives/beacon/src/fork_data.rs b/primitives/beacon/src/fork_data.rs new file mode 100644 index 00000000..3174faf3 --- /dev/null +++ b/primitives/beacon/src/fork_data.rs @@ -0,0 +1,32 @@ +use crate::{Hash256, SignedRoot}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Specifies a fork of the `BeaconChain`, to prevent replay attacks. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ForkData { + #[serde(with = "eth2_serde_utils::bytes_4_hex")] + pub current_version: [u8; 4], + pub genesis_validators_root: Hash256, +} + +impl SignedRoot for ForkData {} diff --git a/primitives/beacon/src/fork_name.rs b/primitives/beacon/src/fork_name.rs new file mode 100644 index 00000000..87be9635 --- /dev/null +++ b/primitives/beacon/src/fork_name.rs @@ -0,0 +1,141 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use core::convert::TryFrom; +use core::fmt::{self, Display, Formatter}; +use core::str::FromStr; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(try_from = "String")] +#[serde(into = "String")] +pub enum ForkName { + Base, + Altair, + Merge, + Capella, +} + +impl ForkName { + pub fn list_all() -> Vec { + vec![ + ForkName::Base, + ForkName::Altair, + ForkName::Merge, + ForkName::Capella, + ] + } + + /// Return the name of the fork immediately prior to the current one. + /// + /// If `self` is `ForkName::Base` then `Base` is returned. + pub fn previous_fork(self) -> Option { + match self { + ForkName::Base => None, + ForkName::Altair => Some(ForkName::Base), + ForkName::Merge => Some(ForkName::Altair), + ForkName::Capella => Some(ForkName::Merge), + } + } + + /// Return the name of the fork immediately after the current one. + /// + /// If `self` is the last known fork and has no successor, `None` is returned. + pub fn next_fork(self) -> Option { + match self { + ForkName::Base => Some(ForkName::Altair), + ForkName::Altair => Some(ForkName::Merge), + ForkName::Merge => Some(ForkName::Capella), + ForkName::Capella => None, + } + } +} + +/// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`. +/// +/// The `$body` expression is where the magic happens. The macro allows us to achieve polymorphism +/// in the return type, which is not usually possible in Rust without trait objects. +/// +/// E.g. you could call `map_fork_name!(fork, BeaconBlock, serde_json::from_str(s))` to decode +/// different `BeaconBlock` variants depending on the value of `fork`. Note how the type of the body +/// will change between `BeaconBlockBase` and `BeaconBlockAltair` depending on which branch is +/// taken, the important thing is that they are re-unified by injecting them back into the +/// `BeaconBlock` parent enum. +/// +/// If you would also like to extract additional data alongside the superstruct type, use +/// the more flexible `map_fork_name_with` macro. +#[macro_export] +macro_rules! map_fork_name { + ($fork_name:expr, $t:tt, $body:expr) => { + map_fork_name_with!($fork_name, $t, { ($body, ()) }).0 + }; +} + +/// Map a fork name into a tuple of `(t, extra)` where `t` is a superstruct type. +#[macro_export] +macro_rules! map_fork_name_with { + ($fork_name:expr, $t:tt, $body:block) => { + match $fork_name { + ForkName::Base => { + let (value, extra_data) = $body; + ($t::Base(value), extra_data) + } + ForkName::Altair => { + let (value, extra_data) = $body; + ($t::Altair(value), extra_data) + } + ForkName::Merge => { + let (value, extra_data) = $body; + ($t::Merge(value), extra_data) + } + ForkName::Capella => { + let (value, extra_data) = $body; + ($t::Capella(value), extra_data) + } + } + }; +} + +impl FromStr for ForkName { + type Err = String; + + fn from_str(fork_name: &str) -> Result { + Ok(match fork_name.to_lowercase().as_ref() { + "phase0" | "base" => ForkName::Base, + "altair" => ForkName::Altair, + "bellatrix" | "merge" => ForkName::Merge, + "capella" => ForkName::Capella, + _ => return Err(format!("unknown fork name: {}", fork_name)), + }) + } +} + +impl Display for ForkName { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ForkName::Base => Display::fmt(&"phase0", f), + ForkName::Altair => Display::fmt(&"altair", f), + ForkName::Merge => Display::fmt(&"bellatrix", f), + ForkName::Capella => Display::fmt(&"capella", f), + } + } +} + +impl From for String { + fn from(fork: ForkName) -> String { + fork.to_string() + } +} + +impl TryFrom for ForkName { + type Error = String; + + fn try_from(s: String) -> Result { + Self::from_str(&s) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct InconsistentFork { + pub fork_at_slot: ForkName, + pub object_fork: ForkName, +} diff --git a/primitives/beacon/src/fork_versioned_response.rs b/primitives/beacon/src/fork_versioned_response.rs new file mode 100644 index 00000000..30d5c0c3 --- /dev/null +++ b/primitives/beacon/src/fork_versioned_response.rs @@ -0,0 +1,91 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use crate::ForkName; +use serde::Serialize; +#[cfg(feature = "std")] +use serde::{de::DeserializeOwned, Deserialize, Deserializer}; +#[cfg(feature = "std")] +use serde_json::value::Value; + +// Deserialize is only implemented for types that implement ForkVersionDeserialize +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct ExecutionOptimisticForkVersionedResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + pub execution_optimistic: Option, + pub data: T, +} + +#[cfg(feature = "std")] +impl<'de, F> serde::Deserialize<'de> for ExecutionOptimisticForkVersionedResponse +where + F: ForkVersionDeserialize, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + version: Option, + execution_optimistic: Option, + data: serde_json::Value, + } + + let helper = Helper::deserialize(deserializer)?; + let data = match helper.version { + Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?, + None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?, + }; + + Ok(ExecutionOptimisticForkVersionedResponse { + version: helper.version, + execution_optimistic: helper.execution_optimistic, + data, + }) + } +} + +#[cfg(feature = "std")] +pub trait ForkVersionDeserialize: Sized + DeserializeOwned { + fn deserialize_by_fork<'de, D: Deserializer<'de>>( + value: Value, + fork_name: ForkName, + ) -> Result; +} + +// Deserialize is only implemented for types that implement ForkVersionDeserialize +#[derive(Debug, PartialEq, Clone, Serialize)] +pub struct ForkVersionedResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub version: Option, + pub data: T, +} + +#[cfg(feature = "std")] +impl<'de, F> serde::Deserialize<'de> for ForkVersionedResponse +where + F: ForkVersionDeserialize, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper { + version: Option, + data: serde_json::Value, + } + + let helper = Helper::deserialize(deserializer)?; + let data = match helper.version { + Some(fork_name) => F::deserialize_by_fork::<'de, D>(helper.data, fork_name)?, + None => serde_json::from_value(helper.data).map_err(serde::de::Error::custom)?, + }; + + Ok(ForkVersionedResponse { + version: helper.version, + data, + }) + } +} diff --git a/primitives/beacon/src/graffiti.rs b/primitives/beacon/src/graffiti.rs new file mode 100644 index 00000000..6631652e --- /dev/null +++ b/primitives/beacon/src/graffiti.rs @@ -0,0 +1,173 @@ +use crate::prelude::*; +use core::fmt; +use core::str::FromStr; +use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; +use ssz::{Decode, DecodeError, Encode}; +use tree_hash::{PackedEncoding, TreeHash}; + +pub const GRAFFITI_BYTES_LEN: usize = 32; + +/// The 32-byte `graffiti` field on a beacon block. +#[derive( + Default, + Debug, + PartialEq, + Hash, + Clone, + Copy, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Graffiti(#[serde(with = "serde_graffiti")] pub [u8; GRAFFITI_BYTES_LEN]); + +impl fmt::Display for Graffiti { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", eth2_serde_utils::hex::encode(self.0)) + } +} + +impl From<[u8; GRAFFITI_BYTES_LEN]> for Graffiti { + fn from(bytes: [u8; GRAFFITI_BYTES_LEN]) -> Self { + Self(bytes) + } +} + +impl Into<[u8; GRAFFITI_BYTES_LEN]> for Graffiti { + fn into(self) -> [u8; GRAFFITI_BYTES_LEN] { + self.0 + } +} + +#[derive(Debug, Clone, PartialEq, Serialize, Default)] +#[serde(transparent)] +pub struct GraffitiString(String); + +impl FromStr for GraffitiString { + type Err = String; + + fn from_str(s: &str) -> Result { + if s.as_bytes().len() > GRAFFITI_BYTES_LEN { + return Err(format!( + "Graffiti exceeds max length {}", + GRAFFITI_BYTES_LEN + )); + } + Ok(Self(s.to_string())) + } +} + +impl<'de> Deserialize<'de> for GraffitiString { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s: String = serde::Deserialize::deserialize(deserializer)?; + GraffitiString::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl Into for GraffitiString { + fn into(self) -> Graffiti { + let graffiti_bytes = self.0.as_bytes(); + let mut graffiti = [0; GRAFFITI_BYTES_LEN]; + + let graffiti_len = core::cmp::min(graffiti_bytes.len(), GRAFFITI_BYTES_LEN); + + // Copy the provided bytes over. + // + // Panic-free because `graffiti_bytes.len()` <= `GRAFFITI_BYTES_LEN`. + graffiti + .get_mut(..graffiti_len) + .expect("graffiti_len <= GRAFFITI_BYTES_LEN") + .copy_from_slice(graffiti_bytes); + graffiti.into() + } +} + +pub mod serde_graffiti { + use super::*; + + pub fn serialize(bytes: &[u8; GRAFFITI_BYTES_LEN], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(ð2_serde_utils::hex::encode(bytes)) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; GRAFFITI_BYTES_LEN], D::Error> + where + D: Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + + let bytes = eth2_serde_utils::hex::decode(&s).map_err(D::Error::custom)?; + + if bytes.len() != GRAFFITI_BYTES_LEN { + return Err(D::Error::custom(format!( + "incorrect byte length {}, expected {}", + bytes.len(), + GRAFFITI_BYTES_LEN + ))); + } + + let mut array = [0; GRAFFITI_BYTES_LEN]; + array[..].copy_from_slice(&bytes); + + Ok(array) + } +} + +impl Encode for Graffiti { + fn is_ssz_fixed_len() -> bool { + <[u8; GRAFFITI_BYTES_LEN] as Encode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; GRAFFITI_BYTES_LEN] as Encode>::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + self.0.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } +} + +impl Decode for Graffiti { + fn is_ssz_fixed_len() -> bool { + <[u8; GRAFFITI_BYTES_LEN] as Decode>::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + <[u8; GRAFFITI_BYTES_LEN] as Decode>::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + <[u8; GRAFFITI_BYTES_LEN]>::from_ssz_bytes(bytes).map(Self) + } +} + +impl TreeHash for Graffiti { + fn tree_hash_type() -> tree_hash::TreeHashType { + <[u8; GRAFFITI_BYTES_LEN]>::tree_hash_type() + } + + fn tree_hash_packed_encoding(&self) -> PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + <[u8; GRAFFITI_BYTES_LEN]>::tree_hash_packing_factor() + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + self.0.tree_hash_root() + } +} diff --git a/primitives/beacon/src/indexed_attestation.rs b/primitives/beacon/src/indexed_attestation.rs new file mode 100644 index 00000000..7ac2efaf --- /dev/null +++ b/primitives/beacon/src/indexed_attestation.rs @@ -0,0 +1,105 @@ +use crate::prelude::*; +use crate::{AggregateSignature, AttestationData, EthSpec, VariableList}; +use core::hash::{Hash, Hasher}; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz::Encode; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Details an attestation that can be slashable. +/// +/// To be included in an `AttesterSlashing`. +/// +/// Spec v0.12.1 +#[derive( + Derivative, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Eq)] // to satisfy Clippy's lint about `Hash` +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct IndexedAttestation { + /// Lists validator registry indices, not committee indices. + #[serde(with = "quoted_variable_list_u64")] + pub attesting_indices: VariableList, + pub data: AttestationData, + pub signature: AggregateSignature, +} + +impl IndexedAttestation { + /// Check if ``attestation_data_1`` and ``attestation_data_2`` have the same target. + /// + /// Spec v0.12.1 + pub fn is_double_vote(&self, other: &Self) -> bool { + self.data.target.epoch == other.data.target.epoch && self.data != other.data + } + + /// Check if ``attestation_data_1`` surrounds ``attestation_data_2``. + /// + /// Spec v0.12.1 + pub fn is_surround_vote(&self, other: &Self) -> bool { + self.data.source.epoch < other.data.source.epoch + && other.data.target.epoch < self.data.target.epoch + } +} + +/// Implementation of non-crypto-secure `Hash`, for use with `HashMap` and `HashSet`. +/// +/// Guarantees `att1 == att2 -> hash(att1) == hash(att2)`. +/// +/// Used in the operation pool. +impl Hash for IndexedAttestation { + fn hash(&self, state: &mut H) { + self.attesting_indices.hash(state); + self.data.hash(state); + self.signature.as_ssz_bytes().hash(state); + } +} + +/// Serialize a variable list of `u64` such that each int is quoted. Deserialize a variable +/// list supporting both quoted and un-quoted ints. +/// +/// E.g.,`["0", "1", "2"]` +mod quoted_variable_list_u64 { + use super::*; + use crate::Unsigned; + use eth2_serde_utils::quoted_u64_vec::{QuotedIntVecVisitor, QuotedIntWrapper}; + use serde::ser::SerializeSeq; + use serde::{Deserializer, Serializer}; + + pub fn serialize(value: &VariableList, serializer: S) -> Result + where + S: Serializer, + T: Unsigned, + { + let mut seq = serializer.serialize_seq(Some(value.len()))?; + for &int in value.iter() { + seq.serialize_element(&QuotedIntWrapper { int })?; + } + seq.end() + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + T: Unsigned, + { + deserializer + .deserialize_any(QuotedIntVecVisitor) + .and_then(|vec| { + VariableList::new(vec) + .map_err(|e| serde::de::Error::custom(format!("invalid length: {:?}", e))) + }) + } +} diff --git a/primitives/beacon/src/int_to_bytes.rs b/primitives/beacon/src/int_to_bytes.rs new file mode 100644 index 00000000..55b28807 --- /dev/null +++ b/primitives/beacon/src/int_to_bytes.rs @@ -0,0 +1,78 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; +use bytes::{BufMut, BytesMut}; + +/// Returns `int` as little-endian bytes with a length of 1. +pub fn int_to_bytes1(int: u8) -> Vec { + vec![int] +} + +/// Returns `int` as little-endian bytes with a length of 2. +pub fn int_to_bytes2(int: u16) -> Vec { + let mut bytes = BytesMut::with_capacity(2); + bytes.put_u16_le(int); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 3. +/// +/// An `Option` is returned as Rust does not support a native +/// `u24` type. +/// +/// The Eth 2.0 specification uses `int.to_bytes(2, 'little')`, which throws an error if `int` +/// doesn't fit within 3 bytes. The specification relies upon implicit asserts for some validity +/// conditions, so we ensure the calling function is aware of the error condition as opposed to +/// hiding it with a modulo. +pub fn int_to_bytes3(int: u32) -> Option> { + if int < 2_u32.pow(3 * 8) { + let mut bytes = BytesMut::with_capacity(4); + bytes.put_u32_le(int); + Some(bytes[0..3].to_vec()) + } else { + None + } +} + +/// Returns `int` as little-endian bytes with a length of 4. +pub fn int_to_bytes4(int: u32) -> [u8; 4] { + int.to_le_bytes() +} + +/// Returns `int` as little-endian bytes with a length of 8. +pub fn int_to_bytes8(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(8); + bytes.put_u64_le(int); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 32. +pub fn int_to_bytes32(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(32); + bytes.put_u64_le(int); + bytes.resize(32, 0); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 32. +pub fn int_to_fixed_bytes32(int: u64) -> [u8; 32] { + let mut bytes = [0; 32]; + let int_bytes = int.to_le_bytes(); + bytes[0..int_bytes.len()].copy_from_slice(&int_bytes); + bytes +} + +/// Returns `int` as little-endian bytes with a length of 48. +pub fn int_to_bytes48(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(48); + bytes.put_u64_le(int); + bytes.resize(48, 0); + bytes.to_vec() +} + +/// Returns `int` as little-endian bytes with a length of 96. +pub fn int_to_bytes96(int: u64) -> Vec { + let mut bytes = BytesMut::with_capacity(96); + bytes.put_u64_le(int); + bytes.resize(96, 0); + bytes.to_vec() +} diff --git a/primitives/beacon/src/lib.rs b/primitives/beacon/src/lib.rs new file mode 100644 index 00000000..82c37c10 --- /dev/null +++ b/primitives/beacon/src/lib.rs @@ -0,0 +1,178 @@ +//! Ethereum 2.0 types + +#![cfg_attr(not(feature = "std"), no_std)] +// Required for big type-level numbers +#![recursion_limit = "128"] +// Clippy lint set up +#![cfg_attr( + not(test), + deny( + clippy::integer_arithmetic, + clippy::disallowed_methods, + clippy::indexing_slicing + ) +)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +#[cfg(not(feature = "std"))] +mod prelude { + pub use alloc::borrow::Cow; + pub use alloc::collections::{BTreeMap, BTreeSet}; + pub use alloc::format; + pub use alloc::string::String; + pub use alloc::string::ToString; + pub use alloc::{vec, vec::Vec}; + pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; + pub use core::fmt::Debug; + pub use core::hash::Hash; + pub use derivative::Derivative; + pub use scale_info::TypeInfo; + pub use ssz_derive::{Decode, Encode}; + pub use superstruct::superstruct; + pub use tree_hash::TreeHash; + pub use tree_hash_derive::TreeHash; +} + +#[cfg(feature = "std")] +mod prelude { + pub use codec::{Decode as ScaleDecode, Encode as ScaleEncode, MaxEncodedLen}; + pub use derivative::Derivative; + pub use scale_info::TypeInfo; + pub use ssz_derive::{Decode, Encode}; + pub use std::borrow::Cow; + pub use std::collections::{BTreeMap, BTreeSet}; + pub use superstruct::superstruct; + pub use tree_hash::TreeHash; + pub use tree_hash_derive::TreeHash; +} + +pub mod attestation; +pub mod attestation_data; +pub mod attester_slashing; +pub mod beacon_block; +pub mod beacon_block_body; +pub mod beacon_block_header; +pub mod beacon_state; +pub mod bls_to_execution_change; +pub mod checkpoint; +pub mod consts; +pub mod deposit; +pub mod deposit_data; +pub mod eth1_data; +pub mod eth_spec; +pub mod execution_block_hash; +pub mod execution_payload; +pub mod execution_payload_header; +pub mod fork; +pub mod fork_data; +pub mod fork_name; +pub mod fork_versioned_response; +pub mod graffiti; +pub mod indexed_attestation; +pub mod light_client_bootstrap; +pub mod light_client_update; +pub mod proposer_slashing; +pub mod signed_beacon_block; +pub mod signed_beacon_block_header; +pub mod signed_bls_to_execution_change; +pub mod signed_voluntary_exit; +pub mod signing_data; +pub mod voluntary_exit; +#[macro_use] +pub mod slot_epoch_macros; +pub mod int_to_bytes; +pub mod payload; +pub mod slot_epoch; +pub mod sync_aggregate; +pub mod sync_committee; +pub mod withdrawal; + +pub mod slot_data; + +pub mod beacon_config; +pub mod light_client_header; +pub mod safe_arith; + +use ethereum_types::{H160, H256}; + +pub use crate::attestation::{Attestation, Error as AttestationError}; +pub use crate::attestation_data::AttestationData; +pub use crate::attester_slashing::AttesterSlashing; +pub use crate::beacon_block::{ + BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockMerge, + BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, +}; +pub use crate::beacon_block_body::{ + BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyCapella, + BeaconBlockBodyMerge, BeaconBlockBodyRef, BeaconBlockBodyRefMut, +}; +pub use crate::beacon_block_header::BeaconBlockHeader; +pub use crate::beacon_state::{Error as BeaconStateError, *}; +pub use crate::bls_to_execution_change::BlsToExecutionChange; +pub use crate::checkpoint::Checkpoint; +pub use crate::deposit::{Deposit, DEPOSIT_TREE_DEPTH}; +pub use crate::deposit_data::DepositData; +pub use crate::eth1_data::Eth1Data; +pub use crate::eth_spec::EthSpecId; +pub use crate::eth_spec::{EthSpec, GnosisEthSpec, MainnetEthSpec, MinimalEthSpec}; +pub use crate::execution_block_hash::ExecutionBlockHash; +pub use crate::execution_payload::{ + ExecutionPayload, ExecutionPayloadCapella, ExecutionPayloadMerge, ExecutionPayloadRef, + Transaction, Transactions, Withdrawals, +}; +pub use crate::execution_payload_header::{ + ExecutionPayloadHeader, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderMerge, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, +}; +pub use crate::fork::Fork; +pub use crate::fork_data::ForkData; +pub use crate::fork_name::{ForkName, InconsistentFork}; +#[cfg(feature = "std")] +pub use crate::fork_versioned_response::ForkVersionDeserialize; +pub use crate::fork_versioned_response::{ + ExecutionOptimisticForkVersionedResponse, ForkVersionedResponse, +}; +pub use crate::graffiti::{Graffiti, GRAFFITI_BYTES_LEN}; +pub use crate::indexed_attestation::IndexedAttestation; +pub use crate::light_client_header::{ + LightClientHeader, LightClientHeaderCapella, LightClientHeaderMerge, +}; +pub use crate::payload::{ + AbstractExecPayload, BlindedPayload, BlindedPayloadCapella, BlindedPayloadMerge, + BlindedPayloadRef, BlockType, ExecPayload, FullPayload, FullPayloadCapella, FullPayloadMerge, + FullPayloadRef, OwnedExecPayload, +}; +pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::signed_beacon_block::{ + SignedBeaconBlock, SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockCapella, + SignedBeaconBlockHash, SignedBeaconBlockMerge, SignedBlindedBeaconBlock, +}; +pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader; +pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; +pub use crate::signed_voluntary_exit::SignedVoluntaryExit; +pub use crate::signing_data::{SignedRoot, SigningData}; +pub use crate::slot_epoch::{Epoch, Slot}; +pub use crate::sync_aggregate::SyncAggregate; +pub use crate::sync_committee::SyncCommittee; +pub use crate::voluntary_exit::VoluntaryExit; +pub use crate::withdrawal::Withdrawal; +pub use beacon_config::{ConsensusConfig, ForkInfo, ForkSchedule}; + +pub type CommitteeIndex = u64; +pub type Hash256 = H256; +pub type Uint256 = ethereum_types::U256; +pub type Address = H160; +pub type ForkVersion = [u8; 4]; +pub type BLSFieldElement = Uint256; +// pub type Blob = FixedVector::FieldElementsPerBlob>; +pub type VersionedHash = Hash256; +pub type Hash64 = ethereum_types::H64; + +pub use bls::{ + AggregatePublicKey, AggregateSignature, Keypair, PublicKey, PublicKeyBytes, SecretKey, + Signature, SignatureBytes, +}; +pub use ssz_types::{typenum, typenum::Unsigned, BitList, BitVector, FixedVector, VariableList}; +pub use superstruct::superstruct; diff --git a/primitives/beacon/src/light_client_bootstrap.rs b/primitives/beacon/src/light_client_bootstrap.rs new file mode 100644 index 00000000..a4145e55 --- /dev/null +++ b/primitives/beacon/src/light_client_bootstrap.rs @@ -0,0 +1,103 @@ +use super::{EthSpec, FixedVector, Hash256, SyncCommittee}; +use crate::light_client_header::LightClientHeaderRef; +use crate::light_client_update::*; +use crate::prelude::*; +use crate::LightClientHeaderCapella; +use crate::LightClientHeaderMerge; +use serde::{Deserialize, Serialize}; + +/// A LightClientBootstrap is the initializer we send over to lightclient nodes +/// that are trying to generate their basic storage when booting up. +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq)] +#[serde(bound = "T: EthSpec", untagged)] +#[scale_info(skip_type_params(T))] +pub struct LightClientBootstrap { + /// Requested beacon block header. + #[superstruct(only(Merge), partial_getter(rename = "header_merge"))] + pub header: LightClientHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "header_capella"))] + pub header: LightClientHeaderCapella, + /// The `SyncCommittee` used in the requested period. + pub current_sync_committee: SyncCommittee, + /// Merkle proof for sync committee + pub current_sync_committee_branch: FixedVector, +} + +impl LightClientBootstrap { + pub fn header(&self) -> LightClientHeaderRef { + match self { + Self::Merge(update) => LightClientHeaderRef::Merge(&update.header), + Self::Capella(update) => LightClientHeaderRef::Capella(&update.header), + } + } +} + +impl<'a, T: EthSpec> LightClientBootstrapRef<'a, T> { + pub fn owned(&self) -> LightClientBootstrap { + match *self { + Self::Merge(update) => LightClientBootstrap::Merge(update.clone()), + Self::Capella(update) => LightClientBootstrap::Capella(update.clone()), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientBootstrap { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/light_client_header.rs b/primitives/beacon/src/light_client_header.rs new file mode 100644 index 00000000..6bcde9e0 --- /dev/null +++ b/primitives/beacon/src/light_client_header.rs @@ -0,0 +1,93 @@ +use core::marker::PhantomData; + +use super::BeaconBlockHeader; +use crate::{ + light_client_update::ExecutionPayloadProofLen, prelude::*, EthSpec, + ExecutionPayloadHeaderCapella, Hash256, +}; +use serde::{Deserialize, Serialize}; +use ssz_types::FixedVector; + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec", untagged)] +#[scale_info(skip_type_params(T))] +pub struct LightClientHeader { + pub beacon: BeaconBlockHeader, + #[superstruct(only(Capella))] + pub execution: ExecutionPayloadHeaderCapella, + #[superstruct(only(Capella))] + pub execution_branch: FixedVector, + #[superstruct(only(Merge))] + #[serde(skip)] + _phantom: PhantomData, +} + +impl<'a, T: EthSpec> LightClientHeaderRef<'a, T> { + pub fn owned(&self) -> LightClientHeader { + match *self { + Self::Merge(update) => LightClientHeader::Merge(update.clone()), + Self::Capella(update) => LightClientHeader::Capella(update.clone()), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientHeader { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/light_client_update.rs b/primitives/beacon/src/light_client_update.rs new file mode 100644 index 00000000..e043215a --- /dev/null +++ b/primitives/beacon/src/light_client_update.rs @@ -0,0 +1,180 @@ +use super::{EthSpec, FixedVector, Hash256, Slot, SyncAggregate, SyncCommittee}; +use crate::beacon_state; +use crate::light_client_header::LightClientHeaderRef; +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::LightClientHeaderCapella; +use crate::LightClientHeaderMerge; +use serde::{Deserialize, Serialize}; +use ssz_types::typenum::U4; +use ssz_types::typenum::{U5, U6}; + +pub const FINALIZED_ROOT_INDEX: usize = 41; +pub const CURRENT_SYNC_COMMITTEE_INDEX: usize = 22; +pub const NEXT_SYNC_COMMITTEE_INDEX: usize = 23; +pub const EXECUTION_PAYLOAD_INDEX: usize = 9; + +pub type FinalizedRootProofLen = U6; +pub type CurrentSyncCommitteeProofLen = U5; +pub type NextSyncCommitteeProofLen = U5; +pub type ExecutionPayloadProofLen = U4; + +pub const FINALIZED_ROOT_PROOF_LEN: usize = 6; +pub const CURRENT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const NEXT_SYNC_COMMITTEE_PROOF_LEN: usize = 5; +pub const EXECUTION_PAYLOAD_PROOF_LEN: usize = 4; + +#[derive(Debug, PartialEq, Clone)] +pub enum Error { + SszTypesError(ssz_types::Error), + BeaconStateError(beacon_state::Error), + ArithError(ArithError), + AltairForkNotActive, + NotEnoughSyncCommitteeParticipants, + MismatchingPeriods, + InvalidFinalizedBlock, +} + +impl From for Error { + fn from(e: ssz_types::Error) -> Error { + Error::SszTypesError(e) + } +} + +impl From for Error { + fn from(e: beacon_state::Error) -> Error { + Error::BeaconStateError(e) + } +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +/// A LightClientUpdate is the update we request solely to either complete the bootstraping process, +/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period +/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD]. +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq), + serde(bound = "T: EthSpec", deny_unknown_fields), + scale_info(skip_type_params(T)) + ), + ref_attributes(derive(PartialEq)) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq)] +#[serde(bound = "T: EthSpec", untagged)] +#[scale_info(skip_type_params(T))] +pub struct LightClientUpdate { + /// The last `BeaconBlockHeader` from the last attested block by the sync committee. + #[superstruct(only(Merge), partial_getter(rename = "attested_header_merge"))] + pub attested_header: LightClientHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "attested_header_capella"))] + pub attested_header: LightClientHeaderCapella, + /// The `SyncCommittee` used in the next period. + #[serde(default)] + pub next_sync_committee: Option>, + /// Merkle proof for next sync committee + #[serde(default)] + pub next_sync_committee_branch: Option>, + /// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch). + #[superstruct(only(Merge), partial_getter(rename = "finalized_header_merge"))] + #[serde(default)] + pub finalized_header: Option>, + #[superstruct(only(Capella), partial_getter(rename = "finalized_header_capella"))] + #[serde(default)] + pub finalized_header: Option>, + /// Merkle proof attesting finalized header. + #[serde(default)] + pub finality_branch: Option>, + /// current sync aggreggate + pub sync_aggregate: SyncAggregate, + /// Slot of the sync aggregated singature + #[superstruct(getter(copy))] + pub signature_slot: Slot, +} + +impl LightClientUpdate { + pub fn is_sync_committee_update(&self) -> bool { + self.next_sync_committee().is_some() && self.next_sync_committee_branch().is_some() + } + + pub fn is_finality_update(&self) -> bool { + self.finalized_header().is_some() && self.finality_branch().is_some() + } + + pub fn finalized_header(&self) -> Option> { + match self { + Self::Merge(update) => update + .finalized_header + .as_ref() + .map(|x| LightClientHeaderRef::Merge(x)), + Self::Capella(update) => update + .finalized_header + .as_ref() + .map(|x| LightClientHeaderRef::Capella(x)), + } + } + + pub fn attested_header(&self) -> LightClientHeaderRef { + match self { + Self::Merge(update) => LightClientHeaderRef::Merge(&update.attested_header), + Self::Capella(update) => LightClientHeaderRef::Capella(&update.attested_header), + } + } +} + +#[cfg(feature = "std")] +impl crate::ForkVersionDeserialize for LightClientUpdate { + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: crate::ForkName, + ) -> Result { + let convert_err = |e| { + serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: {:?}", + e + )) + }; + + Ok(match fork_name { + crate::ForkName::Merge => { + Self::Merge(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Capella => { + Self::Capella(serde_json::from_value(value).map_err(convert_err)?) + } + crate::ForkName::Base | crate::ForkName::Altair => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + fork_name + ))); + } + }) + } +} diff --git a/primitives/beacon/src/payload.rs b/primitives/beacon/src/payload.rs new file mode 100644 index 00000000..2a29ff1f --- /dev/null +++ b/primitives/beacon/src/payload.rs @@ -0,0 +1,924 @@ +use crate::prelude::*; +use crate::*; +use core::convert::TryFrom; +use core::fmt::Debug; +use core::hash::Hash; +use derivative::Derivative; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, Encode}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive(Debug)] +pub enum BlockType { + Blinded, + Full, +} + +/// A trait representing behavior of an `ExecutionPayload` that either has a full list of transactions +/// or a transaction hash in it's place. +pub trait ExecPayload: Debug + Clone + PartialEq + Hash + TreeHash + Send { + fn block_type() -> BlockType; + + /// Convert the payload into a payload header. + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader; + + /// We provide a subset of field accessors, for the fields used in `consensus`. + /// + /// More fields can be added here if you wish. + fn parent_hash(&self) -> ExecutionBlockHash; + fn prev_randao(&self) -> Hash256; + fn block_number(&self) -> u64; + fn timestamp(&self) -> u64; + fn block_hash(&self) -> ExecutionBlockHash; + fn fee_recipient(&self) -> Address; + fn gas_limit(&self) -> u64; + fn transactions(&self) -> Option<&Transactions>; + /// fork-specific fields + fn withdrawals_root(&self) -> Result; + + /// Is this a default payload with 0x0 roots for transactions and withdrawals? + fn is_default_with_zero_roots(&self) -> bool; + + /// Is this a default payload with the hash of the empty list for transactions and withdrawals? + fn is_default_with_empty_roots(&self) -> bool; +} + +/// `ExecPayload` functionality the requires ownership. +pub trait OwnedExecPayload: + ExecPayload + Default + Serialize + DeserializeOwned + Encode + Decode + 'static +{ +} + +impl OwnedExecPayload for P where + P: ExecPayload + Default + Serialize + DeserializeOwned + Encode + Decode + 'static +{ +} + +pub trait AbstractExecPayload: + ExecPayload + + Sized + + From> + + TryFrom> + + TryInto + + TryInto +{ + type Ref<'a>: ExecPayload + Copy + From<&'a Self::Merge> + From<&'a Self::Capella>; + + type Merge: OwnedExecPayload + + Into + + for<'a> From>> + + TryFrom>; + type Capella: OwnedExecPayload + + Into + + for<'a> From>> + + TryFrom>; + + fn default_at_fork(fork_name: ForkName) -> Result; +} + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + ssz(struct_behaviour = "transparent"), + ), + ref_attributes( + derive(Debug, Derivative, TreeHash), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + tree_hash(enum_behaviour = "transparent"), + ), + map_into(ExecutionPayload), + map_ref_into(ExecutionPayloadRef), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct FullPayload { + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + pub execution_payload: ExecutionPayloadMerge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload: ExecutionPayloadCapella, +} + +impl From> for ExecutionPayload { + fn from(full_payload: FullPayload) -> Self { + map_full_payload_into_execution_payload!(full_payload, move |payload, cons| { + cons(payload.execution_payload) + }) + } +} + +impl<'a, T: EthSpec> From> for ExecutionPayload { + fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self { + map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| { + cons(payload); + payload.execution_payload.clone().into() + }) + } +} + +impl<'a, T: EthSpec> From> for FullPayload { + fn from(full_payload_ref: FullPayloadRef<'a, T>) -> Self { + map_full_payload_ref!(&'a _, full_payload_ref, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecPayload for FullPayload { + fn block_type() -> BlockType { + BlockType::Full + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_full_payload_ref!(&'a _, self.to_ref(), move |inner, cons| { + cons(inner); + let exec_payload_ref: ExecutionPayloadRef<'a, T> = From::from(&inner.execution_payload); + ExecutionPayloadHeader::from(exec_payload_ref) + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload.gas_limit + }) + } + + fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + Some(&payload.execution_payload.transactions) + }) + } + + fn withdrawals_root(&self) -> Result { + match self { + FullPayload::Merge(_) => Err(Error::IncorrectStateVariant), + FullPayload::Capella(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) + } + + fn is_default_with_empty_roots(&self) -> bool { + // For full payloads the empty/zero distinction does not exist. + self.is_default_with_zero_roots() + } +} + +impl FullPayload { + pub fn execution_payload(self) -> ExecutionPayload { + map_full_payload_into_execution_payload!(self, |inner, cons| { + cons(inner.execution_payload) + }) + } +} + +impl<'a, T: EthSpec> FullPayloadRef<'a, T> { + pub fn execution_payload_ref(self) -> ExecutionPayloadRef<'a, T> { + map_full_payload_ref_into_execution_payload_ref!(&'a _, self, |inner, cons| { + cons(&inner.execution_payload) + }) + } +} + +impl<'b, T: EthSpec> ExecPayload for FullPayloadRef<'b, T> { + fn block_type() -> BlockType { + BlockType::Full + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.to_execution_payload_header() + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload.gas_limit + }) + } + + fn transactions<'a>(&'a self) -> Option<&'a Transactions> { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + Some(&payload.execution_payload.transactions) + }) + } + + fn withdrawals_root(&self) -> Result { + match self { + FullPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant), + FullPayloadRef::Capella(inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_full_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload == <_>::default() + }) + } + + fn is_default_with_empty_roots(&self) -> bool { + // For full payloads the empty/zero distinction does not exist. + self.is_default_with_zero_roots() + } +} + +impl AbstractExecPayload for FullPayload { + type Ref<'a> = FullPayloadRef<'a, T>; + type Merge = FullPayloadMerge; + type Capella = FullPayloadCapella; + + fn default_at_fork(fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant), + ForkName::Merge => Ok(FullPayloadMerge::default().into()), + ForkName::Capella => Ok(FullPayloadCapella::default().into()), + } + } +} + +impl From> for FullPayload { + fn from(execution_payload: ExecutionPayload) -> Self { + map_execution_payload_into_full_payload!(execution_payload, |inner, cons| { + cons(inner.into()) + }) + } +} + +impl TryFrom> for FullPayload { + type Error = (); + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(()) + } +} + +#[superstruct( + variants(Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + ScaleDecode, + ScaleEncode, + TypeInfo, + MaxEncodedLen, + TreeHash, + Derivative, + ), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + serde(bound = "T: EthSpec", deny_unknown_fields), + ssz(struct_behaviour = "transparent"), + scale_info(skip_type_params(T)) + ), + ref_attributes( + derive(Debug, Derivative, TreeHash), + derivative(PartialEq, Hash(bound = "T: EthSpec")), + tree_hash(enum_behaviour = "transparent"), + ), + map_into(ExecutionPayloadHeader), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + TreeHash, + ScaleDecode, + ScaleEncode, + TypeInfo, + MaxEncodedLen, + Derivative, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[tree_hash(enum_behaviour = "transparent")] +#[scale_info(skip_type_params(T))] +pub struct BlindedPayload { + #[superstruct(only(Merge), partial_getter(rename = "execution_payload_merge"))] + pub execution_payload_header: ExecutionPayloadHeaderMerge, + #[superstruct(only(Capella), partial_getter(rename = "execution_payload_capella"))] + pub execution_payload_header: ExecutionPayloadHeaderCapella, +} + +impl<'a, T: EthSpec> From> for BlindedPayload { + fn from(blinded_payload_ref: BlindedPayloadRef<'a, T>) -> Self { + map_blinded_payload_ref!(&'a _, blinded_payload_ref, move |payload, cons| { + cons(payload); + payload.clone().into() + }) + } +} + +impl ExecPayload for BlindedPayload { + fn block_type() -> BlockType { + BlockType::Blinded + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + map_blinded_payload_into_execution_payload_header!(self.clone(), |inner, cons| { + cons(inner.execution_payload_header) + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) + } + + fn transactions(&self) -> Option<&Transactions> { + None + } + + fn withdrawals_root(&self) -> Result { + match self { + BlindedPayload::Merge(_) => Err(Error::IncorrectStateVariant), + BlindedPayload::Capella(ref inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + } + } + + fn is_default_with_zero_roots(&self) -> bool { + self.to_ref().is_default_with_zero_roots() + } + + // For blinded payloads we must check "defaultness" against the default `ExecutionPayload` + // which has been blinded into an `ExecutionPayloadHeader`, NOT against the default + // `ExecutionPayloadHeader` which has a zeroed out `transactions_root`. The transactions root + // should be the root of the empty list. + fn is_default_with_empty_roots(&self) -> bool { + self.to_ref().is_default_with_empty_roots() + } +} + +impl<'b, T: EthSpec> ExecPayload for BlindedPayloadRef<'b, T> { + fn block_type() -> BlockType { + BlockType::Blinded + } + + fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.to_execution_payload_header() + }) + } + + fn parent_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.parent_hash + }) + } + + fn prev_randao<'a>(&'a self) -> Hash256 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.prev_randao + }) + } + + fn block_number<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_number + }) + } + + fn timestamp<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.timestamp + }) + } + + fn block_hash<'a>(&'a self) -> ExecutionBlockHash { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.block_hash + }) + } + + fn fee_recipient<'a>(&'a self) -> Address { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.fee_recipient + }) + } + + fn gas_limit<'a>(&'a self) -> u64 { + map_blinded_payload_ref!(&'a _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header.gas_limit + }) + } + + fn transactions(&self) -> Option<&Transactions> { + None + } + + fn withdrawals_root(&self) -> Result { + match self { + BlindedPayloadRef::Merge(_) => Err(Error::IncorrectStateVariant), + BlindedPayloadRef::Capella(inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + } + } + + fn is_default_with_zero_roots<'a>(&'a self) -> bool { + map_blinded_payload_ref!(&'b _, self, move |payload, cons| { + cons(payload); + payload.execution_payload_header == <_>::default() + }) + } + + fn is_default_with_empty_roots<'a>(&'a self) -> bool { + map_blinded_payload_ref!(&'b _, self, move |payload, cons| { + cons(payload); + payload.is_default_with_empty_roots() + }) + } +} + +macro_rules! impl_exec_payload_common { + ($wrapper_type:ident, // BlindedPayloadMerge | FullPayloadMerge + $wrapped_type:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadMerge + $wrapped_type_full:ident, // ExecutionPayloadMerge | ExecutionPayloadMerge + $wrapped_type_header:ident, // ExecutionPayloadHeaderMerge | ExecutionPayloadHeaderMerge + $wrapped_field:ident, // execution_payload_header | execution_payload + $fork_variant:ident, // Merge | Merge + $block_type_variant:ident, // Blinded | Full + $is_default_with_empty_roots:block, + $f:block, + $g:block) => { + impl ExecPayload for $wrapper_type { + fn block_type() -> BlockType { + BlockType::$block_type_variant + } + + fn to_execution_payload_header(&self) -> ExecutionPayloadHeader { + ExecutionPayloadHeader::$fork_variant($wrapped_type_header::from( + &self.$wrapped_field, + )) + } + + fn parent_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.parent_hash + } + + fn prev_randao(&self) -> Hash256 { + self.$wrapped_field.prev_randao + } + + fn block_number(&self) -> u64 { + self.$wrapped_field.block_number + } + + fn timestamp(&self) -> u64 { + self.$wrapped_field.timestamp + } + + fn block_hash(&self) -> ExecutionBlockHash { + self.$wrapped_field.block_hash + } + + fn fee_recipient(&self) -> Address { + self.$wrapped_field.fee_recipient + } + + fn gas_limit(&self) -> u64 { + self.$wrapped_field.gas_limit + } + + fn is_default_with_zero_roots(&self) -> bool { + self.$wrapped_field == $wrapped_type::default() + } + + fn is_default_with_empty_roots(&self) -> bool { + let f = $is_default_with_empty_roots; + f(self) + } + + fn transactions(&self) -> Option<&Transactions> { + let f = $f; + f(self) + } + + fn withdrawals_root(&self) -> Result { + let g = $g; + g(self) + } + } + + impl From<$wrapped_type> for $wrapper_type { + fn from($wrapped_field: $wrapped_type) -> Self { + Self { $wrapped_field } + } + } + }; +} + +macro_rules! impl_exec_payload_for_fork { + // BlindedPayloadMerge, FullPayloadMerge, ExecutionPayloadHeaderMerge, ExecutionPayloadMerge, Merge + ($wrapper_type_header:ident, $wrapper_type_full:ident, $wrapped_type_header:ident, $wrapped_type_full:ident, $fork_variant:ident) => { + //*************** Blinded payload implementations ******************// + + impl_exec_payload_common!( + $wrapper_type_header, // BlindedPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + execution_payload_header, + $fork_variant, // Merge + Blinded, + { + |wrapper: &$wrapper_type_header| { + wrapper.execution_payload_header + == $wrapped_type_header::from(&$wrapped_type_full::default()) + } + }, + { |_| { None } }, + { + let c: for<'a> fn(&'a $wrapper_type_header) -> Result = + |payload: &$wrapper_type_header| { + let wrapper_ref_type = BlindedPayloadRef::$fork_variant(&payload); + wrapper_ref_type.withdrawals_root() + }; + c + } + ); + + impl TryInto<$wrapper_type_header> for BlindedPayload { + type Error = Error; + + fn try_into(self) -> Result<$wrapper_type_header, Self::Error> { + match self { + BlindedPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::IncorrectStateVariant), + } + } + } + + // NOTE: the `Default` implementation for `BlindedPayload` needs to be different from the `Default` + // implementation for `ExecutionPayloadHeader` because payloads are checked for equality against the + // default payload in `is_merge_transition_block` to determine whether the merge has occurred. + // + // The default `BlindedPayload` is therefore the payload header that results from blinding the + // default `ExecutionPayload`, which differs from the default `ExecutionPayloadHeader` in that + // its `transactions_root` is the hash of the empty list rather than 0x0. + impl Default for $wrapper_type_header { + fn default() -> Self { + Self { + execution_payload_header: $wrapped_type_header::from( + &$wrapped_type_full::default(), + ), + } + } + } + + impl TryFrom> for $wrapper_type_header { + type Error = Error; + fn try_from(header: ExecutionPayloadHeader) -> Result { + match header { + ExecutionPayloadHeader::$fork_variant(execution_payload_header) => { + Ok(execution_payload_header.into()) + } + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } + + // BlindedPayload* from CoW reference to ExecutionPayload* (hopefully just a reference). + impl<'a, T: EthSpec> From>> for $wrapper_type_header { + fn from(execution_payload: Cow<'a, $wrapped_type_full>) -> Self { + Self { + execution_payload_header: $wrapped_type_header::from(&*execution_payload), + } + } + } + + //*************** Full payload implementations ******************// + + impl_exec_payload_common!( + $wrapper_type_full, // FullPayloadMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_full, // ExecutionPayloadMerge + $wrapped_type_header, // ExecutionPayloadHeaderMerge + execution_payload, + $fork_variant, // Merge + Full, + { + |wrapper: &$wrapper_type_full| { + wrapper.execution_payload == $wrapped_type_full::default() + } + }, + { + let c: for<'a> fn(&'a $wrapper_type_full) -> Option<&'a Transactions> = + |payload: &$wrapper_type_full| Some(&payload.execution_payload.transactions); + c + }, + { + let c: for<'a> fn(&'a $wrapper_type_full) -> Result = + |payload: &$wrapper_type_full| { + let wrapper_ref_type = FullPayloadRef::$fork_variant(&payload); + wrapper_ref_type.withdrawals_root() + }; + c + } + ); + + impl Default for $wrapper_type_full { + fn default() -> Self { + Self { + execution_payload: $wrapped_type_full::default(), + } + } + } + + // FullPayload * from CoW reference to ExecutionPayload* (hopefully already owned). + impl<'a, T: EthSpec> From>> for $wrapper_type_full { + fn from(execution_payload: Cow<'a, $wrapped_type_full>) -> Self { + Self { + execution_payload: $wrapped_type_full::from(execution_payload.into_owned()), + } + } + } + + impl TryFrom> for $wrapper_type_full { + type Error = Error; + fn try_from(_: ExecutionPayloadHeader) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryFrom<$wrapped_type_header> for $wrapper_type_full { + type Error = Error; + fn try_from(_: $wrapped_type_header) -> Result { + Err(Error::PayloadConversionLogicFlaw) + } + } + + impl TryInto<$wrapper_type_full> for FullPayload { + type Error = Error; + + fn try_into(self) -> Result<$wrapper_type_full, Self::Error> { + match self { + FullPayload::$fork_variant(payload) => Ok(payload), + _ => Err(Error::PayloadConversionLogicFlaw), + } + } + } + }; +} + +impl_exec_payload_for_fork!( + BlindedPayloadMerge, + FullPayloadMerge, + ExecutionPayloadHeaderMerge, + ExecutionPayloadMerge, + Merge +); +impl_exec_payload_for_fork!( + BlindedPayloadCapella, + FullPayloadCapella, + ExecutionPayloadHeaderCapella, + ExecutionPayloadCapella, + Capella +); + +impl AbstractExecPayload for BlindedPayload { + type Ref<'a> = BlindedPayloadRef<'a, T>; + type Merge = BlindedPayloadMerge; + type Capella = BlindedPayloadCapella; + + fn default_at_fork(fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base | ForkName::Altair => Err(Error::IncorrectStateVariant), + ForkName::Merge => Ok(BlindedPayloadMerge::default().into()), + ForkName::Capella => Ok(BlindedPayloadCapella::default().into()), + } + } +} + +impl From> for BlindedPayload { + fn from(payload: ExecutionPayload) -> Self { + // This implementation is a bit wasteful in that it discards the payload body. + // Required by the top-level constraint on AbstractExecPayload but could maybe be loosened + // in future. + map_execution_payload_into_blinded_payload!(payload, |inner, cons| cons(From::from( + Cow::Owned(inner) + ))) + } +} + +impl From> for BlindedPayload { + fn from(execution_payload_header: ExecutionPayloadHeader) -> Self { + match execution_payload_header { + ExecutionPayloadHeader::Merge(execution_payload_header) => { + Self::Merge(BlindedPayloadMerge { + execution_payload_header, + }) + } + ExecutionPayloadHeader::Capella(execution_payload_header) => { + Self::Capella(BlindedPayloadCapella { + execution_payload_header, + }) + } + } + } +} + +impl From> for ExecutionPayloadHeader { + fn from(blinded: BlindedPayload) -> Self { + match blinded { + BlindedPayload::Merge(blinded_payload) => { + ExecutionPayloadHeader::Merge(blinded_payload.execution_payload_header) + } + BlindedPayload::Capella(blinded_payload) => { + ExecutionPayloadHeader::Capella(blinded_payload.execution_payload_header) + } + } + } +} diff --git a/primitives/beacon/src/proposer_slashing.rs b/primitives/beacon/src/proposer_slashing.rs new file mode 100644 index 00000000..d715d229 --- /dev/null +++ b/primitives/beacon/src/proposer_slashing.rs @@ -0,0 +1,37 @@ +use crate::SignedBeaconBlockHeader; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// Two conflicting proposals from the same proposer (validator). +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct ProposerSlashing { + pub signed_header_1: SignedBeaconBlockHeader, + pub signed_header_2: SignedBeaconBlockHeader, +} + +impl ProposerSlashing { + /// Get proposer index, assuming slashing validity has already been checked. + pub fn proposer_index(&self) -> u64 { + self.signed_header_1.message.proposer_index + } +} diff --git a/primitives/beacon/src/safe_arith.rs b/primitives/beacon/src/safe_arith.rs new file mode 100644 index 00000000..707edcfd --- /dev/null +++ b/primitives/beacon/src/safe_arith.rs @@ -0,0 +1,132 @@ +#[cfg(not(feature = "std"))] +use crate::prelude::*; + +/// Extension trait for iterators, providing a safe replacement for `sum`. +pub trait SafeArithIter { + fn safe_sum(self) -> Result; +} + +impl SafeArithIter for I +where + I: Iterator + Sized, + T: SafeArith, +{ + fn safe_sum(mut self) -> Result { + self.try_fold(T::ZERO, |acc, x| acc.safe_add(x)) + } +} + +/// Error representing the failure of an arithmetic operation. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ArithError { + Overflow, + DivisionByZero, +} + +pub type Result = core::result::Result; + +macro_rules! assign_method { + ($name:ident, $op:ident, $doc_op:expr) => { + assign_method!($name, $op, Self, $doc_op); + }; + ($name:ident, $op:ident, $rhs_ty:ty, $doc_op:expr) => { + #[doc = "Safe variant of `"] + #[doc = $doc_op] + #[doc = "`."] + #[inline] + fn $name(&mut self, other: $rhs_ty) -> Result<()> { + *self = self.$op(other)?; + Ok(()) + } + }; +} + +/// Trait providing safe arithmetic operations for built-in types. +pub trait SafeArith: Sized + Copy { + const ZERO: Self; + const ONE: Self; + + /// Safe variant of `+` that guards against overflow. + fn safe_add(&self, other: Rhs) -> Result; + + /// Safe variant of `-` that guards against overflow. + fn safe_sub(&self, other: Rhs) -> Result; + + /// Safe variant of `*` that guards against overflow. + fn safe_mul(&self, other: Rhs) -> Result; + + /// Safe variant of `/` that guards against division by 0. + fn safe_div(&self, other: Rhs) -> Result; + + /// Safe variant of `%` that guards against division by 0. + fn safe_rem(&self, other: Rhs) -> Result; + + /// Safe variant of `<<` that guards against overflow. + fn safe_shl(&self, other: u32) -> Result; + + /// Safe variant of `>>` that guards against overflow. + fn safe_shr(&self, other: u32) -> Result; + + assign_method!(safe_add_assign, safe_add, Rhs, "+="); + assign_method!(safe_sub_assign, safe_sub, Rhs, "-="); + assign_method!(safe_mul_assign, safe_mul, Rhs, "*="); + assign_method!(safe_div_assign, safe_div, Rhs, "/="); + assign_method!(safe_rem_assign, safe_rem, Rhs, "%="); + assign_method!(safe_shl_assign, safe_shl, u32, "<<="); + assign_method!(safe_shr_assign, safe_shr, u32, ">>="); +} + +macro_rules! impl_safe_arith { + ($typ:ty) => { + impl SafeArith for $typ { + const ZERO: Self = 0; + const ONE: Self = 1; + + #[inline] + fn safe_add(&self, other: Self) -> Result { + self.checked_add(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_sub(&self, other: Self) -> Result { + self.checked_sub(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_mul(&self, other: Self) -> Result { + self.checked_mul(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_div(&self, other: Self) -> Result { + self.checked_div(other).ok_or(ArithError::DivisionByZero) + } + + #[inline] + fn safe_rem(&self, other: Self) -> Result { + self.checked_rem(other).ok_or(ArithError::DivisionByZero) + } + + #[inline] + fn safe_shl(&self, other: u32) -> Result { + self.checked_shl(other).ok_or(ArithError::Overflow) + } + + #[inline] + fn safe_shr(&self, other: u32) -> Result { + self.checked_shr(other).ok_or(ArithError::Overflow) + } + } + }; +} + +impl_safe_arith!(u8); +impl_safe_arith!(u16); +impl_safe_arith!(u32); +impl_safe_arith!(u64); +impl_safe_arith!(usize); +impl_safe_arith!(i8); +impl_safe_arith!(i16); +impl_safe_arith!(i32); +impl_safe_arith!(i64); +impl_safe_arith!(isize); diff --git a/primitives/beacon/src/signed_beacon_block.rs b/primitives/beacon/src/signed_beacon_block.rs new file mode 100644 index 00000000..0de9167a --- /dev/null +++ b/primitives/beacon/src/signed_beacon_block.rs @@ -0,0 +1,403 @@ +use crate::prelude::*; +use crate::*; +use bls::Signature; +use core::fmt; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive(PartialEq, Eq, Hash, Clone, Copy)] +pub struct SignedBeaconBlockHash(Hash256); + +impl fmt::Debug for SignedBeaconBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "SignedBeaconBlockHash({:?})", self.0) + } +} + +impl fmt::Display for SignedBeaconBlockHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for SignedBeaconBlockHash { + fn from(hash: Hash256) -> SignedBeaconBlockHash { + SignedBeaconBlockHash(hash) + } +} + +impl From for Hash256 { + fn from(signed_beacon_block_hash: SignedBeaconBlockHash) -> Hash256 { + signed_beacon_block_hash.0 + } +} + +/// A `BeaconBlock` and a signature from its proposer. +#[superstruct( + variants(Base, Altair, Merge, Capella), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, + ), + derivative(PartialEq, Hash(bound = "E: EthSpec")), + serde(bound = "E: EthSpec, Payload: AbstractExecPayload"), + ), + map_into(BeaconBlock), + map_ref_into(BeaconBlockRef), + map_ref_mut_into(BeaconBlockRefMut) +)] +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "E: EthSpec"))] +#[serde(untagged)] +#[serde(bound = "E: EthSpec, Payload: AbstractExecPayload")] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] +pub struct SignedBeaconBlock = FullPayload> { + #[superstruct(only(Base), partial_getter(rename = "message_base"))] + pub message: BeaconBlockBase, + #[superstruct(only(Altair), partial_getter(rename = "message_altair"))] + pub message: BeaconBlockAltair, + #[superstruct(only(Merge), partial_getter(rename = "message_merge"))] + pub message: BeaconBlockMerge, + #[superstruct(only(Capella), partial_getter(rename = "message_capella"))] + pub message: BeaconBlockCapella, + pub signature: Signature, +} + +pub type SignedBlindedBeaconBlock = SignedBeaconBlock>; + +impl> SignedBeaconBlock { + /// SSZ decode which attempts to decode all variants (slow). + pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result { + Self::from_ssz_bytes_with(bytes, BeaconBlock::any_from_ssz_bytes) + } + + /// SSZ decode with custom decode function. + pub fn from_ssz_bytes_with( + bytes: &[u8], + block_decoder: impl FnOnce(&[u8]) -> Result, ssz::DecodeError>, + ) -> Result { + // We need the customer decoder for `BeaconBlock`, which doesn't compose with the other + // SSZ utils, so we duplicate some parts of `ssz_derive` here. + let mut builder = ssz::SszDecoderBuilder::new(bytes); + + builder.register_anonymous_variable_length_item()?; + builder.register_type::()?; + + let mut decoder = builder.build()?; + + // Read the first item as a `BeaconBlock`. + let message = decoder.decode_next_with(block_decoder)?; + let signature = decoder.decode_next()?; + + Ok(Self::from_block(message, signature)) + } + + /// Create a new `SignedBeaconBlock` from a `BeaconBlock` and `Signature`. + pub fn from_block(block: BeaconBlock, signature: Signature) -> Self { + match block { + BeaconBlock::Base(message) => { + SignedBeaconBlock::Base(SignedBeaconBlockBase { message, signature }) + } + BeaconBlock::Altair(message) => { + SignedBeaconBlock::Altair(SignedBeaconBlockAltair { message, signature }) + } + BeaconBlock::Merge(message) => { + SignedBeaconBlock::Merge(SignedBeaconBlockMerge { message, signature }) + } + BeaconBlock::Capella(message) => { + SignedBeaconBlock::Capella(SignedBeaconBlockCapella { message, signature }) + } + } + } + + /// Deconstruct the `SignedBeaconBlock` into a `BeaconBlock` and `Signature`. + /// + /// This is necessary to get a `&BeaconBlock` from a `SignedBeaconBlock` because + /// `SignedBeaconBlock` only contains a `BeaconBlock` _variant_. + pub fn deconstruct(self) -> (BeaconBlock, Signature) { + map_signed_beacon_block_into_beacon_block!(self, |block, beacon_block_cons| { + (beacon_block_cons(block.message), block.signature) + }) + } + + /// Accessor for the block's `message` field as a ref. + pub fn message<'a>(&'a self) -> BeaconBlockRef<'a, E, Payload> { + map_signed_beacon_block_ref_into_beacon_block_ref!( + &'a _, + self.to_ref(), + |inner, cons| cons(&inner.message) + ) + } + + /// Accessor for the block's `message` as a mutable reference (for testing only). + pub fn message_mut<'a>(&'a mut self) -> BeaconBlockRefMut<'a, E, Payload> { + map_signed_beacon_block_ref_mut_into_beacon_block_ref_mut!( + &'a _, + self.to_mut(), + |inner, cons| cons(&mut inner.message) + ) + } + + /// Produce a signed beacon block header corresponding to this block. + pub fn signed_block_header(&self) -> SignedBeaconBlockHeader { + SignedBeaconBlockHeader { + message: self.message().block_header(), + signature: self.signature().clone(), + } + } + + /// Convenience accessor for the block's slot. + pub fn slot(&self) -> Slot { + self.message().slot() + } + + /// Convenience accessor for the block's parent root. + pub fn parent_root(&self) -> Hash256 { + self.message().parent_root() + } + + /// Convenience accessor for the block's state root. + pub fn state_root(&self) -> Hash256 { + self.message().state_root() + } + + /// Returns the `tree_hash_root` of the block. + pub fn canonical_root(&self) -> Hash256 { + self.message().tree_hash_root() + } +} + +// We can convert pre-Bellatrix blocks without payloads into blocks with payloads. +impl From>> + for SignedBeaconBlockBase> +{ + fn from(signed_block: SignedBeaconBlockBase>) -> Self { + let SignedBeaconBlockBase { message, signature } = signed_block; + SignedBeaconBlockBase { + message: message.into(), + signature, + } + } +} + +impl From>> + for SignedBeaconBlockAltair> +{ + fn from(signed_block: SignedBeaconBlockAltair>) -> Self { + let SignedBeaconBlockAltair { message, signature } = signed_block; + SignedBeaconBlockAltair { + message: message.into(), + signature, + } + } +} + +// Post-Bellatrix blocks can be "unblinded" by adding the full payload. +// NOTE: It might be nice to come up with a `superstruct` pattern to abstract over this before +// the first fork after Bellatrix. +impl SignedBeaconBlockMerge> { + pub fn into_full_block( + self, + execution_payload: ExecutionPayloadMerge, + ) -> SignedBeaconBlockMerge> { + let SignedBeaconBlockMerge { + message: + BeaconBlockMerge { + slot, + proposer_index, + parent_root, + state_root, + body: + BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadMerge { .. }, + }, + }, + signature, + } = self; + SignedBeaconBlockMerge { + message: BeaconBlockMerge { + slot, + proposer_index, + parent_root, + state_root, + body: BeaconBlockBodyMerge { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadMerge { execution_payload }, + }, + }, + signature, + } + } +} + +impl SignedBeaconBlockCapella> { + pub fn into_full_block( + self, + execution_payload: ExecutionPayloadCapella, + ) -> SignedBeaconBlockCapella> { + let SignedBeaconBlockCapella { + message: + BeaconBlockCapella { + slot, + proposer_index, + parent_root, + state_root, + body: + BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: BlindedPayloadCapella { .. }, + bls_to_execution_changes, + }, + }, + signature, + } = self; + SignedBeaconBlockCapella { + message: BeaconBlockCapella { + slot, + proposer_index, + parent_root, + state_root, + body: BeaconBlockBodyCapella { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + execution_payload: FullPayloadCapella { execution_payload }, + bls_to_execution_changes, + }, + }, + signature, + } + } +} + +impl SignedBeaconBlock> { + pub fn try_into_full_block( + self, + execution_payload: Option>, + ) -> Option>> { + let full_block = match (self, execution_payload) { + (SignedBeaconBlock::Base(block), _) => SignedBeaconBlock::Base(block.into()), + (SignedBeaconBlock::Altair(block), _) => SignedBeaconBlock::Altair(block.into()), + (SignedBeaconBlock::Merge(block), Some(ExecutionPayload::Merge(payload))) => { + SignedBeaconBlock::Merge(block.into_full_block(payload)) + } + (SignedBeaconBlock::Capella(block), Some(ExecutionPayload::Capella(payload))) => { + SignedBeaconBlock::Capella(block.into_full_block(payload)) + } + // avoid wildcard matching forks so that compiler will + // direct us here when a new fork has been added + (SignedBeaconBlock::Merge(_), _) => return None, + (SignedBeaconBlock::Capella(_), _) => return None, + }; + Some(full_block) + } +} + +// We can blind blocks with payloads by converting the payload into a header. +// +// We can optionally keep the header, or discard it. +impl From> + for (SignedBlindedBeaconBlock, Option>) +{ + fn from(signed_block: SignedBeaconBlock) -> Self { + let (block, signature) = signed_block.deconstruct(); + let (blinded_block, payload) = block.into(); + ( + SignedBeaconBlock::from_block(blinded_block, signature), + payload, + ) + } +} + +impl From> for SignedBlindedBeaconBlock { + fn from(signed_block: SignedBeaconBlock) -> Self { + let (blinded_block, _) = signed_block.into(); + blinded_block + } +} + +// We can blind borrowed blocks with payloads by converting the payload into a header (without +// cloning the payload contents). +impl SignedBeaconBlock { + pub fn clone_as_blinded(&self) -> SignedBlindedBeaconBlock { + SignedBeaconBlock::from_block(self.message().into(), self.signature().clone()) + } +} + +#[cfg(feature = "std")] +impl> ForkVersionDeserialize + for SignedBeaconBlock +{ + fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>( + value: serde_json::value::Value, + fork_name: ForkName, + ) -> Result { + Ok(map_fork_name!( + fork_name, + Self, + serde_json::from_value(value).map_err(|e| serde::de::Error::custom(format!( + "SignedBeaconBlock failed to deserialize: {:?}", + e + )))? + )) + } +} diff --git a/primitives/beacon/src/signed_beacon_block_header.rs b/primitives/beacon/src/signed_beacon_block_header.rs new file mode 100644 index 00000000..bd4f8602 --- /dev/null +++ b/primitives/beacon/src/signed_beacon_block_header.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; +use crate::{BeaconBlockHeader, Signature}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// A signed header of a `BeaconBlock`. +/// +/// Spec v0.12.1 +#[derive( + Debug, + Clone, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedBeaconBlockHeader { + pub message: BeaconBlockHeader, + pub signature: Signature, +} diff --git a/primitives/beacon/src/signed_bls_to_execution_change.rs b/primitives/beacon/src/signed_bls_to_execution_change.rs new file mode 100644 index 00000000..ebe88c93 --- /dev/null +++ b/primitives/beacon/src/signed_bls_to_execution_change.rs @@ -0,0 +1,27 @@ +use crate::prelude::*; +use crate::*; +use bls::Signature; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedBlsToExecutionChange { + pub message: BlsToExecutionChange, + pub signature: Signature, +} diff --git a/primitives/beacon/src/signed_voluntary_exit.rs b/primitives/beacon/src/signed_voluntary_exit.rs new file mode 100644 index 00000000..6cd0a93e --- /dev/null +++ b/primitives/beacon/src/signed_voluntary_exit.rs @@ -0,0 +1,30 @@ +use crate::VoluntaryExit; +use bls::Signature; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// An exit voluntarily submitted a validator who wishes to withdraw. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SignedVoluntaryExit { + pub message: VoluntaryExit, + pub signature: Signature, +} diff --git a/primitives/beacon/src/signing_data.rs b/primitives/beacon/src/signing_data.rs new file mode 100644 index 00000000..4bb90d9a --- /dev/null +++ b/primitives/beacon/src/signing_data.rs @@ -0,0 +1,36 @@ +use crate::Hash256; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash::TreeHash; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct SigningData { + pub object_root: Hash256, + pub domain: Hash256, +} + +pub trait SignedRoot: TreeHash { + fn signing_root(&self, domain: Hash256) -> Hash256 { + SigningData { + object_root: self.tree_hash_root(), + domain, + } + .tree_hash_root() + } +} diff --git a/primitives/beacon/src/slot_data.rs b/primitives/beacon/src/slot_data.rs new file mode 100644 index 00000000..19775913 --- /dev/null +++ b/primitives/beacon/src/slot_data.rs @@ -0,0 +1,13 @@ +use crate::Slot; + +/// A trait providing a `Slot` getter for messages that are related to a single slot. Useful in +/// making parts of attestation and sync committee processing generic. +pub trait SlotData { + fn get_slot(&self) -> Slot; +} + +impl SlotData for Slot { + fn get_slot(&self) -> Slot { + *self + } +} diff --git a/primitives/beacon/src/slot_epoch.rs b/primitives/beacon/src/slot_epoch.rs new file mode 100644 index 00000000..55d4f0b5 --- /dev/null +++ b/primitives/beacon/src/slot_epoch.rs @@ -0,0 +1,173 @@ +//! The `Slot` and `Epoch` types are defined as new types over u64 to enforce type-safety between +//! the two types. +//! +//! `Slot` and `Epoch` have implementations which permit conversion, comparison and math operations +//! between each and `u64`, however specifically not between each other. +//! +//! All math operations on `Slot` and `Epoch` are saturating, they never wrap. +//! +//! It would be easy to define `PartialOrd` and other traits generically across all types which +//! implement `Into`, however this would allow operations between `Slots` and `Epochs` which +//! may lead to programming errors which are not detected by the compiler. + +use crate::{EthSpec, SignedRoot}; + +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use core::fmt; +use core::hash::Hash; +use core::iter::Iterator; +use serde::{Deserialize, Serialize}; +use ssz::{Decode, DecodeError, Encode}; +use ssz_types::typenum::Unsigned; + +use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, Sub, SubAssign}; + +#[derive( + Clone, + Copy, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Slot(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +#[derive( + Clone, + Copy, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(transparent)] +pub struct Epoch(#[serde(with = "eth2_serde_utils::quoted_u64")] u64); + +impl_common!(Slot); +impl_common!(Epoch); + +impl Slot { + pub const fn new(slot: u64) -> Slot { + Slot(slot) + } + + pub fn epoch(self, slots_per_epoch: u64) -> Epoch { + Epoch::new(self.0) + .safe_div(slots_per_epoch) + .expect("slots_per_epoch is not 0") + } + + pub fn epoch_with_spec(self) -> Epoch { + self.epoch(T::slots_per_epoch()) + } + + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period_with_spec(&self) -> u64 { + self.epoch_with_spec::() + .sync_committee_period_with_spec::() + } + + pub fn max_value() -> Slot { + Slot(u64::max_value()) + } +} + +impl Epoch { + pub const fn new(slot: u64) -> Epoch { + Epoch(slot) + } + + pub fn max_value() -> Epoch { + Epoch(u64::max_value()) + } + + /// The first slot in the epoch. + pub fn start_slot(self, slots_per_epoch: u64) -> Slot { + Slot::from(self.0.saturating_mul(slots_per_epoch)) + } + + /// The last slot in the epoch. + pub fn end_slot(self, slots_per_epoch: u64) -> Slot { + Slot::from( + self.0 + .saturating_mul(slots_per_epoch) + .saturating_add(slots_per_epoch.saturating_sub(1)), + ) + } + + /// Position of some slot inside an epoch, if any. + /// + /// E.g., the first `slot` in `epoch` is at position `0`. + pub fn position(self, slot: Slot, slots_per_epoch: u64) -> Option { + let start = self.start_slot(slots_per_epoch); + let end = self.end_slot(slots_per_epoch); + + if slot >= start && slot <= end { + slot.as_usize().checked_sub(start.as_usize()) + } else { + None + } + } + + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period( + &self, + epochs_per_sync_committee_period: Epoch, + ) -> Result { + Ok(self.safe_div(epochs_per_sync_committee_period)?.as_u64()) + } + + /// Compute the sync committee period for an epoch. + pub fn sync_committee_period_with_spec(&self) -> u64 { + self.safe_div(Epoch::new(T::EpochsPerSyncCommitteePeriod::to_u64())) + .expect("epoch per sync committee period is not 0") + .as_u64() + } + + pub fn slot_iter(&self, slots_per_epoch: u64) -> SlotIter { + SlotIter { + current_iteration: 0, + epoch: self, + slots_per_epoch, + } + } +} + +pub struct SlotIter<'a> { + current_iteration: u64, + epoch: &'a Epoch, + slots_per_epoch: u64, +} + +impl<'a> Iterator for SlotIter<'a> { + type Item = Slot; + + fn next(&mut self) -> Option { + if self.current_iteration >= self.slots_per_epoch { + None + } else { + let start_slot = self.epoch.start_slot(self.slots_per_epoch); + let previous = self.current_iteration; + self.current_iteration = self.current_iteration.checked_add(1)?; + start_slot.safe_add(previous).ok() + } + } +} diff --git a/primitives/beacon/src/slot_epoch_macros.rs b/primitives/beacon/src/slot_epoch_macros.rs new file mode 100644 index 00000000..bb3cc582 --- /dev/null +++ b/primitives/beacon/src/slot_epoch_macros.rs @@ -0,0 +1,324 @@ +macro_rules! impl_from_into_u64 { + ($main: ident) => { + impl From for $main { + fn from(n: u64) -> $main { + $main(n) + } + } + + impl Into for $main { + fn into(self) -> u64 { + self.0 + } + } + + impl $main { + pub fn as_u64(&self) -> u64 { + self.0 + } + } + }; +} + +macro_rules! impl_from_into_usize { + ($main: ident) => { + impl From for $main { + fn from(n: usize) -> $main { + $main(n as u64) + } + } + + impl Into for $main { + fn into(self) -> usize { + self.0 as usize + } + } + + impl $main { + pub fn as_usize(&self) -> usize { + self.0 as usize + } + } + }; +} + +macro_rules! impl_u64_eq_ord { + ($type: ident) => { + impl PartialEq for $type { + fn eq(&self, other: &u64) -> bool { + self.as_u64() == *other + } + } + + impl PartialOrd for $type { + fn partial_cmp(&self, other: &u64) -> Option { + self.as_u64().partial_cmp(other) + } + } + }; +} + +macro_rules! impl_safe_arith { + ($type: ident, $rhs_ty: ident) => { + impl crate::safe_arith::SafeArith<$rhs_ty> for $type { + const ZERO: Self = $type::new(0); + const ONE: Self = $type::new(1); + + fn safe_add(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_add(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_sub(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_sub(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_mul(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_mul(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_div(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_div(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::DivisionByZero) + } + + fn safe_rem(&self, other: $rhs_ty) -> crate::safe_arith::Result { + self.0 + .checked_rem(other.into()) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::DivisionByZero) + } + + fn safe_shl(&self, other: u32) -> crate::safe_arith::Result { + self.0 + .checked_shl(other) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + + fn safe_shr(&self, other: u32) -> crate::safe_arith::Result { + self.0 + .checked_shr(other) + .map(Self::new) + .ok_or(crate::safe_arith::ArithError::Overflow) + } + } + }; +} + +macro_rules! impl_math_between { + ($main: ident, $other: ident) => { + impl Add<$other> for $main { + type Output = $main; + + fn add(self, other: $other) -> $main { + $main::from(self.0.saturating_add(other.into())) + } + } + + impl AddAssign<$other> for $main { + fn add_assign(&mut self, other: $other) { + self.0 = self.0.saturating_add(other.into()); + } + } + + impl Sub<$other> for $main { + type Output = $main; + + fn sub(self, other: $other) -> $main { + $main::from(self.0.saturating_sub(other.into())) + } + } + + impl SubAssign<$other> for $main { + fn sub_assign(&mut self, other: $other) { + self.0 = self.0.saturating_sub(other.into()); + } + } + + impl Mul<$other> for $main { + type Output = $main; + + fn mul(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + $main::from(self.0.saturating_mul(rhs)) + } + } + + impl MulAssign<$other> for $main { + fn mul_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + self.0 = self.0.saturating_mul(rhs) + } + } + + impl Div<$other> for $main { + type Output = $main; + + fn div(self, rhs: $other) -> $main { + let rhs: u64 = rhs.into(); + $main::from( + self.0 + .checked_div(rhs) + .expect("Cannot divide by zero-valued Slot/Epoch"), + ) + } + } + + impl DivAssign<$other> for $main { + fn div_assign(&mut self, rhs: $other) { + let rhs: u64 = rhs.into(); + self.0 = self + .0 + .checked_div(rhs) + .expect("Cannot divide by zero-valued Slot/Epoch"); + } + } + + impl Rem<$other> for $main { + type Output = $main; + + fn rem(self, modulus: $other) -> $main { + let modulus: u64 = modulus.into(); + $main::from( + self.0 + .checked_rem(modulus) + .expect("Cannot divide by zero-valued Slot/Epoch"), + ) + } + } + }; +} + +macro_rules! impl_math { + ($type: ident) => { + impl $type { + pub fn saturating_sub>(&self, other: T) -> $type { + $type::new(self.as_u64().saturating_sub(other.into().as_u64())) + } + + pub fn saturating_add>(&self, other: T) -> $type { + $type::new(self.as_u64().saturating_add(other.into().as_u64())) + } + + pub fn is_power_of_two(&self) -> bool { + self.0.is_power_of_two() + } + } + }; +} + +macro_rules! impl_display { + ($type: ident) => { + impl fmt::Display for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } + } + }; +} + +macro_rules! impl_debug { + ($type: ident) => { + impl fmt::Debug for $type { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}({:?})", stringify!($type), self.0) + } + } + }; +} + +macro_rules! impl_ssz { + ($type: ident) => { + impl Encode for $type { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn ssz_bytes_len(&self) -> usize { + 0_u64.ssz_bytes_len() + } + + fn ssz_append(&self, buf: &mut Vec) { + self.0.ssz_append(buf) + } + } + + impl Decode for $type { + fn is_ssz_fixed_len() -> bool { + ::is_ssz_fixed_len() + } + + fn ssz_fixed_len() -> usize { + ::ssz_fixed_len() + } + + fn from_ssz_bytes(bytes: &[u8]) -> Result { + Ok($type(u64::from_ssz_bytes(bytes)?)) + } + } + + impl tree_hash::TreeHash for $type { + fn tree_hash_type() -> tree_hash::TreeHashType { + tree_hash::TreeHashType::Basic + } + + fn tree_hash_packed_encoding(&self) -> tree_hash::PackedEncoding { + self.0.tree_hash_packed_encoding() + } + + fn tree_hash_packing_factor() -> usize { + 32usize.wrapping_div(8) + } + + fn tree_hash_root(&self) -> tree_hash::Hash256 { + tree_hash::Hash256::from_slice(&crate::int_to_bytes::int_to_fixed_bytes32(self.0)) + } + } + + impl SignedRoot for $type {} + }; +} + +macro_rules! impl_from_str { + ($type: ident) => { + impl core::str::FromStr for $type { + type Err = core::num::ParseIntError; + + fn from_str(s: &str) -> Result<$type, Self::Err> { + u64::from_str(s).map($type) + } + } + }; +} + +macro_rules! impl_common { + ($type: ident) => { + impl_from_into_u64!($type); + impl_from_into_usize!($type); + impl_u64_eq_ord!($type); + impl_safe_arith!($type, $type); + impl_safe_arith!($type, u64); + impl_math_between!($type, $type); + impl_math_between!($type, u64); + impl_math!($type); + impl_display!($type); + impl_debug!($type); + impl_ssz!($type); + impl_from_str!($type); + }; +} diff --git a/primitives/beacon/src/sync_aggregate.rs b/primitives/beacon/src/sync_aggregate.rs new file mode 100644 index 00000000..fdd0360a --- /dev/null +++ b/primitives/beacon/src/sync_aggregate.rs @@ -0,0 +1,68 @@ +use crate::prelude::*; +use crate::safe_arith::ArithError; +use crate::{AggregateSignature, BitVector, EthSpec}; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive(Debug, PartialEq)] +pub enum Error { + SszTypesError(ssz_types::Error), + ArithError(ArithError), +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +#[derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Derivative, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[derivative(PartialEq, Hash(bound = "T: EthSpec"))] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SyncAggregate { + pub sync_committee_bits: BitVector, + pub sync_committee_signature: AggregateSignature, +} + +impl SyncAggregate { + /// New aggregate to be used as the seed for aggregating other signatures. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + sync_committee_bits: BitVector::default(), + sync_committee_signature: AggregateSignature::infinity(), + } + } + + /// Empty aggregate to be used at genesis. + /// + /// Contains an empty signature and should *not* be used as the starting point for aggregation, + /// use `new` instead. + pub fn empty() -> Self { + Self { + sync_committee_bits: BitVector::default(), + sync_committee_signature: AggregateSignature::empty(), + } + } + + /// Returns how many bits are `true` in `self.sync_committee_bits`. + pub fn num_set_bits(&self) -> usize { + self.sync_committee_bits.num_set_bits() + } +} diff --git a/primitives/beacon/src/sync_committee.rs b/primitives/beacon/src/sync_committee.rs new file mode 100644 index 00000000..8d094434 --- /dev/null +++ b/primitives/beacon/src/sync_committee.rs @@ -0,0 +1,81 @@ +use crate::prelude::*; +use crate::safe_arith::{ArithError, SafeArith}; +use crate::typenum::Unsigned; +use crate::{EthSpec, FixedVector}; +use bls::PublicKeyBytes; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive(Debug, PartialEq)] +pub enum Error { + ArithError(ArithError), + InvalidSubcommitteeRange { + start_subcommittee_index: usize, + end_subcommittee_index: usize, + subcommittee_index: usize, + }, +} + +impl From for Error { + fn from(e: ArithError) -> Error { + Error::ArithError(e) + } +} + +#[derive( + Debug, + PartialEq, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +#[serde(bound = "T: EthSpec")] +#[scale_info(skip_type_params(T))] +pub struct SyncCommittee { + pub pubkeys: FixedVector, + pub aggregate_pubkey: PublicKeyBytes, +} + +impl SyncCommittee { + /// Create a temporary sync committee that should *never* be included in a legitimate consensus object. + pub fn temporary() -> Result { + Ok(Self { + pubkeys: FixedVector::new(vec![ + PublicKeyBytes::empty(); + T::SyncCommitteeSize::to_usize() + ])?, + aggregate_pubkey: PublicKeyBytes::empty(), + }) + } + + /// Return the pubkeys in this `SyncCommittee` for the given `subcommittee_index`. + pub fn get_subcommittee_pubkeys( + &self, + subcommittee_index: usize, + ) -> Result, Error> { + let start_subcommittee_index = subcommittee_index.safe_mul(T::sync_subcommittee_size())?; + let end_subcommittee_index = + start_subcommittee_index.safe_add(T::sync_subcommittee_size())?; + self.pubkeys + .get(start_subcommittee_index..end_subcommittee_index) + .ok_or(Error::InvalidSubcommitteeRange { + start_subcommittee_index, + end_subcommittee_index, + subcommittee_index, + }) + .map(|s| s.to_vec()) + } + + /// Returns `true` if the pubkey exists in the `SyncCommittee`. + pub fn contains(&self, pubkey: &PublicKeyBytes) -> bool { + self.pubkeys.contains(pubkey) + } +} diff --git a/primitives/beacon/src/voluntary_exit.rs b/primitives/beacon/src/voluntary_exit.rs new file mode 100644 index 00000000..c54f1c50 --- /dev/null +++ b/primitives/beacon/src/voluntary_exit.rs @@ -0,0 +1,33 @@ +use crate::{Epoch, SignedRoot}; + +use crate::prelude::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +/// An exit voluntarily submitted a validator who wishes to withdraw. +/// +/// Spec v0.12.1 +#[derive( + Debug, + PartialEq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct VoluntaryExit { + /// Earliest epoch when voluntary exit can be processed. + pub epoch: Epoch, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, +} + +impl SignedRoot for VoluntaryExit {} diff --git a/primitives/beacon/src/withdrawal.rs b/primitives/beacon/src/withdrawal.rs new file mode 100644 index 00000000..d985192c --- /dev/null +++ b/primitives/beacon/src/withdrawal.rs @@ -0,0 +1,31 @@ +use crate::prelude::*; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + ScaleEncode, + ScaleDecode, + TypeInfo, + MaxEncodedLen, +)] +pub struct Withdrawal { + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub index: u64, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub validator_index: u64, + pub address: Address, + #[serde(with = "eth2_serde_utils::quoted_u64")] + pub amount: u64, +} diff --git a/primitives/ethereum/Cargo.toml b/primitives/ethereum/Cargo.toml new file mode 100644 index 00000000..ea8aa602 --- /dev/null +++ b/primitives/ethereum/Cargo.toml @@ -0,0 +1,69 @@ +[package] +name = "ethereum-primitives" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codec = { package = "parity-scale-codec", version = "3", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2", default-features = false, features = ["derive"] } +ethbloom = { version = "0.13.0", default-features = false, features = [ + "rlp", + "codec", +] } +ethereum-types = { version = "0.14.1", default-features = false, features = [ + "codec", + "rlp", + "serialize", +] } +hex = { version = "0.4", default-features = false } +hex-literal = { version = "0.3.1", default-features = false } +libsecp256k1 = { version = "0.7", default-features = false } +parity-bytes = { version = "0.1.2", default-features = false } +rlp = { version = "0.5", default-features = false } +serde = { version = "1.0.101", optional = true, features = ["derive"], default-features = false } + +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.31", default-features = false } + +ethabi = { git = "https://github.com/sora-xor/ethabi.git", branch = "sora-v1.6.0", default-features = false } +ethash = { git = "https://github.com/sora-xor/ethash.git", branch = "sora-v1.6.0", default-features = false } +serde_json = { version = "1.0", optional = true } + +beacon = {path = "../beacon", default-features = false} + +[dev-dependencies] +wasm-bindgen-test = "0.3.19" +rand = "0.7.3" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +[features] +default = ["std"] +expensive_tests = [] +test = ["serde_json"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "ethabi/std", + "ethbloom/std", + "ethereum-types/std", + "hex/std", + "libsecp256k1/std", + "parity-bytes/std", + "rlp/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + "beacon/std" +] + +runtime-benchmarks = ["frame-support/runtime-benchmarks"] diff --git a/pallets/types/src/difficulty.rs b/primitives/ethereum/src/difficulty.rs similarity index 98% rename from pallets/types/src/difficulty.rs rename to primitives/ethereum/src/difficulty.rs index 06cc5000..7075aa24 100644 --- a/pallets/types/src/difficulty.rs +++ b/primitives/ethereum/src/difficulty.rs @@ -33,7 +33,7 @@ use crate::U256; use sp_runtime::RuntimeDebug; use sp_std::convert::TryFrom; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -67,7 +67,9 @@ pub enum BombDelay { /// Describes when hard forks occurred in Ethereum Mainnet based networks /// that affect difficulty calculations. These values are network-specific. -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ForkConfig { // Block number on which Byzantium (EIP-649) rules activated @@ -153,7 +155,9 @@ impl ForkConfig { /// Describes when hard forks occurred in Ethereum Classic based networks /// that affect difficulty calculations. These values are network-specific. -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct ClassicForkConfig { // https://ecips.ethereumclassic.org/ECIPs/ecip-1041 diff --git a/pallets/types/src/ethashdata.rs b/primitives/ethereum/src/ethashdata.rs similarity index 100% rename from pallets/types/src/ethashdata.rs rename to primitives/ethereum/src/ethashdata.rs diff --git a/pallets/types/src/ethashproof.rs b/primitives/ethereum/src/ethashproof.rs similarity index 100% rename from pallets/types/src/ethashproof.rs rename to primitives/ethereum/src/ethashproof.rs diff --git a/pallets/types/src/header.rs b/primitives/ethereum/src/header.rs similarity index 97% rename from pallets/types/src/header.rs rename to primitives/ethereum/src/header.rs index 03564991..4c1eedbb 100644 --- a/pallets/types/src/header.rs +++ b/primitives/ethereum/src/header.rs @@ -114,14 +114,21 @@ impl Header { &self, proof: &[Vec], ) -> Option> { - match self.apply_merkle_proof(proof) { - Some((root, data)) if root == self.receipts_root => Some(rlp::decode(&data)), + Self::check_receipt_proof_with_root(self.receipts_root, proof) + } + + pub fn check_receipt_proof_with_root( + receipts_root: H256, + proof: &[Vec], + ) -> Option> { + match Self::apply_merkle_proof(proof) { + Some((root, data)) if root == receipts_root => Some(rlp::decode(&data)), Some((_, _)) => None, None => None, } } - pub fn apply_merkle_proof(&self, proof: &[Vec]) -> Option<(H256, Vec)> { + pub fn apply_merkle_proof(proof: &[Vec]) -> Option<(H256, Vec)> { let mut iter = proof.iter().rev(); let first_bytes = match iter.next() { Some(b) => b, diff --git a/primitives/ethereum/src/lib.rs b/primitives/ethereum/src/lib.rs new file mode 100644 index 00000000..651d29e0 --- /dev/null +++ b/primitives/ethereum/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod difficulty; +pub mod ethashdata; +pub mod ethashproof; +pub mod header; +pub mod log; +pub mod mpt; +pub mod network_config; +pub mod receipt; +#[cfg(feature = "std")] +pub mod serde_utils; +#[cfg(any(feature = "test", test))] +pub mod test_utils; + +use codec::Encode; +pub use ethereum_types::{Address as EthAddress, H128, H160, H256, H512, H64, U256}; +pub use header::Header; +pub use header::HeaderId; +pub use log::Log; +pub use receipt::Receipt; +use sp_std::prelude::*; + +pub type EVMChainId = U256; + +pub fn import_digest(network_id: &EVMChainId, header: &Header) -> Vec +where + EVMChainId: Encode, + Header: Encode, +{ + let mut digest = vec![]; + network_id.encode_to(&mut digest); + header.encode_to(&mut digest); + digest +} diff --git a/pallets/types/src/log.rs b/primitives/ethereum/src/log.rs similarity index 100% rename from pallets/types/src/log.rs rename to primitives/ethereum/src/log.rs diff --git a/pallets/types/src/mpt.rs b/primitives/ethereum/src/mpt.rs similarity index 100% rename from pallets/types/src/mpt.rs rename to primitives/ethereum/src/mpt.rs diff --git a/pallets/types/src/network_config.rs b/primitives/ethereum/src/network_config.rs similarity index 63% rename from pallets/types/src/network_config.rs rename to primitives/ethereum/src/network_config.rs index b7185007..381fcaa0 100644 --- a/pallets/types/src/network_config.rs +++ b/primitives/ethereum/src/network_config.rs @@ -32,37 +32,41 @@ use crate::{ difficulty::{ClassicForkConfig, ForkConfig}, EVMChainId, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use sp_runtime::RuntimeDebug; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum Consensus { Ethash { fork_config: ForkConfig }, Etchash { fork_config: ClassicForkConfig }, Clique { period: u64, epoch: u64 }, + Beacon(beacon::ConsensusConfig), } impl Consensus { - pub fn calc_epoch_length(&self, block_number: u64) -> u64 { + pub fn calc_epoch_length(&self, block_number: u64) -> Option { match self { - Consensus::Clique { epoch, .. } => *epoch, - Consensus::Ethash { fork_config } => fork_config.epoch_length(), - Consensus::Etchash { fork_config } => fork_config.calc_epoch_length(block_number), + Consensus::Clique { epoch, .. } => Some(*epoch), + Consensus::Ethash { fork_config } => Some(fork_config.epoch_length()), + Consensus::Etchash { fork_config } => Some(fork_config.calc_epoch_length(block_number)), + Consensus::Beacon(_consensus) => None, } } } -#[derive(Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + Copy, Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum NetworkConfig { Mainnet, - Ropsten, Sepolia, - Rinkeby, Goerli, Classic, Mordor, @@ -70,51 +74,60 @@ pub enum NetworkConfig { chain_id: EVMChainId, consensus: Consensus, }, + // Keep it for tests + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + RopstenEthash, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + SepoliaEthash, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + MainnetEthash, } impl NetworkConfig { pub fn chain_id(&self) -> EVMChainId { match self { NetworkConfig::Mainnet => 1u32.into(), - NetworkConfig::Ropsten => 3u32.into(), NetworkConfig::Sepolia => 11155111u32.into(), - NetworkConfig::Rinkeby => 4u32.into(), NetworkConfig::Goerli => 5u32.into(), NetworkConfig::Classic => 61u32.into(), NetworkConfig::Mordor => 63u32.into(), NetworkConfig::Custom { chain_id, .. } => *chain_id, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::RopstenEthash => 3u32.into(), + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::MainnetEthash => 1u32.into(), + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::SepoliaEthash => 11155111u32.into(), } } pub fn consensus(&self) -> Consensus { match self { - NetworkConfig::Mainnet => Consensus::Ethash { - fork_config: ForkConfig::mainnet(), - }, - NetworkConfig::Ropsten => Consensus::Ethash { - fork_config: ForkConfig::ropsten(), - }, - NetworkConfig::Sepolia => Consensus::Ethash { - fork_config: ForkConfig::sepolia(), - }, + NetworkConfig::Mainnet => Consensus::Beacon(beacon::ConsensusConfig::mainnet()), + NetworkConfig::Goerli => Consensus::Beacon(beacon::ConsensusConfig::goerli()), + NetworkConfig::Sepolia => Consensus::Beacon(beacon::ConsensusConfig::sepolia()), NetworkConfig::Classic => Consensus::Etchash { fork_config: ClassicForkConfig::classic(), }, NetworkConfig::Mordor => Consensus::Etchash { fork_config: ClassicForkConfig::mordor(), }, - NetworkConfig::Rinkeby => Consensus::Clique { - period: 15, - epoch: 30000, - }, - NetworkConfig::Goerli => Consensus::Clique { - period: 15, - epoch: 30000, - }, NetworkConfig::Custom { consensus: protocol, .. } => *protocol, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::RopstenEthash => Consensus::Ethash { + fork_config: ForkConfig::ropsten(), + }, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::MainnetEthash => Consensus::Ethash { + fork_config: ForkConfig::mainnet(), + }, + #[cfg(any(test, feature = "test", feature = "runtime-benchmarks"))] + NetworkConfig::SepoliaEthash => Consensus::Ethash { + fork_config: ForkConfig::sepolia(), + }, } } } diff --git a/pallets/types/src/receipt.rs b/primitives/ethereum/src/receipt.rs similarity index 100% rename from pallets/types/src/receipt.rs rename to primitives/ethereum/src/receipt.rs diff --git a/primitives/ethereum/src/serde_utils.rs b/primitives/ethereum/src/serde_utils.rs new file mode 100644 index 00000000..e5f22fc7 --- /dev/null +++ b/primitives/ethereum/src/serde_utils.rs @@ -0,0 +1,51 @@ +pub mod serde_str { + use core::str::FromStr; + use serde::Deserialize; + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: TryFrom, + >::Error: std::fmt::Debug, + { + let value = String::deserialize(deserializer)?; + let res = u128::from_str(&value).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from string: {:?}", err)) + })?; + let res = T::try_from(res).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from string: {:?}", err)) + })?; + Ok(res) + } + + pub fn serialize( + value: &T, + serializer: S, + ) -> Result { + serializer.serialize_str(&value.to_string()) + } +} + +pub mod serde_hex { + use serde::Deserialize; + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + let value = if value.len() > 1 && value[0..2] == *"0x" { + &value[2..] + } else { + &value + }; + let res = hex::decode(value).map_err(|err| { + serde::de::Error::custom(format!("Failed to deserialize from hex: {:?}", err)) + })?; + Ok(res) + } + + pub fn serialize(value: &[u8], serializer: S) -> Result { + serializer.serialize_str(&format!("0x{}", hex::encode(value))) + } +} diff --git a/pallets/types/src/test_utils.rs b/primitives/ethereum/src/test_utils.rs similarity index 97% rename from pallets/types/src/test_utils.rs rename to primitives/ethereum/src/test_utils.rs index ea232127..efd06184 100644 --- a/pallets/types/src/test_utils.rs +++ b/primitives/ethereum/src/test_utils.rs @@ -47,10 +47,9 @@ impl<'de> Deserialize<'de> for Hex { s = s[2..].to_string(); } if s.len() % 2 == 1 { - s.insert(0, '0'); + s.insert_str(0, "0"); } - let v: Vec = hex::FromHexIter::new(&s).map(|x| x.unwrap()).collect(); - Ok(Hex(v)) + Ok(Hex(hex::decode(&s).unwrap())) } } diff --git a/pallets/types/tests/fixtures/11090290.json b/primitives/ethereum/tests/fixtures/11090290.json similarity index 100% rename from pallets/types/tests/fixtures/11090290.json rename to primitives/ethereum/tests/fixtures/11090290.json diff --git a/pallets/types/tests/fixtures/11550000.json b/primitives/ethereum/tests/fixtures/11550000.json similarity index 100% rename from pallets/types/tests/fixtures/11550000.json rename to primitives/ethereum/tests/fixtures/11550000.json diff --git a/pallets/types/tests/fixtures/3.json b/primitives/ethereum/tests/fixtures/3.json similarity index 100% rename from pallets/types/tests/fixtures/3.json rename to primitives/ethereum/tests/fixtures/3.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacier.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierForkBlock.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierMinus1.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierMinus1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierMinus1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierMinus1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json b/primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json rename to primitives/ethereum/tests/fixtures/tests/difficultyArrowGlacierTimeDiff2.json diff --git a/pallets/types/tests/fixtures/tests/difficultyByzantium.json b/primitives/ethereum/tests/fixtures/tests/difficultyByzantium.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyByzantium.json rename to primitives/ethereum/tests/fixtures/tests/difficultyByzantium.json diff --git a/pallets/types/tests/fixtures/tests/difficultyConstantinople.json b/primitives/ethereum/tests/fixtures/tests/difficultyConstantinople.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyConstantinople.json rename to primitives/ethereum/tests/fixtures/tests/difficultyConstantinople.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384_random.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384_random.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random.json diff --git a/pallets/types/tests/fixtures/tests/difficultyEIP2384_random_to20M.json b/primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random_to20M.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyEIP2384_random_to20M.json rename to primitives/ethereum/tests/fixtures/tests/difficultyEIP2384_random_to20M.json diff --git a/pallets/types/tests/fixtures/tests/difficultyFrontier.json b/primitives/ethereum/tests/fixtures/tests/difficultyFrontier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyFrontier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyFrontier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacier.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacier.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacier.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacier.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierForkBlock.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierMinus1.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierMinus1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierMinus1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierMinus1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff1.json diff --git a/pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json b/primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json rename to primitives/ethereum/tests/fixtures/tests/difficultyGrayGlacierTimeDiff2.json diff --git a/pallets/types/tests/fixtures/tests/difficultyHomestead.json b/primitives/ethereum/tests/fixtures/tests/difficultyHomestead.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyHomestead.json rename to primitives/ethereum/tests/fixtures/tests/difficultyHomestead.json diff --git a/pallets/types/tests/fixtures/tests/difficultyMainNetwork.json b/primitives/ethereum/tests/fixtures/tests/difficultyMainNetwork.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyMainNetwork.json rename to primitives/ethereum/tests/fixtures/tests/difficultyMainNetwork.json diff --git a/pallets/types/tests/fixtures/tests/difficultyRopsten.json b/primitives/ethereum/tests/fixtures/tests/difficultyRopsten.json similarity index 100% rename from pallets/types/tests/fixtures/tests/difficultyRopsten.json rename to primitives/ethereum/tests/fixtures/tests/difficultyRopsten.json