From 68aa8a944d5e78e68bb57ad591bf68e27a9c46c1 Mon Sep 17 00:00:00 2001 From: wenyuanhust <873741746@qq.com> Date: Thu, 2 Nov 2023 16:48:33 +0800 Subject: [PATCH] feat: move axon-tools to axon (#1519) * feat: move axon-tools to axon * refactor: improve code organization * test: add axon-tools unit test --- Cargo.lock | 499 +++++++++++- Cargo.toml | 1 + devtools/axon-tools/Cargo.toml | 87 +++ devtools/axon-tools/README.md | 3 + devtools/axon-tools/src/consts.rs | 16 + devtools/axon-tools/src/error.rs | 121 +++ devtools/axon-tools/src/hash.rs | 22 + devtools/axon-tools/src/hex.rs | 23 + devtools/axon-tools/src/lib.rs | 29 + devtools/axon-tools/src/proof.rs | 104 +++ devtools/axon-tools/src/rlp_codec.rs | 52 ++ devtools/axon-tools/src/tests/block.json | 30 + devtools/axon-tools/src/tests/metadata.json | 54 ++ devtools/axon-tools/src/tests/mod.rs | 2 + devtools/axon-tools/src/tests/proof.json | 7 + devtools/axon-tools/src/tests/verify_proof.rs | 76 ++ .../axon-tools/src/tests/verify_trie_proof.rs | 178 +++++ devtools/axon-tools/src/types.rs | 708 ++++++++++++++++++ 18 files changed, 2010 insertions(+), 2 deletions(-) create mode 100644 devtools/axon-tools/Cargo.toml create mode 100644 devtools/axon-tools/README.md create mode 100644 devtools/axon-tools/src/consts.rs create mode 100644 devtools/axon-tools/src/error.rs create mode 100644 devtools/axon-tools/src/hash.rs create mode 100644 devtools/axon-tools/src/hex.rs create mode 100644 devtools/axon-tools/src/lib.rs create mode 100644 devtools/axon-tools/src/proof.rs create mode 100644 devtools/axon-tools/src/rlp_codec.rs create mode 100644 devtools/axon-tools/src/tests/block.json create mode 100644 devtools/axon-tools/src/tests/metadata.json create mode 100644 devtools/axon-tools/src/tests/mod.rs create mode 100644 devtools/axon-tools/src/tests/proof.json create mode 100644 devtools/axon-tools/src/tests/verify_proof.rs create mode 100644 devtools/axon-tools/src/tests/verify_trie_proof.rs create mode 100644 devtools/axon-tools/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 6472c960b..006c0cfe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,6 +188,9 @@ name = "arbitrary" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "arc-swap" @@ -303,7 +306,7 @@ dependencies = [ "async-trait", "bincode", "bytes", - "cita_trie", + "cita_trie 5.0.0", "ckb-hash", "ckb-jsonrpc-types", "ckb-sdk", @@ -335,6 +338,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "axon-tools" +version = "0.1.1" +dependencies = [ + "bit-vec", + "blst", + "bytes", + "cita_trie 4.1.0", + "derive_more", + "eth_light_client_in_ckb-prover", + "ethereum", + "ethereum-types 0.14.1", + "ethers-core", + "faster-hex 0.8.1", + "hex", + "log", + "overlord", + "rand 0.8.5", + "rlp", + "rlp-derive", + "serde", + "serde_json", + "tiny-keccak", +] + [[package]] name = "axum" version = "0.6.18" @@ -583,6 +611,25 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "bls" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "arbitrary", + "blst", + "eth2_hashing", + "eth2_serde_utils", + "eth2_ssz", + "ethereum-types 0.14.1", + "hex", + "rand 0.7.3", + "serde", + "serde_derive", + "tree_hash", + "zeroize", +] + [[package]] name = "blst" version = "0.3.10" @@ -659,6 +706,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "cached_tree_hash" +version = "0.1.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "eth2_hashing", + "eth2_ssz", + "eth2_ssz_derive", + "eth2_ssz_types", + "ethereum-types 0.14.1", + "smallvec", + "tree_hash", +] + [[package]] name = "camino" version = "1.1.4" @@ -860,6 +921,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cita_trie" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c3d2abadaa28e0d277f9f6d07a2052544f045d929cd4d6f7bcfb43567c9767" +dependencies = [ + "hasher", + "parking_lot 0.12.1", + "rlp", +] + [[package]] name = "cita_trie" version = "5.0.0" @@ -1047,6 +1119,15 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "ckb-merkle-mountain-range" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15193decfa1e0b151ce19e42d118db048459a27720fb3de7d3103c30adccb12" +dependencies = [ + "cfg-if 1.0.0", +] + [[package]] name = "ckb-mock-tx-types" version = "0.111.0" @@ -1235,7 +1316,7 @@ dependencies = [ "ckb-fixed-hash", "ckb-gen-types", "ckb-hash", - "ckb-merkle-mountain-range", + "ckb-merkle-mountain-range 0.5.2", "ckb-occupied-capacity", "ckb-rational", "derive_more", @@ -1602,6 +1683,20 @@ dependencies = [ "semver", ] +[[package]] +name = "compare_fields" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" + +[[package]] +name = "compare_fields_derive" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "console" version = "0.14.1" @@ -2177,6 +2272,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[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 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -2238,6 +2368,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -2549,6 +2690,118 @@ dependencies = [ "uuid 0.8.2", ] +[[package]] +name = "eth2_hashing" +version = "0.3.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "cpufeatures", + "lazy_static", + "ring", + "sha2 0.10.6", +] + +[[package]] +name = "eth2_interop_keypairs" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "bls", + "eth2_hashing", + "hex", + "lazy_static", + "num-bigint", + "serde", + "serde_derive", + "serde_yaml", +] + +[[package]] +name = "eth2_serde_utils" +version = "0.1.1" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "ethereum-types 0.14.1", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "eth2_ssz" +version = "0.4.1" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "ethereum-types 0.14.1", + "itertools", + "smallvec", +] + +[[package]] +name = "eth2_ssz_derive" +version = "0.3.1" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "eth2_ssz_types" +version = "0.2.2" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "arbitrary", + "derivative", + "eth2_serde_utils", + "eth2_ssz", + "serde", + "serde_derive", + "smallvec", + "tree_hash", + "typenum", +] + +[[package]] +name = "eth_light_client_in_ckb-prover" +version = "0.3.0-alpha" +source = "git+https://github.com/synapseweb3/eth-light-client-in-ckb#3812d220a7cf9da8cb3da35ebb19ead2c7fd7657" +dependencies = [ + "cita_trie 4.1.0", + "eth_light_client_in_ckb-verification", + "ethers-core", + "hasher", + "merkle_proof", + "tree_hash", + "types", +] + +[[package]] +name = "eth_light_client_in_ckb-verification" +version = "0.3.0-alpha" +source = "git+https://github.com/synapseweb3/eth-light-client-in-ckb#3812d220a7cf9da8cb3da35ebb19ead2c7fd7657" +dependencies = [ + "bls", + "ckb-merkle-mountain-range 0.6.0", + "eth2_hashing", + "eth2_ssz", + "eth2_ssz_derive", + "eth2_ssz_types", + "ethereum-types 0.14.1", + "faster-hex 0.6.1", + "log", + "merkle_proof", + "molecule", + "rlp", + "tiny-keccak", + "tree_hash", + "tree_hash_derive", + "types", +] + [[package]] name = "ethabi" version = "17.0.0" @@ -2999,6 +3252,18 @@ dependencies = [ "tempfile", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "faster-hex" version = "0.6.1" @@ -3473,6 +3738,15 @@ dependencies = [ "fxhash", ] +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.0", +] + [[package]] name = "heapsize" version = "0.4.2" @@ -3676,6 +3950,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -3810,6 +4090,14 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "int_to_bytes" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "bytes", +] + [[package]] name = "io-lifetimes" version = "1.0.10" @@ -4080,6 +4368,17 @@ dependencies = [ "rand 0.3.23", ] +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -4170,6 +4469,12 @@ dependencies = [ "hashbrown 0.14.0", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4224,6 +4529,40 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "merkle_proof" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "eth2_hashing", + "ethereum-types 0.14.1", + "lazy_static", + "safe_arith", +] + +[[package]] +name = "metastruct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfbb8826226b09b05bb62a0937cf6abb16f1f7d4b746eb95a83db14aec60f06" +dependencies = [ + "metastruct_macro", +] + +[[package]] +name = "metastruct_macro" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37cb4045d5677b7da537f8cb5d0730d5b6414e3cc81c61e4b50e1f0cbdc73909" +dependencies = [ + "darling", + "itertools", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + [[package]] name = "mime" version = "0.3.17" @@ -5502,6 +5841,15 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rayon" version = "1.7.0" @@ -5730,6 +6078,20 @@ dependencies = [ "libc", ] +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags 1.3.2", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust-ini" version = "0.19.0" @@ -5905,6 +6267,11 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "safe_arith" +version = "0.1.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" + [[package]] name = "salsa20" version = "0.10.2" @@ -6170,6 +6537,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_yaml" version = "0.8.26" @@ -6286,6 +6675,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + [[package]] name = "smallvec" version = "1.10.0" @@ -6465,6 +6860,20 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "superstruct" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b9e5728aa1a87141cefd4e7509903fc01fa0dcb108022b1e841a67c5159fc5" +dependencies = [ + "darling", + "itertools", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + [[package]] name = "svm-rs" version = "0.2.22" @@ -6496,6 +6905,15 @@ dependencies = [ "zip", ] +[[package]] +name = "swap_or_not_shuffle" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "eth2_hashing", + "ethereum-types 0.14.1", +] + [[package]] name = "symbolic-common" version = "10.2.1" @@ -6687,6 +7105,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "test_random_derive" +version = "0.2.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -7135,6 +7562,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tree_hash" +version = "0.4.1" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "eth2_hashing", + "ethereum-types 0.14.1", + "smallvec", +] + +[[package]] +name = "tree_hash_derive" +version = "0.4.0" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "darling", + "quote", + "syn 1.0.109", +] + [[package]] name = "triehash" version = "0.8.4" @@ -7187,6 +7634,54 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "types" +version = "0.2.1" +source = "git+https://github.com/synapseweb3/lighthouse?rev=be911e6#be911e6bd3f54ae84b01310b411be19e2b274634" +dependencies = [ + "arbitrary", + "bls", + "cached_tree_hash", + "compare_fields", + "compare_fields_derive", + "derivative", + "eth2_hashing", + "eth2_interop_keypairs", + "eth2_serde_utils", + "eth2_ssz", + "eth2_ssz_derive", + "eth2_ssz_types", + "ethereum-types 0.14.1", + "hex", + "int_to_bytes", + "itertools", + "lazy_static", + "log", + "maplit", + "merkle_proof", + "metastruct", + "parking_lot 0.12.1", + "rand 0.8.5", + "rand_xorshift", + "rayon", + "regex", + "rusqlite", + "safe_arith", + "serde", + "serde_derive", + "serde_json", + "serde_with", + "serde_yaml", + "slog", + "smallvec", + "superstruct", + "swap_or_not_shuffle", + "tempfile", + "test_random_derive", + "tree_hash", + "tree_hash_derive", +] + [[package]] name = "uint" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index b4a9b6015..0448e3739 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "core/run", "core/storage", "devtools/abi-generator", + "devtools/axon-tools", "devtools/keypair", "protocol", ] diff --git a/devtools/axon-tools/Cargo.toml b/devtools/axon-tools/Cargo.toml new file mode 100644 index 000000000..670171390 --- /dev/null +++ b/devtools/axon-tools/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "axon-tools" +version = "0.1.1" +edition = "2021" +authors = ["Axon Dev "] +license = "MIT" +include = ["src/*", "README.md", "LICENSE"] +readme = "README.md" +keywords = ["axon", "tool"] +categories = ["cryptography"] +repository = "https://github.com/axonweb3/axon" +description = """ +Some axon related utilities. +""" + +[dev-dependencies] +eth_light_client_in_ckb-prover = { version = "0.3.0-alpha", git = "https://github.com/synapseweb3/eth-light-client-in-ckb" } +ethereum = "0.14" +ethers-core = "2.0.10" +hex = "0.4" +rand = "0.8" + +[dependencies] +derive_more = "0.99" +log = "0.4.19" +overlord = "0.4" +serde_json = "1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[dependencies.bit-vec] +version = "0.6" +default_features = false +optional = true + +[dependencies.blst] +version = "0.3" +optional = true + +[dependencies.bytes] +version = "1.4" +default-features = false +features = ["serde"] + +[dependencies.cita_trie] +version = "4.0" +optional = true + +[dependencies.ethereum-types] +version = "0.14" +default-features = false +features = ["serialize"] + +[dependencies.faster-hex] +version = "0.8" +optional = true + +[dependencies.rlp] +version = "0.5" +default-features = false +optional = true + +[dependencies.rlp-derive] +version = "0.1" +optional = true + +[dependencies.serde] +version = "1.0" +default_features = false +optional = true +features = ["derive"] + +[dependencies.tiny-keccak] +version = "2.0" +optional = true +features = ["keccak"] + +[features] +default = ["impl-serde", "proof"] +proof = ["blst", "bit-vec", "cita_trie", "hash", "impl-rlp"] +hash = ["tiny-keccak"] +hex = ["faster-hex"] +impl-rlp = ["rlp", "rlp-derive", "ethereum-types/rlp"] +impl-serde = ["serde", "ethereum-types/serialize", "hex"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "doc_cfg"] diff --git a/devtools/axon-tools/README.md b/devtools/axon-tools/README.md new file mode 100644 index 000000000..2b236bbb9 --- /dev/null +++ b/devtools/axon-tools/README.md @@ -0,0 +1,3 @@ +This crate is used by forcerelay. +- Data structures like Block, Proposal etc. +- Block and Transaction verification APIs. \ No newline at end of file diff --git a/devtools/axon-tools/src/consts.rs b/devtools/axon-tools/src/consts.rs new file mode 100644 index 000000000..42d4dc1c0 --- /dev/null +++ b/devtools/axon-tools/src/consts.rs @@ -0,0 +1,16 @@ +use ethereum_types::H160; + +pub const METADATA_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x01, +]); + +pub const CKB_LIGHT_CLIENT_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x02, +]); + +pub const IMAGE_CELL_CONTRACT_ADDRESS: H160 = H160([ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x03, +]); diff --git a/devtools/axon-tools/src/error.rs b/devtools/axon-tools/src/error.rs new file mode 100644 index 000000000..53f8ee61f --- /dev/null +++ b/devtools/axon-tools/src/error.rs @@ -0,0 +1,121 @@ +use derive_more::{Display, From}; +use ethereum_types::H256; +use std::fmt::{self, Display}; + +#[allow(dead_code)] +#[derive(Debug)] +pub enum Error { + InvalidProofBlockHash, + NotEnoughSignatures, + VerifyMptProof, + HexPrefix, + + #[cfg(feature = "hex")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] + Hex(faster_hex::Error), + + #[cfg(feature = "proof")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] + Bls(blst::BLST_ERROR), + + #[cfg(feature = "proof")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] + Trie(cita_trie::TrieError), +} + +#[cfg(feature = "hex")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] +impl From for Error { + fn from(value: faster_hex::Error) -> Self { + Self::Hex(value) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl From for Error { + fn from(e: blst::BLST_ERROR) -> Self { + Self::Bls(e) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl From for Error { + fn from(e: cita_trie::TrieError) -> Self { + Self::Trie(e) + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Error::InvalidProofBlockHash => write!(f, "Invalid proof block hash"), + Error::NotEnoughSignatures => write!(f, "Not enough signatures"), + Error::VerifyMptProof => write!(f, "Verify mpt proof"), + Error::HexPrefix => write!(f, "Hex prefix"), + #[cfg(feature = "hex")] + Error::Hex(e) => write!(f, "Hex error: {:?}", e), + #[cfg(feature = "proof")] + Error::Bls(e) => write!(f, "Bls error: {:?}", e), + #[cfg(feature = "proof")] + Error::Trie(e) => write!(f, "Trie error: {:?}", e), + } + } +} + +#[derive(Debug, Display, From)] +pub enum TypesError { + #[display(fmt = "Expect {:?}, get {:?}.", expect, real)] + LengthMismatch { expect: usize, real: usize }, + + #[display( + fmt = "Eip1559Transaction hash mismatch origin {:?}, computed {:?}", + origin, + calc + )] + TxHashMismatch { origin: H256, calc: H256 }, + + #[display(fmt = "{:?}", _0)] + FromHex(faster_hex::Error), + + #[display(fmt = "{:?} is an invalid address", _0)] + InvalidAddress(String), + + #[display(fmt = "Hex should start with 0x")] + HexPrefix, + + #[display(fmt = "Invalid public key")] + InvalidPublicKey, + + #[display(fmt = "Invalid check sum")] + InvalidCheckSum, + + #[display(fmt = "Unsigned")] + Unsigned, + + // #[display(fmt = "Crypto error {:?}", _0)] + // Crypto(CryptoError), + #[display(fmt = "Missing signature")] + MissingSignature, + + #[display(fmt = "Invalid crosschain direction")] + InvalidDirection, + + #[display(fmt = "Signature R is empty")] + SignatureRIsEmpty, + + #[display(fmt = "Invalid signature R type")] + InvalidSignatureRType, + + #[display(fmt = "Invalid address source type")] + InvalidAddressSourceType, + + #[display(fmt = "Missing interoperation sender")] + MissingInteroperationSender, + + #[display(fmt = "InvalidBlockVersion {:?}", _0)] + InvalidBlockVersion(u8), +} + +impl std::error::Error for TypesError {} diff --git a/devtools/axon-tools/src/hash.rs b/devtools/axon-tools/src/hash.rs new file mode 100644 index 000000000..bc051bbe6 --- /dev/null +++ b/devtools/axon-tools/src/hash.rs @@ -0,0 +1,22 @@ +use tiny_keccak::{Hasher, Keccak}; + +#[cfg(feature = "hash")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hash")))] +pub fn keccak_256(data: &[u8]) -> [u8; 32] { + let mut ret = [0u8; 32]; + let mut hasher = Keccak::v256(); + hasher.update(data); + hasher.finalize(&mut ret); + ret +} + +#[derive(Default)] +pub(crate) struct InnerKeccak; + +impl cita_trie::Hasher for InnerKeccak { + const LENGTH: usize = 32; + + fn digest(&self, data: &[u8]) -> Vec { + keccak_256(data).to_vec() + } +} diff --git a/devtools/axon-tools/src/hex.rs b/devtools/axon-tools/src/hex.rs new file mode 100644 index 000000000..38caaebb6 --- /dev/null +++ b/devtools/axon-tools/src/hex.rs @@ -0,0 +1,23 @@ +use crate::Error; + +pub fn hex_encode>(src: T) -> String { + faster_hex::hex_string(src.as_ref()) +} + +pub fn hex_decode(src: &str) -> Result, Error> { + if src.is_empty() { + return Ok(Vec::new()); + } + + let src = if src.starts_with("0x") { + src.split_at(2).1 + } else { + src + }; + + let src = src.as_bytes(); + let mut ret = vec![0u8; src.len() / 2]; + faster_hex::hex_decode(src, &mut ret)?; + + Ok(ret) +} diff --git a/devtools/axon-tools/src/lib.rs b/devtools/axon-tools/src/lib.rs new file mode 100644 index 000000000..d10380c22 --- /dev/null +++ b/devtools/axon-tools/src/lib.rs @@ -0,0 +1,29 @@ +#![cfg_attr(doc_cfg, feature(doc_cfg))] + +extern crate alloc; + +mod error; +#[cfg(feature = "hash")] +pub mod hash; +#[cfg(feature = "hex")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hex")))] +pub mod hex; +#[cfg(feature = "proof")] +mod proof; +mod rlp_codec; +pub mod types; + +pub use error::Error; + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +pub use proof::{verify_proof, verify_trie_proof}; + +#[cfg(feature = "hash")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "hash")))] +pub use hash::keccak_256; + +pub mod consts; + +#[cfg(test)] +mod tests; diff --git a/devtools/axon-tools/src/proof.rs b/devtools/axon-tools/src/proof.rs new file mode 100644 index 000000000..9715d4426 --- /dev/null +++ b/devtools/axon-tools/src/proof.rs @@ -0,0 +1,104 @@ +use alloc::vec::Vec; + +use bit_vec::BitVec; +use blst::min_pk::{AggregatePublicKey, PublicKey, Signature}; +use blst::BLST_ERROR; +use bytes::Bytes; +use ethereum_types::H256; +use rlp::Encodable; + +use crate::types::{AxonBlock, Proof, Proposal, ValidatorExtend, Vote}; +use crate::{error::Error, hash::InnerKeccak, keccak_256}; + +const DST: &str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RONUL"; + +pub fn verify_trie_proof( + root: H256, + key: &[u8], + proof: Vec>, +) -> Result>, Error> { + let value = cita_trie::verify_proof(&root.0, key, proof, InnerKeccak)?; + log::debug!("key: {:?}, value: {:?}", key, value); + Ok(value) +} + +pub fn verify_proof( + block: AxonBlock, + previous_state_root: H256, + validator_list: &mut [ValidatorExtend], + proof: Proof, +) -> Result<(), Error> { + let raw_proposal = Proposal { + version: block.header.version, + prev_hash: block.header.prev_hash, + proposer: block.header.proposer, + prev_state_root: previous_state_root, + transactions_root: block.header.transactions_root, + signed_txs_hash: block.header.signed_txs_hash, + timestamp: block.header.timestamp, + number: block.header.number, + gas_limit: block.header.gas_limit, + extra_data: block.header.extra_data, + base_fee_per_gas: block.header.base_fee_per_gas, + proof: block.header.proof, + chain_id: block.header.chain_id, + call_system_script_count: block.header.call_system_script_count, + tx_hashes: block.tx_hashes, + } + .rlp_bytes(); + + if keccak_256(&raw_proposal) != proof.block_hash.0 { + return Err(Error::InvalidProofBlockHash); + } + + let vote = Vote { + height: proof.number, + round: proof.round, + vote_type: 2u8, + block_hash: Bytes::from(proof.block_hash.0.to_vec()), + }; + + let hash_vote = keccak_256(rlp::encode(&vote).as_ref()); + let pks = extract_pks(&proof, validator_list)?; + let pks = pks.iter().collect::>(); + let c_pk = PublicKey::from_aggregate(&AggregatePublicKey::aggregate(&pks, true)?); + let sig = Signature::from_bytes(&proof.signature)?; + let res = sig.verify(true, &hash_vote, DST.as_bytes(), &[], &c_pk, true); + + if res == BLST_ERROR::BLST_SUCCESS { + return Ok(()); + } + + Err(res.into()) +} + +fn extract_pks( + proof: &Proof, + validator_list: &mut [ValidatorExtend], +) -> Result, Error> { + validator_list.sort(); + + let bit_map = BitVec::from_bytes(&proof.bitmap); + let mut pks = Vec::with_capacity(validator_list.len()); + let mut count = 0usize; + + for (v, bit) in validator_list.iter().zip(bit_map.iter()) { + if !bit { + continue; + } + + pks.push(PublicKey::from_bytes(&v.bls_pub_key.as_bytes())?); + count += 1; + } + + log::debug!( + "extract_pks count: {}, validator len: {}", + count, + validator_list.len() + ); + if count * 3 <= validator_list.len() * 2 { + return Err(Error::NotEnoughSignatures); + } + + Ok(pks) +} diff --git a/devtools/axon-tools/src/rlp_codec.rs b/devtools/axon-tools/src/rlp_codec.rs new file mode 100644 index 000000000..ba0778126 --- /dev/null +++ b/devtools/axon-tools/src/rlp_codec.rs @@ -0,0 +1,52 @@ +use crate::types::{BlockVersion, Proposal, Vote}; +#[cfg(feature = "impl-rlp")] +use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; + +#[cfg(feature = "impl-rlp")] +impl Encodable for BlockVersion { + fn rlp_append(&self, s: &mut RlpStream) { + let ver: u8 = (*self).into(); + s.begin_list(1).append(&ver); + } +} + +#[cfg(feature = "impl-rlp")] +impl Decodable for BlockVersion { + fn decode(r: &Rlp) -> Result { + let ver: u8 = r.val_at(0)?; + ver.try_into() + .map_err(|_| DecoderError::Custom("Invalid block version")) + } +} + +#[cfg(feature = "impl-rlp")] +impl Encodable for Vote { + fn rlp_append(&self, s: &mut RlpStream) { + let vote_type: u8 = self.vote_type; + s.begin_list(4) + .append(&self.height) + .append(&self.round) + .append(&vote_type) + .append(&self.block_hash.to_vec()); + } +} + +#[cfg(feature = "impl-rlp")] +impl Encodable for Proposal { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(13) + .append(&self.version) + .append(&self.prev_hash) + .append(&self.proposer) + .append(&self.prev_state_root) + .append(&self.transactions_root) + .append(&self.signed_txs_hash) + .append(&self.timestamp) + .append(&self.number) + .append(&self.gas_limit.as_u64()) + .append_list(&self.extra_data) + .append(&self.proof) + .append(&self.call_system_script_count) + .append_list(&self.tx_hashes); + } +} diff --git a/devtools/axon-tools/src/tests/block.json b/devtools/axon-tools/src/tests/block.json new file mode 100644 index 000000000..f0c60095f --- /dev/null +++ b/devtools/axon-tools/src/tests/block.json @@ -0,0 +1,30 @@ +{ + "header": { + "version": "V0", + "prev_hash": "0xa5643d9a0983004a662c154d6b2ae2a9aa10566b21333467400c5dcb6558bb38", + "proposer": "0x8af204ac5d7cb8815a6c53a50b72d01e729d3b22", + "state_root": "0x3e7e13a5fdd338fc7254d52745136dde2bbeb82718713415be596dc635274bbe", + "transactions_root": "0x93ac653e9457b39685218933d81145df1b4fd06a9b2e6be0217cad4a608ad6fb", + "signed_txs_hash": "0x2077d36822b7bd8f570d99355c62c8c04d4bf8bcfcb3bfdd5c90be783b713983", + "receipts_root": "0x08c61b0c680f18a1f33a8bce9b09d0fffee3a595cd5c95519fa21e80894d87e9", + "log_bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000", + "timestamp": "0x6538e7e0", + "number": "0xe7c3", + "gas_used": "0x2a3d5", + "gas_limit": "0x1c9c380", + "extra_data": [], + "base_fee_per_gas": "0x539", + "proof": { + "number": "0xe7c2", + "round": "0x0", + "block_hash": "0x734f6e28a7caf9df326c21d8c57b89034ed02f782a11d04d18284b7aa4de43f0", + "signature": "0x933be0cd674477473bd68a55a53511a652fbcecfa4cafaa244d0b55ca3f6a7f296d50a2744337365fd6407d737cc389f054b70b3c82ee27a1669a43cc344ba7ad42a7cc663faa63190a739dc1c6a1a5d80d45f5903f42d118fc5e4d695cc4815", + "bitmap": "0xb0" + }, + "call_system_script_count": "0x0", + "chain_id": "0x41786f6e" + }, + "tx_hashes": [ + "0x6917d8ef5fbac4fab865a872cd4ed2ca9fd0c514491d7b148530392fa9d656c8" + ] +} diff --git a/devtools/axon-tools/src/tests/metadata.json b/devtools/axon-tools/src/tests/metadata.json new file mode 100644 index 000000000..ef3caaa85 --- /dev/null +++ b/devtools/axon-tools/src/tests/metadata.json @@ -0,0 +1,54 @@ +{ + "version": { + "start": "0x0", + "end": "0x3b9aca00" + }, + "epoch": "0x0", + "verifier_list": [ + { + "bls_pub_key": "0x98eef09a3927acb225191101a1d9aa85775fdcdc87b9ba36898f6c132b485d66aef91c0f51cda331be4f985c3be6761c", + "pub_key": "0x0232c489c23b1207107e9a24648c1e4754a8c1c0b38db96df57a526201035058cb", + "address": "0xf4cc1652dcec2e5de9ce6fb1b6f9fa9456e957f1", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0xa26e3fe1cf51bd4822072c61bdc315ac32e3d3c2e2484bb92942666399e863b4bf56cf2926383cc706ffc15dfebc85c6", + "pub_key": "0x031ddc35212b7fc7ff6685b17d91f77c972535aee5c7ae5684d3e72b986f08834b", + "address": "0x8ab0cf264df99d83525e9e11c7e4db01558ae1b1", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0x80310fa9df724b5603d283b472ed3bf85254a8a4ceda8a274b421f6cf2be1d9184267cdfe9a199d36ff14e57668a55d0", + "pub_key": "0x02b77c74eb68af3d4d6cc7884ed6709f1a2a1af0f713382a4438ec2ea3a70d4d7f", + "address": "0xf386573563c3a75dbbd269fce9782620826ddac2", + "propose_weight": "0x1", + "vote_weight": "0x1" + }, + { + "bls_pub_key": "0x897721e9016864141a8b982a48217f66ef318ce598aa31842cddaaebe3cd7feab17050022afa6c2123aba39938fe4142", + "pub_key": "0x027ffd6a6a231561f2afe5878b1c743323b34263d16787130b1815fe35649b0bf5", + "address": "0x8af204ac5d7cb8815a6c53a50b72d01e729d3b22", + "propose_weight": "0x1", + "vote_weight": "0x1" + } + ], + "propose_counter": [ + { + "address": "0x0000000000000000000000000000000000000000", + "count": "0x0" + } + ], + "consensus_config": { + "gas_limit": "0xffffffff", + "gas_price": "0x1", + "interval": "0xbb8", + "propose_ratio": "0xf", + "prevote_ratio": "0xa", + "precommit_ratio": "0xa", + "brake_ratio": "0xa", + "tx_num_limit": "0x4e20", + "max_tx_size": "0x400" + } +} diff --git a/devtools/axon-tools/src/tests/mod.rs b/devtools/axon-tools/src/tests/mod.rs new file mode 100644 index 000000000..a628842fa --- /dev/null +++ b/devtools/axon-tools/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod verify_proof; +mod verify_trie_proof; diff --git a/devtools/axon-tools/src/tests/proof.json b/devtools/axon-tools/src/tests/proof.json new file mode 100644 index 000000000..795e672bc --- /dev/null +++ b/devtools/axon-tools/src/tests/proof.json @@ -0,0 +1,7 @@ +{ + "number": "0xe7c3", + "round": "0x0", + "block_hash": "0x6e48fa2d2002453abd9b51e4ac0342abb1f027b8272d6e27ce79a630b3f26673", + "signature": "0xb3e8e27db04baec18c04bc0b8ffe7fdbf2b5f7d6ef243c8be02bfd75defee32f1c0ff73a9fa67fee24630d2da5aa70f111ba41c0212be5d91b95f3bb84e5c0406b4742cca8c8f8362c07024fd8081d16875b01f43c6aa11c60b196af8c671a9b", + "bitmap": "0x70" +} diff --git a/devtools/axon-tools/src/tests/verify_proof.rs b/devtools/axon-tools/src/tests/verify_proof.rs new file mode 100644 index 000000000..726c866e7 --- /dev/null +++ b/devtools/axon-tools/src/tests/verify_proof.rs @@ -0,0 +1,76 @@ +use crate::hash::keccak_256; +use crate::types::{AxonBlock, BlockVersion, Metadata, Proof, Proposal, ValidatorExtend}; +use bytes::Bytes; +use ethereum_types::{H160, H256, U256}; +use rlp::Encodable; +use serde::de::DeserializeOwned; + +fn read_json(path: &str) -> T { + let json = std::fs::read_to_string(path).unwrap(); + serde_json::from_str(&json).unwrap() +} + +#[test] +fn test_proposal() { + let proposal = Proposal { + version: BlockVersion::V0, + prev_hash: H256::from([1u8; 32]), + proposer: H160::from([2u8; 20]), + prev_state_root: H256::from([3u8; 32]), + transactions_root: H256::from([4u8; 32]), + signed_txs_hash: H256::from([5u8; 32]), + timestamp: 0, + number: 100, + gas_limit: U256::from(6), + extra_data: Vec::new(), + base_fee_per_gas: U256::from(7), + proof: Proof { + number: 0, + round: 1, + block_hash: H256::from([1u8; 32]), + signature: Bytes::from("1234"), + bitmap: Bytes::from("abcd"), + }, + chain_id: 1000, + call_system_script_count: 1, + tx_hashes: vec![], + }; + + let rlp_bytes = proposal.rlp_bytes(); + let hash = keccak_256(&rlp_bytes); + let ref_hash = [ + 95u8, 14, 98, 221, 57, 150, 113, 31, 144, 120, 33, 170, 102, 71, 19, 108, 141, 247, 85, 17, + 193, 76, 201, 242, 128, 231, 44, 231, 204, 189, 67, 230, + ]; + assert_eq!(hash, ref_hash); +} + +#[test] +fn test_verify_proof() { + let block: AxonBlock = read_json("src/tests/block.json"); + let proof: Proof = read_json("src/tests/proof.json"); + let metadata: Metadata = read_json("src/tests/metadata.json"); + let mut validators = metadata + .verifier_list + .iter() + .map(|v| ValidatorExtend { + bls_pub_key: v.bls_pub_key.clone(), + pub_key: v.pub_key.clone(), + address: v.address, + propose_weight: v.propose_weight, + vote_weight: v.vote_weight, + }) + .collect::>(); + + let previous_state_root = + hex::decode("9fc948be2cfb0127e979dc9c7e6d2f4a2890b54e0e81fd69c687303e6b25ddde").unwrap(); + + let result = crate::verify_proof( + block, + H256::from_slice(&previous_state_root), + &mut validators, + proof, + ); + + assert!(result.is_ok()); +} diff --git a/devtools/axon-tools/src/tests/verify_trie_proof.rs b/devtools/axon-tools/src/tests/verify_trie_proof.rs new file mode 100644 index 000000000..168be3701 --- /dev/null +++ b/devtools/axon-tools/src/tests/verify_trie_proof.rs @@ -0,0 +1,178 @@ +use crate::verify_trie_proof; +use eth_light_client_in_ckb_prover::{encode_receipt, Receipts}; +use ethereum_types::{Bloom, H256, U256}; +use ethers_core::{ + types::{Log, TransactionReceipt, U64}, + utils::{keccak256, rlp}, +}; + +#[test] +fn test_receipt() { + let mut tx_receipts = Vec::::new(); + + { + let logs = vec![Log::default()]; + let receipt = TransactionReceipt { + transaction_hash: H256::from([0u8; 32]), + transaction_index: 0.into(), + cumulative_gas_used: U256::from(10), + transaction_type: Some(U64::from(2)), + status: Some(U64::from(1)), + logs_bloom: logs_bloom(logs.iter()), + logs, + ..Default::default() + }; + + let receipt_encode = encode_receipt(&receipt); + let reference_encode: Vec = [ + 2u8, 249, 1, 30, 1, 10, 185, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 216, 215, 148, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 192, 128, + ] + .to_vec(); + + assert_eq!(receipt_encode, reference_encode); + tx_receipts.push(receipt); + } + + let receipts: Receipts = tx_receipts.into(); + let ref_root = [ + 197u8, 180, 204, 76, 181, 157, 142, 152, 246, 237, 148, 126, 24, 207, 94, 119, 119, 205, + 11, 16, 193, 17, 102, 157, 61, 7, 166, 133, 173, 208, 124, 6, + ]; + assert_eq!(receipts.root().0, ref_root); +} + +#[test] +fn test_verify_trie_proof() { + let mut tx_receipts = Vec::::new(); + + { + let receipt = TransactionReceipt { + transaction_hash: H256::from([0u8; 32]), + transaction_index: 0.into(), + cumulative_gas_used: U256::from(100), + transaction_type: Some(U64::from(0)), + status: Some(U64::from(1)), + ..Default::default() + }; + tx_receipts.push(receipt); + } + + { + let receipt = TransactionReceipt { + transaction_hash: H256::from([1u8; 32]), + transaction_index: 1.into(), + gas_used: Some(U256::from(100)), + transaction_type: Some(U64::from(1)), + status: Some(U64::from(1)), + ..Default::default() + }; + tx_receipts.push(receipt); + } + + let receipts: Receipts = tx_receipts.into(); + + { + println!("proof of index 0"); + let proof_index = 0u64; + let receipt_proof = receipts.generate_proof(proof_index as usize); + + { + println!("test key 0"); + let key = rlp::encode(&proof_index); + let result = verify_trie_proof(receipts.root(), &key, receipt_proof.clone()); + assert!(result.unwrap().is_some()); + } + + { + println!("test key 1"); + let key = rlp::encode(&(1u64)); + let result = verify_trie_proof(receipts.root(), &key, receipt_proof.clone()); + assert!(result.unwrap().is_none()); + } + + { + println!("test key 2"); + let key = rlp::encode(&(2u64)); + let result = verify_trie_proof(receipts.root(), &key, receipt_proof.clone()); + assert!(result.unwrap().is_none()); + } + + { + println!("test illegal trie root"); + let key = rlp::encode(&(200u64)); + let result = verify_trie_proof(H256::from([4u8; 32]), &key, receipt_proof.clone()); + assert!(result.is_err()); + } + } + + { + println!("proof of index 1, wrong"); + let proof_index = 1u64; + let receipt_proof = receipts.generate_proof(proof_index as usize); + + { + println!("test key 0"); + let key = rlp::encode(&(0u64)); + let result = verify_trie_proof(receipts.root(), &key, receipt_proof.clone()); + assert!(result.unwrap().is_none()); + } + + { + println!("test key 1"); + let key = rlp::encode(&(1u64)); + let result = verify_trie_proof(receipts.root(), &key, receipt_proof.clone()); + assert!(result.unwrap().is_some()); + } + } +} + +pub fn logs_bloom<'a, I>(logs: I) -> Bloom +where + I: Iterator, +{ + let mut bloom = Bloom::zero(); + + for log in logs { + m3_2048(&mut bloom, log.address.as_bytes()); + for topic in log.topics.iter() { + m3_2048(&mut bloom, topic.as_bytes()); + } + } + bloom +} + +pub struct Hasher; + +impl Hasher { + pub fn digest>(bytes: B) -> H256 { + if bytes.as_ref().is_empty() { + return NIL_DATA; + } + + H256(keccak256(bytes)) + } +} + +pub const NIL_DATA: H256 = H256([ + 0xc5, 0xd2, 0x46, 0x01, 0x86, 0xf7, 0x23, 0x3c, 0x92, 0x7e, 0x7d, 0xb2, 0xdc, 0xc7, 0x03, 0xc0, + 0xe5, 0x00, 0xb6, 0x53, 0xca, 0x82, 0x27, 0x3b, 0x7b, 0xfa, 0xd8, 0x04, 0x5d, 0x85, 0xa4, 0x70, +]); +const BLOOM_BYTE_LENGTH: usize = 256; + +fn m3_2048(bloom: &mut Bloom, x: &[u8]) { + let hash = Hasher::digest(x).0; + for i in [0, 2, 4] { + let bit = (hash[i + 1] as usize + ((hash[i] as usize) << 8)) & 0x7FF; + bloom.0[BLOOM_BYTE_LENGTH - 1 - bit / 8] |= 1 << (bit % 8); + } +} diff --git a/devtools/axon-tools/src/types.rs b/devtools/axon-tools/src/types.rs new file mode 100644 index 000000000..cc7e7c415 --- /dev/null +++ b/devtools/axon-tools/src/types.rs @@ -0,0 +1,708 @@ +use crate::error::TypesError; +use alloc::vec::Vec; +use bytes::{Bytes, BytesMut}; +use core::cmp::Ordering; +use core::str::FromStr; +use ethereum_types::{Bloom, H160, H256, U256}; +use faster_hex::withpfx_lowercase; + +#[cfg(feature = "impl-serde")] +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "impl-rlp")] +use rlp_derive::{RlpDecodable, RlpEncodable}; + +#[cfg(feature = "hex")] +use crate::hex::{hex_decode, hex_encode}; +#[cfg(feature = "hex")] +use crate::Error; + +const HEX_PREFIX: &str = "0x"; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +#[cfg(feature = "impl-rlp")] +#[derive(RlpEncodable, RlpDecodable)] +pub struct Hex(Bytes); + +impl Hex { + pub fn empty() -> Self { + Hex(Bytes::default()) + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn encode>(src: T) -> Self { + Hex(BytesMut::from(src.as_ref()).freeze()) + } + + pub fn as_string(&self) -> String { + HEX_PREFIX.to_string() + &hex_encode(self.0.as_ref()) + } + + pub fn as_string_trim0x(&self) -> String { + hex_encode(self.0.as_ref()) + } + + pub fn as_bytes(&self) -> Bytes { + self.0.clone() + } + + fn is_prefixed(s: &str) -> bool { + s.starts_with(HEX_PREFIX) + } +} + +impl Default for Hex { + fn default() -> Self { + Hex(vec![0u8; 8].into()) + } +} + +impl AsRef<[u8]> for Hex { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl FromStr for Hex { + type Err = Error; + + fn from_str(s: &str) -> Result { + if !Self::is_prefixed(s) { + return Err(crate::Error::HexPrefix); + } + + Ok(Hex(hex_decode(&s[2..])?.into())) + } +} + +impl From for Bytes { + fn from(bytes: Hex) -> Self { + bytes.0 + } +} + +impl Serialize for Hex { + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + withpfx_lowercase::serialize(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for Hex { + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + String::deserialize(deserializer) + .and_then(|s| Hex::from_str(&s).map_err(serde::de::Error::custom)) + } +} + +#[derive(Default, Copy, Clone, Debug, PartialEq, Eq)] +#[cfg(feature = "impl-serde")] +#[derive(Serialize, Deserialize)] +pub enum BlockVersion { + #[default] + V0, +} + +impl From for u8 { + fn from(value: BlockVersion) -> Self { + match value { + BlockVersion::V0 => 0, + } + } +} + +impl TryFrom for BlockVersion { + type Error = TypesError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(BlockVersion::V0), + _ => Err(TypesError::InvalidBlockVersion(value)), + } + } +} + +pub type Hash = H256; +pub type MerkleRoot = Hash; +pub type BlockNumber = u64; + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ExtraData { + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub inner: Bytes, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AxonHeader { + pub version: BlockVersion, + pub prev_hash: Hash, + pub proposer: H160, + pub state_root: MerkleRoot, + pub transactions_root: MerkleRoot, + pub signed_txs_hash: Hash, + pub receipts_root: MerkleRoot, + pub log_bloom: Bloom, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub timestamp: u64, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub number: BlockNumber, + pub gas_used: U256, + pub gas_limit: U256, + /// Extra data for the block header + /// The first index of extra_data is used to store hardfork information: + /// `HardforkInfoInner` + pub extra_data: Vec, + pub base_fee_per_gas: U256, + pub proof: Proof, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u32" + ) + )] + pub call_system_script_count: u32, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub chain_id: u64, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct AxonBlock { + pub header: AxonHeader, + pub tx_hashes: Vec, +} + +#[cfg(feature = "proof")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Deserialize))] +pub struct Proposal { + pub version: BlockVersion, + pub prev_hash: Hash, + pub proposer: H160, + pub prev_state_root: MerkleRoot, + pub transactions_root: MerkleRoot, + pub signed_txs_hash: Hash, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub timestamp: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub number: BlockNumber, + pub gas_limit: U256, + pub extra_data: Vec, + pub base_fee_per_gas: U256, + pub proof: Proof, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub chain_id: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub call_system_script_count: u32, + pub tx_hashes: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Proof { + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub number: u64, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "encode::serialize_uint", + deserialize_with = "decode::deserialize_hex_u64" + ) + )] + pub round: u64, + pub block_hash: Hash, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub signature: Bytes, + #[cfg_attr( + feature = "impl-serde", + serde( + serialize_with = "withpfx_lowercase::serialize", + deserialize_with = "withpfx_lowercase::deserialize" + ) + )] + pub bitmap: Bytes, +} + +#[cfg(feature = "proof")] +#[derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Validator { + pub pub_key: Bytes, + pub propose_weight: u32, + pub vote_weight: u32, +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl core::cmp::PartialOrd for Validator { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(feature = "proof")] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +impl core::cmp::Ord for Validator { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.pub_key.cmp(&other.pub_key) + } +} + +#[cfg(feature = "proof")] +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(doc_cfg, doc(cfg(feature = "proof")))] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Vote { + pub height: u64, + pub round: u64, + pub vote_type: u8, + pub block_hash: Bytes, +} + +#[cfg(test)] +impl Vote { + fn random() -> Self { + Self { + height: rand::random(), + round: rand::random(), + vote_type: 2, + block_hash: tests::random_bytes(32), + } + } +} + +#[derive(Default, Clone, Debug, Copy, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct MetadataVersion { + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub start: BlockNumber, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub end: BlockNumber, +} + +impl MetadataVersion { + pub fn new(start: BlockNumber, end: BlockNumber) -> Self { + MetadataVersion { start, end } + } + + pub fn contains(&self, number: BlockNumber) -> bool { + self.start <= number && number <= self.end + } +} + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Metadata { + pub version: MetadataVersion, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub epoch: u64, + pub verifier_list: Vec, + #[serde(skip_deserializing)] + pub propose_counter: Vec, + pub consensus_config: ConsensusConfig, +} + +#[derive(Default, Clone, Debug, PartialEq, Eq)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ConsensusConfig { + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub gas_limit: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub interval: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub propose_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub prevote_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub precommit_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub brake_ratio: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub tx_num_limit: u64, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub max_tx_size: u64, +} + +#[derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ProposeCount { + pub address: H160, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u64") + )] + pub count: u64, +} + +#[derive(Clone, PartialEq, Eq, Default)] +#[cfg_attr( + feature = "impl-rlp", + derive(rlp_derive::RlpEncodable, rlp_derive::RlpDecodable) +)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ValidatorExtend { + pub bls_pub_key: Hex, + pub pub_key: Hex, + pub address: H160, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub propose_weight: u32, + #[cfg_attr( + feature = "impl-serde", + serde(deserialize_with = "decode::deserialize_hex_u32") + )] + pub vote_weight: u32, +} + +impl PartialOrd for ValidatorExtend { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ValidatorExtend { + fn cmp(&self, other: &Self) -> Ordering { + self.pub_key.as_bytes().cmp(&other.pub_key.as_bytes()) + } +} + +impl From for Validator { + fn from(ve: ValidatorExtend) -> Self { + Validator { + pub_key: ve.pub_key.as_bytes(), + propose_weight: ve.propose_weight, + vote_weight: ve.vote_weight, + } + } +} + +impl std::fmt::Debug for ValidatorExtend { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let bls_pub_key = self.bls_pub_key.as_string_trim0x(); + let pk = if bls_pub_key.len() > 8 { + unsafe { bls_pub_key.get_unchecked(0..8) } + } else { + bls_pub_key.as_str() + }; + + write!( + f, + "bls public key {:?}, public key {:?}, address {:?} propose weight {}, vote weight {}", + pk, self.pub_key, self.address, self.propose_weight, self.vote_weight + ) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct NodePubKey { + pub bls_pub_key: Bytes, + pub pub_key: Bytes, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "impl-serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CkbRelatedInfo { + pub metadata_type_id: H256, + pub checkpoint_type_id: H256, + pub xudt_args: H256, + pub stake_smt_type_id: H256, + pub delegate_smt_type_id: H256, + pub reward_smt_type_id: H256, +} + +#[cfg(feature = "impl-serde")] +mod encode { + use ethereum_types::U256; + use serde::ser::Serializer; + static CHARS: &[u8] = b"0123456789abcdef"; + + fn to_hex_raw<'a>(v: &'a mut [u8], bytes: &[u8], skip_leading_zero: bool) -> &'a str { + debug_assert!(v.len() > 1 + bytes.len() * 2); + + v[0] = b'0'; + v[1] = b'x'; + + let mut idx = 2; + let first_nibble = bytes[0] >> 4; + if first_nibble != 0 || !skip_leading_zero { + v[idx] = CHARS[first_nibble as usize]; + idx += 1; + } + v[idx] = CHARS[(bytes[0] & 0xf) as usize]; + idx += 1; + + for &byte in bytes.iter().skip(1) { + v[idx] = CHARS[(byte >> 4) as usize]; + v[idx + 1] = CHARS[(byte & 0xf) as usize]; + idx += 2; + } + + // SAFETY: all characters come either from CHARS or "0x", therefore valid UTF8 + unsafe { std::str::from_utf8_unchecked(&v[0..idx]) } + } + + pub fn serialize_uint(val: &U, s: S) -> Result + where + S: Serializer, + U: Into + Copy, + { + let val: U256 = (*val).into(); + let mut slice = [0u8; 2 + 64]; + let mut bytes = [0u8; 32]; + val.to_big_endian(&mut bytes); + let non_zero = bytes.iter().take_while(|b| **b == 0).count(); + let bytes = &bytes[non_zero..]; + + if bytes.is_empty() { + s.serialize_str("0x0") + } else { + s.serialize_str(to_hex_raw(&mut slice, bytes, true)) + } + } +} + +#[cfg(feature = "impl-serde")] +mod decode { + use ethereum_types::U256; + use serde::de::{Deserialize, Deserializer}; + + pub fn from_hex(hex: &str) -> Result, &'static str> { + let mut bytes = Vec::with_capacity((hex.len() + 1) / 2); + + let mut start_i = 0; + if hex.len() % 2 != 0 { + let byte = + u8::from_str_radix(&hex[0..1], 16).map_err(|_| "Failed to parse hex string")?; + bytes.push(byte); + start_i = 1; + } + + for i in (start_i..hex.len()).step_by(2) { + let end_i = if i + 2 > hex.len() { i + 1 } else { i + 2 }; + let byte = + u8::from_str_radix(&hex[i..end_i], 16).map_err(|_| "Failed to parse hex string")?; + bytes.push(byte); + } + + Ok(bytes) + } + + pub fn deserialize_hex_u32<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s == "0x0" { + return Ok(0); + } + + if s.len() >= 2 && &s[0..2] == "0x" { + let bytes = from_hex(&s[2..]).map_err(serde::de::Error::custom)?; + let val = U256::from_big_endian(&bytes); + Ok(val.low_u32()) + } else { + Err(serde::de::Error::custom("Invalid format")) + } + } + + pub fn deserialize_hex_u64<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + if s == "0x0" { + return Ok(0); + } + + if s.len() >= 2 && &s[0..2] == "0x" { + let bytes = from_hex(&s[2..]).map_err(serde::de::Error::custom)?; + let val = U256::from_big_endian(&bytes); + Ok(val.low_u64()) + } else { + Err(serde::de::Error::custom("Invalid format")) + } + } + + #[cfg(test)] + mod tests { + #[cfg(all( + feature = "hex", + feature = "proof", + feature = "impl-serde", + feature = "impl-rlp" + ))] + #[test] + fn test_deserialize_hex_u64() { + use crate::types::MetadataVersion; + + { + let json_str = r#"{"start": "0x0", "end": "0x7"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x0); + assert_eq!(my_struct.end, 0x7); + } + + { + let json_str = r#"{"start": "0x12", "end": "0x233"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x12); + assert_eq!(my_struct.end, 0x233); + } + + { + let json_str = r#"{"start": "0x67fed12", "end": "0x8ddefa09"}"#; + let my_struct: MetadataVersion = serde_json::from_str(json_str).unwrap(); + assert_eq!(my_struct.start, 0x67fed12); + assert_eq!(my_struct.end, 0x8ddefa09); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + pub fn random_bytes(len: usize) -> Bytes { + (0..len).map(|_| rand::random()).collect::>().into() + } + + #[test] + fn test_vote_codec() { + let vote = Vote::random(); + let raw = rlp::encode(&vote); + let decoded: overlord::types::Vote = rlp::decode(&raw).unwrap(); + assert_eq!(vote.height, decoded.height); + assert_eq!(vote.round, decoded.round); + assert_eq!(vote.block_hash, decoded.block_hash); + } +}