diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index e62398013b..0000000000 --- a/.cargo/config +++ /dev/null @@ -1,3 +0,0 @@ -[net] -git-fetch-with-cli = true - diff --git a/Cargo.lock b/Cargo.lock index 0a1aea7811..f89c6e6bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,6 +119,18 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if 1.0.0", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -128,6 +140,193 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "alloy-consensus" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", + "sha2 0.10.6", +] + +[[package]] +name = "alloy-eips" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "c-kzg", + "once_cell", + "serde", +] + +[[package]] +name = "alloy-genesis" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-primitives", + "alloy-serde", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-primitives" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8aa973e647ec336810a9356af8aea787249c9d00b1525359f3db29a68d231b" +dependencies = [ + "alloy-rlp", + "bytes 1.4.0", + "cfg-if 1.0.0", + "const-hex", + "derive_more", + "hex-literal", + "itoa 1.0.6", + "k256", + "keccak-asm", + "proptest", + "rand 0.8.5", + "ruint", + "serde", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b155716bab55763c95ba212806cf43d05bcc70e5f35b02bad20cf5ec7fe11fed" +dependencies = [ + "alloy-rlp-derive", + "arrayvec 0.7.4", + "bytes 1.4.0", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8037e03c7f462a063f28daec9fda285a9a89da003c552f8637a80b9c8fd96241" +dependencies = [ + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + +[[package]] +name = "alloy-rpc-types" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.12.1", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types", + "alloy-serde", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-serde" +version = "0.1.0" +source = "git+https://github.com/alloy-rs/alloy?rev=4e22b9e#4e22b9e1de80f1b1cc5dfdcd9461d44b27cf27ca" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-sol-macro" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dbd17d67f3e89478c8a634416358e539e577899666c927bc3d2b1328ee9b6ca" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6da95adcf4760bb4b108fefa51d50096c5e5fdd29ee72fed3e86ee414f2e34" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck 0.4.1", + "indexmap 2.2.6", + "proc-macro-error", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", + "syn-solidity", + "tiny-keccak 2.0.2", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c8da04c1343871fb6ce5a489218f9c85323c8340a36e9106b5fc98d4dd59d5" +dependencies = [ + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-types" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a64d2d2395c1ac636b62419a7b17ec39031d6b2367e66e9acbf566e6055e9c" +dependencies = [ + "alloy-primitives", + "alloy-sol-macro", + "const-hex", + "serde", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -170,6 +369,130 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.5", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.5", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.5", + "num-traits", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.5", + "num-traits", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.5", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "array-macro" version = "1.0.5" @@ -220,8 +543,8 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -231,9 +554,9 @@ version = "0.1.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86ea188f25f0255d8f92797797c97ebf5631fa88178beb1a46fdf5622c9a00e4" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -264,6 +587,27 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "aurora-engine-modexp" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aef7712851e524f35fbbb74fa6599c5cd8692056a1c36f9ca0d2001b670e7e5" +dependencies = [ + "hex 0.4.3", + "num 0.4.3", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + [[package]] name = "autocfg" version = "0.1.8" @@ -295,6 +639,12 @@ dependencies = [ "serde", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -338,7 +688,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1374191e2dd25f9ae02e3aa95041ed5d747fc77b3c102b49fe2dd9a8117a6244" dependencies = [ - "num-bigint", + "num-bigint 0.2.6", "num-integer", "num-traits", ] @@ -349,14 +699,14 @@ version = "0.64.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "regex", "rustc-hash", "shlex", @@ -399,6 +749,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "bitvec" version = "0.17.4" @@ -665,6 +1021,9 @@ name = "bytes" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +dependencies = [ + "serde", +] [[package]] name = "bzip2-sys" @@ -677,6 +1036,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "c-kzg" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf100c4cea8f207e883ff91ca886d621d8a166cb04971dfaa9bb8fd99ed95df" +dependencies = [ + "blst", + "cc", + "glob", + "hex 0.4.3", + "libc", + "serde", +] + [[package]] name = "c_linked_list" version = "1.1.1" @@ -707,11 +1080,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" dependencies = [ "jobserver", + "libc", ] [[package]] @@ -767,27 +1141,34 @@ dependencies = [ name = "cfx-execute-helper" version = "2.0.2" dependencies = [ + "alloy-primitives", + "alloy-rpc-types-trace", + "alloy-sol-types", "cfx-bytes", "cfx-executor", "cfx-internal-common", "cfx-parameters", "cfx-statedb", "cfx-types", + "cfx-vm-interpreter", "cfx-vm-tracer-derive", "cfx-vm-types", "error-chain", + "geth-tracer", "log", "malloc_size_of", "malloc_size_of_derive", "pow-types", "primitives", + "revm", "rlp 0.4.6", "rlp_derive", "serde", "serde_derive", + "serde_json", "solidity-abi", "strum_macros 0.20.1", - "typemap", + "typemap-ors", ] [[package]] @@ -796,6 +1177,7 @@ version = "2.0.2" dependencies = [ "bls-signatures", "byteorder", + "c-kzg", "cfx-bytes", "cfx-internal-common", "cfx-math", @@ -807,9 +1189,11 @@ dependencies = [ "cfx-vm-tracer-derive", "cfx-vm-types", "cfxkey", + "derive_more", "diem-crypto", "diem-types", "error-chain", + "hex-literal", "impl-tools", "impl-trait-for-tuples 0.2.2", "keccak-hash", @@ -817,7 +1201,8 @@ dependencies = [ "log", "malloc_size_of", "malloc_size_of_derive", - "num", + "num 0.2.1", + "once_cell", "parity-crypto", "parking_lot 0.11.2", "pow-types", @@ -833,9 +1218,9 @@ dependencies = [ "solidity-abi-derive", "strum 0.20.0", "strum_macros 0.20.1", - "substrate-bn", + "substrate-bn 0.6.0 (git+https://github.com/paritytech/bn?rev=63f8c587356a67b33c7396af98e065b66fca5dda)", "tiny-keccak 2.0.2", - "typemap", + "typemap-ors", ] [[package]] @@ -865,7 +1250,7 @@ version = "0.1.0" dependencies = [ "cfx-types", "criterion", - "num", + "num 0.2.1", "rand 0.8.5", "rand_xorshift 0.3.0", "static_assertions", @@ -1020,8 +1405,8 @@ dependencies = [ name = "cfx-vm-tracer-derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -1029,7 +1414,6 @@ dependencies = [ name = "cfx-vm-types" version = "2.0.2" dependencies = [ - "bls-signatures", "cfx-bytes", "cfx-db-errors", "cfx-types", @@ -1044,8 +1428,9 @@ dependencies = [ [[package]] name = "cfxcore" -version = "2.3.5" +version = "2.4.0" dependencies = [ + "alloy-rpc-types-trace", "anyhow", "async-oneshot", "async-trait", @@ -1096,6 +1481,7 @@ dependencies = [ "fallible-iterator", "fs_extra", "futures 0.3.27", + "geth-tracer", "hashbrown 0.7.2", "heap-map", "hibitset", @@ -1121,7 +1507,7 @@ dependencies = [ "mirai-annotations", "move-core-types", "network", - "num", + "num 0.2.1", "num-derive", "num-traits", "once_cell", @@ -1165,7 +1551,7 @@ dependencies = [ "strum 0.20.0", "strum_macros 0.20.1", "subscription-service", - "substrate-bn", + "substrate-bn 0.6.0 (git+https://github.com/paritytech/bn?rev=63f8c587356a67b33c7396af98e065b66fca5dda)", "tempdir", "thiserror", "threadpool", @@ -1211,7 +1597,7 @@ dependencies = [ "parity-crypto", "parity-secp256k1", "parity-wordlist", - "quick-error 1.2.3", + "quick-error", "rand 0.7.3", "rustc-hex", "serde", @@ -1332,7 +1718,7 @@ checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "ansi_term", "atty", - "bitflags", + "bitflags 1.3.2", "strsim 0.8.0", "textwrap 0.11.0", "unicode-width", @@ -1342,8 +1728,9 @@ dependencies = [ [[package]] name = "client" -version = "2.3.5" +version = "2.4.0" dependencies = [ + "alloy-rpc-types-trace", "anyhow", "app_dirs", "bigdecimal", @@ -1387,6 +1774,7 @@ dependencies = [ "fail", "futures 0.3.27", "futures01", + "geth-tracer", "io", "itertools 0.9.0", "jsonrpc-core", @@ -1405,7 +1793,7 @@ dependencies = [ "metrics", "mio 0.6.23", "network", - "num-bigint", + "num-bigint 0.2.6", "order-stat", "parity-version", "parking_lot 0.11.2", @@ -1423,6 +1811,7 @@ dependencies = [ "rustc-hex", "secret-store", "serde", + "serde-utils", "serde_derive", "serde_json", "serial_test", @@ -1450,7 +1839,7 @@ version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -1474,7 +1863,7 @@ dependencies = [ [[package]] name = "conflux" -version = "2.3.5" +version = "2.4.0" dependencies = [ "app_dirs", "base64ct", @@ -1556,12 +1945,37 @@ dependencies = [ "short-hex-str", ] +[[package]] +name = "const-hex" +version = "1.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ff96486ccc291d36a958107caf2c0af8c78c0af7d31ae2f35ce055130de1a6" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "hex 0.4.3", + "proptest", + "serde", +] + [[package]] name = "const-oid" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "core-foundation" version = "0.9.3" @@ -1779,6 +2193,18 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.6", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -1899,10 +2325,10 @@ dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "scratch", - "syn 2.0.42", + "syn 2.0.66", ] [[package]] @@ -1917,9 +2343,9 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631569015d0d8d54e6c241733f944042623ab6df7bc3be7466874b05fcdb1c5f" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -1942,8 +2368,8 @@ dependencies = [ name = "delegate" version = "0.4.2" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -1953,8 +2379,8 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd733b5bf0bb5ca3c7cdea2135c91234c80b730e6e8a270851455a63b46c830" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -1964,7 +2390,17 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" dependencies = [ - "const-oid", + "const-oid 0.6.2", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid 0.9.6", + "zeroize", ] [[package]] @@ -1973,8 +2409,21 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2 1.0.84", + "quote 1.0.36", + "rustc_version 0.4.0", "syn 1.0.109", ] @@ -2034,7 +2483,7 @@ dependencies = [ "once_cell", "openssl", "parking_lot 0.11.2", - "pkcs8", + "pkcs8 0.7.6", "proptest", "proptest-derive", "rand 0.8.5", @@ -2059,8 +2508,8 @@ name = "diem-crypto-derive" version = "0.1.0" dependencies = [ "anyhow", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -2112,8 +2561,8 @@ dependencies = [ name = "diem-log-derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -2335,11 +2784,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", + "const-oid 0.9.6", "crypto-common", "subtle", ] @@ -2386,6 +2836,32 @@ dependencies = [ "strsim 0.10.0", ] +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.9", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature 2.2.0", + "spki 0.7.3", +] + [[package]] name = "ed25519" version = "1.5.3" @@ -2393,7 +2869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ "serde", - "signature", + "signature 1.6.4", ] [[package]] @@ -2423,6 +2899,25 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array 0.14.6", + "group", + "pkcs8 0.10.2", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.32" @@ -2447,8 +2942,8 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "355f93763ef7b0ae1c43c4d8eccc9d5848d84ad1a1d8ce61c421d1ac85a19d05" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -2469,8 +2964,8 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5c450cf304c9e18d45db562025a14fb1ca0f5c769b6f609309f81d4c31de455" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -2481,11 +2976,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11f36e95862220b211a6e2aa5eca09b4fa391b13cd52ceb8035a24bf65a79de2" dependencies = [ "once_cell", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] +[[package]] +name = "enumn" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" +dependencies = [ + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + [[package]] name = "env_logger" version = "0.5.13" @@ -2499,6 +3005,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "erased-serde" version = "0.3.25" @@ -2701,8 +3213,8 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "synstructure", ] @@ -2735,6 +3247,17 @@ dependencies = [ "instant", ] +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec 0.7.4", + "auto_impl", + "bytes 1.4.0", +] + [[package]] name = "ff" version = "0.13.0" @@ -2789,6 +3312,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.2.0" @@ -2865,7 +3400,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" dependencies = [ - "bitflags", + "bitflags 1.3.2", "fuchsia-zircon-sys", ] @@ -2957,8 +3492,8 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -3034,6 +3569,7 @@ checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -3058,6 +3594,22 @@ dependencies = [ "libc", ] +[[package]] +name = "geth-tracer" +version = "2.4.0" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-trace", + "alloy-sol-types", + "cfx-executor", + "cfx-types", + "cfx-vm-interpreter", + "cfx-vm-types", + "primitives", + "revm", + "typemap-ors", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -3087,8 +3639,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -3114,7 +3666,7 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf7f68c2995f392c49fffb4f95ae2c873297830eb25c6bc4c114ce8f4562acc" dependencies = [ - "bitflags", + "bitflags 1.3.2", "libc", "libgit2-sys", "log", @@ -3164,7 +3716,7 @@ dependencies = [ "fnv", "futures 0.1.31", "http 0.1.21", - "indexmap", + "indexmap 1.9.2", "log", "slab", "string", @@ -3183,7 +3735,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.9", - "indexmap", + "indexmap 1.9.2", "slab", "tokio 1.26.0", "tokio-util", @@ -3208,7 +3760,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf" dependencies = [ - "ahash", + "ahash 0.3.8", "autocfg 1.1.0", ] @@ -3218,6 +3770,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.11", + "allocator-api2", +] + [[package]] name = "heap-map" version = "0.1.0" @@ -3237,6 +3799,18 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -3272,6 +3846,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -3336,7 +3913,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -3438,7 +4015,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" dependencies = [ - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -3589,6 +4166,15 @@ dependencies = [ "parity-scale-codec 2.3.1", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec 3.6.11", +] + [[package]] name = "impl-rlp" version = "0.2.1" @@ -3634,7 +4220,7 @@ dependencies = [ "autocfg 1.1.0", "impl-tools-lib", "proc-macro-error", - "syn 2.0.42", + "syn 2.0.66", ] [[package]] @@ -3644,9 +4230,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85d3946d886eaab0702fa0c6585adcced581513223fa9df7ccfabbd9fa331a88" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -3655,8 +4241,8 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef5550a42e3740a0e71f909d4c861056a284060af885ae7aa6242820f920d9d" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -3666,8 +4252,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -3681,6 +4267,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + [[package]] name = "influx_db_client" version = "0.5.1" @@ -3771,6 +4367,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -3868,8 +4473,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99a847f9ec7bb52149b2786a17c9cb260d6effc6b8eeb8c16b343a487a7563a3" dependencies = [ "proc-macro-crate 0.1.5", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -3944,6 +4549,19 @@ dependencies = [ "slab", ] +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2 0.10.6", +] + [[package]] name = "keccak" version = "0.1.3" @@ -3953,6 +4571,16 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "keccak-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "keccak-hash" version = "0.5.1" @@ -4084,7 +4712,7 @@ checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" [[package]] name = "librocksdb_sys" version = "0.1.0" -source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=a1ce5bd3322a7b732dfb300c2571dc4d99f1edae#a1ce5bd3322a7b732dfb300c2571dc4d99f1edae" +source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=3773afe5b953997188f37c39308105b5deb0faac#3773afe5b953997188f37c39308105b5deb0faac" dependencies = [ "bindgen", "bzip2-sys", @@ -4101,7 +4729,7 @@ dependencies = [ [[package]] name = "libtitan_sys" version = "0.0.1" -source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=a1ce5bd3322a7b732dfb300c2571dc4d99f1edae#a1ce5bd3322a7b732dfb300c2571dc4d99f1edae" +source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=3773afe5b953997188f37c39308105b5deb0faac#3773afe5b953997188f37c39308105b5deb0faac" dependencies = [ "bzip2-sys", "cc", @@ -4275,7 +4903,7 @@ dependencies = [ name = "malloc_size_of_derive" version = "0.1.1" dependencies = [ - "proc-macro2 1.0.71", + "proc-macro2 1.0.84", "syn 1.0.109", "synstructure", ] @@ -4595,11 +5223,25 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" dependencies = [ - "num-bigint", - "num-complex", + "num-bigint 0.2.6", + "num-complex 0.2.4", "num-integer", "num-iter", - "num-rational", + "num-rational 0.2.4", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.5", + "num-complex 0.4.6", + "num-integer", + "num-iter", + "num-rational 0.4.2", "num-traits", ] @@ -4614,6 +5256,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-complex" version = "0.2.4" @@ -4624,32 +5276,40 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg 1.1.0", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg 1.1.0", "num-integer", @@ -4663,16 +5323,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg 1.1.0", - "num-bigint", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.5", "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg 1.1.0", "libm", @@ -4682,8 +5353,8 @@ dependencies = [ name = "num-variants" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -4718,9 +5389,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -4746,7 +5417,7 @@ version = "0.10.47" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b277f87dacc05a6b709965d1cbafac4649d6ce9f3ce9ceb88508b5666dfec9" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if 1.0.0", "foreign-types", "libc", @@ -4761,8 +5432,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -4840,7 +5511,7 @@ dependencies = [ "ripemd160", "rustc-hex", "scrypt 0.5.0", - "secp256k1", + "secp256k1 0.20.3", "sha2 0.9.9", "subtle", "tiny-keccak 2.0.2", @@ -4878,19 +5549,45 @@ dependencies = [ "bitvec 0.20.4", "byte-slice-cast 1.2.2", "impl-trait-for-tuples 0.2.2", - "parity-scale-codec-derive", + "parity-scale-codec-derive 2.3.1", + "serde", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b5927e4a9ae8d6cdb6a69e4e04a0ec73381a358e21b8a576f44769f34e7c24" +dependencies = [ + "arrayvec 0.7.4", + "bitvec 1.0.1", + "byte-slice-cast 1.2.2", + "impl-trait-for-tuples 0.2.2", + "parity-scale-codec-derive 3.6.9", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "2.3.1" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 1.0.109", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro-crate 2.0.2", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -4924,7 +5621,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ - "proc-macro2 1.0.71", + "proc-macro2 1.0.84", "syn 1.0.109", "synstructure", ] @@ -5077,6 +5774,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "path-absolutize" version = "3.0.14" @@ -5138,7 +5841,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -5159,6 +5862,17 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "pest" +version = "2.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + [[package]] name = "petgraph" version = "0.5.1" @@ -5166,7 +5880,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ "fixedbitset", - "indexmap", + "indexmap 1.9.2", ] [[package]] @@ -5184,8 +5898,8 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -5226,12 +5940,12 @@ checksum = "5423c5ee112e07717eb3634de80e5d34f0019dd65f3eead74028d9c579ce7dd4" dependencies = [ "aes 0.7.5", "block-modes 0.8.1", - "der", + "der 0.4.5", "hmac 0.11.0", "pbkdf2 0.9.0", "scrypt 0.8.1", "sha2 0.9.9", - "spki", + "spki 0.4.1", ] [[package]] @@ -5240,13 +5954,23 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ - "der", + "der 0.4.5", "pkcs5", "rand_core 0.6.4", - "spki", + "spki 0.4.1", "zeroize", ] +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.9", + "spki 0.7.3", +] + [[package]] name = "pkg-config" version = "0.3.26" @@ -5429,6 +6153,17 @@ dependencies = [ "uint 0.9.5", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash 0.8.0", + "impl-codec 0.6.0", + "uint 0.9.5", +] + [[package]] name = "primitives" version = "0.2.0" @@ -5439,6 +6174,7 @@ dependencies = [ "cfxkey", "criterion", "fixed-hash 0.5.2", + "itertools 0.10.5", "keccak-hash", "lazy_static", "log", @@ -5474,7 +6210,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.7", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", ] [[package]] @@ -5484,8 +6230,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "version_check", ] @@ -5496,8 +6242,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "version_check", ] @@ -5512,9 +6258,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -5528,7 +6274,7 @@ dependencies = [ "cfg-if 0.1.10", "fnv", "lazy_static", - "quick-error 1.2.3", + "quick-error", "spin", ] @@ -5548,20 +6294,19 @@ dependencies = [ [[package]] name = "proptest" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set 0.5.3", - "bitflags", - "byteorder", + "bit-vec 0.6.3", + "bitflags 2.5.0", "lazy_static", "num-traits", - "quick-error 2.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift 0.3.0", - "regex-syntax", + "regex-syntax 0.8.2", "rusty-fork", "tempfile", "unarray", @@ -5600,12 +6345,6 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" - [[package]] name = "quote" version = "0.6.13" @@ -5617,11 +6356,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.71", + "proc-macro2 1.0.84", ] [[package]] @@ -5903,7 +6642,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -5938,9 +6677,9 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -5951,7 +6690,7 @@ checksum = "cce168fea28d3e05f158bda4576cf0c844d5045bc2cc3620fa0292ed5bb5814c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] @@ -5960,6 +6699,12 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -6006,6 +6751,85 @@ dependencies = [ "winreg", ] +[[package]] +name = "revm" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a454c1c650b2b2e23f0c461af09e6c31e1d15e1cbebe905a701c46b8a50afc" +dependencies = [ + "auto_impl", + "cfg-if 1.0.0", + "dyn-clone", + "revm-interpreter", + "revm-precompile", + "serde", + "serde_json", +] + +[[package]] +name = "revm-interpreter" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d322f2730cd300e99d271a1704a2dfb8973d832428f5aa282aaa40e2473b5eec" +dependencies = [ + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "931f692f3f4fc72ec39d5d270f8e9d208c4a6008de7590ee96cf948e3b6d3f8d" +dependencies = [ + "aurora-engine-modexp", + "c-kzg", + "k256", + "once_cell", + "revm-primitives", + "ripemd", + "secp256k1 0.28.2", + "sha2 0.10.6", + "substrate-bn 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "revm-primitives" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbbc9640790cebcb731289afb7a7d96d16ad94afeb64b5d0b66443bd151e79d6" +dependencies = [ + "alloy-primitives", + "auto_impl", + "bitflags 2.5.0", + "bitvec 1.0.1", + "cfg-if 1.0.0", + "dyn-clone", + "enumn", + "hashbrown 0.14.5", + "hex 0.4.3", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "ripemd160" version = "0.9.1" @@ -6055,7 +6879,7 @@ dependencies = [ [[package]] name = "rocksdb" version = "0.3.0" -source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=a1ce5bd3322a7b732dfb300c2571dc4d99f1edae#a1ce5bd3322a7b732dfb300c2571dc4d99f1edae" +source = "git+https://github.com/Conflux-Chain/rust-rocksdb.git?rev=3773afe5b953997188f37c39308105b5deb0faac#3773afe5b953997188f37c39308105b5deb0faac" dependencies = [ "libc", "librocksdb_sys", @@ -6081,6 +6905,36 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "ruint" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f308135fef9fc398342da5472ce7c484529df23743fb7c734e0f3d472971e62" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "bytes 1.4.0", + "fastrlp", + "num-bigint 0.4.5", + "num-traits", + "parity-scale-codec 3.6.11", + "primitive-types 0.12.2", + "proptest", + "rand 0.8.5", + "rlp 0.5.2", + "ruint-macro", + "serde", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86854cf50259291520509879a5c294c3c9a4c334e9ff65071c51e42ef1e2343" + [[package]] name = "runtime" version = "0.1.0" @@ -6116,6 +6970,15 @@ dependencies = [ "semver 0.9.0", ] +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -6131,7 +6994,7 @@ version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno", "io-lifetimes", "libc", @@ -6152,7 +7015,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", - "quick-error 1.2.3", + "quick-error", "tempfile", "wait-timeout", ] @@ -6302,6 +7165,20 @@ dependencies = [ "sha2 0.10.6", ] +[[package]] +name = "sec1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48518a2b5775ba8ca5b46596aae011caa431e6ce7e4a67ead66d92f08884220e" +dependencies = [ + "base16ct", + "der 0.7.9", + "generic-array 0.14.6", + "pkcs8 0.10.2", + "subtle", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.20.3" @@ -6309,7 +7186,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" dependencies = [ "rand 0.6.5", - "secp256k1-sys", + "secp256k1-sys 0.4.2", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "rand 0.8.5", + "secp256k1-sys 0.9.2", ] [[package]] @@ -6321,6 +7208,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + [[package]] name = "secret-store" version = "0.1.0" @@ -6340,7 +7236,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -6363,7 +7259,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser", + "semver-parser 0.7.0", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser 0.10.2", ] [[package]] @@ -6378,6 +7283,15 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.193" @@ -6397,6 +7311,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "serde-utils" +version = "2.4.0" +dependencies = [ + "alloy-primitives", + "cfx-types", + "serde", + "serde_json", +] + [[package]] name = "serde-value" version = "0.7.0" @@ -6432,9 +7356,9 @@ version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -6443,6 +7367,7 @@ version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ + "indexmap 1.9.2", "itoa 1.0.6", "ryu", "serde", @@ -6466,7 +7391,7 @@ version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" dependencies = [ - "indexmap", + "indexmap 1.9.2", "ryu", "serde", "yaml-rust 0.4.5", @@ -6489,8 +7414,8 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -6527,7 +7452,7 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6542,13 +7467,23 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha3-asm" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" +dependencies = [ + "cc", + "cfg-if 1.0.0", +] + [[package]] name = "sha3-macro" version = "0.1.0" dependencies = [ "keccak-hash", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -6595,6 +7530,16 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -6631,8 +7576,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -6672,8 +7617,8 @@ name = "solidity-abi-derive" version = "0.1.0" dependencies = [ "proc-macro-crate 1.3.1", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -6689,7 +7634,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ - "der", + "der 0.4.5", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.9", ] [[package]] @@ -6790,7 +7745,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8baacebd7b7c9b864d83a6ba7a246232983e277b86fa5cdec77f565715a4b136" dependencies = [ - "heck", + "heck 0.3.3", "proc-macro2 0.4.30", "quote 0.6.13", "syn 0.15.44", @@ -6802,9 +7757,9 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8bc6b87a5112aeeab1f4a9f7ab634fe6cbefc4850006df31267f4cfb9e3149" dependencies = [ - "heck", - "proc-macro2 1.0.71", - "quote 1.0.33", + "heck 0.3.3", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -6817,6 +7772,19 @@ dependencies = [ "diem-types", ] +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b5bbfa79abbae15dd642ea8176a21a635ff3c00059961d1ea27ad04e5b441c" +dependencies = [ + "byteorder", + "crunchy", + "lazy_static", + "rand 0.8.5", + "rustc-hex", +] + [[package]] name = "substrate-bn" version = "0.6.0" @@ -6852,30 +7820,42 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.42" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8db114c44cf843a8bacd37a146e37987a0b823a0e8bc4fdc610c9c72ab397a5" +dependencies = [ + "paste", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "unicode-xid 0.2.4", ] @@ -6971,9 +7951,9 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", - "syn 2.0.42", + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", ] [[package]] @@ -7234,8 +8214,8 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -7245,8 +8225,8 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", ] @@ -7433,9 +8413,20 @@ version = "0.19.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274" dependencies = [ - "indexmap", + "indexmap 1.9.2", + "toml_datetime", + "winnow 0.3.6", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.2.6", "toml_datetime", - "winnow", + "winnow 0.5.40", ] [[package]] @@ -7464,12 +8455,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "transient-hashmap" version = "0.4.1" @@ -7537,15 +8522,6 @@ dependencies = [ "toml", ] -[[package]] -name = "typemap" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" -dependencies = [ - "unsafe-any", -] - [[package]] name = "typemap-ors" version = "1.0.0" @@ -7561,6 +8537,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + [[package]] name = "uint" version = "0.8.5" @@ -7666,19 +8648,10 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" dependencies = [ - "quote 1.0.33", + "quote 1.0.36", "syn 1.0.109", ] -[[package]] -name = "unsafe-any" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" -dependencies = [ - "traitobject", -] - [[package]] name = "unsafe-any-ors" version = "1.0.0" @@ -7727,6 +8700,12 @@ dependencies = [ "percent-encoding 2.2.0", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -7851,8 +8830,8 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "wasm-bindgen-shared", ] @@ -7875,7 +8854,7 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ - "quote 1.0.33", + "quote 1.0.36", "wasm-bindgen-macro-support", ] @@ -7885,8 +8864,8 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -8050,6 +9029,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.10.1" @@ -8134,11 +9122,31 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2 1.0.84", + "quote 1.0.36", + "syn 2.0.66", +] + [[package]] name = "zeroize" -version = "1.5.7" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] @@ -8149,8 +9157,8 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ - "proc-macro2 1.0.71", - "quote 1.0.33", + "proc-macro2 1.0.84", + "quote 1.0.36", "syn 1.0.109", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index caf9b6a747..63f9263857 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,11 +51,15 @@ members = [ "crates/util/solidity-abi-derive", "crates/util/throttling", "crates/util/treap-map", - "crates/util/version" + "crates/util/version", + "crates/serde_utils", + "crates/cfxcore/geth-tracer", ] +resolver = "2" + [workspace.package] -version = "2.3.5" +version = "2.4.0" authors = ["peilun-conflux", "ChenxingLi"] description = "A rust implementation of the Conflux-Protocol" documentation = "https://doc.confluxnetwork.org" @@ -81,4 +85,12 @@ overflow-checks = true [profile.release] overflow-checks = true # Temporarily run with debug assertion before main-net release. -debug-assertions = true \ No newline at end of file +debug-assertions = true + +[workspace.dependencies] +serde_derive = "1.0" +serde = { version = "1.0", features = ["derive", "alloc"] } +serde_json = "1.0" + +alloy-primitives = "0.7.1" +alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "4e22b9e" } diff --git a/README.md b/README.md index 947ae3f5a8..6f38fb7133 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,12 @@ significant changes to the Conflux protocol, please submit a Unit tests come together with the Rust code. They can be invoked via `cargo test --release --all`. See the [Getting Started](https://doc.confluxnetwork.org/docs/general/run-a-node/) -page for more information. Integration tests are Python test scripts with the -`_test.py` suffix in the `tests` directory. To run these tests, first compile Conflux -in _release_ mode using `cargo build --release`. Then, you can run all -integration tests using the script `tests/test_all.py`. +page for more information. + +Integration tests are Python test scripts with the `_test.py` suffix in the `tests` directory. +To run these tests, first compile Conflux in _release_ mode using `cargo build --release` and +fetch all submodule using `git submodule update --remote --recursive --init`. +Then, you can run all integration tests using the script `tests/test_all.py`. ## Resources diff --git a/bins/conflux/Cargo.toml b/bins/conflux/Cargo.toml index ce3ba6da1d..daba982097 100644 --- a/bins/conflux/Cargo.toml +++ b/bins/conflux/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "conflux" build = "build.rs" -edition = "2018" +edition = "2021" version.workspace = true authors.workspace = true description.workspace = true diff --git a/bins/conflux/src/command/helpers.rs b/bins/conflux/src/command/helpers.rs index a41a5cef2a..594c8c239c 100644 --- a/bins/conflux/src/command/helpers.rs +++ b/bins/conflux/src/command/helpers.rs @@ -25,8 +25,6 @@ use std::{ io::{self, BufRead, BufReader, Write}, }; -pub use dir::helpers::{replace_home, replace_home_and_local}; - const PASSWORD_STDIN_ERROR: &str = "Unable to ask for password on non-interactive terminal."; diff --git a/changelogs/JSONRPC.md b/changelogs/JSONRPC.md index f8221e6863..faa0628c5b 100644 --- a/changelogs/JSONRPC.md +++ b/changelogs/JSONRPC.md @@ -1,5 +1,78 @@ # JSON-RPC CHANGELOG +## vNext + +This RPC upgrade is primarily to support Conflux 1559 transactions, with the main changes as follows: + +### eSpace + +#### New RPC + +- `eth_maxPriorityFeePerGas`: Returns an estimated `maxPriorityFeePerGas` value based on on-chain data. +- `eth_feeHistory`: Returns an array containing the `baseFeePerGas` values of several consecutive blocks, with the block range specifiable by parameters. + +#### RPC Updates + +Transaction adds fields: + +- `type`: 0 - Legacy tx 1 - 2930 tx 2 - 1559 tx +- 2930 tx adds fields: `accessList`, `yParity` +- 1559 tx adds fields: `accessList`, `yParity`, `maxPriorityFeePerGas`, `maxFeePerGas` + +Receipt adds fields: + +- `type`: 0/1/2 +- `burntGasFee` +- `effectiveGasPrice` + +Block adds fields: + +- `baseFeePerGas` + +`eth_call`, `eth_estimate` Request adds fields: + +- `type` +- `accessList` +- `maxPriorityPerGas` +- `maxFeePerGas` + +### Core Space + +#### New RPC + +- `cfx_maxPriorityFeePerGas`: Returns an estimated `maxPriorityFeePerGas` value based on on-chain data. +- `cfx_feeHistory`: Returns an array containing the `baseFeePerGas` values of several consecutive blocks, with the block range specifiable by parameters. +- `cfx_getFeeBurnt`: Returns the total amount of gas fees burned historically by 1559. + +#### RPC Updates + +Transaction adds fields: + +- `type`: 0 - Legacy tx 1 - 2930 tx 2 - 1559 tx +- 2930 tx adds fields: `accessList`, `yParity` +- 1559 tx adds fields: `accessList`, `yParity`, `maxPriorityFeePerGas`, `maxFeePerGas` + +Receipt adds fields: + +- `type`: 0/1/2 +- `burntGasFee` +- `effectiveGasPrice` + +Block adds fields: + +- `baseFeePerGas` + +`cfx_call`, `cfx_estimate` Request adds fields: + +- `type` +- `accessList` +- `maxPriorityPerGas` +- `maxFeePerGas` + +`cfx_getParamsFromVote` result adds fields: + +- `baseFeeShareProp` + ## v2.3.1 - Return `storagePointProp` in cfx_getParamsFromVote, which is introduced by [CIP-107](https://github.com/Conflux-Chain/CIPs/blob/master/CIPs/cip-107.md#the-voting-of-proportion). diff --git a/crates/accounts/Cargo.toml b/crates/accounts/Cargo.toml index 484fa2e865..4667293273 100644 --- a/crates/accounts/Cargo.toml +++ b/crates/accounts/Cargo.toml @@ -5,7 +5,7 @@ license = "GPL-3.0" name = "cfxcore-accounts" version = "0.1.0" authors = ["Conflux Foundation"] -edition = "2018" +edition = "2021" [dependencies] cfxkey = { path = "../cfx_key" } diff --git a/crates/blockgen/Cargo.toml b/crates/blockgen/Cargo.toml index dcc8811a44..ff0dab5b3e 100644 --- a/crates/blockgen/Cargo.toml +++ b/crates/blockgen/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "blockgen" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] clap = "2" diff --git a/crates/blockgen/src/lib.rs b/crates/blockgen/src/lib.rs index a804d366f4..35b27a0616 100644 --- a/crates/blockgen/src/lib.rs +++ b/crates/blockgen/src/lib.rs @@ -7,8 +7,10 @@ use crate::miner::{ stratum::{Options as StratumOption, Stratum}, work_notify::NotifyWork, }; -use cfx_parameters::consensus::GENESIS_GAS_LIMIT; -use cfx_types::{Address, H256, U256}; +use cfx_parameters::{ + consensus::GENESIS_GAS_LIMIT, consensus_internal::ELASTICITY_MULTIPLIER, +}; +use cfx_types::{Address, SpaceMap, H256, U256}; use cfxcore::{ block_parameters::*, consensus::{consensus_inner::StateBlameInfo, pos_handler::PosVerifier}, @@ -202,6 +204,7 @@ impl BlockGenerator { mut blame_info: StateBlameInfo, block_gas_limit: U256, transactions: Vec>, difficulty: u64, adaptive_opt: Option, maybe_pos_reference: Option, + maybe_base_price: Option>, ) -> Block { trace!("{} txs packed", transactions.len()); let consensus_graph = self.consensus_graph(); @@ -279,6 +282,7 @@ impl BlockGenerator { .with_gas_limit(block_gas_limit) .with_custom(custom) .with_pos_reference(maybe_pos_reference) + .with_base_price(maybe_base_price) .build(); Block::new(block_header, transactions) @@ -288,7 +292,7 @@ impl BlockGenerator { /// only pub fn assemble_new_fixed_block( &self, parent_hash: H256, referee: Vec, num_txs: usize, - difficulty: u64, adaptive: bool, block_gas_limit: u64, + difficulty: u64, adaptive: bool, block_gas_target: u64, pos_reference: Option, ) -> Result { let consensus_graph = self.consensus_graph(); @@ -297,19 +301,55 @@ impl BlockGenerator { &parent_hash, )?; - let block_gas_limit = block_gas_limit.into(); let block_size_limit = self.graph.verification_config.max_block_size_in_bytes; let best_info = consensus_graph.best_info(); - let transactions = self.txpool.pack_transactions( - num_txs, - block_gas_limit, - U256::zero(), - block_size_limit, - best_info.best_epoch_number, - best_info.best_block_number, - ); + let parent_block = self + .txpool + .data_man + .block_header_by_hash(&best_info.best_block_hash) + // The parent block must exists. + .expect("Parent block not found"); + + let machine = self.txpool.machine(); + let params = machine.params(); + let cip1559_height = params.transition_heights.cip1559; + let pack_height = best_info.best_epoch_number + 1; + + let block_gas_limit = if pack_height >= cip1559_height { + (block_gas_target * ELASTICITY_MULTIPLIER as u64).into() + } else { + block_gas_target.into() + }; + + let (transactions, maybe_base_price) = if pack_height < cip1559_height { + let txs = self.txpool.pack_transactions( + num_txs, + block_gas_limit, + U256::zero(), + block_size_limit, + best_info.best_epoch_number, + best_info.best_block_number, + ); + (txs, None) + } else { + let parent_base_price = if cip1559_height == pack_height { + params.init_base_price() + } else { + parent_block.base_price().unwrap() + }; + + let (txs, base_price) = self.txpool.pack_transactions_1559( + num_txs, + block_gas_limit, + parent_base_price, + block_size_limit, + best_info.best_epoch_number, + best_info.best_block_number, + ); + (txs, Some(base_price)) + }; Ok(self.assemble_new_block_impl( parent_hash, @@ -320,6 +360,7 @@ impl BlockGenerator { difficulty, Some(adaptive), pos_reference.or_else(|| self.get_pos_reference(&parent_hash)), + maybe_base_price, )) } @@ -330,7 +371,7 @@ impl BlockGenerator { ) -> Block { let consensus_graph = self.consensus_graph(); - let (best_info, block_gas_limit, transactions) = + let (best_info, block_gas_limit, transactions, maybe_base_price) = self.txpool.get_best_info_with_packed_transactions( num_txs, block_size_limit, @@ -377,6 +418,7 @@ impl BlockGenerator { 0, None, maybe_pos_reference, + maybe_base_price, ) } @@ -392,7 +434,7 @@ impl BlockGenerator { ) -> Block { let consensus_graph = self.consensus_graph(); - let (best_info, block_gas_limit, transactions) = + let (best_info, block_gas_limit, transactions, maybe_base_price) = self.txpool.get_best_info_with_packed_transactions( num_txs, block_size_limit, @@ -431,6 +473,7 @@ impl BlockGenerator { 0, None, self.get_pos_reference(&best_block_hash), + maybe_base_price, ) } @@ -524,9 +567,26 @@ impl BlockGenerator { ) -> H256 { let consensus_graph = self.consensus_graph(); // get the best block - let (best_info, block_gas_limit, _) = self + let (best_info, _, _, _) = self .txpool .get_best_info_with_packed_transactions(0, 0, Vec::new()); + + let parent_hash = best_info.best_block_hash; + let maybe_base_price = self + .txpool + .compute_1559_base_price( + &parent_hash, + (GENESIS_GAS_LIMIT * ELASTICITY_MULTIPLIER as u64).into(), + transactions.iter().map(|x| &**x), + ) + .unwrap(); + let block_gas_limit = GENESIS_GAS_LIMIT + * if maybe_base_price.is_some() { + ELASTICITY_MULTIPLIER as u64 + } else { + 1 + }; + let state_blame_info = consensus_graph .get_blame_and_deferred_state_for_generation( &best_info.best_block_hash, @@ -541,11 +601,12 @@ impl BlockGenerator { best_block_hash, referee, state_blame_info, - block_gas_limit, + block_gas_limit.into(), transactions, 0, adaptive, self.get_pos_reference(&best_block_hash), + maybe_base_price, ); self.generate_block_impl(block) @@ -562,15 +623,31 @@ impl BlockGenerator { &parent_hash, )?; + let maybe_base_price = self + .txpool + .compute_1559_base_price( + &parent_hash, + (GENESIS_GAS_LIMIT * ELASTICITY_MULTIPLIER as u64).into(), + transactions.iter().map(|x| &**x), + ) + .expect("Cannot compute base price"); + + let block_gas_limit = if maybe_base_price.is_some() { + GENESIS_GAS_LIMIT * ELASTICITY_MULTIPLIER as u64 + } else { + GENESIS_GAS_LIMIT + }; + let mut block = self.assemble_new_block_impl( parent_hash, referee, state_blame_info, - GENESIS_GAS_LIMIT.into(), + block_gas_limit.into(), transactions, 0, Some(adaptive), self.get_pos_reference(&parent_hash), + maybe_base_price, ); if let Some(custom) = maybe_custom { block.block_header.set_custom(custom); @@ -590,15 +667,31 @@ impl BlockGenerator { &parent_hash, )?; + let maybe_base_price = self + .txpool + .compute_1559_base_price( + &parent_hash, + (GENESIS_GAS_LIMIT * ELASTICITY_MULTIPLIER as u64).into(), + transactions.iter().map(|x| &**x), + ) + .expect("Cannot compute base price"); + + let block_gas_limit = if maybe_base_price.is_some() { + GENESIS_GAS_LIMIT * ELASTICITY_MULTIPLIER as u64 + } else { + GENESIS_GAS_LIMIT + }; + let mut block = self.assemble_new_block_impl( parent_hash, referee, state_blame_info, - GENESIS_GAS_LIMIT.into(), + block_gas_limit.into(), transactions, 0, Some(adaptive), self.get_pos_reference(&parent_hash), + maybe_base_price, ); block.block_header.set_nonce(nonce); block.block_header.set_timestamp(timestamp); diff --git a/crates/blockgen/src/miner/stratum.rs b/crates/blockgen/src/miner/stratum.rs index c4ba98f684..7f348d7c35 100644 --- a/crates/blockgen/src/miner/stratum.rs +++ b/crates/blockgen/src/miner/stratum.rs @@ -100,7 +100,7 @@ impl SubmitPayload { } #[derive(Debug)] -enum PayloadError { +pub enum PayloadError { ArgumentsAmountUnexpected(usize), InvalidNonce(String), InvalidPowHash(String), diff --git a/crates/cfx_key/src/error.rs b/crates/cfx_key/src/error.rs index f9bde1d280..427fdf648e 100644 --- a/crates/cfx_key/src/error.rs +++ b/crates/cfx_key/src/error.rs @@ -27,6 +27,8 @@ pub enum Error { InvalidAddress, /// Invalid EC signature InvalidSignature, + /// Invalid y-parity + InvalidYParity, /// Invalid AES message InvalidMessage, /// IO Error @@ -42,6 +44,7 @@ impl fmt::Display for Error { Error::InvalidPublic => "Invalid public".into(), Error::InvalidAddress => "Invalid address".into(), Error::InvalidSignature => "Invalid EC signature".into(), + Error::InvalidYParity => "Invalid y Parity".into(), Error::InvalidMessage => "Invalid AES message".into(), Error::Io(ref err) => format!("I/O error: {}", err), Error::Custom(ref s) => s.clone(), diff --git a/crates/cfx_store/src/account/mod.rs b/crates/cfx_store/src/account/mod.rs index e6225fd60f..cc718cc6b1 100644 --- a/crates/cfx_store/src/account/mod.rs +++ b/crates/cfx_store/src/account/mod.rs @@ -23,7 +23,7 @@ mod version; pub use self::{ cipher::{Aes128Ctr, Cipher}, crypto::Crypto, - kdf::{Kdf, Pbkdf2, Prf, Scrypt}, + kdf::{Kdf, Pbkdf2, Prf}, safe_account::SafeAccount, version::Version, }; diff --git a/crates/cfx_store/src/json/mod.rs b/crates/cfx_store/src/json/mod.rs index 6e811bb204..196b70a106 100644 --- a/crates/cfx_store/src/json/mod.rs +++ b/crates/cfx_store/src/json/mod.rs @@ -32,7 +32,7 @@ mod version; pub use self::{ bytes::Bytes, cipher::{Aes128Ctr, Cipher, CipherSer, CipherSerParams}, - crypto::{CipherText, Crypto}, + crypto::Crypto, error::Error, hash::{H128, H160, H256}, id::Uuid, diff --git a/crates/cfx_types/src/lib.rs b/crates/cfx_types/src/lib.rs index 3a9ae689d8..f18636caf0 100644 --- a/crates/cfx_types/src/lib.rs +++ b/crates/cfx_types/src/lib.rs @@ -8,6 +8,8 @@ extern crate rlp_derive; extern crate serde; extern crate serde_derive; +use std::ops::{Add, Index, IndexMut}; + pub use ethereum_types::{ Address, BigEndianHash, Bloom, BloomInput, Public, Secret, Signature, H128, H160, H256, H512, H520, H64, U128, U256, U512, U64, @@ -119,14 +121,14 @@ impl AddressWithSpace { pub fn assert_native(&self) { assert_eq!(self.space, Space::Native) } } -#[derive(Default, Clone)] +#[derive(Default, Clone, Copy, PartialEq, Eq, Debug)] pub struct SpaceMap { native: T, evm: T, } impl SpaceMap { - pub fn new(native: T, evm: T) -> Self { SpaceMap { native, evm } } + pub const fn new(native: T, evm: T) -> Self { SpaceMap { native, evm } } #[inline] pub fn in_space(&self, space: Space) -> &T { @@ -144,12 +146,39 @@ impl SpaceMap { } } - pub fn map_sum usize>(&self, mut f: F) -> usize { + pub fn zip3( + a: SpaceMap, b: SpaceMap, c: SpaceMap, + ) -> SpaceMap<(T, B, C)> { + SpaceMap { + native: (a.native, b.native, c.native), + evm: (a.evm, b.evm, c.evm), + } + } + + pub fn zip4( + a: SpaceMap, b: SpaceMap, c: SpaceMap, d: SpaceMap, + ) -> SpaceMap<(T, B, C, D)> { + SpaceMap { + native: (a.native, b.native, c.native, d.native), + evm: (a.evm, b.evm, c.evm, d.evm), + } + } + + pub fn map_sum U, U: Add>( + &self, mut f: F, + ) -> U { f(&self.native) + f(&self.evm) } pub const fn size(&self) -> usize { 2 } + pub fn map_all U>(self, f: F) -> SpaceMap { + SpaceMap { + native: f(self.native), + evm: f(self.evm), + } + } + pub fn apply_all U>( &mut self, mut f: F, ) -> SpaceMap { @@ -160,6 +189,18 @@ impl SpaceMap { } } +impl Index for SpaceMap { + type Output = T; + + fn index(&self, space: Space) -> &Self::Output { self.in_space(space) } +} + +impl IndexMut for SpaceMap { + fn index_mut(&mut self, space: Space) -> &mut Self::Output { + self.in_space_mut(space) + } +} + impl serde::Serialize for SpaceMap { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { diff --git a/crates/cfx_utils/Cargo.toml b/crates/cfx_utils/Cargo.toml index 8df8343618..9a25935810 100644 --- a/crates/cfx_utils/Cargo.toml +++ b/crates/cfx_utils/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-utils" version = "0.6.0" -edition = "2018" +edition = "2021" [dependencies] log = "0.4" diff --git a/crates/cfxcore/core/Cargo.toml b/crates/cfxcore/core/Cargo.toml index a096024147..dd7f1639c3 100644 --- a/crates/cfxcore/core/Cargo.toml +++ b/crates/cfxcore/core/Cargo.toml @@ -3,8 +3,8 @@ description = "Conflux core library" homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfxcore" -version = "2.3.5" -edition = "2018" +version = "2.4.0" +edition = "2021" [dependencies] bit-set = "0.4" @@ -137,6 +137,8 @@ impl-trait-for-tuples = "^0.2" impl-tools = "^0.10" treap-map = {path = "../../util/treap-map" } cfx-packing-pool = { path = "../packing-pool" } +alloy-rpc-types-trace = { workspace = true } +geth-tracer = { path = "../geth-tracer" } [dev-dependencies] diff --git a/crates/cfxcore/core/src/client/chain_notify.rs b/crates/cfxcore/core/src/client/chain_notify.rs index cd53a4e1cf..136ca103cd 100644 --- a/crates/cfxcore/core/src/client/chain_notify.rs +++ b/crates/cfxcore/core/src/client/chain_notify.rs @@ -22,6 +22,7 @@ impl NewBlocks { pub fn new() -> NewBlocks { NewBlocks {} } } +#[allow(unused)] pub trait ChainNotify: Send + Sync { /// fires when chain has new blocks. fn new_blocks(&self, _new_blocks: NewBlocks) { diff --git a/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/epoch_execution.rs b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/epoch_execution.rs new file mode 100644 index 0000000000..7bc1ad0a6f --- /dev/null +++ b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/epoch_execution.rs @@ -0,0 +1,658 @@ +use super::ConsensusExecutionHandler; +use std::{convert::From, sync::Arc}; + +use alloy_rpc_types_trace::geth::GethDebugTracingOptions; +use geth_tracer::{GethTraceWithHash, GethTracer, TxExecContext}; +use pow_types::StakingEvent; + +use cfx_statedb::{ErrorKind as DbErrorKind, Result as DbResult}; +use cfx_types::{Space, SpaceMap, H256, U256}; +use primitives::{ + receipt::BlockReceipts, Action, Block, BlockNumber, EpochId, Receipt, + SignedTransaction, TransactionIndex, +}; + +use crate::{ + block_data_manager::BlockDataManager, + consensus::consensus_inner::consensus_executor::GOOD_TPS_METER, + state_prefetcher::{prefetch_accounts, PrefetchTaskHandle}, +}; +use cfx_execute_helper::{ + exec_tracer::TransactionExecTraces, + observer::Observer, + tx_outcome::{make_process_tx_outcome, ProcessTxOutcome}, +}; +use cfx_executor::{ + executive::{ExecutiveContext, TransactOptions, TransactSettings}, + internal_contract::{ + block_hash_slot, epoch_hash_slot, initialize_internal_contract_accounts, + }, + state::{ + initialize_cip107, initialize_cip137, + initialize_or_update_dao_voted_params, State, + }, +}; +use cfx_vm_types::Env; + +pub enum VirtualCall<'a> { + GethTrace(GethTask<'a>), +} + +pub struct GethTask<'a> { + pub(super) tx_hash: Option, + pub(super) opts: GethDebugTracingOptions, + pub(super) answer: &'a mut Vec, +} + +impl ConsensusExecutionHandler { + pub(super) fn process_epoch_transactions<'a>( + &self, epoch_id: EpochId, state: &mut State, + epoch_blocks: &Vec>, start_block_number: u64, + on_local_pivot: bool, virtual_call: Option>, + ) -> DbResult>> { + self.prefetch_storage_for_execution(epoch_id, state, epoch_blocks); + + let pivot_block = epoch_blocks.last().expect("Epoch not empty"); + + let dry_run = virtual_call.is_some(); + + self.before_epoch_execution(state, &*pivot_block)?; + + let base_gas_price = + pivot_block.block_header.base_price().unwrap_or_default(); + + let burnt_gas_price = + base_gas_price.map_all(|x| state.burnt_gas_price(x)); + let context = EpochProcessContext { + on_local_pivot, + executive_trace: self.config.executive_trace, + dry_run, + virtual_call, + pivot_block, + base_gas_price, + burnt_gas_price, + }; + + let mut epoch_recorder = EpochProcessRecorder::new(); + + let mut block_context = BlockProcessContext::first_block( + &context, + epoch_blocks.first().unwrap(), + start_block_number, + ); + + for (idx, block) in epoch_blocks.iter().enumerate() { + if idx > 0 { + block_context.next_block(block); + } + + self.process_block_transactions( + &block_context, + state, + &mut epoch_recorder, + )?; + } + + if let Some(VirtualCall::GethTrace(task)) = context.virtual_call { + std::mem::swap(&mut epoch_recorder.geth_traces, task.answer); + } + + if !dry_run && self.pos_verifier.pos_option().is_some() { + debug!( + "put_staking_events: {:?} height={} len={}", + pivot_block.hash(), + pivot_block.block_header.height(), + epoch_recorder.staking_events.len() + ); + self.pos_verifier + .consensus_db() + .put_staking_events( + pivot_block.block_header.height(), + pivot_block.hash(), + epoch_recorder.staking_events, + ) + .map_err(|e| { + cfx_statedb::Error::from(DbErrorKind::PosDatabaseError( + format!("{:?}", e), + )) + })?; + } + + if !dry_run && on_local_pivot { + self.tx_pool.recycle_transactions(epoch_recorder.repack_tx); + } + + debug!("Finish processing tx for epoch"); + Ok(epoch_recorder.receipts) + } + + fn prefetch_storage_for_execution( + &self, epoch_id: EpochId, state: &mut State, + epoch_blocks: &Vec>, + ) { + // Prefetch accounts for transactions. + // The return value _prefetch_join_handles is used to join all threads + // before the exit of this function. + let prefetch_join_handles = match self + .execution_state_prefetcher + .as_ref() + { + Some(prefetcher) => { + let mut accounts = vec![]; + for block in epoch_blocks.iter() { + for transaction in block.transactions.iter() { + accounts.push(&transaction.sender); + match transaction.action() { + Action::Call(ref address) => accounts.push(address), + _ => {} + } + } + } + + prefetch_accounts(prefetcher, epoch_id, state, accounts) + } + None => PrefetchTaskHandle { + task_epoch_id: epoch_id, + state, + prefetcher: None, + accounts: vec![], + }, + }; + + // TODO: + // Make the state shared ref for vm execution, then remove this drop. + // When the state can be made shared, prefetch can happen at the same + // time of the execution, the vm execution do not have to wait + // for prefetching to finish. + prefetch_join_handles.wait_for_task(); + drop(prefetch_join_handles); + } + + fn make_block_env(&self, block_context: &BlockProcessContext) -> Env { + let BlockProcessContext { + epoch_context: + &EpochProcessContext { + pivot_block, + base_gas_price, + burnt_gas_price, + .. + }, + block, + block_number, + last_hash, + } = *block_context; + + let last_block_header = &self.data_man.block_header_by_hash(&last_hash); + + let pos_id = last_block_header + .as_ref() + .and_then(|header| header.pos_reference().as_ref()); + let pos_view_number = + pos_id.and_then(|id| self.pos_verifier.get_pos_view(id)); + let pivot_decision_epoch = pos_id + .and_then(|id| self.pos_verifier.get_pivot_decision(id)) + .and_then(|hash| self.data_man.block_header_by_hash(&hash)) + .map(|header| header.height()); + + let epoch_height = pivot_block.block_header.height(); + let chain_id = self.machine.params().chain_id_map(epoch_height); + Env { + chain_id, + number: block_number, + author: block.block_header.author().clone(), + timestamp: pivot_block.block_header.timestamp(), + difficulty: block.block_header.difficulty().clone(), + accumulated_gas_used: U256::zero(), + last_hash, + gas_limit: U256::from(block.block_header.gas_limit()), + epoch_height, + pos_view: pos_view_number, + finalized_epoch: pivot_decision_epoch, + transaction_epoch_bound: self + .verification_config + .transaction_epoch_bound, + base_gas_price, + burnt_gas_price, + } + } + + fn process_block_transactions( + &self, block_context: &BlockProcessContext, state: &mut State, + epoch_recorder: &mut EpochProcessRecorder, + ) -> DbResult<()> { + let BlockProcessContext { + epoch_context: &EpochProcessContext { on_local_pivot, .. }, + block, + block_number, + .. + } = *block_context; + + debug!( + "process txs in block: hash={:?}, tx count={:?}", + block.hash(), + block.transactions.len() + ); + + // TODO: ideally, this function should not have return value. + // However, the previous implementation read `secondary_reward` in an + // intermediate step. Since we are not sure which steps will influnce + // `secondary_reward`, we must `secondary_reward` at the same point to + // keep the backward compatible. + let secondary_reward = + self.before_block_execution(state, block_number, block)?; + + let mut env = self.make_block_env(block_context); + + let mut block_recorder = + BlockProcessRecorder::new(epoch_recorder.evm_tx_idx); + + for (idx, transaction) in block.transactions.iter().enumerate() { + self.process_transaction( + idx, + transaction, + block_context, + state, + &mut env, + on_local_pivot, + &mut block_recorder, + )?; + } + + block_recorder.finish_block( + &self.data_man, + epoch_recorder, + block_context, + secondary_reward, + ); + + Ok(()) + } + + fn process_transaction( + &self, idx: usize, transaction: &Arc, + block_context: &BlockProcessContext, state: &mut State, env: &mut Env, + on_local_pivot: bool, recorder: &mut BlockProcessRecorder, + ) -> DbResult<()> { + let rpc_index = recorder.tx_idx[transaction.space()]; + + let block = &block_context.block; + let dry_run = block_context.epoch_context.dry_run; + + let machine = self.machine.as_ref(); + + let spec = machine.spec(env.number, env.epoch_height); + + let options = TransactOptions { + observer: self.make_observer(transaction, block_context), + settings: TransactSettings::all_checks(), + }; + + let execution_outcome = + ExecutiveContext::new(state, env, machine, &spec) + .transact(transaction, options)?; + execution_outcome.log(transaction, &block_context.block.hash()); + + if let Some(burnt_fee) = execution_outcome + .try_as_executed() + .and_then(|e| e.burnt_fee) + { + state.burn_by_cip1559(burnt_fee); + }; + + let r = make_process_tx_outcome( + execution_outcome, + &mut env.accumulated_gas_used, + transaction.hash, + &spec, + ); + + if r.receipt.tx_success() { + GOOD_TPS_METER.mark(1); + } + + let tx_skipped = r.receipt.tx_skipped(); + let phantom_txs = r.phantom_txs.clone(); + + recorder.receive_tx_outcome(r, transaction, block_context); + + if !on_local_pivot || tx_skipped || dry_run { + // Skip transaction index persist + return Ok(()); + } + + let hash = transaction.hash(); + + self.data_man.insert_transaction_index( + &hash, + &TransactionIndex { + block_hash: block.hash(), + real_index: idx, + is_phantom: false, + rpc_index: Some(rpc_index), + }, + ); + + // persist tx index for phantom transactions. + // note: in some cases, pivot chain reorgs will result in + // different phantom txs (with different hashes) for the + // same Conflux space tx. we do not remove invalidated + // hashes here, but leave it up to the RPC layer to handle + // this instead. + let evm_chain_id = env.chain_id[&Space::Ethereum]; + let evm_tx_index = &mut recorder.tx_idx[Space::Ethereum]; + + for ptx in phantom_txs { + self.data_man.insert_transaction_index( + &ptx.into_eip155(evm_chain_id).hash(), + &TransactionIndex { + block_hash: block.hash(), + real_index: idx, + is_phantom: true, + rpc_index: Some(*evm_tx_index), + }, + ); + + *evm_tx_index += 1; + } + + Ok(()) + } + + fn make_observer( + &self, transaction: &Arc, + block_context: &BlockProcessContext, + ) -> Observer { + use alloy_rpc_types_trace::geth::{ + GethDebugBuiltInTracerType::*, GethDebugTracerType::BuiltInTracer, + }; + + let mut observer = if self.config.executive_trace { + Observer::with_tracing() + } else { + Observer::with_no_tracing() + }; + + if let Some(VirtualCall::GethTrace(ref task)) = + block_context.epoch_context.virtual_call + { + let need_trace = + task.tx_hash.map_or(true, |hash| transaction.hash() == hash); + let support_tracer = matches!( + task.opts.tracer, + Some(BuiltInTracer( + FourByteTracer | CallTracer | PreStateTracer | NoopTracer + )) | None + ); + let tx_gas_limit = transaction.gas_limit().as_u64(); + + if need_trace && support_tracer { + observer.geth_tracer = Some(GethTracer::new( + TxExecContext { + tx_gas_limit, + block_height: block_context + .epoch_context + .pivot_block + .block_header + .height(), + block_number: block_context.block_number, + }, + Arc::clone(&self.machine), + task.opts.clone(), + )) + } + } + observer + } + + fn before_epoch_execution( + &self, state: &mut State, pivot_block: &Block, + ) -> DbResult<()> { + let params = self.machine.params(); + + let epoch_number = pivot_block.block_header.height(); + let hash = pivot_block.hash(); + + if epoch_number >= params.transition_heights.cip133e { + state.set_system_storage( + epoch_hash_slot(epoch_number).into(), + U256::from_big_endian(&hash.0), + )?; + } + Ok(()) + } + + pub fn before_block_execution( + &self, state: &mut State, block_number: BlockNumber, block: &Block, + ) -> DbResult { + let params = self.machine.params(); + let transition_numbers = ¶ms.transition_numbers; + + let cip94_start = transition_numbers.cip94n; + let period = params.params_dao_vote_period; + // Update/initialize parameters before processing rewards. + if block_number >= cip94_start + && (block_number - cip94_start) % period == 0 + { + let set_pos_staking = block_number > transition_numbers.cip105; + initialize_or_update_dao_voted_params(state, set_pos_staking)?; + } + + // Initialize old_storage_point_prop_ratio in the state. + // The time may not be in the vote period boundary, so this is not + // integrated with `initialize_or_update_dao_voted_params`, but + // that function will update the value after cip107 is enabled + // here. + if block_number == transition_numbers.cip107 { + initialize_cip107(state)?; + } + + if block_number >= transition_numbers.cip133b { + state.set_system_storage( + block_hash_slot(block_number).into(), + U256::from_big_endian(&block.hash().0), + )?; + } + + if block_number == transition_numbers.cip137 { + initialize_cip137(state); + } + + if block_number < transition_numbers.cip43a { + state.bump_block_number_accumulate_interest(); + } + + let secondary_reward = state.secondary_reward(); + + state.inc_distributable_pos_interest(block_number)?; + + initialize_internal_contract_accounts( + state, + self.machine + .internal_contracts() + .initialized_at(block_number), + )?; + + Ok(secondary_reward) + } +} + +struct EpochProcessContext<'a> { + on_local_pivot: bool, + executive_trace: bool, + virtual_call: Option>, + dry_run: bool, + + pivot_block: &'a Block, + + base_gas_price: SpaceMap, + burnt_gas_price: SpaceMap, +} + +struct BlockProcessContext<'a, 'b> { + epoch_context: &'b EpochProcessContext<'a>, + block: &'b Block, + block_number: u64, + last_hash: H256, +} + +impl<'a, 'b> BlockProcessContext<'a, 'b> { + fn first_block( + epoch_context: &'b EpochProcessContext<'a>, block: &'b Block, + start_block_number: u64, + ) -> Self { + let EpochProcessContext { pivot_block, .. } = *epoch_context; + let last_hash = *pivot_block.block_header.parent_hash(); + Self { + epoch_context, + block, + block_number: start_block_number, + last_hash, + } + } + + fn next_block(&mut self, block: &'b Block) { + self.last_hash = self.block.hash(); + self.block_number += 1; + self.block = block; + } +} + +#[derive(Default)] +struct EpochProcessRecorder { + receipts: Vec>, + staking_events: Vec, + repack_tx: Vec>, + geth_traces: Vec, + + evm_tx_idx: usize, +} + +impl EpochProcessRecorder { + fn new() -> Self { Default::default() } +} + +struct BlockProcessRecorder { + receipt: Vec, + tx_error_msg: Vec, + traces: Vec, + geth_traces: Vec, + repack_tx: Vec>, + staking_events: Vec, + + tx_idx: SpaceMap, +} + +impl BlockProcessRecorder { + fn new(evm_tx_idx: usize) -> BlockProcessRecorder { + let mut tx_idx = SpaceMap::default(); + tx_idx[Space::Ethereum] = evm_tx_idx; + Self { + receipt: vec![], + tx_error_msg: vec![], + traces: vec![], + geth_traces: vec![], + repack_tx: vec![], + staking_events: vec![], + tx_idx, + } + } + + fn receive_tx_outcome( + &mut self, r: ProcessTxOutcome, tx: &Arc, + block_context: &BlockProcessContext, + ) { + let EpochProcessContext { + on_local_pivot, + executive_trace, + .. + } = *block_context.epoch_context; + + if on_local_pivot && r.consider_repacked { + self.repack_tx.push(tx.clone()) + } + + let not_skipped = !r.receipt.tx_skipped(); + + if executive_trace { + self.traces.push(r.tx_traces.into()); + } + + self.receipt.push(r.receipt); + self.tx_error_msg.push(r.tx_exec_error_msg); + self.staking_events.extend(r.tx_staking_events); + + if let Some(trace) = r.geth_trace { + self.geth_traces.push(GethTraceWithHash { + trace, + tx_hash: tx.hash(), + space: tx.space(), + }); + } + + match tx.space() { + Space::Native => { + self.tx_idx[Space::Native] += 1; + } + Space::Ethereum if not_skipped => { + self.tx_idx[Space::Ethereum] += 1; + } + _ => {} + }; + } + + fn finish_block( + self, data_man: &BlockDataManager, + epoch_recorder: &mut EpochProcessRecorder, + block_context: &BlockProcessContext, secondary_reward: U256, + ) { + let BlockProcessContext { + epoch_context: + &EpochProcessContext { + on_local_pivot, + executive_trace, + pivot_block, + dry_run, + .. + }, + block, + block_number, + .. + } = *block_context; + + let block_receipts = Arc::new(BlockReceipts { + receipts: self.receipt, + // An existing bug makes the block_number is one larger than the + // actual. + block_number: block_number + 1, + secondary_reward, + tx_execution_error_messages: self.tx_error_msg, + }); + + epoch_recorder.receipts.push(block_receipts.clone()); + epoch_recorder.staking_events.extend(self.staking_events); + epoch_recorder.repack_tx.extend(self.repack_tx); + epoch_recorder.geth_traces.extend(self.geth_traces); + + epoch_recorder.evm_tx_idx = self.tx_idx[Space::Ethereum]; + + if dry_run { + return; + } + + if executive_trace { + data_man.insert_block_traces( + block.hash(), + self.traces.into(), + pivot_block.hash(), + on_local_pivot, + ); + } + + data_man.insert_block_execution_result( + block.hash(), + pivot_block.hash(), + block_receipts.clone(), + on_local_pivot, + ); + } +} diff --git a/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor.rs b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs similarity index 77% rename from crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor.rs rename to crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs index d913aff919..fa2058c594 100644 --- a/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor.rs +++ b/crates/cfxcore/core/src/consensus/consensus_inner/consensus_executor/mod.rs @@ -2,6 +2,8 @@ // Conflux is free software and distributed under GNU General Public License. // See http://www.gnu.org/licenses/ +mod epoch_execution; + use core::convert::TryFrom; use std::{ collections::{BTreeMap, BTreeSet, HashMap}, @@ -23,7 +25,7 @@ use cfx_internal_common::{ debug::*, EpochExecutionCommitment, StateRootWithAuxInfo, }; use cfx_parameters::consensus::*; -use cfx_statedb::{ErrorKind as DbErrorKind, Result as DbResult, StateDb}; +use cfx_statedb::{Result as DbResult, StateDb}; use cfx_storage::{ defaults::DEFAULT_EXECUTION_PREFETCH_THREADS, StateIndex, StorageManagerTrait, @@ -34,9 +36,8 @@ use cfx_types::{ }; use metrics::{register_meter_with_group, Meter, MeterTimer}; use primitives::{ - compute_block_number, receipt::BlockReceipts, Action, Block, - BlockHeaderBuilder, BlockNumber, EpochId, SignedTransaction, - TransactionIndex, MERKLE_NULL_NODE, + compute_block_number, receipt::BlockReceipts, Block, BlockHeader, + BlockHeaderBuilder, SignedTransaction, MERKLE_NULL_NODE, }; use crate::{ @@ -50,34 +51,30 @@ use crate::{ ConsensusGraphInner, }, rpc_errors::{invalid_params_check, Result as RpcResult}, - state_prefetcher::{ - prefetch_accounts, ExecutionStatePrefetcher, PrefetchTaskHandle, - }, + state_prefetcher::ExecutionStatePrefetcher, verification::{ compute_receipts_root, VerificationConfig, VerifyTxLocalMode, VerifyTxMode, }, SharedTransactionPool, }; -use cfx_execute_helper::{ - estimation::{EstimateExt, EstimateRequest, EstimationContext}, - exec_tracer::TransactionExecTraces, - observer::Observer, - tx_outcome::make_process_tx_outcome, +use cfx_execute_helper::estimation::{ + EstimateExt, EstimateRequest, EstimationContext, }; use cfx_executor::{ - executive::{ - ExecutionOutcome, ExecutiveContext, TransactOptions, TransactSettings, - }, - internal_contract::initialize_internal_contract_accounts, + executive::ExecutionOutcome, machine::Machine, state::{ - distribute_pos_interest, initialize_cip107, - initialize_or_update_dao_voted_params, update_pos_status, CleanupMode, - State, + distribute_pos_interest, update_pos_status, CleanupMode, State, + StateCommitResult, }, }; use cfx_vm_types::{Env, Spec}; +use geth_tracer::GethTraceWithHash; + +use alloy_rpc_types_trace::geth::GethDebugTracingOptions; + +use self::epoch_execution::{GethTask, VirtualCall}; lazy_static! { static ref CONSENSIS_EXECUTION_TIMER: Arc = @@ -616,13 +613,14 @@ impl ConsensusExecutor { debug_record: Option<&mut ComputeEpochDebugRecord>, recover_mpt_during_construct_pivot_state: bool, ) { - if !self.consensus_graph_bench_mode { - self.handler.handle_epoch_execution( - task, - debug_record, - recover_mpt_during_construct_pivot_state, - ) + if self.consensus_graph_bench_mode { + return; } + self.handler.handle_epoch_execution( + task, + debug_record, + recover_mpt_during_construct_pivot_state, + ) } pub fn epoch_executed_and_recovered( @@ -646,6 +644,14 @@ impl ConsensusExecutor { self.handler.call_virtual(tx, epoch_id, epoch_size, request) } + pub fn collect_epoch_geth_trace( + &self, epoch_block_hashes: Vec, tx_hash: Option, + opts: GethDebugTracingOptions, + ) -> RpcResult> { + self.handler + .collect_epoch_geth_trace(epoch_block_hashes, tx_hash, opts) + } + pub fn stop(&self) { // `stopped` is used to allow the execution thread to stopped even the // queue is not empty and `ExecutionTask::Stop` has not been @@ -907,6 +913,41 @@ impl ConsensusExecutionHandler { .get_epoch_execution_commitment_with_db(epoch_hash) } + fn new_state( + &self, pivot_block: &Block, + recover_mpt_during_construct_pivot_state: bool, + ) -> DbResult { + let state_root_with_aux_info = &self + .data_man + .get_epoch_execution_commitment( + pivot_block.block_header.parent_hash(), + ) + // Unwrapping is safe because the state exists. + .unwrap() + .state_root_with_aux_info; + + let state_index = StateIndex::new_for_next_epoch( + pivot_block.block_header.parent_hash(), + &state_root_with_aux_info, + pivot_block.block_header.height() - 1, + self.data_man.get_snapshot_epoch_count(), + ); + + let storage = self + .data_man + .storage_manager + .get_state_for_next_epoch( + state_index, + recover_mpt_during_construct_pivot_state, + ) + .expect("No db error") + // Unwrapping is safe because the state exists. + .expect("State exists"); + + let state_db = StateDb::new(storage); + State::new(state_db) + } + pub fn epoch_executed_and_recovered( &self, epoch_hash: &H256, epoch_block_hashes: &Vec, on_local_pivot: bool, @@ -961,10 +1002,10 @@ impl ConsensusExecutionHandler { // execution is skipped. when `compute_epoch` is called, it is // guaranteed that `epoch_hash` is on the current pivot chain. for (index, hash) in epoch_block_hashes.iter().enumerate() { - self.data_man.insert_hash_by_block_number( - compute_block_number(start_block_number, index as u64), - hash, - ); + let block_number = + compute_block_number(start_block_number, index as u64); + self.data_man + .insert_hash_by_block_number(block_number, hash); } let pivot_block_header = self @@ -972,7 +1013,7 @@ impl ConsensusExecutionHandler { .block_header_by_hash(epoch_hash) .expect("must exists"); - // Check if the state has been computed + // Check if epoch is computed if !force_recompute && debug_record.is_none() && self.epoch_executed_and_recovered( @@ -983,43 +1024,11 @@ impl ConsensusExecutionHandler { pivot_block_header.height(), ) { - if on_local_pivot { - // Unwrap is safe here because it's guaranteed by outer if. - let state_root = &self - .data_man - .get_epoch_execution_commitment(epoch_hash) - .unwrap() - .state_root_with_aux_info; - // When the state have expired, don't inform TransactionPool. - // TransactionPool doesn't require a precise best_executed_state - // when pivot chain oscillates. - if self - .data_man - .state_availability_boundary - .read() - .check_availability(pivot_block_header.height(), epoch_hash) - { - self.tx_pool - .set_best_executed_epoch(StateIndex::new_for_readonly( - epoch_hash, - &state_root, - )) - // FIXME: propogate error. - .expect(&concat!( - file!(), - ":", - line!(), - ":", - column!() - )); - } - } - self.data_man - .state_availability_boundary - .write() - .adjust_upper_bound(pivot_block_header.as_ref()); - debug!("Skip execution in prefix {:?}", epoch_hash); - + self.update_on_skipped_execution( + epoch_hash, + &pivot_block_header, + on_local_pivot, + ); return; } @@ -1039,30 +1048,9 @@ impl ConsensusExecutionHandler { epoch_blocks.len(), ); - let mut state = State::new(StateDb::new( - self.data_man - .storage_manager - .get_state_for_next_epoch( - StateIndex::new_for_next_epoch( - pivot_block.block_header.parent_hash(), - &self - .data_man - .get_epoch_execution_commitment( - pivot_block.block_header.parent_hash(), - ) - // Unwrapping is safe because the state exists. - .unwrap() - .state_root_with_aux_info, - pivot_block.block_header.height() - 1, - self.data_man.get_snapshot_epoch_count(), - ), - recover_mpt_during_construct_pivot_state, - ) - .expect("No db error") - // Unwrapping is safe because the state exists. - .expect("State exists"), - )) - .expect("Failed to initialize state"); + let mut state = self + .new_state(pivot_block, recover_mpt_during_construct_pivot_state) + .expect("Cannot init state"); let epoch_receipts = self .process_epoch_transactions( @@ -1071,6 +1059,7 @@ impl ConsensusExecutionHandler { &epoch_blocks, start_block_number, on_local_pivot, + /* virtual_call */ None, ) // TODO: maybe propagate the error all the way up so that the // program may restart by itself. @@ -1080,6 +1069,9 @@ impl ConsensusExecutionHandler { start_block_number + epoch_receipts.len() as u64 - 1; if let Some(reward_execution_info) = reward_execution_info { + let spec = self + .machine + .spec(current_block_number, pivot_block.block_header.height()); // Calculate the block reward for blocks inside the epoch // All transaction fees are shared among blocks inside one epoch self.process_rewards_and_fees( @@ -1088,10 +1080,86 @@ impl ConsensusExecutionHandler { epoch_hash, on_local_pivot, debug_record.as_deref_mut(), - self.machine.spec(current_block_number), + spec, ); } + self.process_pos_interest( + &mut state, + pivot_block, + current_block_number, + ) + .expect("db error"); + + let commit_result = state + .commit(*epoch_hash, debug_record.as_deref_mut()) + .expect(&concat!(file!(), ":", line!(), ":", column!())); + + if on_local_pivot { + self.notify_txpool(&commit_result, epoch_hash); + }; + + self.data_man.insert_epoch_execution_commitment( + pivot_block.hash(), + commit_result.state_root.clone(), + compute_receipts_root(&epoch_receipts), + BlockHeaderBuilder::compute_block_logs_bloom_hash(&epoch_receipts), + ); + + let epoch_execution_commitment = self + .data_man + .get_epoch_execution_commitment(&epoch_hash) + .unwrap(); + debug!( + "compute_epoch: on_local_pivot={}, epoch={:?} state_root={:?} receipt_root={:?}, logs_bloom_hash={:?}", + on_local_pivot, epoch_hash, commit_result.state_root, epoch_execution_commitment.receipts_root, epoch_execution_commitment.logs_bloom_hash, + ); + self.data_man + .state_availability_boundary + .write() + .adjust_upper_bound(&pivot_block.block_header); + } + + fn update_on_skipped_execution( + &self, epoch_hash: &H256, pivot_block_header: &BlockHeader, + on_local_pivot: bool, + ) { + if on_local_pivot { + // Unwrap is safe here because it's guaranteed by outer if. + let state_root = &self + .data_man + .get_epoch_execution_commitment(epoch_hash) + .unwrap() + .state_root_with_aux_info; + // When the state have expired, don't inform TransactionPool. + // TransactionPool doesn't require a precise best_executed_state + // when pivot chain oscillates. + if self + .data_man + .state_availability_boundary + .read() + .check_availability(pivot_block_header.height(), epoch_hash) + { + self.tx_pool + .set_best_executed_epoch(StateIndex::new_for_readonly( + epoch_hash, + &state_root, + )) + // FIXME: propogate error. + .expect(&concat!(file!(), ":", line!(), ":", column!())); + } + } + self.data_man + .state_availability_boundary + .write() + .adjust_upper_bound(pivot_block_header); + debug!("Skip execution in prefix {:?}", epoch_hash); + } + + fn process_pos_interest( + &self, state: &mut State, pivot_block: &Block, + current_block_number: u64, + ) -> DbResult<()> { // TODO(peilun): Specify if we unlock before or after executing the // transactions. let maybe_parent_pos_ref = self @@ -1118,8 +1186,7 @@ impl ConsensusExecutionHandler { .get_unlock_nodes(current_pos_ref, parent_pos_ref) { debug!("unlock node: {:?} {}", unlock_node_id, votes); - update_pos_status(&mut state, unlock_node_id, votes) - .expect("db error"); + update_pos_status(state, unlock_node_id, votes)?; } if let Some((pos_epoch, reward_event)) = self .pos_verifier @@ -1130,325 +1197,44 @@ impl ConsensusExecutionHandler { debug!("distribute_pos_interest: {:?}", reward_event); let account_rewards: Vec<(H160, H256, U256)> = distribute_pos_interest( - &mut state, + state, reward_event.rewards(), current_block_number, - ) - .expect("db error"); + )?; self.data_man.insert_pos_reward( *pos_epoch, - &PosRewardInfo::new(account_rewards, *epoch_hash), + &PosRewardInfo::new(account_rewards, pivot_block.hash()), ) } } + Ok(()) + } + fn notify_txpool( + &self, commit_result: &StateCommitResult, epoch_hash: &H256, + ) { // FIXME: We may want to propagate the error up. - let state_root; - if on_local_pivot { - let commit_result = state - .commit(*epoch_hash, debug_record.as_deref_mut()) - .expect(&concat!(file!(), ":", line!(), ":", column!())); - state_root = commit_result.state_root; - let accounts_for_txpool = commit_result.accounts_for_txpool; - { - debug!("Notify epoch[{}]", epoch_hash); - - // TODO: use channel to deliver the message. - let txpool_clone = self.tx_pool.clone(); - std::thread::Builder::new() - .name("txpool_update_state".into()) - .spawn(move || { - txpool_clone - .notify_modified_accounts(accounts_for_txpool); - }) - .expect("can not notify tx pool to start state"); - } - - self.tx_pool - .set_best_executed_epoch(StateIndex::new_for_readonly( - epoch_hash, - &state_root, - )) - .expect(&concat!(file!(), ":", line!(), ":", column!())); - } else { - let commit_result = state - .commit(*epoch_hash, debug_record) - .expect(&concat!(file!(), ":", line!(), ":", column!())); - state_root = commit_result.state_root; - }; - - self.data_man.insert_epoch_execution_commitment( - pivot_block.hash(), - state_root.clone(), - compute_receipts_root(&epoch_receipts), - BlockHeaderBuilder::compute_block_logs_bloom_hash(&epoch_receipts), - ); - let epoch_execution_commitment = self - .data_man - .get_epoch_execution_commitment(&epoch_hash) - .unwrap(); - debug!( - "compute_epoch: on_local_pivot={}, epoch={:?} state_root={:?} receipt_root={:?}, logs_bloom_hash={:?}", - on_local_pivot, epoch_hash, state_root, epoch_execution_commitment.receipts_root, epoch_execution_commitment.logs_bloom_hash, - ); - self.data_man - .state_availability_boundary - .write() - .adjust_upper_bound(&pivot_block.block_header); - } - - fn process_epoch_transactions( - &self, epoch_id: EpochId, state: &mut State, - epoch_blocks: &Vec>, start_block_number: u64, - on_local_pivot: bool, - ) -> DbResult>> { - // Prefetch accounts for transactions. - // The return value _prefetch_join_handles is used to join all threads - // before the exit of this function. - let prefetch_join_handles = match self - .execution_state_prefetcher - .as_ref() + let accounts_for_txpool = commit_result.accounts_for_txpool.clone(); { - Some(prefetcher) => { - let mut accounts = vec![]; - for block in epoch_blocks.iter() { - for transaction in block.transactions.iter() { - accounts.push(&transaction.sender); - match transaction.action() { - Action::Call(ref address) => accounts.push(address), - _ => {} - } - } - } - - prefetch_accounts(prefetcher, epoch_id, state, accounts) - } - None => PrefetchTaskHandle { - task_epoch_id: epoch_id, - state, - prefetcher: None, - accounts: vec![], - }, - }; - // TODO: - // Make the state shared ref for vm execution, then remove this drop. - // When the state can be made shared, prefetch can happen at the same - // time of the execution, the vm execution do not have to wait - // for prefetching to finish. - prefetch_join_handles.wait_for_task(); - drop(prefetch_join_handles); - - let pivot_block = epoch_blocks.last().expect("Epoch not empty"); - - let mut epoch_receipts = Vec::with_capacity(epoch_blocks.len()); - let mut epoch_staking_events = Vec::new(); - let mut to_pending = Vec::new(); - let mut block_number = start_block_number; - let mut last_block_hash = - pivot_block.block_header.parent_hash().clone(); - let last_block_header = - &self.data_man.block_header_by_hash(&last_block_hash); - - let mut evm_tx_index = 0; - - for block in epoch_blocks.iter() { - self.maybe_update_state(state, block_number)?; - let mut cfx_tx_index = 0; - - let mut tx_exec_error_messages = - Vec::with_capacity(block.transactions.len()); - let mut receipts = Vec::new(); - debug!( - "process txs in block: hash={:?}, tx count={:?}", - block.hash(), - block.transactions.len() - ); - - let pos_id = last_block_header - .as_ref() - .and_then(|header| header.pos_reference().as_ref()); - let pos_view_number = - pos_id.and_then(|id| self.pos_verifier.get_pos_view(id)); - let pivot_decision_epoch = pos_id - .and_then(|id| self.pos_verifier.get_pivot_decision(id)) - .and_then(|hash| self.data_man.block_header_by_hash(&hash)) - .map(|header| header.height()); - - let epoch_height = pivot_block.block_header.height(); - let chain_id = self.machine.params().chain_id_map(epoch_height); - let mut env = Env { - chain_id, - number: block_number, - author: block.block_header.author().clone(), - timestamp: pivot_block.block_header.timestamp(), - difficulty: block.block_header.difficulty().clone(), - accumulated_gas_used: U256::zero(), - last_hash: last_block_hash, - gas_limit: U256::from(block.block_header.gas_limit()), - epoch_height, - pos_view: pos_view_number, - finalized_epoch: pivot_decision_epoch, - transaction_epoch_bound: self - .verification_config - .transaction_epoch_bound, - }; - let spec = self.machine.spec(env.number); - if !spec.cip43_contract { - state.bump_block_number_accumulate_interest(); - } - let machine = self.machine.as_ref(); - - let secondary_reward = state.secondary_reward(); - state.inc_distributable_pos_interest(env.number)?; - initialize_internal_contract_accounts( - state, - self.machine.internal_contracts().initialized_at(env.number), - ); - block_number += 1; - - last_block_hash = block.hash(); - let mut block_traces: Vec = - Default::default(); - for (idx, transaction) in block.transactions.iter().enumerate() { - let observer = if self.config.executive_trace { - Observer::with_tracing() - } else { - Observer::with_no_tracing() - }; - let options = TransactOptions { - observer, - settings: TransactSettings::all_checks(), - }; - let execution_outcome = - ExecutiveContext::new(state, &env, machine, &spec) - .transact(transaction, options)?; - execution_outcome.log(transaction, &block.hash()); - let r = make_process_tx_outcome( - execution_outcome, - &mut env.accumulated_gas_used, - transaction.hash, - ); - - if r.receipt.tx_success() { - GOOD_TPS_METER.mark(1); - } - - if on_local_pivot && r.consider_repacked { - to_pending.push(transaction.clone()) - } - - let not_skipped = !r.receipt.tx_skipped(); - - if self.config.executive_trace { - block_traces.push(r.tx_traces.into()); - } - - receipts.push(r.receipt); - tx_exec_error_messages.push(r.tx_exec_error_msg); - epoch_staking_events.extend(r.tx_staking_events); - - let get_and_bump = |index: &mut usize| { - let output = *index; - *index += 1; - output - }; - let rpc_index = match transaction.space() { - Space::Native => get_and_bump(&mut cfx_tx_index), - Space::Ethereum if not_skipped => { - get_and_bump(&mut evm_tx_index) - } - _ => usize::MAX, // this will not be used - }; - - if on_local_pivot && not_skipped { - let hash = transaction.hash(); - - self.data_man.insert_transaction_index( - &hash, - &TransactionIndex { - block_hash: block.hash(), - real_index: idx, - is_phantom: false, - rpc_index: Some(rpc_index), - }, - ); - - let evm_chain_id = env.chain_id[&Space::Ethereum]; - - // persist tx index for phantom transactions. - // note: in some cases, pivot chain reorgs will result in - // different phantom txs (with different hashes) for the - // same Conflux space tx. we do not remove invalidated - // hashes here, but leave it up to the RPC layer to handle - // this instead. - for ptx in r.phantom_txs { - self.data_man.insert_transaction_index( - &ptx.into_eip155(evm_chain_id).hash(), - &TransactionIndex { - block_hash: block.hash(), - real_index: idx, - is_phantom: true, - rpc_index: Some(evm_tx_index), - }, - ); - - evm_tx_index += 1; - } - } - } - - if self.config.executive_trace { - self.data_man.insert_block_traces( - block.hash(), - block_traces.into(), - pivot_block.hash(), - on_local_pivot, - ); - } - - let block_receipts = Arc::new(BlockReceipts { - receipts, - block_number, - secondary_reward, - tx_execution_error_messages: tx_exec_error_messages, - }); - self.data_man.insert_block_execution_result( - block.hash(), - pivot_block.hash(), - block_receipts.clone(), - on_local_pivot, - ); - - epoch_receipts.push(block_receipts); - } - if self.pos_verifier.pos_option().is_some() { - debug!( - "put_staking_events: {:?} height={} len={}", - pivot_block.hash(), - pivot_block.block_header.height(), - epoch_staking_events.len() - ); - self.pos_verifier - .consensus_db() - .put_staking_events( - pivot_block.block_header.height(), - pivot_block.hash(), - epoch_staking_events, - ) - .map_err(|e| { - cfx_statedb::Error::from(DbErrorKind::PosDatabaseError( - format!("{:?}", e), - )) - })?; - } - - if on_local_pivot { - self.tx_pool.recycle_transactions(to_pending); + debug!("Notify epoch[{}]", epoch_hash); + + // TODO: use channel to deliver the message. + let txpool_clone = self.tx_pool.clone(); + std::thread::Builder::new() + .name("txpool_update_state".into()) + .spawn(move || { + txpool_clone.notify_modified_accounts(accounts_for_txpool); + }) + .expect("can not notify tx pool to start state"); } - debug!("Finish processing tx for epoch"); - Ok(epoch_receipts) + self.tx_pool + .set_best_executed_epoch(StateIndex::new_for_readonly( + epoch_hash, + &commit_result.state_root, + )) + .expect(&concat!(file!(), ":", line!(), ":", column!())); } fn compute_block_base_reward( @@ -1620,8 +1406,15 @@ impl ConsensusExecutionHandler { debug_assert!( block_receipts.receipts.len() == block.transactions.len() ); - for (idx, tx) in block.transactions.iter().enumerate() { - let fee = block_receipts.receipts[idx].gas_fee; + // TODO: fill base_price. + for (tx, receipt) in block + .transactions + .iter() + .zip(block_receipts.receipts.iter()) + { + let fee = + receipt.gas_fee - receipt.burnt_gas_fee.unwrap_or_default(); + let info = tx_fee .entry(tx.hash()) .or_insert(TxExecutionInfo(fee, BTreeSet::default())); @@ -1779,35 +1572,14 @@ impl ConsensusExecutionHandler { epoch_blocks.len(), ); let pivot_block = epoch_blocks.last().expect("Not empty"); - let mut state = State::new(StateDb::new( - self.data_man - .storage_manager - .get_state_for_next_epoch( - StateIndex::new_for_next_epoch( - pivot_block.block_header.parent_hash(), - &self - .data_man - .get_epoch_execution_commitment( - pivot_block.block_header.parent_hash(), - ) - // Unwrapping is safe because the state exists. - .unwrap() - .state_root_with_aux_info, - pivot_block.block_header.height() - 1, - self.data_man.get_snapshot_epoch_count(), - ), - false, - ) - .unwrap() - // Unwrapping is safe because the state exists. - .unwrap(), - ))?; + let mut state = self.new_state(&pivot_block, false)?; self.process_epoch_transactions( *pivot_hash, &mut state, &epoch_blocks, start_block_number, false, + /* virtual_call */ None, ) } @@ -1834,7 +1606,7 @@ impl ConsensusExecutionHandler { Some(v) => v.start_block_number + epoch_size as u64, None => bail!("cannot obtain the execution context. Database is potentially corrupted!"), }; - let spec = self.machine.spec(start_block_number); + let spec = self.machine.spec(start_block_number, block_height); let transitions = &self.machine.params().transition_heights; invalid_params_check( @@ -1887,6 +1659,10 @@ impl ConsensusExecutionHandler { address }; + let base_gas_price = best_block_header.base_price().unwrap_or_default(); + let burnt_gas_price = + base_gas_price.map_all(|x| state.burnt_gas_price(x)); + let env = Env { chain_id: self.machine.params().chain_id_map(block_height), number: start_block_number, @@ -1902,8 +1678,10 @@ impl ConsensusExecutionHandler { transaction_epoch_bound: self .verification_config .transaction_epoch_bound, + base_gas_price, + burnt_gas_price, }; - let spec = self.machine.spec(env.number); + let spec = self.machine.spec(env.number, env.epoch_height); let mut ex = EstimationContext::new( &mut state, &env, @@ -1916,29 +1694,92 @@ impl ConsensusExecutionHandler { Ok(r?) } - fn maybe_update_state( - &self, state: &mut State, block_number: BlockNumber, - ) -> DbResult<()> { - let cip94_start = self.machine.params().transition_numbers.cip94; - let period = self.machine.params().params_dao_vote_period; - // Update/initialize parameters before processing rewards. - if block_number >= cip94_start - && (block_number - cip94_start) % period == 0 - { - let set_pos_staking = - block_number > self.machine.params().transition_numbers.cip105; - initialize_or_update_dao_voted_params(state, set_pos_staking)?; - } + pub fn collect_epoch_geth_trace( + &self, epoch_block_hashes: Vec, tx_hash: Option, + opts: GethDebugTracingOptions, + ) -> RpcResult> { + // Get blocks in this epoch after skip checking + let epoch_blocks = self + .data_man + .blocks_by_hash_list( + &epoch_block_hashes, + true, /* update_cache */ + ) + .expect("blocks exist"); + + let pivot_block = epoch_blocks.last().expect("Not empty"); + let parent_pivot_block_hash = pivot_block.block_header.parent_hash(); - // Initialize old_storage_point_prop_ratio in the state. - // The time may not be in the vote period boundary, so this is not - // integrated with `initialize_or_update_dao_voted_params`, but - // that function will update the value after cip107 is enabled - // here. - if block_number == self.machine.params().transition_numbers.cip107 { - initialize_cip107(state)?; + // get the state of the parent pivot block + let state_availability_boundary = + self.data_man.state_availability_boundary.read(); + let state_space = None; // None for both core and espace + if !state_availability_boundary.check_read_availability( + pivot_block.block_header.height() - 1, + parent_pivot_block_hash, + state_space, + ) { + bail!("state is not ready"); } - Ok(()) + drop(state_availability_boundary); + + let state_index = self + .data_man + .get_state_readonly_index(parent_pivot_block_hash); + + let storage = self + .data_man + .storage_manager + .get_state_no_commit( + state_index.unwrap(), + /* try_open = */ true, + state_space, + )? + .ok_or("state deleted")?; + let state_db = StateDb::new(storage); + let mut state = State::new(state_db)?; + + let start_block_number = self + .data_man + .get_epoch_execution_context(&parent_pivot_block_hash) + .map(|v| v.start_block_number) + .expect("should exist"); + + self.execute_epoch_tx_to_collect_trace( + &mut state, + &epoch_blocks, + start_block_number, + tx_hash, + opts, + ) + .map_err(|err| err.into()) + } + + /// Execute transactions in the epoch to collect traces. + fn execute_epoch_tx_to_collect_trace( + &self, state: &mut State, epoch_blocks: &Vec>, + start_block_number: u64, tx_hash: Option, + opts: GethDebugTracingOptions, + ) -> DbResult> { + let epoch_id = epoch_blocks.last().unwrap().hash(); + + let mut answer = vec![]; + let virtual_call = VirtualCall::GethTrace(GethTask { + tx_hash, + opts, + answer: &mut answer, + }); + + self.process_epoch_transactions( + epoch_id, + state, + epoch_blocks, + start_block_number, + false, + Some(virtual_call), + )?; + + Ok(answer) } } diff --git a/crates/cfxcore/core/src/consensus/mod.rs b/crates/cfxcore/core/src/consensus/mod.rs index c3b035d6bb..ac059eb733 100644 --- a/crates/cfxcore/core/src/consensus/mod.rs +++ b/crates/cfxcore/core/src/consensus/mod.rs @@ -44,7 +44,9 @@ use cfx_execute_helper::{ phantom_tx::build_bloom_and_recover_phantom, }; use cfx_executor::{executive::ExecutionOutcome, state::State}; +use geth_tracer::GethTraceWithHash; +use alloy_rpc_types_trace::geth::GethDebugTracingOptions; use cfx_internal_common::ChainIdParams; use cfx_parameters::{ consensus::*, @@ -985,7 +987,7 @@ impl ConsensusGraph { let inner = self.inner.read(); // NOTE: as batches are processed atomically and only the - // first batch (last few epochs) is likely to fluctuate, is is unlikely + // first batch (last few epochs) is likely to fluctuate, it is unlikely // that releasing the lock between batches would cause inconsistency: // we assume there are no pivot chain reorgs deeper than batch_size. // However, we still add a simple sanity check here: @@ -1435,6 +1437,29 @@ impl ConsensusGraph { .call_virtual(tx, &epoch_id, epoch_size, request) } + pub fn collect_epoch_geth_trace( + &self, epoch_num: u64, tx_hash: Option, + opts: GethDebugTracingOptions, + ) -> RpcResult> { + // only allow to call against stated epoch + let epoch = EpochNumber::Number(epoch_num); + self.validate_stated_epoch(&epoch)?; + + let epoch_block_hashes = if let Ok(v) = + self.get_block_hashes_by_epoch(epoch) + { + v + } else { + bail!("cannot get block hashes in the specified epoch, maybe it does not exist?"); + }; + + self.executor.collect_epoch_geth_trace( + epoch_block_hashes, + tx_hash, + opts, + ) + } + /// Get the number of processed blocks (i.e., the number of calls to /// on_new_block() pub fn get_processed_block_count(&self) -> usize { @@ -1811,9 +1836,33 @@ impl ConsensusGraph { Ok(Some(bloom)) } + pub fn get_phantom_block_pivot_by_number( + &self, block_num: EpochNumber, pivot_assumption: Option, + include_traces: bool, + ) -> Result, String> { + self.get_phantom_block_by_number_inner( + block_num, + pivot_assumption, + include_traces, + true, + ) + } + pub fn get_phantom_block_by_number( &self, block_num: EpochNumber, pivot_assumption: Option, include_traces: bool, + ) -> Result, String> { + self.get_phantom_block_by_number_inner( + block_num, + pivot_assumption, + include_traces, + false, + ) + } + + fn get_phantom_block_by_number_inner( + &self, block_num: EpochNumber, pivot_assumption: Option, + include_traces: bool, only_pivot: bool, ) -> Result, String> { let hashes = self.get_block_hashes_by_epoch(block_num)?; @@ -1858,9 +1907,17 @@ impl ConsensusGraph { traces: vec![], }; - let mut gas_used = U256::from(0); + let mut accumulated_gas_used = U256::from(0); + let mut gas_used_offset; + + let iter_blocks = if only_pivot { + &blocks[blocks.len() - 1..] + } else { + &blocks[..] + }; - for b in &blocks { + for b in iter_blocks { + gas_used_offset = accumulated_gas_used; // note: we need the receipts to reconstruct a phantom block. // as a result, we cannot return unexecuted blocks in eth_* RPCs. let exec_info = match self @@ -1932,11 +1989,11 @@ impl ConsensusGraph { return Err("Inconsistent state: zero transaction gas price".into()); } - // FIXME(thegaram): is this correct? - gas_used += receipt.gas_fee / tx.gas_price(); + accumulated_gas_used = + gas_used_offset + receipt.accumulated_gas_used; phantom_block.receipts.push(Receipt { - accumulated_gas_used: gas_used, + accumulated_gas_used, outcome_status: receipt.outcome_status, ..receipt.clone() }); @@ -1983,7 +2040,8 @@ impl ConsensusGraph { )); // note: phantom txs consume no gas - let phantom_receipt = p.into_receipt(gas_used); + let phantom_receipt = + p.into_receipt(accumulated_gas_used); phantom_block .bloom diff --git a/crates/cfxcore/core/src/consensus/pos_handler.rs b/crates/cfxcore/core/src/consensus/pos_handler.rs index bba4108795..e7dc5308f1 100644 --- a/crates/cfxcore/core/src/consensus/pos_handler.rs +++ b/crates/cfxcore/core/src/consensus/pos_handler.rs @@ -8,7 +8,7 @@ use diem_crypto::HashValue; use diem_types::{ contract_event::ContractEvent, epoch_state::EpochState, - reward_distribution_event::RewardDistributionEvent, + reward_distribution_event::RewardDistributionEventV2, term_state::{DisputeEvent, UnlockEvent}, validator_config::{ConsensusPrivateKey, ConsensusVRFPrivateKey}, }; @@ -82,7 +82,8 @@ pub trait PosInterface: Send + Sync { &self, start_epoch: u64, end_epoch: u64, ) -> Vec; - fn get_reward_event(&self, epoch: u64) -> Option; + fn get_reward_event(&self, epoch: u64) + -> Option; fn get_epoch_state(&self, block_id: &PosBlockId) -> EpochState; @@ -346,7 +347,7 @@ impl PosHandler { pub fn get_reward_distribution_event( &self, h: &PosBlockId, parent_pos_ref: &PosBlockId, - ) -> Option> { + ) -> Option> { if h == parent_pos_ref { return None; } @@ -610,7 +611,9 @@ impl PosInterface for PosConnection { .collect() } - fn get_reward_event(&self, epoch: u64) -> Option { + fn get_reward_event( + &self, epoch: u64, + ) -> Option { self.pos_storage.get_reward_event(epoch).ok() } diff --git a/crates/cfxcore/core/src/error.rs b/crates/cfxcore/core/src/error.rs index a6471c3222..c891f36a5d 100644 --- a/crates/cfxcore/core/src/error.rs +++ b/crates/cfxcore/core/src/error.rs @@ -3,7 +3,7 @@ // See http://www.gnu.org/licenses/ use crate::message::Bytes; -use cfx_types::{Address, H256, U256}; +use cfx_types::{Address, SpaceMap, H256, U256}; use primitives::{filter::FilterError, transaction::TransactionError}; use std::{error, fmt, time::SystemTime}; use unexpected::{Mismatch, OutOfBounds}; @@ -34,9 +34,10 @@ pub enum BlockError { /// Gas limit header field is invalid. InvalidGasLimit(OutOfBounds), /// Total gas limits of transactions in block is out of bound. - InvalidBlockGasLimit(OutOfBounds), + InvalidPackedGasLimit(OutOfBounds), /// Total rlp sizes of transactions in block is out of bound. InvalidBlockSize(OutOfBounds), + InvalidBasePrice(Mismatch>), /// Timestamp header field is invalid. InvalidTimestamp(OutOfBounds), /// Timestamp header field is too far in future. @@ -57,6 +58,10 @@ pub enum BlockError { MissingPosReference, /// Should not have a PoS reference but it's set. UnexpectedPosReference, + /// Should have a base fee but it's not set. + MissingBaseFee, + /// Should not have a base fee but it's set. + UnexpectedBaseFee, /// The PoS reference violates the validity rule (it should extend the PoS /// reference of the parent and referees). InvalidPosReference, @@ -90,8 +95,11 @@ impl fmt::Display for BlockError { format!("Block has invalid PoW: {}", oob) } InvalidGasLimit(ref oob) => format!("Invalid gas limit: {}", oob), - InvalidBlockGasLimit(ref oob) => { - format!("Invalid block gas limit: {}", oob) + InvalidBasePrice(ref mis) => { + format!("Invalid base price: {:?}", mis) + } + InvalidPackedGasLimit(ref oob) => { + format!("Invalid packed gas limit: {}", oob) } InvalidBlockSize(ref oob) => format!("Invalid block size: {}", oob), InvalidTimestamp(ref oob) => { @@ -123,6 +131,8 @@ impl fmt::Display for BlockError { } MissingPosReference => "Missing PoS reference".into(), UnexpectedPosReference => "Should not have PoS reference".into(), + MissingBaseFee => "Missing base fee".into(), + UnexpectedBaseFee => "Should not have base fee".into(), InvalidPosReference => "The PoS reference is invalid".into(), }; diff --git a/crates/cfxcore/core/src/genesis_block.rs b/crates/cfxcore/core/src/genesis_block.rs index 810cb590a2..8998f5bdd4 100644 --- a/crates/cfxcore/core/src/genesis_block.rs +++ b/crates/cfxcore/core/src/genesis_block.rs @@ -49,7 +49,7 @@ use cfx_executor::{ }; use cfx_vm_types::{CreateContractAddress, Env}; use diem_types::account_address::AccountAddress; -use primitives::transaction::NativeTransaction; +use primitives::transaction::native_transaction::NativeTransaction; pub fn default(dev_or_test_mode: bool) -> HashMap { if !dev_or_test_mode { @@ -117,7 +117,8 @@ pub fn genesis_block( initialize_internal_contract_accounts( &mut state, machine.internal_contracts().initialized_at_genesis(), - ); + ) + .expect("no db error"); trace!("genesis_accounts: {:?}", genesis_accounts); for (addr, balance) in genesis_accounts { state @@ -478,7 +479,7 @@ fn execute_genesis_transaction( state, &env, machine.as_ref(), - &machine.spec(env.number), + &machine.spec(env.number, env.epoch_height), ) .transact(transaction, options) .unwrap() diff --git a/crates/cfxcore/core/src/pos/common/channel/src/message_queues_test.rs b/crates/cfxcore/core/src/pos/common/channel/src/message_queues_test.rs index de10bbf0e1..514cd5b779 100644 --- a/crates/cfxcore/core/src/pos/common/channel/src/message_queues_test.rs +++ b/crates/cfxcore/core/src/pos/common/channel/src/message_queues_test.rs @@ -17,6 +17,7 @@ struct ProposalMsg { /// This represents a vote message from a validator #[derive(Debug, PartialEq)] +#[allow(dead_code)] struct VoteMsg { msg: String, } diff --git a/crates/cfxcore/core/src/pos/common/logger/derive/src/lib.rs b/crates/cfxcore/core/src/pos/common/logger/derive/src/lib.rs index a104766db5..891bc9ff5e 100644 --- a/crates/cfxcore/core/src/pos/common/logger/derive/src/lib.rs +++ b/crates/cfxcore/core/src/pos/common/logger/derive/src/lib.rs @@ -158,30 +158,37 @@ fn extract_attr(attrs: &[Attribute]) -> Option { { for segment in path.segments { // Only handle schema attrs - if segment.ident == "schema" { - for meta in &nested { - let path = - if let NestedMeta::Meta(Meta::Path(path)) = meta { - path - } else { - panic!("unsupported schema attribute"); - }; - - match path - .segments - .first() - .unwrap() - .ident - .to_string() - .as_ref() - { - "debug" => return Some(ValueType::Debug), - "display" => return Some(ValueType::Display), - "serde" => return Some(ValueType::Serde), - _ => panic!("unsupported schema attribute"), - } - } + if segment.ident != "schema" { + continue; } + + let meta = if let Some(meta) = nested.first() { + meta + } else { + continue; + }; + + let path = if let NestedMeta::Meta(Meta::Path(path)) = meta { + path + } else { + panic!("unsupported schema attribute"); + }; + + let answer = match path + .segments + .first() + .unwrap() + .ident + .to_string() + .as_ref() + { + "debug" => ValueType::Debug, + "display" => ValueType::Display, + "serde" => ValueType::Serde, + _ => panic!("unsupported schema attribute"), + }; + + return Some(answer); } } } diff --git a/crates/cfxcore/core/src/pos/consensus/block_storage/mod.rs b/crates/cfxcore/core/src/pos/consensus/block_storage/mod.rs index 4b9aab2abc..9c81f2afbf 100644 --- a/crates/cfxcore/core/src/pos/consensus/block_storage/mod.rs +++ b/crates/cfxcore/core/src/pos/consensus/block_storage/mod.rs @@ -49,6 +49,7 @@ pub trait BlockReader: Send + Sync { ) -> Option>>; /// Return the certified block with the highest round. + #[allow(dead_code)] fn highest_certified_block(&self) -> Arc; /// Return the quorum certificate with the highest round diff --git a/crates/cfxcore/core/src/pos/consensus/consensus-types/src/block_data.rs b/crates/cfxcore/core/src/pos/consensus/consensus-types/src/block_data.rs index dfc6752aa5..cefcbd6c89 100644 --- a/crates/cfxcore/core/src/pos/consensus/consensus-types/src/block_data.rs +++ b/crates/cfxcore/core/src/pos/consensus/consensus-types/src/block_data.rs @@ -36,7 +36,7 @@ pub enum BlockType { /// A genesis block is the first committed block in any epoch that is /// identically constructed on all validators by any (potentially /// different) LedgerInfo that justifies the epoch change - /// from the previous epoch. The genesis block is used as the the first + /// from the previous epoch. The genesis block is used as the first /// root block of the BlockTree for all epochs. Genesis, } diff --git a/crates/cfxcore/core/src/pos/consensus/executor/src/lib.rs b/crates/cfxcore/core/src/pos/consensus/executor/src/lib.rs index a709337739..803b0d4f44 100644 --- a/crates/cfxcore/core/src/pos/consensus/executor/src/lib.rs +++ b/crates/cfxcore/core/src/pos/consensus/executor/src/lib.rs @@ -40,7 +40,7 @@ use diem_types::{ ledger_info::LedgerInfoWithSignatures, on_chain_config, proof::accumulator::InMemoryAccumulator, - reward_distribution_event::{RewardDistributionEvent, VoteCount}, + reward_distribution_event::{RewardDistributionEventV2, VoteCount}, term_state::{ ElectionEvent, PosState, RegisterEvent, RetireEvent, UpdateVotingPowerEvent, @@ -1252,12 +1252,13 @@ impl BlockExecutor for Executor { } } - let reward_event = RewardDistributionEvent { + let reward_event = RewardDistributionEventV2 { candidates: pos_state_to_commit.next_evicted_term(), elected: elected .into_iter() .map(|(k, v)| (H256::from_slice(k.as_ref()), v)) .collect(), + view: pos_state_to_commit.current_view(), }; self.db_with_cache.db.writer.save_reward_event( ledger_info_with_sigs.ledger_info().epoch(), diff --git a/crates/cfxcore/core/src/pos/consensus/executor/src/vm.rs b/crates/cfxcore/core/src/pos/consensus/executor/src/vm.rs index 31c0b1d483..08dc04f80e 100644 --- a/crates/cfxcore/core/src/pos/consensus/executor/src/vm.rs +++ b/crates/cfxcore/core/src/pos/consensus/executor/src/vm.rs @@ -75,17 +75,16 @@ impl PosVM { diem_debug!("get_unlock_events: {}", events.len()); let next_view = state_view.pos_state().current_view() + 1; - let round_per_term = POS_STATE_CONFIG.round_per_term(); + let (term, view_in_term) = POS_STATE_CONFIG.get_term_view(next_view); // TODO(lpl): Simplify. - if next_view % round_per_term == 0 { - let term = next_view / round_per_term; + if view_in_term == 0 { let (validator_verifier, vrf_seed) = state_view.pos_state().get_committee_at(term).map_err(|e| { diem_warn!("get_new_committee error: {:?}", e); VMStatus::Error(StatusCode::CFX_INVALID_TX) })?; - let epoch = next_view / round_per_term + 1; + let epoch = term + 1; let validator_bytes = bcs::to_bytes(&EpochState::new( epoch, validator_verifier, diff --git a/crates/cfxcore/core/src/pos/consensus/persistent_liveness_storage.rs b/crates/cfxcore/core/src/pos/consensus/persistent_liveness_storage.rs index 9006d31006..c460b1d9d1 100644 --- a/crates/cfxcore/core/src/pos/consensus/persistent_liveness_storage.rs +++ b/crates/cfxcore/core/src/pos/consensus/persistent_liveness_storage.rs @@ -45,6 +45,7 @@ pub trait PersistentLivenessStorage: Send + Sync { fn save_vote(&self, vote: &Vote) -> Result<()>; /// Construct data that can be recovered from ledger + #[allow(dead_code)] fn recover_from_ledger(&self) -> LedgerRecoveryData; /// Construct necessary data to start consensus. @@ -70,6 +71,7 @@ pub trait PersistentLivenessStorage: Send + Sync { unimplemented!() } + #[allow(dead_code)] fn prune_staking_events( &self, _committed_pivot_decision: &PivotBlockDecision, ) -> Result<()> { diff --git a/crates/cfxcore/core/src/pos/consensus/state_computer.rs b/crates/cfxcore/core/src/pos/consensus/state_computer.rs index 279e17c262..07fe973abb 100644 --- a/crates/cfxcore/core/src/pos/consensus/state_computer.rs +++ b/crates/cfxcore/core/src/pos/consensus/state_computer.rs @@ -111,7 +111,7 @@ impl StateComputer for ExecutionProxy { }); // Here to start to do state synchronization where ChunkExecutor inside // will process chunks and commit to Storage. However, after - // block execution and commitments, the the sync state of + // block execution and commitments, the sync state of // ChunkExecutor may be not up to date so it is required to // reset the cache of ChunkExecutor in State Sync when requested // to sync. diff --git a/crates/cfxcore/core/src/pos/mempool/core_mempool/mod.rs b/crates/cfxcore/core/src/pos/mempool/core_mempool/mod.rs index 6b52e74e78..5e631eb4ee 100644 --- a/crates/cfxcore/core/src/pos/mempool/core_mempool/mod.rs +++ b/crates/cfxcore/core/src/pos/mempool/core_mempool/mod.rs @@ -11,8 +11,6 @@ mod transaction; mod transaction_store; mod ttl_cache; -#[cfg(test)] -pub use self::ttl_cache::TtlCache; pub use self::{ index::TxnPointer, mempool::Mempool as CoreMempool, transaction::TimelineState, diff --git a/crates/cfxcore/core/src/pos/storage/backup/backup-cli/src/coordinators/backup.rs b/crates/cfxcore/core/src/pos/storage/backup/backup-cli/src/coordinators/backup.rs index bea34ab977..bd736b3b4d 100644 --- a/crates/cfxcore/core/src/pos/storage/backup/backup-cli/src/coordinators/backup.rs +++ b/crates/cfxcore/core/src/pos/storage/backup/backup-cli/src/coordinators/backup.rs @@ -111,7 +111,7 @@ impl BackupCoordinator { // On new DbState retrieved: // `watch_db_state` informs `backup_epoch_endings` via channel 1, - // and the the latter informs the other backup type workers via channel 2, after epoch + // and the latter informs the other backup type workers via channel 2, after epoch // ending is properly backed up, if necessary. This way, the epoch ending LedgerInfo needed // for proof verification is always available in the same backup storage. let (tx1, rx1) = watch::channel::>(None); diff --git a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/ledger_store/mod.rs b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/ledger_store/mod.rs index cbde0b0292..8502c7c6b2 100644 --- a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/ledger_store/mod.rs +++ b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/ledger_store/mod.rs @@ -29,7 +29,7 @@ use diem_types::{ TransactionAccumulatorProof, TransactionAccumulatorRangeProof, TransactionInfoWithProof, }, - reward_distribution_event::RewardDistributionEvent, + reward_distribution_event::RewardDistributionEventV2, term_state::PosState, transaction::{TransactionInfo, Version}, }; @@ -403,7 +403,7 @@ impl LedgerStore { ) } - /// Write `txn_infos` to `batch`. Assigned `first_version` to the the + /// Write `txn_infos` to `batch`. Assigned `first_version` to the /// version number of the first transaction, and so on. pub fn put_transaction_infos( &self, first_version: u64, txn_infos: &[TransactionInfo], @@ -501,7 +501,7 @@ impl LedgerStore { } pub fn put_reward_event( - &self, epoch: u64, event: &RewardDistributionEvent, + &self, epoch: u64, event: &RewardDistributionEventV2, ) -> Result<()> { let mut cs = ChangeSet::new(); cs.batch.put::(&epoch, event)?; @@ -510,7 +510,7 @@ impl LedgerStore { pub fn get_reward_event( &self, epoch: u64, - ) -> Result { + ) -> Result { self.db.get::(&epoch)?.ok_or_else(|| { DiemDbError::NotFound(format!("RewardEvent of epoch {}", epoch)) .into() diff --git a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/lib.rs b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/lib.rs index 6dd2433eb3..36a895fbec 100644 --- a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/lib.rs +++ b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/lib.rs @@ -43,7 +43,7 @@ use diem_types::{ AccountStateProof, AccumulatorConsistencyProof, SparseMerkleProof, TransactionListProof, }, - reward_distribution_event::RewardDistributionEvent, + reward_distribution_event::RewardDistributionEventV2, term_state::PosState, transaction::{ Transaction, TransactionInfo, TransactionListWithProof, @@ -995,7 +995,7 @@ impl DbWriter for PosLedgerDB { } fn save_reward_event( - &self, epoch: u64, event: &RewardDistributionEvent, + &self, epoch: u64, event: &RewardDistributionEventV2, ) -> Result<()> { self.ledger_store.put_reward_event(epoch, event) } @@ -1042,7 +1042,9 @@ impl DBReaderForPoW for PosLedgerDB { Ok(ending_blocks) } - fn get_reward_event(&self, epoch: u64) -> Result { + fn get_reward_event( + &self, epoch: u64, + ) -> Result { self.ledger_store.get_reward_event(epoch) } diff --git a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/schema/reward_event/mod.rs b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/schema/reward_event/mod.rs index 14aeb43347..e71eaa1e06 100644 --- a/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/schema/reward_event/mod.rs +++ b/crates/cfxcore/core/src/pos/storage/pos-ledger-db/src/schema/reward_event/mod.rs @@ -20,7 +20,9 @@ use crate::schema::{ensure_slice_len_eq, REWARD_EVENT_CF_NAME}; use anyhow::Result; use byteorder::{BigEndian, ReadBytesExt}; -use diem_types::reward_distribution_event::RewardDistributionEvent; +use diem_types::reward_distribution_event::{ + RewardDistributionEventV1, RewardDistributionEventV2, +}; use schemadb::{ define_schema, schema::{KeyCodec, ValueCodec}, @@ -30,7 +32,7 @@ use std::mem::size_of; define_schema!( RewardEventSchema, u64, /* epoch num */ - RewardDistributionEvent, + RewardDistributionEventV2, REWARD_EVENT_CF_NAME ); @@ -43,12 +45,17 @@ impl KeyCodec for u64 { } } -impl ValueCodec for RewardDistributionEvent { +impl ValueCodec for RewardDistributionEventV2 { fn encode_value(&self) -> Result> { bcs::to_bytes(self).map_err(Into::into) } fn decode_value(data: &[u8]) -> Result { - bcs::from_bytes(data).map_err(Into::into) + bcs::from_bytes(data) + // If `data` cannot be deserialized to `Self`, it may be stored in + // an older version. + .or(bcs::from_bytes::(data) + .map(Into::into)) + .map_err(Into::into) } } diff --git a/crates/cfxcore/core/src/pos/storage/schemadb/Cargo.toml b/crates/cfxcore/core/src/pos/storage/schemadb/Cargo.toml index 3cb234662c..ebe14b34f6 100644 --- a/crates/cfxcore/core/src/pos/storage/schemadb/Cargo.toml +++ b/crates/cfxcore/core/src/pos/storage/schemadb/Cargo.toml @@ -18,7 +18,7 @@ diem-metrics = { path = "../../common/metrics" } [dependencies.rocksdb] git = "https://github.com/Conflux-Chain/rust-rocksdb.git" -rev = "a1ce5bd3322a7b732dfb300c2571dc4d99f1edae" +rev = "3773afe5b953997188f37c39308105b5deb0faac" [dev-dependencies] byteorder = "1.4.3" diff --git a/crates/cfxcore/core/src/pos/storage/storage-interface/src/lib.rs b/crates/cfxcore/core/src/pos/storage/storage-interface/src/lib.rs index 5ead9c40e0..db8914221b 100644 --- a/crates/cfxcore/core/src/pos/storage/storage-interface/src/lib.rs +++ b/crates/cfxcore/core/src/pos/storage/storage-interface/src/lib.rs @@ -23,7 +23,7 @@ use diem_types::{ proof::{ definition::LeafCount, AccumulatorConsistencyProof, SparseMerkleProof, }, - reward_distribution_event::RewardDistributionEvent, + reward_distribution_event::RewardDistributionEventV2, term_state::PosState, transaction::{ TransactionInfo, TransactionListWithProof, TransactionToCommit, @@ -426,7 +426,7 @@ pub trait DbWriter: Send + Sync { ) -> Result<()>; fn save_reward_event( - &self, epoch: u64, event: &RewardDistributionEvent, + &self, epoch: u64, event: &RewardDistributionEventV2, ) -> Result<()>; fn delete_pos_state_by_block(&self, block_id: &HashValue) -> Result<()>; @@ -529,7 +529,8 @@ pub trait DBReaderForPoW: Send + Sync + DbReader { &self, start_epoch: u64, end_epoch: u64, ) -> Result>; - fn get_reward_event(&self, epoch: u64) -> Result; + fn get_reward_event(&self, epoch: u64) + -> Result; fn get_committed_block_by_hash( &self, block_hash: &HashValue, diff --git a/crates/cfxcore/core/src/pos/storage/storage-interface/src/mock.rs b/crates/cfxcore/core/src/pos/storage/storage-interface/src/mock.rs index 5a84189548..5fc880069e 100644 --- a/crates/cfxcore/core/src/pos/storage/storage-interface/src/mock.rs +++ b/crates/cfxcore/core/src/pos/storage/storage-interface/src/mock.rs @@ -18,7 +18,7 @@ use diem_types::{ epoch_change::EpochChangeProof, ledger_info::LedgerInfoWithSignatures, proof::{AccumulatorConsistencyProof, SparseMerkleProof}, - reward_distribution_event::RewardDistributionEvent, + reward_distribution_event::RewardDistributionEventV2, transaction::{TransactionListWithProof, TransactionWithProof, Version}, }; @@ -139,7 +139,7 @@ impl DBReaderForPoW for MockDbReader { fn get_reward_event( &self, _epoch: u64, - ) -> anyhow::Result { + ) -> anyhow::Result { todo!() } diff --git a/crates/cfxcore/core/src/pos/storage/storage-interface/src/state_view.rs b/crates/cfxcore/core/src/pos/storage/storage-interface/src/state_view.rs index 430f51eaa7..fcaf2b3592 100644 --- a/crates/cfxcore/core/src/pos/storage/storage-interface/src/state_view.rs +++ b/crates/cfxcore/core/src/pos/storage/storage-interface/src/state_view.rs @@ -48,7 +48,7 @@ pub struct VerifiedStateView<'a> { /// The cache of verified account states from `reader` and /// `speculative_state_view`, represented by a hashmap with an account - /// address as key and a pair of an ordered account state map and an an + /// address as key and a pair of an ordered account state map and an /// optional account state proof as value. When the VM queries an /// `access_path`, this cache will first check whether `reader_cache` is /// hit. If hit, it will return the corresponding value of that diff --git a/crates/cfxcore/core/src/pos/types/src/account_state_blob.rs b/crates/cfxcore/core/src/pos/types/src/account_state_blob.rs index 5c728d2e6e..db9b1251aa 100644 --- a/crates/cfxcore/core/src/pos/types/src/account_state_blob.rs +++ b/crates/cfxcore/core/src/pos/types/src/account_state_blob.rs @@ -165,7 +165,7 @@ impl AccountStateWithProof { } } - /// Verifies the the account state blob with the proof, both carried by + /// Verifies the account state blob with the proof, both carried by /// `self`. /// /// Two things are ensured if no error is raised: diff --git a/crates/cfxcore/core/src/pos/types/src/proof/position/mod.rs b/crates/cfxcore/core/src/pos/types/src/proof/position/mod.rs index e843e74b0e..7a85e8733b 100644 --- a/crates/cfxcore/core/src/pos/types/src/proof/position/mod.rs +++ b/crates/cfxcore/core/src/pos/types/src/proof/position/mod.rs @@ -140,7 +140,7 @@ impl Position { /// This method takes in a node position and return its sibling position /// /// The observation is that, after stripping out the right-most common bits, - /// two sibling nodes flip the the next right-most bits with each other. + /// two sibling nodes flip the next right-most bits with each other. /// To find out the right-most common bits, first remove all the right-most /// ones because they are corresponding to level's indicator. Then /// remove next zero right after. @@ -496,7 +496,7 @@ fn nodes_to_left_of(node: u64) -> u64 { } /// Given `node`, an index in an in-order traversal of a perfect binary tree, -/// what order would the node be visited in in post-order traversal? +/// what order would the node be visited in post-order traversal? /// For example, consider this tree of in-order nodes. /// /// ```text diff --git a/crates/cfxcore/core/src/pos/types/src/reward_distribution_event.rs b/crates/cfxcore/core/src/pos/types/src/reward_distribution_event.rs index 7521d4819f..171ee045ea 100644 --- a/crates/cfxcore/core/src/pos/types/src/reward_distribution_event.rs +++ b/crates/cfxcore/core/src/pos/types/src/reward_distribution_event.rs @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize}; use cfx_types::H256; use crate::term_state::{ - BONUS_VOTE_POINTS, COMMITTEE_POINTS, ELECTION_POINTS, LEADER_POINTS, + bonus_vote_points, leader_points, COMMITTEE_POINTS, ELECTION_POINTS, }; #[derive(Clone, Serialize, Deserialize, Default, Debug, Eq, PartialEq)] @@ -26,28 +26,30 @@ pub struct VoteCount { } impl VoteCount { - pub fn reward_points(&self) -> u64 { + pub fn reward_points(&self, view: u64) -> u64 { if self.vote_count == 0 { return 0; } - self.leader_count as u64 * LEADER_POINTS - + self.included_vote_count as u64 * BONUS_VOTE_POINTS + self.leader_count as u64 * leader_points(view) + + self.included_vote_count * bonus_vote_points(view) + self.total_votes * COMMITTEE_POINTS } } #[derive(Clone, Serialize, Deserialize, Default, Debug, Eq, PartialEq)] -pub struct RewardDistributionEvent { +pub struct RewardDistributionEventV2 { pub candidates: BTreeMap, pub elected: BTreeMap, + pub view: u64, } -impl RewardDistributionEvent { +impl RewardDistributionEventV2 { pub fn rewards(&self) -> impl Iterator { + let view = self.view; let committee_rewards = self .elected .iter() - .map(|(id, vote_count)| (id, vote_count.reward_points())); + .map(move |(id, vote_count)| (id, vote_count.reward_points(view))); let participate_rewards = self .candidates .iter() @@ -55,3 +57,19 @@ impl RewardDistributionEvent { committee_rewards.chain(participate_rewards) } } + +#[derive(Clone, Serialize, Deserialize, Default, Debug, Eq, PartialEq)] +pub struct RewardDistributionEventV1 { + pub candidates: BTreeMap, + pub elected: BTreeMap, +} + +impl From for RewardDistributionEventV2 { + fn from(value: RewardDistributionEventV1) -> Self { + Self { + candidates: value.candidates, + elected: value.elected, + view: 0, + } + } +} diff --git a/crates/cfxcore/core/src/pos/types/src/term_state.rs b/crates/cfxcore/core/src/pos/types/src/term_state.rs index 427d7db648..7cff8768eb 100644 --- a/crates/cfxcore/core/src/pos/types/src/term_state.rs +++ b/crates/cfxcore/core/src/pos/types/src/term_state.rs @@ -54,8 +54,9 @@ pub const TERM_MAX_SIZE: usize = 10000; pub const TERM_ELECTED_SIZE: usize = 50; mod incentives { - use super::{ - ROUND_PER_TERM, TERM_ELECTED_SIZE, TERM_LIST_LEN, TERM_MAX_SIZE, + use super::{TERM_ELECTED_SIZE, TERM_LIST_LEN, TERM_MAX_SIZE}; + use crate::term_state::pos_state_config::{ + PosStateConfigTrait, POS_STATE_CONFIG, }; const BONUS_VOTE_MAX_SIZE: u64 = 100; @@ -73,12 +74,19 @@ mod incentives { / 100 / (TERM_ELECTED_SIZE as u64) / (TERM_LIST_LEN as u64); - pub const LEADER_POINTS: u64 = - MAX_TERM_POINTS * LEADER_PERCENTAGE / 100 / ROUND_PER_TERM; - pub const BONUS_VOTE_POINTS: u64 = MAX_TERM_POINTS * BONUS_VOTE_PERCENTAGE - / 100 - / ROUND_PER_TERM - / BONUS_VOTE_MAX_SIZE; + + pub fn leader_points(view: u64) -> u64 { + MAX_TERM_POINTS * LEADER_PERCENTAGE + / 100 + / POS_STATE_CONFIG.round_per_term(view) + } + + pub fn bonus_vote_points(view: u64) -> u64 { + MAX_TERM_POINTS * BONUS_VOTE_PERCENTAGE + / 100 + / POS_STATE_CONFIG.round_per_term(view) + / BONUS_VOTE_MAX_SIZE + } } #[derive(Copy, Clone, Eq, PartialEq, Serialize, Deserialize, Debug)] @@ -260,6 +268,10 @@ pub struct TermData { impl TermData { pub fn start_view(&self) -> u64 { self.start_view } + pub fn get_term(&self) -> u64 { + POS_STATE_CONFIG.get_term_view(self.start_view).0 + } + pub fn node_list(&self) -> &NodeList { &self.node_list } } @@ -289,7 +301,8 @@ impl Eq for ElectingHeap {} impl TermData { fn next_term(&self, node_list: NodeList, seed: Vec) -> Self { TermData { - start_view: self.start_view + POS_STATE_CONFIG.round_per_term(), + start_view: self.start_view + + POS_STATE_CONFIG.round_per_term(self.start_view), seed, node_list, } @@ -403,8 +416,8 @@ impl TermList { } // This double-check should always pass. debug_assert!( - self.term_list[TERM_LIST_LEN].start_view - == new_term.saturating_mul(POS_STATE_CONFIG.round_per_term()) + Some(self.term_list[TERM_LIST_LEN].start_view) + == POS_STATE_CONFIG.get_starting_view_for_term(new_term) ); self.term_list.remove(0); let new_term = self @@ -635,16 +648,14 @@ impl PosState { } }; - if election_tx - .target_term - .checked_mul(POS_STATE_CONFIG.round_per_term()) - .is_none() + let target_view = match POS_STATE_CONFIG + .get_starting_view_for_term(election_tx.target_term) { - return Some(DiscardedVMStatus::ELECTION_TERGET_TERM_NOT_OPEN); - } - - let target_view = - election_tx.target_term * POS_STATE_CONFIG.round_per_term(); + None => { + return Some(DiscardedVMStatus::ELECTION_TERGET_TERM_NOT_OPEN) + } + Some(v) => v, + }; if node.lock_status.available_votes() == 0 { return Some(DiscardedVMStatus::ELECTION_WITHOUT_VOTES); @@ -652,7 +663,8 @@ impl PosState { // Do not check `ELECTION_TERM_END_ROUND` because we are using the // committed state in this simple validation. if target_view - <= self.current_view + POS_STATE_CONFIG.election_term_end_round() + <= self.current_view + + POS_STATE_CONFIG.election_term_end_round(self.current_view) { return Some(DiscardedVMStatus::ELECTION_TERGET_TERM_NOT_OPEN); } @@ -691,13 +703,21 @@ impl PosState { if node.lock_status.available_votes() == 0 { bail!("Election without any votes"); } - let target_view = - election_tx.target_term * POS_STATE_CONFIG.round_per_term(); + let target_view = match POS_STATE_CONFIG + .get_starting_view_for_term(election_tx.target_term) + { + None => { + bail!("target view overflows, election_tx={:?}", election_tx) + } + Some(v) => v, + }; if target_view - > self.current_view + POS_STATE_CONFIG.election_term_start_round() + > self.current_view + + POS_STATE_CONFIG.election_term_start_round(self.current_view) || target_view <= self.current_view - + POS_STATE_CONFIG.election_term_end_round() + + POS_STATE_CONFIG + .election_term_end_round(self.current_view) { bail!( "Target term is not open for election: target={} current={}", @@ -857,25 +877,28 @@ impl PosState { } pub fn final_serving_view(&self, author: &AccountAddress) -> Option { - let mut final_elected_view = None; + let mut final_elected_term = None; for term in self.term_list.term_list.iter().rev() { match &term.node_list { NodeList::Electing(heap) => { if heap.1.contains(author) { - final_elected_view = Some(term.start_view); + final_elected_term = Some(term.get_term()); break; } } NodeList::Elected(map) => { if map.0.contains_key(author) { - final_elected_view = Some(term.start_view); + final_elected_term = Some(term.get_term()); break; } } } } - final_elected_view.map(|v| { - v + TERM_LIST_LEN as u64 * POS_STATE_CONFIG.round_per_term() + 1 + final_elected_term.map(|t| { + POS_STATE_CONFIG + .get_starting_view_for_term(t + TERM_LIST_LEN as u64) + .expect("checked term") + + 1 }) } @@ -1012,27 +1035,30 @@ impl PosState { let (verifier, term_seed) = self.get_committee_at(0)?; // genesis Some(EpochState::new(1, verifier, term_seed.clone())) - } else if self.current_view % POS_STATE_CONFIG.round_per_term() == 0 { - let new_term = - self.current_view / POS_STATE_CONFIG.round_per_term(); - let (verifier, term_seed) = self.get_committee_at(new_term)?; - // generate new epoch for new term. - self.term_list.new_term( - new_term, - self.pivot_decision.block_hash.as_bytes().to_vec(), - ); - // TODO(lpl): If we allow epoch changes within a term, this - // should be updated. - Some(EpochState::new(new_term + 1, verifier, term_seed.clone())) - } else if self.current_view - >= POS_STATE_CONFIG.first_end_election_view() - && self.current_view % POS_STATE_CONFIG.round_per_term() - == POS_STATE_CONFIG.round_per_term() / 2 - { - self.term_list.finalize_election(); - None } else { - None + let (term, view_in_term) = + POS_STATE_CONFIG.get_term_view(self.current_view); + if view_in_term == 0 { + let new_term = term; + let (verifier, term_seed) = self.get_committee_at(new_term)?; + // generate new epoch for new term. + self.term_list.new_term( + new_term, + self.pivot_decision.block_hash.as_bytes().to_vec(), + ); + // TODO(lpl): If we allow epoch changes within a term, this + // should be updated. + Some(EpochState::new(new_term + 1, verifier, term_seed.clone())) + } else if self.current_view + >= POS_STATE_CONFIG.first_end_election_view() + && view_in_term + == POS_STATE_CONFIG.round_per_term(self.current_view) / 2 + { + self.term_list.finalize_election(); + None + } else { + None + } }; if let Some(epoch_state) = &epoch_state { self.epoch_state = epoch_state.clone(); diff --git a/crates/cfxcore/core/src/pos/types/src/term_state/pos_state_config.rs b/crates/cfxcore/core/src/pos/types/src/term_state/pos_state_config.rs index b0630dd1e9..e72becaecc 100644 --- a/crates/cfxcore/core/src/pos/types/src/term_state/pos_state_config.rs +++ b/crates/cfxcore/core/src/pos/types/src/term_state/pos_state_config.rs @@ -24,14 +24,19 @@ pub struct PosStateConfig { fix_cip99_in_queue_locked_views: u64, fix_cip99_out_queue_locked_views: u64, + cip136_transition_view: u64, + cip136_out_queue_locked_views: u64, + cip136_in_queue_locked_views: u64, + cip136_round_per_term: u64, + nonce_limit_transition_view: u64, max_nonce_per_account: u64, } pub trait PosStateConfigTrait { - fn round_per_term(&self) -> Round; - fn election_term_start_round(&self) -> Round; - fn election_term_end_round(&self) -> Round; + fn round_per_term(&self, view: u64) -> Round; + fn election_term_start_round(&self, view: u64) -> Round; + fn election_term_end_round(&self, view: u64) -> Round; fn first_start_election_view(&self) -> u64; fn first_end_election_view(&self) -> u64; fn term_max_size(&self) -> usize; @@ -42,6 +47,8 @@ pub trait PosStateConfigTrait { fn force_retire_check_epoch_count(&self, view: u64) -> u64; fn max_nonce_per_account(&self, view: u64) -> u64; + fn get_term_view(&self, view: u64) -> (u64, u64); + fn get_starting_view_for_term(&self, term: u64) -> Option; } impl PosStateConfig { @@ -52,7 +59,9 @@ impl PosStateConfig { cip99_out_queue_locked_views: u64, fix_cip99_transition_view: u64, fix_cip99_in_queue_locked_views: u64, fix_cip99_out_queue_locked_views: u64, - nonce_limit_transition_view: u64, max_nonce_per_account: u64, + nonce_limit_transition_view: u64, max_nonce_per_account: u64, cip136_transition_view: u64, + cip136_in_queue_locked_views: u64, cip136_out_queue_locked_views: u64, + cip136_round_per_term: u64, ) -> Self { Self { round_per_term, @@ -66,6 +75,10 @@ impl PosStateConfig { fix_cip99_transition_view, fix_cip99_in_queue_locked_views, fix_cip99_out_queue_locked_views, + cip136_transition_view, + cip136_out_queue_locked_views, + cip136_in_queue_locked_views, + cip136_round_per_term, nonce_limit_transition_view, max_nonce_per_account, } @@ -73,25 +86,34 @@ impl PosStateConfig { } impl PosStateConfigTrait for OnceCell { - fn round_per_term(&self) -> Round { self.get().unwrap().round_per_term } + fn round_per_term(&self, view: u64) -> Round { + let conf = self.get().unwrap(); + if view < conf.cip136_transition_view { + conf.round_per_term + } else { + conf.cip136_round_per_term + } + } /// A term `n` is open for election in the view range /// `(n * ROUND_PER_TERM - ELECTION_TERM_START_ROUND, n * ROUND_PER_TERM - /// ELECTION_TERM_END_ROUND]` - fn election_term_start_round(&self) -> Round { - self.round_per_term() / 2 * 3 + fn election_term_start_round(&self, view: u64) -> Round { + self.round_per_term(view) / 2 * 3 } - fn election_term_end_round(&self) -> Round { self.round_per_term() / 2 } + fn election_term_end_round(&self, view: u64) -> Round { + self.round_per_term(view) / 2 + } fn first_start_election_view(&self) -> u64 { - TERM_LIST_LEN as u64 * self.round_per_term() - - self.election_term_start_round() + TERM_LIST_LEN as u64 * self.round_per_term(0) + - self.election_term_start_round(0) } fn first_end_election_view(&self) -> u64 { - TERM_LIST_LEN as u64 * self.round_per_term() - - self.election_term_end_round() + TERM_LIST_LEN as u64 * self.round_per_term(0) + - self.election_term_end_round(0) } fn term_max_size(&self) -> usize { self.get().unwrap().term_max_size } @@ -102,12 +124,14 @@ impl PosStateConfigTrait for OnceCell { fn in_queue_locked_views(&self, view: u64) -> u64 { let conf = self.get().unwrap(); - if view >= conf.fix_cip99_transition_view { + if view >= conf.fix_cip99_transition_view && view < conf.cip136_transition_view { conf.fix_cip99_in_queue_locked_views } else if view >= conf.cip99_transition_view && view < conf.fix_cip99_transition_view { conf.cip99_in_queue_locked_views + } else if view >= conf.cip136_transition_view { + conf.cip136_in_queue_locked_views } else { conf.in_queue_locked_views } @@ -117,8 +141,12 @@ impl PosStateConfigTrait for OnceCell { let conf = self.get().unwrap(); if view >= conf.fix_cip99_transition_view { conf.fix_cip99_out_queue_locked_views - } else if view >= conf.cip99_transition_view { + } else if view >= conf.cip99_transition_view + && view < conf.cip136_transition_view + { conf.cip99_out_queue_locked_views + } else if view >= conf.cip136_transition_view { + conf.cip136_out_queue_locked_views } else { conf.out_queue_locked_views } @@ -147,6 +175,33 @@ impl PosStateConfigTrait for OnceCell { u64::MAX } } + + fn get_term_view(&self, view: u64) -> (u64, u64) { + let conf = self.get().unwrap(); + if view < conf.cip136_transition_view { + (view / conf.round_per_term, view % conf.round_per_term) + } else { + let transition_term = + conf.cip136_transition_view / conf.round_per_term; + let view_after = view - conf.cip136_transition_view; + ( + transition_term + view_after / conf.cip136_round_per_term, + view_after % conf.cip136_round_per_term, + ) + } + } + + fn get_starting_view_for_term(&self, term: u64) -> Option { + let conf = self.get().unwrap(); + let transition_term = conf.cip136_transition_view / conf.round_per_term; + if term < transition_term { + Some(term * conf.round_per_term) + } else { + (term - transition_term) + .checked_mul(conf.cip136_round_per_term) + .map(|v| v + conf.cip136_transition_view) + } + } } pub static POS_STATE_CONFIG: OnceCell = OnceCell::new(); @@ -165,6 +220,10 @@ impl Default for PosStateConfig { fix_cip99_transition_view: u64::MAX, fix_cip99_out_queue_locked_views: IN_QUEUE_LOCKED_VIEWS, fix_cip99_in_queue_locked_views: OUT_QUEUE_LOCKED_VIEWS, + cip136_transition_view: u64::MAX, + cip136_out_queue_locked_views: IN_QUEUE_LOCKED_VIEWS, + cip136_in_queue_locked_views: OUT_QUEUE_LOCKED_VIEWS, + cip136_round_per_term: ROUND_PER_TERM, nonce_limit_transition_view: u64::MAX, max_nonce_per_account: u64::MAX, } diff --git a/crates/cfxcore/core/src/pos/types/src/transaction/authenticator.rs b/crates/cfxcore/core/src/pos/types/src/transaction/authenticator.rs index 2c1a0d6f89..4bf2f774dd 100644 --- a/crates/cfxcore/core/src/pos/types/src/transaction/authenticator.rs +++ b/crates/cfxcore/core/src/pos/types/src/transaction/authenticator.rs @@ -27,7 +27,7 @@ use rand::{rngs::OsRng, Rng}; use serde::{Deserialize, Serialize}; use std::{convert::TryFrom, fmt, str::FromStr}; -/// A `TransactionAuthenticator` is an an abstraction of a signature scheme. It +/// A `TransactionAuthenticator` is an abstraction of a signature scheme. It /// must know: (1) How to check its signature against a message and public key /// (2) How to convert its public key into an `AuthenticationKeyPreimage` /// structured as (public_key | signaure_scheme_id). diff --git a/crates/cfxcore/core/src/sync/message/get_block_txn_response.rs b/crates/cfxcore/core/src/sync/message/get_block_txn_response.rs index 175c04e616..64f32d2ee2 100644 --- a/crates/cfxcore/core/src/sync/message/get_block_txn_response.rs +++ b/crates/cfxcore/core/src/sync/message/get_block_txn_response.rs @@ -17,7 +17,7 @@ use primitives::{Block, TransactionWithSignature}; use rlp_derive::{RlpDecodable, RlpEncodable}; use std::collections::HashSet; -#[derive(Debug, PartialEq, Default, RlpDecodable, RlpEncodable)] +#[derive(Debug, PartialEq, Default, RlpEncodable, RlpDecodable)] pub struct GetBlockTxnResponse { pub request_id: RequestId, pub block_hash: H256, diff --git a/crates/cfxcore/core/src/sync/synchronization_graph.rs b/crates/cfxcore/core/src/sync/synchronization_graph.rs index 7314786641..f4fa10cfd5 100644 --- a/crates/cfxcore/core/src/sync/synchronization_graph.rs +++ b/crates/cfxcore/core/src/sync/synchronization_graph.rs @@ -14,6 +14,7 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +use cfx_parameters::consensus_internal::ELASTICITY_MULTIPLIER; use futures::executor::block_on; use parking_lot::RwLock; use slab::Slab; @@ -572,7 +573,7 @@ impl SynchronizationGraphInner { minimal_status, ) { debug!( - "Block {:?} not not ready for its pos_reference: {:?}", + "Block {:?} not ready for its pos_reference: {:?}", self.arena[index].block_header.hash(), self.pos_verifier.get_pivot_decision( self.arena[index] @@ -756,6 +757,13 @@ impl SynchronizationGraphInner { ))); } + let parent_gas_limit = parent_gas_limit + * if epoch == self.machine.params().transition_heights.cip1559 { + ELASTICITY_MULTIPLIER + } else { + 1 + }; + // Verify the gas limit is respected let self_gas_limit = *self.arena[index].block_header.gas_limit(); let gas_limit_divisor = self.machine.params().gas_limit_bound_divisor; @@ -851,6 +859,21 @@ impl SynchronizationGraphInner { Ok(()) } + fn verify_graph_ready_block( + &self, index: usize, verification_config: &VerificationConfig, + ) -> Result<(), Error> { + let block_header = &self.arena[index].block_header; + let parent = self + .data_man + .block_header_by_hash(block_header.parent_hash()) + .expect("headers will not be deleted"); + let block = self + .data_man + .block_by_hash(&block_header.hash(), true) + .expect("received"); + verification_config.verify_sync_graph_ready_block(&block, &parent) + } + fn process_invalid_blocks(&mut self, invalid_set: &HashSet) { for index in invalid_set { let hash = self.arena[*index].block_header.hash(); @@ -1697,6 +1720,23 @@ impl SynchronizationGraph { index, ); } else if inner.new_to_be_block_graph_ready(index) { + let verify_result = inner + .verify_graph_ready_block(index, &self.verification_config); + if verify_result.is_err() { + warn!( + "Invalid block! inserted_header={:?} err={:?}", + inner.arena[index].block_header.clone(), + verify_result + ); + invalid_set.insert(index); + inner.arena[index].graph_status = BLOCK_INVALID; + inner.set_and_propagate_invalid( + &mut queue, + &mut invalid_set, + index, + ); + continue; + } self.set_graph_ready(inner, index); for child in &inner.arena[index].children { debug_assert!( @@ -1764,7 +1804,7 @@ impl SynchronizationGraph { ErrorKind::Block(BlockError::InvalidTransactionsRoot(e)), _, )) => { - warn ! ("BlockTransactionRoot not match! inserted_block={:?} err={:?}", block, e); + warn!("BlockTransactionRoot not match! inserted_block={:?} err={:?}", block, e); // If the transaction root does not match, it might be // caused by receiving wrong // transactions because of conflicting ShortId in @@ -2092,7 +2132,7 @@ pub enum BlockInsertionResult { // We should request again to get // the correct transactions for full verification. RequestAgain, - // This is only for the case the the header is removed, possibly because + // This is only for the case the header is removed, possibly because // we switch phases. // We ignore the block without verification. Ignored, diff --git a/crates/cfxcore/core/src/sync/utils.rs b/crates/cfxcore/core/src/sync/utils.rs index 6f0342a69b..7725f47ae0 100644 --- a/crates/cfxcore/core/src/sync/utils.rs +++ b/crates/cfxcore/core/src/sync/utils.rs @@ -42,7 +42,10 @@ use crate::{ verification::VerificationConfig, ConsensusGraph, NodeType, Notifications, TransactionPool, }; -use cfx_executor::machine::{new_machine_with_builtin, VmFactory}; +use cfx_executor::{ + machine::{new_machine_with_builtin, VmFactory}, + spec::CommonParams, +}; pub fn create_simple_block_impl( parent_hash: H256, ref_hashes: Vec, height: u64, nonce: U256, @@ -181,7 +184,9 @@ pub fn initialize_synchronization_graph_with_data_manager( data_man: Arc, beta: u64, h: u64, tcr: u64, tcb: u64, era_epoch_count: u64, pow: Arc, vm: VmFactory, ) -> (Arc, Arc) { - let machine = Arc::new(new_machine_with_builtin(Default::default(), vm)); + let mut params = CommonParams::default(); + params.transition_heights.cip1559 = u64::MAX; + let machine = Arc::new(new_machine_with_builtin(params, vm)); let mut rng = StdRng::from_seed([0u8; 32]); let pos_verifier = Arc::new(PosVerifier::new( None, diff --git a/crates/cfxcore/core/src/transaction_pool/mod.rs b/crates/cfxcore/core/src/transaction_pool/mod.rs index d609ef3d82..20826d0488 100644 --- a/crates/cfxcore/core/src/transaction_pool/mod.rs +++ b/crates/cfxcore/core/src/transaction_pool/mod.rs @@ -22,10 +22,15 @@ use account_cache::AccountCache; use cfx_executor::{ machine::Machine, spec::TransitionsEpochHeight, state::State, }; -use cfx_parameters::block::DEFAULT_TARGET_BLOCK_GAS_LIMIT; +use cfx_parameters::{ + block::DEFAULT_TARGET_BLOCK_GAS_LIMIT, + consensus_internal::ELASTICITY_MULTIPLIER, +}; use cfx_statedb::{Result as StateDbResult, StateDb}; use cfx_storage::{StateIndex, StorageManagerTrait}; -use cfx_types::{AddressWithSpace as Address, AllChainID, Space, H256, U256}; +use cfx_types::{ + AddressWithSpace as Address, AllChainID, Space, SpaceMap, H256, U256, +}; use cfx_vm_types::Spec; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use metrics::{ @@ -34,8 +39,8 @@ use metrics::{ }; use parking_lot::{Mutex, RwLock}; use primitives::{ - block::BlockHeight, Account, SignedTransaction, Transaction, - TransactionWithSignature, + block::BlockHeight, block_header::compute_next_price_tuple, Account, + SignedTransaction, Transaction, TransactionWithSignature, }; use std::{ cmp::{max, min}, @@ -374,9 +379,6 @@ impl TransactionPool { let mut failure = HashMap::new(); let current_best_info = self.consensus_best_info.lock().clone(); - // filter out invalid transactions. - let mut index = 0; - let (chain_id, best_height, best_block_number) = { ( current_best_info.best_chain_id(), @@ -387,9 +389,11 @@ impl TransactionPool { // FIXME: Needs further discussion here, some transactions may be valid // and invalid back and forth does this matters? But for the epoch // height check, it may also become valid and invalid back and forth. - let vm_spec = self.machine.spec(best_block_number); + let vm_spec = self.machine.spec(best_block_number, best_height); let transitions = &self.machine.params().transition_heights; + // filter out invalid transactions. + let mut index = 0; while let Some(tx) = transactions.get(index) { match self.verify_transaction_tx_pool( tx, @@ -498,7 +502,7 @@ impl TransactionPool { }; // FIXME: Needs further discussion here, some transactions may be valid // and invalid back and forth does this matters? - let vm_spec = self.machine.spec(best_block_number); + let vm_spec = self.machine.spec(best_block_number, best_height); let transitions = &self.machine.params().transition_heights; while let Some(tx) = signed_transactions.get(index) { @@ -731,6 +735,104 @@ impl TransactionPool { ) } + pub fn pack_transactions_1559<'a>( + &self, num_txs: usize, block_gas_limit: U256, + parent_base_price: SpaceMap, block_size_limit: usize, + mut best_epoch_height: u64, mut best_block_number: u64, + ) -> (Vec>, SpaceMap) { + let mut inner = self.inner.write_with_metric(&PACK_TRANSACTION_LOCK); + best_epoch_height += 1; + // The best block number is not necessary an exact number. + best_block_number += 1; + + let spec = self.machine.spec(best_block_number, best_epoch_height); + let transitions = &self.machine.params().transition_heights; + + let validity = |tx: &SignedTransaction| { + self.verification_config.fast_recheck( + tx, + best_epoch_height, + transitions, + &spec, + ) + }; + + inner.pack_transactions_1559( + num_txs, + block_gas_limit, + parent_base_price, + block_size_limit, + best_epoch_height, + &self.machine, + validity, + ) + } + + // A helper function for python test. Not intented to be used in the + // production mode because of its inefficiency + // LINT: this function should not belongs to txpool, since it does not + // access the pools. However, since transaction pool has context for + // computing the base price, it is the most proper position at this + // time. May be fixed in future refactoring. + pub fn compute_1559_base_price<'a, I>( + &self, parent_hash: &H256, block_gas_limit: U256, txs: I, + ) -> Result>, String> + where I: Iterator + 'a { + let parent = self + .data_man + .block_header_by_hash(parent_hash) + .ok_or("Cannot find parent block")?; + let current_height = parent.height() + 1; + + let params = self.machine.params(); + let cip_1559_height = params.transition_heights.cip1559; + if current_height < cip_1559_height { + return Ok(None); + } + + let mut gas_used = SpaceMap::default(); + let mut min_gas_price = + SpaceMap::new(U256::max_value(), U256::max_value()); + for tx in txs { + gas_used[tx.space()] += *tx.gas_limit(); + min_gas_price[tx.space()] = + min_gas_price[tx.space()].min(*tx.gas_limit()); + } + + let core_gas_limit = block_gas_limit * 9 / 10; + let eth_gas_limit = if params.can_pack_evm_transaction(current_height) { + block_gas_limit * 5 / 10 + } else { + U256::zero() + }; + let gas_target = + SpaceMap::new(core_gas_limit, eth_gas_limit).map_all(|x| x / 2); + + let parent_base_price = if current_height == cip_1559_height { + params.init_base_price() + } else { + parent.base_price().unwrap() + }; + + let min_base_price = params.min_base_price(); + + let base_price = SpaceMap::zip4( + gas_target, + gas_used, + parent_base_price, + min_base_price, + ) + .map_all(compute_next_price_tuple); + + for space in [Space::Native, Space::Ethereum] { + if base_price[space] > min_gas_price[space] { + return Err(format!("Not sufficient min price in space {:?}, expected {:?}, actual {:?}", space, base_price[space], min_gas_price[space])); + } + } + + Ok(Some(base_price)) + } + pub fn notify_modified_accounts( &self, accounts_from_execution: Vec, ) { @@ -821,7 +923,7 @@ impl TransactionPool { }; // FIXME: Needs further discussion here, some transactions may be valid // and invalid back and forth, does this matters? - let vm_spec = self.machine.spec(best_block_number); + let vm_spec = self.machine.spec(best_block_number, best_height); let transitions = &self.machine.params().transition_heights; while let Some(tx) = recycle_tx_buffer.pop() { @@ -862,10 +964,46 @@ impl TransactionPool { Ok(()) } + // For RPC use only + pub fn get_best_info_with_parent_base_price( + &self, + ) -> (Arc, Option>) { + let consensus_best_info_clone = self.consensus_best_info.lock().clone(); + debug!( + "get_best_info_with_base_price: {:?}", + consensus_best_info_clone + ); + + let params = self.machine.params(); + let parent_block = self + .data_man + .block_header_by_hash(&consensus_best_info_clone.best_block_hash) + // The parent block must exists. + .expect(&concat!(file!(), ":", line!(), ":", column!())); + + let cip1559_height = params.transition_heights.cip1559; + let pack_height = consensus_best_info_clone.best_epoch_number + 1; + + ( + consensus_best_info_clone, + if pack_height <= cip1559_height { + None + } else { + // TODO: should we compute for the current base_price? + Some(parent_block.base_price().unwrap()) + }, + ) + } + pub fn get_best_info_with_packed_transactions( &self, num_txs: usize, block_size_limit: usize, additional_transactions: Vec>, - ) -> (Arc, U256, Vec>) { + ) -> ( + Arc, + U256, + Vec>, + Option>, + ) { // We do not need to hold the lock because it is fine for us to generate // blocks that are slightly behind the best state. // We do not want to stall the consensus thread. @@ -875,16 +1013,25 @@ impl TransactionPool { consensus_best_info_clone ); - let parent_block_gas_limit = self + let params = self.machine.params(); + + let cip1559_height = params.transition_heights.cip1559; + let pack_height = consensus_best_info_clone.best_epoch_number + 1; + + let parent_block = self .data_man .block_header_by_hash(&consensus_best_info_clone.best_block_hash) // The parent block must exists. - .expect(&concat!(file!(), ":", line!(), ":", column!())) - .gas_limit() - .clone(); - - let gas_limit_divisor = self.machine.params().gas_limit_bound_divisor; - let min_gas_limit = self.machine.params().min_gas_limit; + .expect(&concat!(file!(), ":", line!(), ":", column!())); + let parent_block_gas_limit = *parent_block.gas_limit() + * if cip1559_height == pack_height { + ELASTICITY_MULTIPLIER + } else { + 1 + }; + + let gas_limit_divisor = params.gas_limit_bound_divisor; + let min_gas_limit = params.min_gas_limit; assert!(parent_block_gas_limit >= min_gas_limit); let gas_lower = max( parent_block_gas_limit - parent_block_gas_limit / gas_limit_divisor @@ -895,24 +1042,80 @@ impl TransactionPool { + parent_block_gas_limit / gas_limit_divisor - 1; - let target_gas_limit = self.config.target_block_gas_limit.into(); - let self_gas_limit = min(max(target_gas_limit, gas_lower), gas_upper); - let evm_gas_limit = if self.machine.params().can_pack_evm_transaction( - consensus_best_info_clone.best_epoch_number + 1, - ) { - self_gas_limit / self.machine.params().evm_transaction_gas_ratio + let target_gas_limit = self.config.target_block_gas_limit + * if pack_height >= cip1559_height { + ELASTICITY_MULTIPLIER as u64 + } else { + 1 + }; + + let self_gas_limit = + min(max(target_gas_limit.into(), gas_lower), gas_upper); + + let (transactions_from_pool, maybe_base_price) = if pack_height + < cip1559_height + { + let evm_gas_limit = if self + .machine + .params() + .can_pack_evm_transaction(pack_height) + { + self_gas_limit / params.evm_transaction_gas_ratio + } else { + U256::zero() + }; + + let txs = self.pack_transactions( + num_txs, + self_gas_limit.clone(), + evm_gas_limit, + block_size_limit, + consensus_best_info_clone.best_epoch_number, + consensus_best_info_clone.best_block_number, + ); + (txs, None) } else { - U256::zero() - }; + let parent_base_price = if pack_height == cip1559_height { + params.init_base_price() + } else { + parent_block.base_price().unwrap() + }; + + let (txs, packing_base_price) = self.pack_transactions_1559( + num_txs, + self_gas_limit.clone(), + parent_base_price, + block_size_limit, + consensus_best_info_clone.best_epoch_number, + consensus_best_info_clone.best_block_number, + ); - let transactions_from_pool = self.pack_transactions( - num_txs, - self_gas_limit.clone(), - evm_gas_limit, - block_size_limit, - consensus_best_info_clone.best_epoch_number, - consensus_best_info_clone.best_block_number, - ); + let mut base_price = packing_base_price; + + // May only happens in test mode + if !additional_transactions.is_empty() { + let iter = additional_transactions + .iter() + .chain(txs.iter()) + .map(|x| &**x); + match self.compute_1559_base_price( + &parent_block.hash(), + self_gas_limit, + iter, + ) { + Ok(Some(p)) => { + base_price = p; + } + Ok(None) => { + warn!("Should not happen"); + } + Err(e) => { + error!("Cannot compute base price with additinal transactions: {}", e); + } + } + } + (txs, Some(base_price)) + }; let transactions = [ additional_transactions.as_slice(), @@ -920,7 +1123,12 @@ impl TransactionPool { ] .concat(); - (consensus_best_info_clone, self_gas_limit, transactions) + ( + consensus_best_info_clone, + self_gas_limit, + transactions, + maybe_base_price, + ) } fn best_executed_state( diff --git a/crates/cfxcore/core/src/transaction_pool/nonce_pool/mod.rs b/crates/cfxcore/core/src/transaction_pool/nonce_pool/mod.rs index 0233331dd8..969ea9f3be 100644 --- a/crates/cfxcore/core/src/transaction_pool/nonce_pool/mod.rs +++ b/crates/cfxcore/core/src/transaction_pool/nonce_pool/mod.rs @@ -78,8 +78,8 @@ impl TxWithReadyInfo { if let Transaction::Native(ref other) = x.unsigned { // FIXME: Use epoch_bound in spec. It's still a part of // normal config. - if tx.epoch_height - > other.epoch_height.saturating_add( + if *tx.epoch_height() + > other.epoch_height().saturating_add( TRANSACTION_DEFAULT_EPOCH_BOUND.saturating_mul(2), ) { @@ -91,7 +91,7 @@ impl TxWithReadyInfo { // verification anymore and should be dropped. return true; } - tx.epoch_height > other.epoch_height + tx.epoch_height() > other.epoch_height() } else { // Should be unreachable. But I'm not very sure about this. // Return false is safe. @@ -128,7 +128,7 @@ impl TxWithReadyInfo { }; let storage_collateral_requirement = if let Transaction::Native(ref tx) = transaction.unsigned { - U256::from(tx.storage_limit - sponsored_storage) + U256::from(*tx.storage_limit() - sponsored_storage) * *DRIPS_PER_STORAGE_COLLATERAL_UNIT } else { U256::zero() @@ -390,7 +390,8 @@ mod nonce_pool_test { use cfx_types::{Address, U128, U256}; use keylib::{Generator, KeyPair, Random}; use primitives::{ - Action, NativeTransaction, SignedTransaction, Transaction, + transaction::native_transaction::NativeTransaction, Action, + SignedTransaction, Transaction, }; use rand::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; diff --git a/crates/cfxcore/core/src/transaction_pool/transaction_pool_inner.rs b/crates/cfxcore/core/src/transaction_pool/transaction_pool_inner.rs index ec206be178..612d1c3c60 100644 --- a/crates/cfxcore/core/src/transaction_pool/transaction_pool_inner.rs +++ b/crates/cfxcore/core/src/transaction_pool/transaction_pool_inner.rs @@ -7,7 +7,10 @@ use super::{ use crate::verification::{PackingCheckResult, VerificationConfig}; use cfx_executor::machine::Machine; use cfx_packing_pool::{PackingPool, PackingPoolConfig}; -use cfx_parameters::staking::DRIPS_PER_STORAGE_COLLATERAL_UNIT; +use cfx_parameters::{ + consensus_internal::ELASTICITY_MULTIPLIER, + staking::DRIPS_PER_STORAGE_COLLATERAL_UNIT, +}; use cfx_statedb::Result as StateDbResult; use cfx_types::{ @@ -19,14 +22,14 @@ use metrics::{ register_meter_with_group, Counter, CounterUsize, Meter, MeterTimer, }; use primitives::{ - Account, Action, SignedTransaction, Transaction, TransactionWithSignature, + block_header::compute_next_price, Account, Action, SignedTransaction, + Transaction, TransactionWithSignature, }; use rand::SeedableRng; use rand_xorshift::XorShiftRng; use rlp::*; use serde::Serialize; use std::{ - cmp::Ordering, collections::{BTreeSet, HashMap}, sync::Arc, time::{SystemTime, UNIX_EPOCH}, @@ -104,10 +107,33 @@ impl DeferredPool { self.packing_pool.apply_all(|x| x.clear()); } + fn estimate_packing_gas_limit( + &self, space: Space, gas_target: U256, parent_base_price: U256, + min_base_price: U256, + ) -> (U256, U256) { + let estimated_gas_limit = self + .packing_pool + .in_space(space) + .estimate_packing_gas_limit( + gas_target, + parent_base_price, + min_base_price, + ); + let packing_gas_limit = U256::min(gas_target * 2, estimated_gas_limit); + let price_limit = compute_next_price( + gas_target, + packing_gas_limit, + parent_base_price, + min_base_price, + ); + (packing_gas_limit, price_limit) + } + #[inline] fn packing_sampler<'a, F: Fn(&SignedTransaction) -> PackingCheckResult>( &'a mut self, space: Space, block_gas_limit: U256, - block_size_limit: usize, tx_num_limit: usize, validity: F, + block_size_limit: usize, tx_num_limit: usize, tx_min_price: U256, + validity: F, ) -> (Vec>, U256, usize) { if block_gas_limit.is_zero() || block_size_limit == 0 @@ -145,6 +171,9 @@ impl DeferredPool { .tx_sampler(&mut rng, block_gas_limit.into()) { 'sender: for tx in sender_txs.iter() { + if tx.gas_price() < &tx_min_price { + break 'sender; + } match validity(&*tx) { PackingCheckResult::Pack => {} PackingCheckResult::Pending => { @@ -459,29 +488,6 @@ impl DeferredPool { } } -#[derive(DeriveMallocSizeOf, Clone)] -struct PriceOrderedTransaction(Arc); - -impl PartialEq for PriceOrderedTransaction { - fn eq(&self, other: &Self) -> bool { - self.0.gas_price().eq(other.0.gas_price()) - } -} - -impl Eq for PriceOrderedTransaction {} - -impl PartialOrd for PriceOrderedTransaction { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PriceOrderedTransaction { - fn cmp(&self, other: &Self) -> Ordering { - self.0.gas_price().cmp(other.0.gas_price()) - } -} - #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub enum TransactionStatus { @@ -586,6 +592,9 @@ impl TransactionPoolInner { } } + #[cfg(test)] + pub fn new_for_test() -> Self { Self::new(50_000, 3_000_000, 50, 4) } + pub fn clear(&mut self) { self.deferred_pool.clear(); self.ready_nonces_and_balances.clear(); @@ -787,6 +796,22 @@ impl TransactionPoolInner { pub fn capacity(&self) -> usize { self.capacity } + #[cfg(test)] + fn insert_transaction_for_test( + &mut self, transaction: Arc, sender_nonce: U256, + ) -> InsertResult { + let sender = transaction.sender(); + let res = self.insert_transaction_without_readiness_check( + transaction, + false, + true, + (sender_nonce, U256::from(u64::MAX)), + (0.into(), 0), + ); + self.recalculate_readiness(&sender, sender_nonce, U256::from(u64::MAX)); + res + } + // the new inserting will fail if tx_pool is full (even if `force` is true) fn insert_transaction_without_readiness_check( &mut self, transaction: Arc, packed: bool, @@ -981,6 +1006,7 @@ impl TransactionPoolInner { .unwrap_or(state_nonce) } + #[allow(dead_code)] fn recalculate_readiness_with_local_info( &mut self, addr: &AddressWithSpace, ) { @@ -1066,7 +1092,7 @@ impl TransactionPoolInner { return packed_transactions; } - let spec = machine.spec(best_block_number); + let spec = machine.spec(best_block_number, best_epoch_height); let transitions = &machine.params().transition_heights; let validity = |tx: &SignedTransaction| { @@ -1084,6 +1110,7 @@ impl TransactionPoolInner { std::cmp::min(block_gas_limit, evm_gas_limit), block_size_limit, num_txs, + U256::zero(), validity, ); packed_transactions.extend_from_slice(&sampled_tx); @@ -1093,6 +1120,7 @@ impl TransactionPoolInner { block_gas_limit - used_gas, block_size_limit - used_size, num_txs - sampled_tx.len(), + U256::zero(), validity, ); packed_transactions.extend_from_slice(&sampled_tx); @@ -1112,6 +1140,164 @@ impl TransactionPoolInner { packed_transactions } + pub fn pack_transactions_1559<'a>( + &mut self, num_txs: usize, block_gas_limit: U256, + parent_base_price: SpaceMap, block_size_limit: usize, + best_epoch_height: u64, machine: &Machine, + validity: impl Fn(&SignedTransaction) -> PackingCheckResult, + ) -> (Vec>, SpaceMap) { + let mut packed_transactions: Vec> = Vec::new(); + if num_txs == 0 { + return (packed_transactions, parent_base_price); + } + + debug!( + "Packing transaction for 1559, parent base price {:?}", + parent_base_price + ); + + let mut block_base_price = parent_base_price.clone(); + + let can_pack_evm = + machine.params().can_pack_evm_transaction(best_epoch_height); + + let (evm_packed_tx_num, evm_used_size) = if can_pack_evm { + let gas_target = block_gas_limit * 5 / 10 / ELASTICITY_MULTIPLIER; + let parent_base_price = parent_base_price[Space::Ethereum]; + let min_base_price = + machine.params().min_base_price()[Space::Ethereum]; + + let (packing_gas_limit, tx_min_price) = + self.deferred_pool.estimate_packing_gas_limit( + Space::Ethereum, + gas_target, + parent_base_price, + min_base_price, + ); + debug!( + "Packing plan (espace): gas limit: {:?}, tx min price: {:?}", + packing_gas_limit, tx_min_price + ); + let (sampled_tx, used_gas, used_size) = + self.deferred_pool.packing_sampler( + Space::Ethereum, + packing_gas_limit, + block_size_limit, + num_txs, + tx_min_price, + &validity, + ); + + // Recompute the base price, it should be <= estimated base price, + // since the actual used gas is <= estimated limit + let base_price = compute_next_price( + gas_target, + used_gas, + parent_base_price, + min_base_price, + ); + + if base_price <= tx_min_price { + debug!( + "Packing result (espace): gas used: {:?}, base price: {:?}", + used_gas, base_price + ); + block_base_price[Space::Ethereum] = base_price; + packed_transactions.extend_from_slice(&sampled_tx); + + (sampled_tx.len(), used_size) + } else { + // Should be unreachable + warn!( + "Inconsistent packing result (espace): gas used: {:?}, base price: {:?}", + used_gas, base_price + ); + block_base_price[Space::Ethereum] = compute_next_price( + gas_target, + U256::zero(), + parent_base_price, + min_base_price, + ); + (0, 0) + } + } else { + (0, 0) + }; + + { + let gas_target = block_gas_limit * 9 / 10 / ELASTICITY_MULTIPLIER; + let parent_base_price = parent_base_price[Space::Native]; + let min_base_price = + machine.params().min_base_price()[Space::Native]; + + let (packing_gas_limit, tx_min_price) = + self.deferred_pool.estimate_packing_gas_limit( + Space::Native, + gas_target, + parent_base_price, + min_base_price, + ); + + debug!( + "Packing plan (core space): gas limit: {:?}, tx min price: {:?}", + packing_gas_limit, tx_min_price + ); + + let (sampled_tx, used_gas, _) = self.deferred_pool.packing_sampler( + Space::Native, + packing_gas_limit, + block_size_limit - evm_used_size, + num_txs - evm_packed_tx_num, + tx_min_price, + &validity, + ); + + // Recompute the base price, it should be <= estimated base price, + // since the actual used gas is <= estimated limit + let base_price = compute_next_price( + gas_target, + used_gas, + parent_base_price, + min_base_price, + ); + + if base_price <= tx_min_price { + debug!( + "Packing result (core space): gas used: {:?}, base price: {:?}", + used_gas, base_price + ); + block_base_price[Space::Native] = base_price; + packed_transactions.extend_from_slice(&sampled_tx); + } else { + // Should be unreachable + warn!( + "Inconsistent packing result (core space): gas used: {:?}, base price: {:?}", + used_gas, base_price + ); + block_base_price[Space::Native] = compute_next_price( + gas_target, + U256::zero(), + parent_base_price, + min_base_price, + ); + } + } + + if log::max_level() >= log::Level::Debug { + let mut rlp_s = RlpStream::new(); + for tx in &packed_transactions { + rlp_s.append::(&**tx); + } + debug!( + "After packing packed_transactions: {}, rlp size: {}", + packed_transactions.len(), + rlp_s.out().len(), + ); + } + + (packed_transactions, block_base_price) + } + pub fn notify_modified_accounts( &mut self, accounts_from_execution: Vec, ) { @@ -1207,17 +1393,17 @@ impl TransactionPoolInner { ); match transaction.unsigned { Transaction::Native(ref utx) => { - need_balance += utx.value.clone(); + need_balance += utx.value().clone(); if sponsored_gas == U256::from(0) { need_balance += estimate_gas_fee; } if sponsored_storage == 0 { - need_balance += U256::from(utx.storage_limit) + need_balance += U256::from(*utx.storage_limit()) * *DRIPS_PER_STORAGE_COLLATERAL_UNIT; } } Transaction::Ethereum(ref utx) => { - need_balance += utx.value.clone(); + need_balance += utx.value().clone(); need_balance += estimate_gas_fee; } } @@ -1277,7 +1463,7 @@ impl TransactionPoolInner { // Compute sponsored_gas for `transaction` if let Transaction::Native(ref utx) = transaction.unsigned { - if let Action::Call(ref callee) = utx.action { + if let Action::Call(ref callee) = utx.action().clone() { // FIXME: This is a quick fix for performance issue. if callee.is_contract_address() { if let Some(sponsor_info) = @@ -1305,15 +1491,15 @@ impl TransactionPoolInner { && estimated_gas <= sponsor_info.sponsor_balance_for_gas { - sponsored_gas = utx.gas; + sponsored_gas = utx.gas().clone(); } let estimated_collateral = - U256::from(utx.storage_limit) + U256::from(*utx.storage_limit()) * *DRIPS_PER_STORAGE_COLLATERAL_UNIT; if estimated_collateral <= sponsor_info.sponsor_balance_for_collateral + sponsor_info.unused_storage_points() { - sponsored_storage = utx.storage_limit; + sponsored_storage = *utx.storage_limit(); } } } @@ -1325,39 +1511,66 @@ impl TransactionPoolInner { } #[cfg(test)] -mod test_transaction_pool_inner { - use super::{DeferredPool, InsertResult, TxWithReadyInfo}; - use cfx_types::{Address, AddressSpaceUtil, U256}; +mod tests { + use crate::verification::PackingCheckResult; + + use super::{ + DeferredPool, InsertResult, TransactionPoolInner, TxWithReadyInfo, + }; + use cfx_executor::{ + machine::{new_machine, Machine, VmFactory}, + spec::CommonParams, + }; + use cfx_types::{Address, AddressSpaceUtil, Space, SpaceMap, U256}; + use itertools::Itertools; use keylib::{Generator, KeyPair, Random}; use primitives::{ - Action, NativeTransaction, SignedTransaction, Transaction, + block_header::compute_next_price_tuple, + transaction::{ + native_transaction::NativeTransaction, Eip155Transaction, + }, + Action, SignedTransaction, Transaction, }; use std::sync::Arc; fn new_test_tx( - sender: &KeyPair, nonce: usize, gas_price: usize, value: usize, + sender: &KeyPair, nonce: usize, gas_price: usize, gas: usize, + value: usize, space: Space, ) -> Arc { - Arc::new( - Transaction::from(NativeTransaction { + let tx: Transaction = match space { + Space::Native => NativeTransaction { nonce: U256::from(nonce), gas_price: U256::from(gas_price), - gas: U256::from(50000), + gas: U256::from(gas), action: Action::Call(Address::random()), value: U256::from(value), storage_limit: 0, epoch_height: 0, chain_id: 1, data: Vec::new(), - }) - .sign(sender.secret()), - ) + } + .into(), + Space::Ethereum => Eip155Transaction { + nonce: U256::from(nonce), + gas_price: U256::from(gas_price), + gas: U256::from(gas), + action: Action::Call(Address::random()), + value: U256::from(value), + chain_id: Some(1), + data: Vec::new(), + } + .into(), + }; + Arc::new(tx.sign(sender.secret())) } fn new_test_tx_with_read_info( sender: &KeyPair, nonce: usize, gas_price: usize, value: usize, packed: bool, ) -> TxWithReadyInfo { - let transaction = new_test_tx(sender, nonce, gas_price, value); + let gas = 50000; + let transaction = + new_test_tx(sender, nonce, gas_price, gas, value, Space::Native); TxWithReadyInfo::new(transaction, packed, U256::from(0), 0) } @@ -1579,4 +1792,125 @@ mod test_transaction_pool_inner { None ); } + + fn pack_transactions_1559_checked( + pool: &mut TransactionPoolInner, machine: &Machine, + ) { + let parent_base_price = SpaceMap::new(100, 200).map_all(U256::from); + let block_gas_limit = U256::from(6000); + let best_epoch_height = 20; + + let (txs, base_price) = pool.pack_transactions_1559( + usize::MAX, + block_gas_limit, + parent_base_price, + usize::MAX, + best_epoch_height, + machine, + |_| PackingCheckResult::Pack, + ); + + let params = machine.params(); + + let core_gas_limit = block_gas_limit * 9 / 10; + let eth_gas_limit = + if params.can_pack_evm_transaction(best_epoch_height) { + block_gas_limit * 5 / 10 + } else { + U256::zero() + }; + + let gas_target = + SpaceMap::new(core_gas_limit, eth_gas_limit).map_all(|x| x / 2); + + let mut gas_used = SpaceMap::default(); + let mut min_gas_price = + SpaceMap::new(U256::max_value(), U256::max_value()); + + for tx in txs { + gas_used[tx.space()] += *tx.gas_limit(); + min_gas_price[tx.space()] = + min_gas_price[tx.space()].min(*tx.gas_price()); + } + + let min_base_price = params.min_base_price(); + + let expected_base_price = SpaceMap::zip4( + gas_target, + gas_used, + parent_base_price, + min_base_price, + ) + .map_all(compute_next_price_tuple); + + assert_eq!(expected_base_price, base_price); + assert!(gas_used[Space::Native] <= core_gas_limit); + assert!(gas_used[Space::Ethereum] <= eth_gas_limit); + + for space in [Space::Native, Space::Ethereum] { + assert!(base_price[space] <= min_gas_price[space]); + } + } + + #[test] + fn test_pack_eip1559_transactions() { + let mut pool = TransactionPoolInner::new_for_test(); + + let mut params = CommonParams::default(); + params.min_base_price = SpaceMap::new(100, 200).map_all(U256::from); + + let machine = Arc::new(new_machine(params, VmFactory::default())); + + let test_block_limit = SpaceMap::new(5400, 3000); + + let senders: Vec<_> = (0..20) + .into_iter() + .map(|_| Random.generate().unwrap()) + .collect(); + + let tasks = [1, 2, 3] + .into_iter() + .cartesian_product( + /* gas_price */ [50usize, 95, 100, 105, 150, 1000], + ) + .cartesian_product( + /* gas_limit_percent */ [5usize, 10, 40, 60, 100], + ) + .cartesian_product(/* price_increasing */ [0usize, 1]); + + for (((space_bits, gas_price), gas_limit_percent), price_inc) in tasks { + let tx_gas_limit = + test_block_limit.map_all(|x| x * gas_limit_percent / 100); + + for (idx, sender) in senders.iter().enumerate() { + let gas_price = gas_price + idx * price_inc; + + if space_bits & 0x1 != 0 { + let tx = new_test_tx( + sender, + 0, + gas_price, + tx_gas_limit[Space::Native], + 0, + Space::Native, + ); + pool.insert_transaction_for_test(tx, U256::zero()); + } + + if space_bits & 0x2 != 0 { + let tx = new_test_tx( + sender, + 0, + gas_price * 2, + tx_gas_limit[Space::Ethereum], + 0, + Space::Ethereum, + ); + pool.insert_transaction_for_test(tx, U256::zero()); + } + } + pack_transactions_1559_checked(&mut pool, &machine); + pool.clear(); + } + } } diff --git a/crates/cfxcore/core/src/verification.rs b/crates/cfxcore/core/src/verification.rs index 587b242f3d..17f8d2adbe 100644 --- a/crates/cfxcore/core/src/verification.rs +++ b/crates/cfxcore/core/src/verification.rs @@ -11,18 +11,22 @@ use crate::{ use cfx_executor::{ executive::gas_required_for, machine::Machine, spec::TransitionsEpochHeight, }; -use cfx_parameters::block::*; +use cfx_parameters::{block::*, consensus_internal::ELASTICITY_MULTIPLIER}; use cfx_storage::{ into_simple_mpt_key, make_simple_mpt, simple_mpt_merkle_root, simple_mpt_proof, SimpleMpt, TrieProof, }; use cfx_types::{ - address_util::AddressUtil, AllChainID, BigEndianHash, Space, H256, U256, + address_util::AddressUtil, AllChainID, BigEndianHash, Space, SpaceMap, + H256, U256, }; use cfx_vm_types::Spec; use primitives::{ block::BlockHeight, - transaction::{NativeTransaction, TransactionError}, + block_header::compute_next_price_tuple, + transaction::{ + native_transaction::TypedNativeTransaction, TransactionError, + }, Action, Block, BlockHeader, BlockReceipts, MerkleHash, Receipt, SignedTransaction, Transaction, TransactionWithSignature, }; @@ -364,6 +368,16 @@ impl VerificationConfig { } } + if header.height() >= self.machine.params().transition_heights.cip1559 { + if header.base_price().is_none() { + bail!(BlockError::MissingBaseFee); + } + } else { + if header.base_price().is_some() { + bail!(BlockError::UnexpectedBaseFee); + } + } + // Note that this is just used to rule out deprecated blocks, so the // change of header struct actually happens before the change of // reward is reflected in the state root. The first state root @@ -454,19 +468,11 @@ impl VerificationConfig { ) -> Result<(), Error> { self.verify_block_integrity(block)?; - let mut block_size = 0; - let mut block_total_gas = U256::zero(); - let block_height = block.block_header.height(); - let evm_space_gas_limit = - if self.machine.params().can_pack_evm_transaction(block_height) { - *block.block_header.gas_limit() - / self.machine.params().evm_transaction_gas_ratio - } else { - U256::zero() - }; - let mut evm_total_gas = U256::zero(); + + let mut block_size = 0; let transitions = &self.machine.params().transition_heights; + for t in &block.transactions { self.verify_transaction_common( t, @@ -476,10 +482,6 @@ impl VerificationConfig { VerifyTxMode::Remote, )?; block_size += t.rlp_size(); - block_total_gas += *t.gas_limit(); - if t.space() == Space::Ethereum { - evm_total_gas += *t.gas_limit(); - } } if block_size > self.max_block_size_in_bytes { @@ -491,11 +493,47 @@ impl VerificationConfig { }, ))); } + Ok(()) + } + + pub fn verify_sync_graph_ready_block( + &self, block: &Block, parent: &BlockHeader, + ) -> Result<(), Error> { + let mut total_gas: SpaceMap = SpaceMap::default(); + for t in &block.transactions { + total_gas[t.space()] += *t.gas_limit(); + } + + if block.block_header.height() + >= self.machine.params().transition_heights.cip1559 + { + self.check_base_fee(block, parent, total_gas)?; + } else { + self.check_hard_gas_limit(block, total_gas)?; + } + Ok(()) + } + + fn check_hard_gas_limit( + &self, block: &Block, total_gas: SpaceMap, + ) -> Result<(), Error> { + let block_height = block.block_header.height(); + + let evm_space_gas_limit = + if self.machine.params().can_pack_evm_transaction(block_height) { + *block.block_header.gas_limit() + / self.machine.params().evm_transaction_gas_ratio + } else { + U256::zero() + }; + + let evm_total_gas = total_gas[Space::Ethereum]; + let block_total_gas = total_gas.map_sum(|x| *x); if evm_total_gas > evm_space_gas_limit { - return Err(From::from(BlockError::InvalidBlockGasLimit( + return Err(From::from(BlockError::InvalidPackedGasLimit( OutOfBounds { - min: Some(evm_space_gas_limit), + min: None, max: Some(evm_space_gas_limit), found: evm_total_gas, }, @@ -503,9 +541,9 @@ impl VerificationConfig { } if block_total_gas > *block.block_header.gas_limit() { - return Err(From::from(BlockError::InvalidBlockGasLimit( + return Err(From::from(BlockError::InvalidPackedGasLimit( OutOfBounds { - min: Some(*block.block_header.gas_limit()), + min: None, max: Some(*block.block_header.gas_limit()), found: block_total_gas, }, @@ -515,13 +553,84 @@ impl VerificationConfig { Ok(()) } + fn check_base_fee( + &self, block: &Block, parent: &BlockHeader, total_gas: SpaceMap, + ) -> Result<(), Error> { + use Space::*; + + let params = self.machine.params(); + let cip1559_init = params.transition_heights.cip1559; + let block_height = block.block_header.height(); + + assert!(block_height >= cip1559_init); + + let core_gas_limit = block.block_header.gas_limit() * 9 / 10; + let espace_gas_limit = + if self.machine.params().can_pack_evm_transaction(block_height) { + block.block_header.gas_limit() * 5 / 10 + } else { + U256::zero() + }; + + if total_gas[Ethereum] > espace_gas_limit { + return Err(From::from(BlockError::InvalidPackedGasLimit( + OutOfBounds { + min: None, + max: Some(espace_gas_limit), + found: total_gas[Ethereum], + }, + ))); + } + + if total_gas[Native] > core_gas_limit { + return Err(From::from(BlockError::InvalidPackedGasLimit( + OutOfBounds { + min: None, + max: Some(core_gas_limit), + found: total_gas[Native], + }, + ))); + } + + let parent_base_price = if block_height == cip1559_init { + params.init_base_price() + } else { + parent.base_price().unwrap() + }; + + let gas_limit = SpaceMap::new(core_gas_limit, espace_gas_limit); + let gas_target = gas_limit.map_all(|x| x / ELASTICITY_MULTIPLIER); + let min_base_price = params.min_base_price(); + + let expected_base_price = SpaceMap::zip4( + gas_target, + total_gas, + parent_base_price, + min_base_price, + ) + .map_all(compute_next_price_tuple); + + let actual_base_price = block.block_header.base_price().unwrap(); + + if actual_base_price != expected_base_price { + return Err(From::from(BlockError::InvalidBasePrice(Mismatch { + expected: expected_base_price, + found: actual_base_price, + }))); + } + + Ok(()) + } + pub fn check_transaction_epoch_bound( - tx: &NativeTransaction, block_height: u64, transaction_epoch_bound: u64, + tx: &TypedNativeTransaction, block_height: u64, + transaction_epoch_bound: u64, ) -> i8 { - if tx.epoch_height.wrapping_add(transaction_epoch_bound) < block_height + if tx.epoch_height().wrapping_add(transaction_epoch_bound) + < block_height { -1 - } else if tx.epoch_height > block_height + transaction_epoch_bound { + } else if *tx.epoch_height() > block_height + transaction_epoch_bound { 1 } else { 0 @@ -529,7 +638,7 @@ impl VerificationConfig { } fn verify_transaction_epoch_height( - tx: &NativeTransaction, block_height: u64, + tx: &TypedNativeTransaction, block_height: u64, transaction_epoch_bound: u64, mode: &VerifyTxMode, ) -> Result<(), TransactionError> { let result = Self::check_transaction_epoch_bound( @@ -543,7 +652,7 @@ impl VerificationConfig { Ok(()) } else { bail!(TransactionError::EpochHeightOutOfBound { - set: tx.epoch_height, + set: *tx.epoch_height(), block_height, transaction_epoch_bound, }); @@ -564,10 +673,15 @@ impl VerificationConfig { transitions: &TransitionsEpochHeight, spec: &Spec, ) -> PackingCheckResult { let cip90a = height >= transitions.cip90a; + let cip1559 = height >= transitions.cip1559; let (can_pack, later_pack) = - if let Transaction::Native(ref tx) = tx.unsigned { - Self::fast_recheck_inner(spec, |mode: &VerifyTxMode| { + Self::fast_recheck_inner(spec, |mode: &VerifyTxMode| { + if !Self::check_eip1559_transaction(tx, cip1559, mode) { + return false; + } + + if let Transaction::Native(ref tx) = tx.unsigned { Self::verify_transaction_epoch_height( tx, height, @@ -575,12 +689,11 @@ impl VerificationConfig { mode, ) .is_ok() - }) - } else { - Self::fast_recheck_inner(spec, |mode: &VerifyTxMode| { + } else { Self::check_eip155_transaction(tx, cip90a, mode) - }) - }; + } + }); + match (can_pack, later_pack) { (true, _) => PackingCheckResult::Pack, (false, true) => PackingCheckResult::Pending, @@ -597,6 +710,7 @@ impl VerificationConfig { mode: VerifyTxMode, ) -> Result<(), TransactionError> { tx.check_low_s()?; + tx.check_y_parity()?; // Disallow unsigned transactions if tx.is_unsigned() { @@ -644,6 +758,8 @@ impl VerificationConfig { // ****************************************** let cip76 = height >= transitions.cip76; let cip90a = height >= transitions.cip90a; + let cip130 = height >= transitions.cip130; + let cip1559 = height >= transitions.cip1559; if let Transaction::Native(ref tx) = tx.unsigned { Self::verify_transaction_epoch_height( @@ -655,10 +771,15 @@ impl VerificationConfig { } if !Self::check_eip155_transaction(tx, cip90a, &mode) { - bail!(TransactionError::InvalidEthereumLike); + bail!(TransactionError::FutureTransactionType); + } + + if !Self::check_eip1559_transaction(tx, cip1559, &mode) { + bail!(TransactionError::FutureTransactionType) } Self::check_gas_limit(tx, cip76, &mode)?; + Self::check_gas_limit_with_calldata(tx, cip130)?; Ok(()) } @@ -677,6 +798,21 @@ impl VerificationConfig { } } + fn check_eip1559_transaction( + tx: &TransactionWithSignature, cip1559: bool, mode: &VerifyTxMode, + ) -> bool { + if tx.is_legacy() { + return true; + } + + use VerifyTxLocalMode::*; + match mode { + VerifyTxMode::Local(Full, _spec) => cip1559, + VerifyTxMode::Local(MaybeLater, _spec) => true, + VerifyTxMode::Remote => cip1559, + } + } + /// Check transaction intrinsic gas. Influenced by CIP-76. fn check_gas_limit( tx: &TransactionWithSignature, cip76: bool, mode: &VerifyTxMode, @@ -696,6 +832,7 @@ impl VerificationConfig { let tx_intrinsic_gas = gas_required_for( *tx.action() == Action::Create, &tx.data(), + tx.access_list(), &spec, ); if *tx.gas() < (tx_intrinsic_gas as usize).into() { @@ -709,6 +846,23 @@ impl VerificationConfig { Ok(()) } + fn check_gas_limit_with_calldata( + tx: &TransactionWithSignature, cip130: bool, + ) -> Result<(), TransactionError> { + if !cip130 { + return Ok(()); + } + let data_length = tx.data().len(); + let min_gas_limit = data_length.saturating_mul(100); + if tx.gas() < &U256::from(min_gas_limit) { + bail!(TransactionError::NotEnoughBaseGas { + required: min_gas_limit.into(), + got: *tx.gas() + }); + } + Ok(()) + } + pub fn check_tx_size( &self, tx: &TransactionWithSignature, ) -> Result<(), TransactionError> { @@ -731,24 +885,20 @@ pub enum PackingCheckResult { #[derive(Copy, Clone)] pub enum VerifyTxMode<'a> { + /// Check transactions in local mode, may have more constraints Local(VerifyTxLocalMode, &'a Spec), - /* Be strict with yourself: We - * apply more checks in packing - * transactions and local - * execution. */ + /// Check transactions for received blocks in sync graph, may have less + /// constraints Remote, - /* Be lenient to others: We apply less - * check for transaction in sync graph. */ } #[derive(Copy, Clone)] pub enum VerifyTxLocalMode { + /// Apply all checks Full, - // Apply all checks. + /// If a transaction is not valid now, but can become valid in the future, + /// the check sould pass MaybeLater, - /* When inserting transactions to tx pool, if its epoch - * height is too large, it can be accept even if it is not - * regarded as a valid transaction. */ } impl<'a> VerifyTxMode<'a> { diff --git a/crates/cfxcore/execute-helper/Cargo.toml b/crates/cfxcore/execute-helper/Cargo.toml index 2716d7de84..49861b8c54 100644 --- a/crates/cfxcore/execute-helper/Cargo.toml +++ b/crates/cfxcore/execute-helper/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-execute-helper" version = "2.0.2" -edition = "2018" +edition = "2021" [dependencies] # substrate-bn = { git = "https://github.com/paritytech/bn", default-features = false, rev="63f8c587356a67b33c7396af98e065b66fca5dda" } @@ -18,7 +18,7 @@ cfx-statedb = { path = "../../dbs/statedb" } cfx-vm-tracer-derive = { path= "../../util/cfx-vm-tracer-derive" } cfx-types = { path = "../../cfx_types" } cfx-vm-types = { path = "../vm-types" } -# cfx-vm-interpreter = { path = "../vm-interpreter" } +cfx-vm-interpreter = { path = "../vm-interpreter" } cfx-executor = { path = "../executor" } error-chain = { version = "0.12", default-features = false } # keccak-hash = "0.5" @@ -35,6 +35,9 @@ rlp_derive = { git = "https://github.com/Conflux-Chain/conflux-parity-deps.git", # rustc-hex = "2.1" serde = { version = "1.0", features = ["rc"] } serde_derive = "1.0" +serde_json = { version = "1.0", default-features = false, features = [ + "alloc", +]} # serde_json = "1.0" solidity-abi = { path= "../../util/solidity-abi" } # solidity-abi-derive = { path="../../util/solidity-abi-derive" } @@ -48,4 +51,10 @@ strum_macros = "0.20" pow-types = {path = "../core/src/pos/types/pow-types" } # impl-trait-for-tuples = "^0.2" # impl-tools = "^0.10" -typemap = "0.3" \ No newline at end of file +typemap = { package = "typemap-ors", version = "1.0"} + +alloy-primitives = { workspace = true } +alloy-sol-types = "0.7.1" +revm = { version = "8.0", default-features = false, features = ["std"] } +alloy-rpc-types-trace = { workspace = true } +geth-tracer = { path = "../geth-tracer" } diff --git a/crates/cfxcore/execute-helper/src/estimation.rs b/crates/cfxcore/execute-helper/src/estimation.rs index 8985ed0694..5036e6bbfc 100644 --- a/crates/cfxcore/execute-helper/src/estimation.rs +++ b/crates/cfxcore/execute-helper/src/estimation.rs @@ -16,9 +16,7 @@ use cfx_types::{ address_util::AddressUtil, Address, AddressSpaceUtil, Space, U256, }; use cfx_vm_types::{self as vm, Env, Spec}; -use primitives::{ - transaction::Action, NativeTransaction, SignedTransaction, Transaction, -}; +use primitives::{transaction::Action, SignedTransaction, Transaction}; use std::{ cmp::{max, min}, fmt::Display, @@ -105,26 +103,27 @@ impl<'a> EstimationContext<'a> { fn sponsored_contract_if_eligible_sender( &self, tx: &SignedTransaction, ty: SponsoredType, ) -> DbResult> { - if let Transaction::Native(NativeTransaction { - action: Action::Call(ref to), - .. - }) = tx.unsigned - { - if to.is_contract_address() { - let sponsor = match ty { - SponsoredType::Gas => self.state.sponsor_for_gas(&to)?, - SponsoredType::Collateral => { - self.state.sponsor_for_collateral(&to)? + if let Transaction::Native(ref native_tx) = tx.unsigned { + if let Action::Call(to) = native_tx.action() { + if to.is_contract_address() { + let sponsor = match ty { + SponsoredType::Gas => { + self.state.sponsor_for_gas(&to)? + } + SponsoredType::Collateral => { + self.state.sponsor_for_collateral(&to)? + } + }; + let has_sponsor = sponsor.map_or(false, |x| !x.is_zero()); + + if has_sponsor + && self.state.check_contract_whitelist( + &to, + &tx.sender().address, + )? + { + return Ok(Some(*to)); } - }; - let has_sponsor = sponsor.map_or(false, |x| !x.is_zero()); - - if has_sponsor - && self - .state - .check_contract_whitelist(&to, &tx.sender().address)? - { - return Ok(Some(*to)); } } } @@ -134,6 +133,10 @@ impl<'a> EstimationContext<'a> { pub fn transact_virtual( &mut self, mut tx: SignedTransaction, request: EstimateRequest, ) -> DbResult<(ExecutionOutcome, EstimateExt)> { + if let Some(outcome) = self.check_cip130(&tx, &request) { + return Ok(outcome); + } + self.process_estimate_request(&mut tx, &request)?; let (executed, overwrite_storage_limit) = match self @@ -148,7 +151,9 @@ impl<'a> EstimationContext<'a> { } ExecutionOutcome::ExecutionErrorBumpNonce(_, executed) => { EstimateExt { - estimated_gas_limit: estimated_gas_limit(executed), + estimated_gas_limit: estimated_gas_limit( + executed, &tx, + ), estimated_storage_limit: storage_limit(executed), } } @@ -166,6 +171,25 @@ impl<'a> EstimationContext<'a> { ) } + fn check_cip130( + &self, tx: &SignedTransaction, request: &EstimateRequest, + ) -> Option<(ExecutionOutcome, EstimateExt)> { + let min_gas_limit = U256::from(tx.data().len() * 100); + if !request.has_gas_limit || *tx.gas_limit() >= min_gas_limit { + return None; + } + + let outcome = ExecutionOutcome::NotExecutedDrop( + cfx_executor::executive::TxDropError::NotEnoughGasLimit { + expected: min_gas_limit, + got: *tx.gas_limit(), + }, + ); + let estimation = Default::default(); + + Some((outcome, estimation)) + } + // For the same transaction, the storage limit paid by user and the // storage limit paid by the sponsor are different values. So // this function will @@ -271,7 +295,7 @@ impl<'a> EstimationContext<'a> { ) -> DbResult<(ExecutionOutcome, EstimateExt)> { let estimated_storage_limit = overwrite_storage_limit.unwrap_or(storage_limit(&executed)); - let estimated_gas_limit = estimated_gas_limit(&executed); + let estimated_gas_limit = estimated_gas_limit(&executed, &tx); let estimation = EstimateExt { estimated_storage_limit, estimated_gas_limit, @@ -374,9 +398,12 @@ impl<'a> EstimationContext<'a> { } } -fn estimated_gas_limit(executed: &Executed) -> U256 { - executed.ext_result.get::().unwrap() * 7 / 6 - + executed.base_gas +fn estimated_gas_limit(executed: &Executed, tx: &SignedTransaction) -> U256 { + let cip130_min_gas_limit = U256::from(tx.data().len() * 100); + let estimated = + executed.ext_result.get::().unwrap() * 7 / 6 + + executed.base_gas; + U256::max(estimated, cip130_min_gas_limit) } fn storage_limit(executed: &Executed) -> u64 { @@ -439,6 +466,7 @@ impl EstimateRequest { charge_collateral, charge_gas: self.charge_gas(), check_epoch_bound: false, + check_base_price: self.has_gas_price, } } diff --git a/crates/cfxcore/execute-helper/src/observer/exec_tracer/mod.rs b/crates/cfxcore/execute-helper/src/observer/exec_tracer/mod.rs index 9cc11808e4..cedb23d152 100644 --- a/crates/cfxcore/execute-helper/src/observer/exec_tracer/mod.rs +++ b/crates/cfxcore/execute-helper/src/observer/exec_tracer/mod.rs @@ -24,11 +24,10 @@ pub use trace_types::{ use super::utils::CheckpointLog; use cfx_executor::{ - executive_observer::{ + observer::{ AddressPocket, CallTracer, CheckpointTracer, DrainTrace, - InternalTransferTracer, + InternalTransferTracer, OpcodeTracer, StorageTracer, }, - observer::{OpcodeTracer, StorageTracer}, stack::{FrameResult, FrameReturn}, }; use cfx_types::U256; @@ -42,6 +41,20 @@ pub struct ExecTracer { valid_indices: CheckpointLog, } +impl ExecTracer { + pub fn drain(self) -> Vec { + let mut validity: Vec = vec![false; self.traces.len()]; + for index in self.valid_indices.drain() { + validity[index] = true; + } + self.traces + .into_iter() + .zip(validity.into_iter()) + .map(|(action, valid)| ExecTrace { action, valid }) + .collect() + } +} + impl DrainTrace for ExecTracer { fn drain_trace(self, map: &mut ShareDebugMap) { map.insert::(self.drain()); @@ -49,6 +62,7 @@ impl DrainTrace for ExecTracer { } pub struct ExecTraceKey; + impl typemap::Key for ExecTraceKey { type Value = Vec; } @@ -137,19 +151,5 @@ impl CallTracer for ExecTracer { } } -impl OpcodeTracer for ExecTracer {} impl StorageTracer for ExecTracer {} - -impl ExecTracer { - pub fn drain(self) -> Vec { - let mut validity: Vec = vec![false; self.traces.len()]; - for index in self.valid_indices.drain() { - validity[index] = true; - } - self.traces - .into_iter() - .zip(validity.into_iter()) - .map(|(action, valid)| ExecTrace { action, valid }) - .collect() - } -} +impl OpcodeTracer for ExecTracer {} diff --git a/crates/cfxcore/execute-helper/src/observer/gasman.rs b/crates/cfxcore/execute-helper/src/observer/gasman.rs index 3e6260abc6..2fed79d8ab 100644 --- a/crates/cfxcore/execute-helper/src/observer/gasman.rs +++ b/crates/cfxcore/execute-helper/src/observer/gasman.rs @@ -1,8 +1,8 @@ use cfx_executor::{ - executive_observer::{ + observer::{ CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer, + OpcodeTracer, StorageTracer, }, - observer::{OpcodeTracer, StorageTracer}, stack::FrameResult, }; use cfx_parameters::{ @@ -59,6 +59,7 @@ impl DrainTrace for GasMan { } pub struct GasLimitEstimation; + impl typemap::Key for GasLimitEstimation { type Value = U256; } diff --git a/crates/cfxcore/execute-helper/src/observer/geth_tracer/mod.rs b/crates/cfxcore/execute-helper/src/observer/geth_tracer/mod.rs deleted file mode 100644 index 9fb67daa56..0000000000 --- a/crates/cfxcore/execute-helper/src/observer/geth_tracer/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -use cfx_executor::observer::{ - CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer, - OpcodeTracer, StorageTracer, -}; - -pub struct GethTracer { - // TODO[geth-tracer]: Fill data here -} - -impl DrainTrace for GethTracer { - fn drain_trace(self, map: &mut typemap::ShareDebugMap) { - // TODO[geth-tracer]: Compute output for one transaction here. - map.insert::(()); - } -} - -pub struct GethTracerKey; - -impl typemap::Key for GethTracerKey { - // TODO[geth-tracer]: Define your output type here - type Value = (); -} - -impl CheckpointTracer for GethTracer { - // TODO[geth-tracer]: Implement hook handler in needed. -} - -impl CallTracer for GethTracer {} - -impl InternalTransferTracer for GethTracer {} - -impl StorageTracer for GethTracer {} - -impl OpcodeTracer for GethTracer { - fn do_trace_opcode(&self, enabled: &mut bool) { - *enabled |= true; - // TODO[geth-tracer]: Tell the executor if trace_opcode is enabled. - } -} diff --git a/crates/cfxcore/execute-helper/src/observer/mod.rs b/crates/cfxcore/execute-helper/src/observer/mod.rs index 77227600f3..6b47519777 100644 --- a/crates/cfxcore/execute-helper/src/observer/mod.rs +++ b/crates/cfxcore/execute-helper/src/observer/mod.rs @@ -1,15 +1,19 @@ pub mod exec_tracer; pub mod gasman; -pub mod geth_tracer; mod utils; use exec_tracer::ExecTracer; use gasman::GasMan; -use cfx_executor::executive_observer::{AsTracer, DrainTrace, TracerTrait}; +use cfx_executor::{ + executive_observer::{AsTracer, DrainTrace, TracerTrait}, + machine::Machine, +}; use cfx_vm_tracer_derive::{AsTracer, DrainTrace}; +use std::sync::Arc; -use self::geth_tracer::GethTracer; +use alloy_rpc_types_trace::geth::GethDebugTracingOptions; +use geth_tracer::{GethTracer, TxExecContext}; #[derive(AsTracer, DrainTrace)] pub struct Observer { @@ -18,8 +22,6 @@ pub struct Observer { pub geth_tracer: Option, } -// TODO[geth-tracer]: instantiation your tracer here. - impl Observer { pub fn with_tracing() -> Self { Observer { @@ -44,4 +46,15 @@ impl Observer { geth_tracer: None, } } + + pub fn geth_tracer( + tx_exec_context: TxExecContext, machine: Arc, + opts: GethDebugTracingOptions, + ) -> Self { + Observer { + tracer: None, + gas_man: None, + geth_tracer: Some(GethTracer::new(tx_exec_context, machine, opts)), + } + } } diff --git a/crates/cfxcore/execute-helper/src/phantom_tx/mod.rs b/crates/cfxcore/execute-helper/src/phantom_tx/mod.rs index a3cf93a0a8..6880911983 100644 --- a/crates/cfxcore/execute-helper/src/phantom_tx/mod.rs +++ b/crates/cfxcore/execute-helper/src/phantom_tx/mod.rs @@ -2,8 +2,8 @@ mod recover; use cfx_types::{Address, AddressSpaceUtil, Bloom, Space, U256}; use primitives::{ - Action, Eip155Transaction, LogEntry, Receipt, SignedTransaction, - TransactionStatus, + transaction::eth_transaction::Eip155Transaction, Action, LogEntry, Receipt, + SignedTransaction, TransactionStatus, }; pub use recover::{build_bloom_and_recover_phantom, recover_phantom}; @@ -63,6 +63,7 @@ impl PhantomTransaction { storage_collateralized: vec![], storage_released: vec![], storage_sponsor_paid: false, + burnt_gas_fee: None, } } } diff --git a/crates/cfxcore/execute-helper/src/tx_outcome.rs b/crates/cfxcore/execute-helper/src/tx_outcome.rs index e758d9e3c2..5c9ba0c9b1 100644 --- a/crates/cfxcore/execute-helper/src/tx_outcome.rs +++ b/crates/cfxcore/execute-helper/src/tx_outcome.rs @@ -2,10 +2,12 @@ use cfx_executor::{ executive::ExecutionOutcome, internal_contract::make_staking_events, }; use cfx_types::{H256, U256}; +use cfx_vm_types::Spec; use pow_types::StakingEvent; use primitives::Receipt; -use crate::observer::geth_tracer::GethTracerKey; +use alloy_rpc_types_trace::geth::GethTrace; +use geth_tracer::GethTraceKey; use super::{ observer::exec_tracer::{ExecTrace, ExecTraceKey}, @@ -19,6 +21,7 @@ pub struct ProcessTxOutcome { pub tx_staking_events: Vec, pub tx_exec_error_msg: String, pub consider_repacked: bool, + pub geth_trace: Option, } fn tx_traces(outcome: &ExecutionOutcome) -> Vec { @@ -28,18 +31,21 @@ fn tx_traces(outcome: &ExecutionOutcome) -> Vec { .unwrap_or_default() } +fn geth_traces(outcome: &ExecutionOutcome) -> Option { + outcome + .try_as_executed() + .and_then(|executed| executed.ext_result.get::().cloned()) +} + pub fn make_process_tx_outcome( outcome: ExecutionOutcome, accumulated_gas_used: &mut U256, tx_hash: H256, + spec: &Spec, ) -> ProcessTxOutcome { - // TODO[geth-tracer]: extract your trace result here. - let _maybe_geth_trace = outcome - .try_as_executed() - .and_then(|executed| executed.ext_result.get::()); - let tx_traces = tx_traces(&outcome); + let geth_trace = geth_traces(&outcome); let tx_exec_error_msg = outcome.error_message(); let consider_repacked = outcome.consider_repacked(); - let receipt = outcome.make_receipt(accumulated_gas_used); + let receipt = outcome.make_receipt(accumulated_gas_used, spec); let tx_staking_events = make_staking_events(receipt.logs()); @@ -52,5 +58,6 @@ pub fn make_process_tx_outcome( tx_staking_events, tx_exec_error_msg, consider_repacked, + geth_trace, } } diff --git a/crates/cfxcore/executor/Cargo.toml b/crates/cfxcore/executor/Cargo.toml index ccd6c694a5..b4b4d5797c 100644 --- a/crates/cfxcore/executor/Cargo.toml +++ b/crates/cfxcore/executor/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-executor" version = "2.0.2" -edition = "2018" +edition = "2021" [dependencies] substrate-bn = { git = "https://github.com/paritytech/bn", default-features = false, rev="63f8c587356a67b33c7396af98e065b66fca5dda" } @@ -47,7 +47,11 @@ diem-types = { path = "../core/src/pos/types" } pow-types = {path = "../core/src/pos/types/pow-types" } impl-trait-for-tuples = "^0.2" impl-tools = "^0.10" -typemap = "0.3" +typemap = { package = "typemap-ors", version = "1.0"} +hex-literal = "0.4.1" +derive_more = "0.99" +c-kzg = { version = "1.0.2", default-features = false} +once_cell = "1.19" [dev-dependencies] cfx-statedb = { path = "../../dbs/statedb", features = ["testonly_code"]} diff --git a/crates/cfxcore/executor/src/builtin/ethereum_trusted_setup_points.rs b/crates/cfxcore/executor/src/builtin/ethereum_trusted_setup_points.rs new file mode 100644 index 0000000000..829e075aa2 --- /dev/null +++ b/crates/cfxcore/executor/src/builtin/ethereum_trusted_setup_points.rs @@ -0,0 +1,58 @@ +// Based on source code from the revm project (https://github.com/bluealloy/revm) under the MIT License. + +use c_kzg::KzgSettings; +use derive_more::{AsMut, AsRef, Deref, DerefMut}; +use once_cell::race::OnceBox; + +pub use c_kzg::{BYTES_PER_G1_POINT, BYTES_PER_G2_POINT}; + +/// Number of G1 Points. +pub const NUM_G1_POINTS: usize = 4096; + +/// Number of G2 Points. +pub const NUM_G2_POINTS: usize = 65; + +/// A newtype over list of G1 point from kzg trusted setup. +#[derive(Debug, Clone, PartialEq, AsRef, AsMut, Deref, DerefMut)] +#[repr(transparent)] +pub struct G1Points(pub [[u8; BYTES_PER_G1_POINT]; NUM_G1_POINTS]); + +impl Default for G1Points { + fn default() -> Self { Self([[0; BYTES_PER_G1_POINT]; NUM_G1_POINTS]) } +} + +/// A newtype over list of G2 point from kzg trusted setup. +#[derive(Debug, Clone, Eq, PartialEq, AsRef, AsMut, Deref, DerefMut)] +#[repr(transparent)] +pub struct G2Points(pub [[u8; BYTES_PER_G2_POINT]; NUM_G2_POINTS]); + +impl Default for G2Points { + fn default() -> Self { Self([[0; BYTES_PER_G2_POINT]; NUM_G2_POINTS]) } +} + +/// Default G1 points. +pub const G1_POINTS: &G1Points = { + const BYTES: &[u8] = include_bytes!("./g1_points.bin"); + assert!(BYTES.len() == core::mem::size_of::()); + unsafe { &*BYTES.as_ptr().cast::() } +}; + +/// Default G2 points. +pub const G2_POINTS: &G2Points = { + const BYTES: &[u8] = include_bytes!("./g2_points.bin"); + assert!(BYTES.len() == core::mem::size_of::()); + unsafe { &*BYTES.as_ptr().cast::() } +}; + +static DEFAULT: OnceBox = OnceBox::new(); + +pub fn default_kzg_settings() -> &'static KzgSettings { + DEFAULT.get_or_init(|| { + let settings = KzgSettings::load_trusted_setup( + G1_POINTS.as_ref(), + G2_POINTS.as_ref(), + ) + .expect("failed to load default trusted setup"); + Box::new(settings) + }) +} diff --git a/crates/cfxcore/executor/src/builtin/g1_points.bin b/crates/cfxcore/executor/src/builtin/g1_points.bin new file mode 100644 index 0000000000..2ac35953ab Binary files /dev/null and b/crates/cfxcore/executor/src/builtin/g1_points.bin differ diff --git a/crates/cfxcore/executor/src/builtin/g2_points.bin b/crates/cfxcore/executor/src/builtin/g2_points.bin new file mode 100644 index 0000000000..ca5625282b Binary files /dev/null and b/crates/cfxcore/executor/src/builtin/g2_points.bin differ diff --git a/crates/cfxcore/executor/src/builtin/kzg_point_evaluations.rs b/crates/cfxcore/executor/src/builtin/kzg_point_evaluations.rs new file mode 100644 index 0000000000..9a62881e0e --- /dev/null +++ b/crates/cfxcore/executor/src/builtin/kzg_point_evaluations.rs @@ -0,0 +1,117 @@ +// Based on source code from the revm project (https://github.com/bluealloy/revm) under the MIT License. + +use c_kzg::{Bytes32, Bytes48, KzgProof, KzgSettings}; + +use hex_literal::hex; +use parity_crypto::digest; +use std::convert::TryInto; + +use super::{ethereum_trusted_setup_points::default_kzg_settings, Error}; + +pub const VERSIONED_HASH_VERSION_KZG: u8 = 0x01; + +/// `U256(FIELD_ELEMENTS_PER_BLOB).to_be_bytes() ++ BLS_MODULUS.to_bytes32()` +pub const RETURN_VALUE: &[u8; 64] = &hex!( + "0000000000000000000000000000000000000000000000000000000000001000" + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001" +); + +/// Run kzg point evaluation precompile. +/// +/// The Env has the KZGSettings that is needed for evaluation. +/// +/// The input is encoded as follows: +/// | versioned_hash | z | y | commitment | proof | +/// | 32 | 32 | 32 | 48 | 48 | +/// with z and y being padded 32 byte big endian values +pub fn run(input: &[u8]) -> Result<(), Error> { + // Verify input length. + if input.len() != 192 { + return Err(Error("Blob invalid input length")); + } + + // Verify commitment matches versioned_hash + let versioned_hash = &input[..32]; + let commitment = &input[96..144]; + if kzg_to_versioned_hash(commitment) != versioned_hash { + return Err(Error("Blob mismatched version")); + } + + // Verify KZG proof with z and y in big endian format + let commitment = as_bytes48(commitment); + let z = as_bytes32(&input[32..64]); + let y = as_bytes32(&input[64..96]); + let proof = as_bytes48(&input[144..192]); + if !verify_kzg_proof(commitment, z, y, proof, default_kzg_settings()) { + return Err(Error("Blob verify kzg proof failed")); + } + Ok(()) +} + +/// `VERSIONED_HASH_VERSION_KZG ++ sha256(commitment)[1..]` +#[inline] +pub fn kzg_to_versioned_hash(commitment: &[u8]) -> [u8; 32] { + let mut hash = [0u8; 32]; + hash.copy_from_slice(&*digest::sha256(commitment)); + hash[0] = VERSIONED_HASH_VERSION_KZG; + hash +} + +#[inline] +pub fn verify_kzg_proof( + commitment: &Bytes48, z: &Bytes32, y: &Bytes32, proof: &Bytes48, + kzg_settings: &KzgSettings, +) -> bool { + KzgProof::verify_kzg_proof(commitment, z, y, proof, kzg_settings) + .unwrap_or(false) +} + +#[inline] +#[track_caller] +pub fn as_array(bytes: &[u8]) -> &[u8; N] { + bytes.try_into().expect("slice with incorrect length") +} + +#[inline] +#[track_caller] +pub fn as_bytes32(bytes: &[u8]) -> &Bytes32 { + // SAFETY: `#[repr(C)] Bytes32([u8; 32])` + unsafe { &*as_array::<32>(bytes).as_ptr().cast() } +} + +#[inline] +#[track_caller] +pub fn as_bytes48(bytes: &[u8]) -> &Bytes48 { + // SAFETY: `#[repr(C)] Bytes48([u8; 48])` + unsafe { &*as_array::<48>(bytes).as_ptr().cast() } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_test() { + // test data from: https://github.com/ethereum/c-kzg-4844/blob/main/tests/verify_kzg_proof/kzg-mainnet/verify_kzg_proof_case_correct_proof_31ebd010e6098750/data.yaml + + let commitment = hex!("8f59a8d2a1a625a17f3fea0fe5eb8c896db3764f3185481bc22f91b4aaffcca25f26936857bc3a7c2539ea8ec3a952b7").to_vec(); + let mut versioned_hash = digest::sha256(&commitment).to_vec(); + versioned_hash[0] = VERSIONED_HASH_VERSION_KZG; + let z = hex!( + "73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000000" + ) + .to_vec(); + let y = hex!( + "1522a4a7f34e1ea350ae07c29c96c7e79655aa926122e95fe69fcbd932ca49e9" + ) + .to_vec(); + let proof = hex!("a62ad71d14c5719385c0686f1871430475bf3a00f0aa3f7b8dd99a9abc2160744faf0070725e00b60ad9a026a15b1a8c").to_vec(); + + let input = [versioned_hash, z, y, commitment, proof].concat(); + run(&input).unwrap(); + + let expected_output = hex!("000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"); + + assert_eq!(RETURN_VALUE[..], expected_output); + } +} diff --git a/crates/cfxcore/executor/src/builtin/mod.rs b/crates/cfxcore/executor/src/builtin/mod.rs index abfe7da623..2b3ba55c2f 100644 --- a/crates/cfxcore/executor/src/builtin/mod.rs +++ b/crates/cfxcore/executor/src/builtin/mod.rs @@ -21,7 +21,9 @@ //! Standard built-in contracts. mod blake2f; +mod ethereum_trusted_setup_points; mod executable; +mod kzg_point_evaluations; pub use executable::BuiltinExec; @@ -277,6 +279,7 @@ pub fn builtin_factory(name: &str) -> Box { "alt_bn128_mul" => Box::new(Bn128MulImpl) as Box, "alt_bn128_pairing" => Box::new(Bn128PairingImpl) as Box, "blake2_f" => Box::new(Blake2FImpl) as Box, + "kzg_point_eval" => Box::new(KzgPointEval) as Box, _ => panic!("invalid builtin name: {}", name), } } @@ -325,6 +328,10 @@ struct Bn128PairingImpl; #[allow(dead_code)] struct Blake2FImpl; +#[derive(Debug)] +#[allow(dead_code)] +struct KzgPointEval; + impl Impl for Identity { fn execute( &self, input: &[u8], output: &mut BytesRef, @@ -771,6 +778,15 @@ impl Impl for Blake2FImpl { } } +impl Impl for KzgPointEval { + fn execute( + &self, input: &[u8], output: &mut BytesRef, + ) -> Result<(), Error> { + kzg_point_evaluations::run(input)?; + output.write(0, &kzg_point_evaluations::RETURN_VALUE[..]); + Ok(()) + } +} #[cfg(test)] mod tests { use super::{ diff --git a/crates/cfxcore/executor/src/context.rs b/crates/cfxcore/executor/src/context.rs index 425e8d6c92..6a7c60e4cd 100644 --- a/crates/cfxcore/executor/src/context.rs +++ b/crates/cfxcore/executor/src/context.rs @@ -6,8 +6,12 @@ use crate::{ executive::contract_address, executive_observer::TracerTrait, - internal_contract::{suicide as suicide_impl, InternalRefContext}, + internal_contract::{ + block_hash_slot, epoch_hash_slot, suicide as suicide_impl, + InternalRefContext, + }, machine::Machine, + return_if, stack::{CallStackInfo, FrameLocal, RuntimeRes}, state::State, substate::Substate, @@ -17,7 +21,8 @@ use cfx_parameters::staking::{ code_collateral_units, DRIPS_PER_STORAGE_COLLATERAL_UNIT, }; use cfx_types::{ - Address, AddressSpaceUtil, AddressWithSpace, Space, H256, U256, + Address, AddressSpaceUtil, AddressWithSpace, BigEndianHash, Space, H256, + U256, }; use cfx_vm_types::{ self as vm, ActionParams, ActionValue, CallType, Context as ContextTrait, @@ -26,6 +31,7 @@ use cfx_vm_types::{ }; use primitives::transaction::UNSIGNED_SENDER; use std::sync::Arc; +use vm::BlockHashSource; /// Transaction properties that externalities need to know about. #[derive(Debug)] @@ -106,6 +112,51 @@ impl<'a> Context<'a> { tracer, } } + + fn blockhash_from_env(&self, number: &U256) -> H256 { + if self.space == Space::Ethereum && self.spec.cip98 { + return if U256::from(self.env().epoch_height) == number + 1 { + self.env().last_hash.clone() + } else { + H256::default() + }; + } + + // In Conflux, we only maintain the block hash of the previous block. + // For other block numbers, it always returns zero. + if U256::from(self.env().number) == number + 1 { + self.env().last_hash.clone() + } else { + H256::default() + } + } + + fn blockhash_from_state(&self, number: &U256) -> vm::Result { + return_if!(number > &U256::from(u64::MAX)); + + let number = number.as_u64(); + + let state_res = match self.space { + Space::Native => { + return_if!(number < self.spec.cip133_b); + return_if!(number > self.env.number); + return_if!(number + .checked_add(65536) + .map_or(false, |n| n <= self.env.number)); + self.state.get_system_storage(&block_hash_slot(number))? + } + Space::Ethereum => { + return_if!(number < self.spec.cip133_e); + return_if!(number > self.env.epoch_height); + return_if!(number + .checked_add(65536) + .map_or(false, |n| n <= self.env.epoch_height)); + self.state.get_system_storage(&epoch_hash_slot(number))? + } + }; + + Ok(BigEndianHash::from_uint(&state_res)) + } } impl<'a> ContextTrait for Context<'a> { @@ -137,6 +188,32 @@ impl<'a> ContextTrait for Context<'a> { } } + fn transient_storage_at(&self, key: &Vec) -> vm::Result { + let receiver = AddressWithSpace { + address: self.origin.address, + space: self.space, + }; + self.state + .transient_storage_at(&receiver, key) + .map_err(Into::into) + } + + fn transient_set_storage( + &mut self, key: Vec, value: U256, + ) -> vm::Result<()> { + let receiver = AddressWithSpace { + address: self.origin.address, + space: self.space, + }; + if self.is_static_or_reentrancy() { + Err(vm::Error::MutableCallInStaticContext) + } else { + self.state + .transient_set_storage(&receiver, key, value) + .map_err(Into::into) + } + } + fn exists(&self, address: &Address) -> vm::Result { let address = AddressWithSpace { address: *address, @@ -165,21 +242,10 @@ impl<'a> ContextTrait for Context<'a> { self.state.balance(&address).map_err(Into::into) } - fn blockhash(&mut self, number: &U256) -> H256 { - if self.space == Space::Ethereum && self.spec.cip98 { - return if U256::from(self.env().epoch_height) == number + 1 { - self.env().last_hash.clone() - } else { - H256::default() - }; - } - - // In Conflux, we only maintain the block hash of the previous block. - // For other block numbers, it always returns zero. - if U256::from(self.env().number) == number + 1 { - self.env().last_hash.clone() - } else { - H256::default() + fn blockhash(&mut self, number: &U256) -> vm::Result { + match self.blockhash_source() { + BlockHashSource::Env => Ok(self.blockhash_from_env(number)), + BlockHashSource::State => self.blockhash_from_state(number), } } @@ -344,6 +410,8 @@ impl<'a> ContextTrait for Context<'a> { return Err(vm::Error::MutableCallInStaticContext); } + self.tracer.log(&self.origin.address, &topics, data); + let address = self.origin.address.clone(); self.substate.logs.push(LogEntry { address, @@ -406,8 +474,15 @@ impl<'a> ContextTrait for Context<'a> { return Err(vm::Error::MutableCallInStaticContext); } + let contract_address = self.origin.address; + let contract_address_with_space = + self.origin.address.with_space(self.space); + let balance = self.state.balance(&contract_address_with_space)?; + self.tracer + .selfdestruct(&contract_address, refund_address, balance); + suicide_impl( - &self.origin.address.with_space(self.space), + &contract_address_with_space, &refund_address.with_space(self.space), self.state, &self.spec, @@ -447,6 +522,14 @@ impl<'a> ContextTrait for Context<'a> { // // TODO // } + fn trace_step(&mut self, interpreter: &dyn vm::InterpreterInfo) { + self.tracer.step(interpreter); + } + + fn trace_step_end(&mut self, interpreter: &dyn vm::InterpreterInfo) { + self.tracer.step_end(interpreter); + } + fn opcode_trace_enabled(&self) -> bool { let mut enabled = false; self.tracer.do_trace_opcode(&mut enabled); @@ -458,6 +541,18 @@ impl<'a> ContextTrait for Context<'a> { fn is_static_or_reentrancy(&self) -> bool { self.static_flag || self.callstack.in_reentrancy(self.spec) } + + fn blockhash_source(&self) -> vm::BlockHashSource { + let from_state = match self.space { + Space::Native => self.env.number >= self.spec.cip133_b, + Space::Ethereum => self.env.epoch_height >= self.spec.cip133_e, + }; + if from_state { + BlockHashSource::State + } else { + BlockHashSource::Env + } + } } impl<'a> Context<'a> { @@ -533,6 +628,8 @@ mod tests { pos_view: None, finalized_epoch: None, transaction_epoch_bound: TRANSACTION_DEFAULT_EPOCH_BOUND, + base_gas_price: Default::default(), + burnt_gas_price: Default::default(), } } @@ -558,7 +655,7 @@ mod tests { Default::default(), ); let env = get_test_env(); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let callstack = CallStackInfo::new(); let mut setup = Self { @@ -632,7 +729,7 @@ mod tests { &"0000000000000000000000000000000000000000000000000000000000120000" .parse::() .unwrap(), - ); + ).unwrap(); assert_eq!(hash, H256::zero()); } diff --git a/crates/cfxcore/executor/src/executive/executed.rs b/crates/cfxcore/executor/src/executive/executed.rs index d627e35550..b8886cc891 100644 --- a/crates/cfxcore/executor/src/executive/executed.rs +++ b/crates/cfxcore/executor/src/executive/executed.rs @@ -29,6 +29,9 @@ pub struct Executed { /// Fee that need to be paid by execution of this transaction. pub fee: U256, + /// Fee burnt by CIP-1559 + pub burnt_fee: Option, + /// Gas charged during execution of transaction. pub gas_charged: U256, @@ -71,13 +74,13 @@ pub type ExecutedExt = ShareDebugMap; impl Executed { pub(super) fn not_enough_balance_fee_charged( - tx: &TransactionWithSignature, fee: &U256, cost: CostInfo, + tx: &TransactionWithSignature, actual_gas_cost: &U256, cost: CostInfo, ext_result: ExecutedExt, spec: &Spec, ) -> Self { - let gas_charged = if *tx.gas_price() == U256::zero() { + let gas_charged = if cost.gas_price == U256::zero() { U256::zero() } else { - fee / tx.gas_price() + actual_gas_cost / cost.gas_price }; let mut gas_sponsor_paid = cost.gas_sponsored; let mut storage_sponsor_paid = cost.storage_sponsored; @@ -85,10 +88,17 @@ impl Executed { gas_sponsor_paid = false; storage_sponsor_paid = false; } + + let burnt_fee = spec.cip1559.then(|| { + let target_burnt = tx.gas().saturating_mul(cost.burnt_gas_price); + U256::min(*actual_gas_cost, target_burnt) + }); + Self { gas_used: *tx.gas(), gas_charged, - fee: fee.clone(), + fee: *actual_gas_cost, + burnt_fee, gas_sponsor_paid, logs: vec![], contracts_created: vec![], @@ -112,10 +122,21 @@ impl Executed { gas_sponsor_paid = false; storage_sponsor_paid = false; } + if spec.cip145 { + gas_sponsor_paid = false; + } + + let fee = tx.gas().saturating_mul(cost.gas_price); + + let burnt_fee = spec + .cip1559 + .then(|| tx.gas().saturating_mul(cost.burnt_gas_price)); + Self { gas_used: *tx.gas(), gas_charged: *tx.gas(), - fee: tx.gas().saturating_mul(*tx.gas_price()), + fee, + burnt_fee, gas_sponsor_paid, logs: vec![], contracts_created: vec![], @@ -147,6 +168,7 @@ impl Executed { gas_used, gas_charged, fees_value: fee, + burnt_fees_value: burnt_fee, .. } = refund_info; let mut storage_sponsor_paid = if spec.cip78a { @@ -165,6 +187,7 @@ impl Executed { gas_used, gas_charged, fee, + burnt_fee, gas_sponsor_paid, logs: substate.logs.to_vec(), contracts_created: substate.contracts_created.to_vec(), diff --git a/crates/cfxcore/executor/src/executive/execution_outcome.rs b/crates/cfxcore/executor/src/executive/execution_outcome.rs index 70ab2469d7..2cd341f061 100644 --- a/crates/cfxcore/executor/src/executive/execution_outcome.rs +++ b/crates/cfxcore/executor/src/executive/execution_outcome.rs @@ -14,6 +14,7 @@ pub enum ExecutionOutcome { ExecutionErrorBumpNonce(ExecutionError, Executed), Finished(Executed), } +use vm::Spec; use ExecutionOutcome::*; #[derive(Debug)] @@ -50,6 +51,11 @@ pub enum ToRepackError { /// Returned when a non-sponsored transaction's sender does not exist yet. SenderDoesNotExist, + + NotEnoughBaseFee { + expected: U256, + got: U256, + }, } #[derive(Debug)] @@ -61,6 +67,9 @@ pub enum TxDropError { /// Although it can be verified in tx packing, /// by spec doc, it is checked in execution. InvalidRecipientAddress(Address), + + /// Not enough gas limit for large transacton, only for estimation + NotEnoughGasLimit { expected: U256, got: U256 }, } #[derive(Debug, PartialEq)] @@ -82,7 +91,9 @@ pub enum ExecutionError { impl ExecutionOutcome { #[inline] - pub fn make_receipt(self, accumulated_gas_used: &mut U256) -> Receipt { + pub fn make_receipt( + self, accumulated_gas_used: &mut U256, spec: &Spec, + ) -> Receipt { *accumulated_gas_used += self.gas_used(); let gas_fee = self.gas_fee(); @@ -94,6 +105,8 @@ impl ExecutionOutcome { let storage_collateralized = self.storage_collateralized(); let storage_released = self.storage_released(); + let burnt_fee = self.burnt_fee(spec); + let log_bloom = build_bloom(&transaction_logs); Receipt::new( @@ -106,6 +119,7 @@ impl ExecutionOutcome { storage_sponsor_paid, storage_collateralized, storage_released, + burnt_fee, ) } @@ -177,6 +191,17 @@ impl ExecutionOutcome { executed.storage_released.clone() } + #[inline] + pub fn burnt_fee(&self, spec: &Spec) -> Option { + if let Some(e) = self.try_as_executed() { + e.burnt_fee + } else if spec.cip1559 { + Some(U256::zero()) + } else { + None + } + } + #[inline] pub fn consider_repacked(&self) -> bool { matches!(self, NotExecutedToReconsiderPacking(_)) diff --git a/crates/cfxcore/executor/src/executive/fresh_executive.rs b/crates/cfxcore/executor/src/executive/fresh_executive.rs index bcdc2e3dfc..a411ed78c2 100644 --- a/crates/cfxcore/executor/src/executive/fresh_executive.rs +++ b/crates/cfxcore/executor/src/executive/fresh_executive.rs @@ -31,16 +31,30 @@ pub struct FreshExecutive<'a, O: ExecutiveObserver> { } pub(super) struct CostInfo { + /// Sender balance pub sender_balance: U512, + /// The intrinsic gas (21000/53000 + tx data gas + access list gas) pub base_gas: u64, + /// Transaction value + gas cost (except the sponsored part) pub total_cost: U512, + /// Gas cost pub gas_cost: U512, + /// Storage collateral cost pub storage_cost: U256, + /// Transaction value + gas cost (except the part that eligible for + /// sponsor) pub sender_intended_cost: U512, + /// Effective gas price + pub gas_price: U256, + /// Burnt gas price + pub burnt_gas_price: U256, + /// Transaction's gas is sponsored pub gas_sponsored: bool, + /// Transaction's collateral is sponsored pub storage_sponsored: bool, + /// Transaction's gas is in the sponsor whitelist pub storage_sponsor_eligible: bool, } @@ -53,6 +67,7 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { let base_gas = gas_required_for( tx.action() == &Action::Create, &tx.data(), + tx.access_list(), context.spec, ); FreshExecutive { @@ -67,6 +82,7 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { pub(super) fn check_all( self, ) -> DbResult, ExecutionOutcome>> { + early_return_on_err!(self.check_base_price()); // Validate transaction nonce early_return_on_err!(self.check_nonce()?); @@ -115,6 +131,24 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { }) } + fn check_base_price(&self) -> Result<(), ExecutionOutcome> { + if !self.settings.check_base_price { + return Ok(()); + } + + let burnt_gas_price = self.context.env.burnt_gas_price[self.tx.space()]; + if self.tx.gas_price() < &burnt_gas_price { + Err(ExecutionOutcome::NotExecutedToReconsiderPacking( + ToRepackError::NotEnoughBaseFee { + expected: burnt_gas_price, + got: *self.tx.gas_price(), + }, + )) + } else { + Ok(()) + } + } + fn check_epoch_bound(&self) -> DbResult> { let tx = if let Transaction::Native(ref tx) = self.tx.transaction.transaction.unsigned @@ -126,13 +160,13 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { let env = self.context.env; - if tx.epoch_height.abs_diff(env.epoch_height) + if tx.epoch_height().abs_diff(env.epoch_height) > env.transaction_epoch_bound { Ok(Err(ExecutionOutcome::NotExecutedToReconsiderPacking( ToRepackError::EpochHeightOutOfBound { block_height: env.epoch_height, - set: tx.epoch_height, + set: *tx.epoch_height(), transaction_epoch_bound: env.transaction_epoch_bound, }, ))) @@ -164,11 +198,29 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { let settings = self.settings; let sender = tx.sender(); let state = &self.context.state; + let env = self.context.env; let spec = self.context.spec; + let check_base_price = self.settings.check_base_price; + + let gas_price = if !spec.cip1559 || !check_base_price { + *tx.gas_price() + } else { + // actual_base_gas >= tx gas_price >= burnt_base_price + let actual_base_gas = + U256::min(*tx.gas_price(), env.base_gas_price[tx.space()]); + tx.effective_gas_price(&actual_base_gas) + }; + + let burnt_gas_price = env.burnt_gas_price[tx.space()]; + // gas_price >= actual_base_gas >= + // either 1. tx gas_price >= burnt_gas_price + // or 2. base_gas_price >= burnt_gas_price + assert!(gas_price >= burnt_gas_price || !check_base_price); + let sender_balance = U512::from(state.balance(&sender)?); let gas_cost = if settings.charge_gas { - tx.gas().full_mul(*tx.gas_price()) + tx.gas().full_mul(gas_price) } else { 0.into() }; @@ -177,7 +229,7 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { &tx.transaction.transaction.unsigned, settings.charge_collateral, ) { - U256::from(tx.storage_limit) + U256::from(*tx.storage_limit()) * *DRIPS_PER_STORAGE_COLLATERAL_UNIT } else { U256::zero() @@ -190,6 +242,8 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { sender_balance, base_gas: self.base_gas, gas_cost, + gas_price, + burnt_gas_price, storage_cost, sender_intended_cost: sender_cost, total_cost: sender_cost, @@ -301,6 +355,8 @@ impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> { sender_intended_cost, base_gas: self.base_gas, gas_cost, + gas_price, + burnt_gas_price, storage_cost, sender_balance, total_cost, diff --git a/crates/cfxcore/executor/src/executive/mod.rs b/crates/cfxcore/executor/src/executive/mod.rs index 59716c21d1..b8250b28b7 100644 --- a/crates/cfxcore/executor/src/executive/mod.rs +++ b/crates/cfxcore/executor/src/executive/mod.rs @@ -15,7 +15,7 @@ use cfx_types::{ U256, }; use cfx_vm_types::{CreateContractAddress, Env, Spec}; -use primitives::SignedTransaction; +use primitives::{AccessList, SignedTransaction}; use fresh_executive::FreshExecutive; use pre_checked_executive::PreCheckedExecutive; @@ -56,29 +56,45 @@ impl<'a> ExecutiveContext<'a> { ) -> DbResult { let fresh_exec = FreshExecutive::new(self, tx, options); - let pre_checked_exec = match fresh_exec.check_all()? { - Ok(executive) => executive, - Err(execution_outcome) => return Ok(execution_outcome), - }; - - pre_checked_exec.execute_transaction() + Ok(match fresh_exec.check_all()? { + Ok(executive) => executive.execute_transaction()?, + Err(execution_outcome) => execution_outcome, + }) } } -pub fn gas_required_for(is_create: bool, data: &[u8], spec: &Spec) -> u64 { - data.iter().fold( - (if is_create { - spec.tx_create_gas - } else { - spec.tx_gas - }) as u64, - |g, b| { - g + (match *b { - 0 => spec.tx_data_zero_gas, - _ => spec.tx_data_non_zero_gas, - }) as u64 - }, - ) +pub fn gas_required_for( + is_create: bool, data: &[u8], access_list: Option<&AccessList>, spec: &Spec, +) -> u64 { + let init_gas = (if is_create { + spec.tx_create_gas + } else { + spec.tx_gas + }) as u64; + + let byte_gas = |b: &u8| { + (match *b { + 0 => spec.tx_data_zero_gas, + _ => spec.tx_data_non_zero_gas, + }) as u64 + }; + let data_gas: u64 = data.iter().map(byte_gas).sum(); + + let access_gas: u64 = if let Some(acc) = access_list { + let address_gas = + acc.len() as u64 * spec.access_list_address_gas as u64; + + let storage_key_num = + acc.iter().map(|e| e.storage_keys.len() as u64).sum::(); + let storage_key_gas = + storage_key_num * spec.access_list_storage_key_gas as u64; + + address_gas + storage_key_gas + } else { + 0 + }; + + init_gas + data_gas + access_gas } pub fn contract_address( diff --git a/crates/cfxcore/executor/src/executive/pre_checked_executive.rs b/crates/cfxcore/executor/src/executive/pre_checked_executive.rs index 72bec1490b..fbe993919a 100644 --- a/crates/cfxcore/executor/src/executive/pre_checked_executive.rs +++ b/crates/cfxcore/executor/src/executive/pre_checked_executive.rs @@ -54,7 +54,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { if self.tx.space() == Space::Native && !self.check_create_address(¶ms)? { - return self.finialize_on_conflict_address(params.address); + return self.finalize_on_conflict_address(params.address); } let result = self.exec_vm(params.clone())?; @@ -71,7 +71,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { // perform suicides self.kill_process()?; - self.finialize_on_executed(result, refund_info) + self.finalize_on_executed(result, refund_info) } } @@ -95,6 +95,9 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { .. } = self.cost; + // Here, we only discuss the case `sender_balance < + // sender_intended_cost` The case `sender_intended_cost <= + // sender_balance < sender_cost` has been handled before nonce bumping. let insufficient_sender_balance = sender_balance < sender_intended_cost; let actual_gas_cost: U256 = if insufficient_sender_balance { @@ -176,7 +179,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { original_sender: sender.address, storage_owner: sender.address, gas: init_gas, - gas_price: *tx.gas_price(), + gas_price: cost.gas_price, value: ActionValue::Transfer(*tx.value()), code: Some(Arc::new(tx.data().clone())), data: None, @@ -200,7 +203,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { original_sender: sender.address, storage_owner, gas: init_gas, - gas_price: *tx.gas_price(), + gas_price: cost.gas_price, value: ActionValue::Transfer(*tx.value()), code: state.code(&receipient)?, code_hash: state.code_hash(&receipient)?, @@ -365,6 +368,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { state.record_storage_and_whitelist_entries_release( &address.address, &mut substate, + spec.cip131, )?; assert!(state.is_fresh_storage(address)?); @@ -464,6 +468,8 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { fn compute_refunded_gas(&self, result: &ExecutiveResult) -> RefundInfo { let tx = self.tx; + let cost = &self.cost; + let spec = self.context.spec; let gas_left = match result { Ok(ExecutiveReturn { gas_left, .. }) => *gas_left, _ => 0.into(), @@ -473,25 +479,26 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { // gas_left should be smaller than 1/4 of gas_limit, otherwise // 3/4 of gas_limit is charged. let charge_all = (gas_left + gas_left + gas_left) >= gas_used; - let (gas_charged, fees_value, refund_value) = if charge_all { + let (gas_charged, gas_refunded) = if charge_all { let gas_refunded = tx.gas() >> 2; let gas_charged = tx.gas() - gas_refunded; - ( - gas_charged, - gas_charged.saturating_mul(*tx.gas_price()), - gas_refunded.saturating_mul(*tx.gas_price()), - ) + (gas_charged, gas_refunded) } else { - ( - gas_used, - gas_used.saturating_mul(*tx.gas_price()), - gas_left.saturating_mul(*tx.gas_price()), - ) + (gas_used, gas_left) }; + + let fees_value = gas_charged.saturating_mul(cost.gas_price); + let burnt_fees_value = spec + .cip1559 + .then(|| gas_charged.saturating_mul(cost.burnt_gas_price)); + + let refund_value = gas_refunded.saturating_mul(cost.gas_price); + RefundInfo { gas_used, gas_charged, fees_value, + burnt_fees_value, refund_value, } } @@ -552,7 +559,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { )); } - fn finialize_on_conflict_address( + fn finalize_on_conflict_address( self, address: Address, ) -> DbResult { return Ok(ExecutionOutcome::ExecutionErrorBumpNonce( @@ -566,7 +573,7 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { )); } - fn finialize_on_executed( + fn finalize_on_executed( self, result: ExecutiveResult, refund_info: RefundInfo, ) -> DbResult { let tx = self.tx; @@ -575,14 +582,14 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { let spec = self.context.spec; let tx_substate = self.substate; - match result { + let outcome = match result { Err(vm::Error::StateDbError(e)) => bail!(e.0), - Err(exception) => Ok(ExecutionOutcome::ExecutionErrorBumpNonce( + Err(exception) => ExecutionOutcome::ExecutionErrorBumpNonce( ExecutionError::VmError(exception), Executed::execution_error_fully_charged( tx, cost, ext_result, spec, ), - )), + ), Ok(r) => { let executed = Executed::from_executive_return( &r, @@ -594,16 +601,18 @@ impl<'a, O: ExecutiveObserver> PreCheckedExecutive<'a, O> { ); if r.apply_state { - Ok(ExecutionOutcome::Finished(executed)) + ExecutionOutcome::Finished(executed) } else { // Transaction reverted by vm instruction. - Ok(ExecutionOutcome::ExecutionErrorBumpNonce( + ExecutionOutcome::ExecutionErrorBumpNonce( ExecutionError::VmError(vm::Error::Reverted), executed, - )) + ) } } - } + }; + + Ok(outcome) } } @@ -612,6 +621,7 @@ pub(super) struct RefundInfo { pub gas_charged: U256, pub fees_value: U256, + pub burnt_fees_value: Option, pub refund_value: U256, } diff --git a/crates/cfxcore/executor/src/executive/tests.rs b/crates/cfxcore/executor/src/executive/tests.rs index 0142977201..2e933e7f03 100644 --- a/crates/cfxcore/executor/src/executive/tests.rs +++ b/crates/cfxcore/executor/src/executive/tests.rs @@ -29,8 +29,9 @@ use cfx_vm_types::{ }; use cfxkey::{Generator, Random}; use primitives::{ - storage::STORAGE_LAYOUT_REGULAR_V0, transaction::Action, EpochId, - NativeTransaction, Transaction, + storage::STORAGE_LAYOUT_REGULAR_V0, + transaction::{native_transaction::NativeTransaction, Action}, + EpochId, Transaction, }; use rustc_hex::FromHex; use std::{ @@ -116,7 +117,7 @@ fn test_sender_balance() { ); let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let mut substate = Substate::new(); let FinalizationResult { gas_left, .. } = { @@ -212,7 +213,7 @@ fn test_create_contract_out_of_depth() { let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let storage_manager = new_state_manager_for_unit_test(); let mut state = get_state_for_genesis_write(&storage_manager); @@ -267,7 +268,7 @@ fn test_suicide_when_creation() { let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let storage_manager = new_state_manager_for_unit_test(); let mut state = get_state_for_genesis_write(&storage_manager); @@ -358,7 +359,7 @@ fn test_call_to_create() { let env = Env::default(); let machine = make_byzantium_machine(5); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let storage_manager = new_state_manager_for_unit_test(); let mut state = get_state_for_genesis_write(&storage_manager); @@ -425,7 +426,7 @@ fn test_revert() { let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let mut substate = Substate::new(); let storage_manager = new_state_manager_for_unit_test(); @@ -506,7 +507,7 @@ fn test_keccak() { let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let storage_manager = new_state_manager_for_unit_test(); let mut state = get_state_for_genesis_write(&storage_manager); @@ -552,7 +553,7 @@ fn test_not_enough_cash() { let mut env = Env::default(); env.gas_limit = U256::from(100_000); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let storage_manager = new_state_manager_for_unit_test(); let mut state = get_state_for_genesis_write(&storage_manager); @@ -596,7 +597,7 @@ fn test_deposit_withdraw_lock() { let mut state = get_state_for_genesis_write(&storage_manager); let env = Env::default(); let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let mut substate = Substate::new(); state .add_balance( @@ -952,7 +953,7 @@ fn test_commission_privilege_all_whitelisted_across_epochs() { let machine = make_byzantium_machine(0); let mut env = Env::default(); env.gas_limit = U256::MAX; - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let sender = Random.generate().unwrap().address(); let sender_with_space = sender.with_native_space(); @@ -1155,7 +1156,7 @@ fn test_commission_privilege() { let mut env = Env::default(); env.gas_limit = U256::MAX; let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let sender_key = Random.generate().unwrap(); let sender = sender_key.address(); @@ -1550,7 +1551,7 @@ fn test_storage_commission_privilege() { let mut env = Env::default(); env.gas_limit = U256::MAX; let machine = make_byzantium_machine(0); - let spec = machine.spec(env.number); + let spec = machine.spec_for_test(env.number); let sender = Random.generate().unwrap(); let sender_with_space = sender.address().with_native_space(); @@ -2185,7 +2186,7 @@ fn test_push0() { // Test case 1 in EIP-3855 { - let mut spec = machine.spec(env.number); + let mut spec = machine.spec_for_test(env.number); spec.cip119 = true; // code: @@ -2212,7 +2213,7 @@ fn test_push0() { // Test case 2 in EIP-3855 { - let mut spec = machine.spec(env.number); + let mut spec = machine.spec_for_test(env.number); spec.cip119 = true; // code: @@ -2239,7 +2240,7 @@ fn test_push0() { // Test case 2 in EIP-3855 { - let mut spec = machine.spec(env.number); + let mut spec = machine.spec_for_test(env.number); spec.cip119 = true; // code: @@ -2259,7 +2260,7 @@ fn test_push0() { // Before activation of EIP-3855 (CIP119) { - let mut spec = machine.spec(env.number); + let mut spec = machine.spec_for_test(env.number); spec.cip119 = false; // code: diff --git a/crates/cfxcore/executor/src/executive/transact_options.rs b/crates/cfxcore/executor/src/executive/transact_options.rs index 007419248d..0d53b318ad 100644 --- a/crates/cfxcore/executor/src/executive/transact_options.rs +++ b/crates/cfxcore/executor/src/executive/transact_options.rs @@ -19,6 +19,7 @@ impl Default for TransactOptions<()> { pub struct TransactSettings { pub charge_collateral: ChargeCollateral, pub charge_gas: bool, + pub check_base_price: bool, pub check_epoch_bound: bool, } @@ -35,6 +36,7 @@ impl TransactSettings { charge_collateral: ChargeCollateral::Normal, charge_gas: true, check_epoch_bound: true, + check_base_price: true, } } } diff --git a/crates/cfxcore/executor/src/internal_contract/components/activation.rs b/crates/cfxcore/executor/src/internal_contract/components/activation.rs index 3fa7926ea5..236105bc71 100644 --- a/crates/cfxcore/executor/src/internal_contract/components/activation.rs +++ b/crates/cfxcore/executor/src/internal_contract/components/activation.rs @@ -1,5 +1,4 @@ use cfx_vm_types::Spec; -pub use primitives::BlockNumber; pub trait IsActive { fn is_active(&self, spec: &Spec) -> bool; diff --git a/crates/cfxcore/executor/src/internal_contract/components/function.rs b/crates/cfxcore/executor/src/internal_contract/components/function.rs index cc1755e8e4..5fde0ab306 100644 --- a/crates/cfxcore/executor/src/internal_contract/components/function.rs +++ b/crates/cfxcore/executor/src/internal_contract/components/function.rs @@ -4,11 +4,9 @@ use cfx_statedb::Result as DbResult; use cfx_types::U256; -use cfx_vm_types::{self as vm, ActionParams, CallType, GasLeft, Spec}; +use cfx_vm_types::{self as vm, ActionParams, CallType, GasLeft}; use solidity_abi::{ABIDecodable, ABIEncodable}; -use crate::stack::CallStackInfo; - use super::{InternalRefContext, InternalTrapResult, IsActive}; use InternalTrapResult::*; @@ -90,7 +88,7 @@ fn preprocessing( sol_fn: &T, input: &[u8], params: &ActionParams, context: &InternalRefContext, ) -> vm::Result<(T::Input, U256)> { - sol_fn.pre_execution_check(params, context.callstack, context.spec)?; + sol_fn.pre_execution_check(params, context)?; let solidity_params = ::abi_decode(&input)?; let cost = sol_fn.upfront_gas_payment(&solidity_params, params, context)?; if cost > params.gas { @@ -108,8 +106,7 @@ pub trait InterfaceTrait { pub trait PreExecCheckTrait: Send + Sync { fn pre_execution_check( - &self, params: &ActionParams, call_stack: &CallStackInfo, - context: &Spec, + &self, params: &ActionParams, context: &InternalRefContext, ) -> vm::Result<()>; } @@ -157,18 +154,22 @@ pub trait PreExecCheckConfTrait: Send + Sync { impl PreExecCheckTrait for T { fn pre_execution_check( - &self, params: &ActionParams, call_stack: &CallStackInfo, spec: &Spec, + &self, params: &ActionParams, context: &InternalRefContext, ) -> vm::Result<()> { if !Self::PAYABLE && !params.value.value().is_zero() { return Err(vm::Error::InternalContract( - "should not transfer balance to Staking contract".into(), + "should not transfer balance to non-payable function".into(), )); } - if Self::HAS_WRITE_OP - && (call_stack.in_reentrancy(spec) - || params.call_type == CallType::StaticCall) - { + let spec = context.spec; + // Check static context before CIP-132 + let mut static_context = context.callstack.in_reentrancy(spec) + || params.call_type == CallType::StaticCall; + // Add the lost constraint after CIP-132 + static_context |= spec.cip132 && context.static_flag; + + if Self::HAS_WRITE_OP && static_context { return Err(vm::Error::MutableCallInStaticContext); } diff --git a/crates/cfxcore/executor/src/internal_contract/components/mod.rs b/crates/cfxcore/executor/src/internal_contract/components/mod.rs index 7bf6679e27..6b37f193be 100644 --- a/crates/cfxcore/executor/src/internal_contract/components/mod.rs +++ b/crates/cfxcore/executor/src/internal_contract/components/mod.rs @@ -14,8 +14,5 @@ pub use contract::{InternalContractTrait, SolFnTable}; pub use contract_map::InternalContractMap; pub use event::SolidityEventTrait; pub use executable::InternalContractExec; -pub use function::{ - ExecutionTrait, InterfaceTrait, SimpleExecutionTrait, - SolidityFunctionTrait, UpfrontPaymentTrait, -}; +pub use function::{InterfaceTrait, SolidityFunctionTrait}; pub use trap_result::InternalTrapResult; diff --git a/crates/cfxcore/executor/src/internal_contract/contracts/context.rs b/crates/cfxcore/executor/src/internal_contract/contracts/context.rs index e4a6fe0c8e..3d985962de 100644 --- a/crates/cfxcore/executor/src/internal_contract/contracts/context.rs +++ b/crates/cfxcore/executor/src/internal_contract/contracts/context.rs @@ -3,9 +3,11 @@ // See http://www.gnu.org/licenses/ use cfx_parameters::internal_contract_addresses::CONTEXT_CONTRACT_ADDRESS; -use cfx_types::{Address, U256}; +use cfx_types::{Address, BigEndianHash, U256}; use cfx_vm_interpreter::GasPriceTier; +use crate::{internal_contract::epoch_hash_slot, return_if}; + use super::preludes::*; make_solidity_contract! { @@ -13,7 +15,7 @@ make_solidity_contract! { } fn generate_fn_table() -> SolFnTable { - make_function_table!(EpochNumber, PoSHeight, FinalizedEpoch) + make_function_table!(EpochNumber, PoSHeight, FinalizedEpoch, EpochHash) } group_impl_is_active!( @@ -23,6 +25,8 @@ group_impl_is_active!( FinalizedEpoch ); +group_impl_is_active!(|spec: &Spec| spec.cip133_core, EpochHash); + make_solidity_function! { struct EpochNumber((), "epochNumber()", U256); } @@ -71,6 +75,31 @@ impl SimpleExecutionTrait for FinalizedEpoch { } } +make_solidity_function! { + struct EpochHash(U256, "epochHash(uint256)", H256); +} + +impl_function_type!(EpochHash, "query", gas: |spec: &Spec| spec.sload_gas); + +impl SimpleExecutionTrait for EpochHash { + fn execute_inner( + &self, number: U256, _params: &ActionParams, + context: &mut InternalRefContext, + ) -> vm::Result { + return_if!(number > U256::from(u64::MAX)); + + let number = number.as_u64(); + + return_if!(number < context.spec.cip133_e); + return_if!(number > context.env.epoch_height); + return_if!(number + .checked_add(65536) + .map_or(false, |n| n <= context.env.epoch_height)); + let res = context.state.get_system_storage(&epoch_hash_slot(number))?; + Ok(BigEndianHash::from_uint(&res)) + } +} + #[test] fn test_context_contract_sig() { check_func_signature!(EpochNumber, "f4145a83"); diff --git a/crates/cfxcore/executor/src/internal_contract/contracts/mod.rs b/crates/cfxcore/executor/src/internal_contract/contracts/mod.rs index 86b16f7e5c..78722ee5d3 100644 --- a/crates/cfxcore/executor/src/internal_contract/contracts/mod.rs +++ b/crates/cfxcore/executor/src/internal_contract/contracts/mod.rs @@ -70,20 +70,15 @@ use primitives::storage::STORAGE_LAYOUT_REGULAR_V0; pub fn initialize_internal_contract_accounts( state: &mut State, addresses: &[Address], -) { - || -> DbResult<()> { - { - for address in addresses { - state.new_contract_with_admin( - &address.with_native_space(), - /* No admin; admin = */ &Address::zero(), - /* balance = */ U256::zero(), - Some(STORAGE_LAYOUT_REGULAR_V0), - false, - )?; - } - Ok(()) - } - }() - .expect(&concat!(file!(), ":", line!(), ":", column!())); +) -> DbResult<()> { + for address in addresses { + state.new_contract_with_admin( + &address.with_native_space(), + /* No admin; admin = */ &Address::zero(), + /* balance = */ U256::zero(), + Some(STORAGE_LAYOUT_REGULAR_V0), + false, + )?; + } + Ok(()) } diff --git a/crates/cfxcore/executor/src/internal_contract/contracts/params_control.rs b/crates/cfxcore/executor/src/internal_contract/contracts/params_control.rs index f57fc74ff8..2803577ba2 100644 --- a/crates/cfxcore/executor/src/internal_contract/contracts/params_control.rs +++ b/crates/cfxcore/executor/src/internal_contract/contracts/params_control.rs @@ -11,7 +11,7 @@ use cfx_vm_interpreter::GasPriceTier; use super::{super::impls::params_control::*, preludes::*}; make_solidity_contract! { - pub struct ParamsControl(PARAMS_CONTROL_CONTRACT_ADDRESS, generate_fn_table, initialize: |params: &CommonParams| params.transition_numbers.cip94, is_active: |spec: &Spec| spec.cip94); + pub struct ParamsControl(PARAMS_CONTROL_CONTRACT_ADDRESS, generate_fn_table, initialize: |params: &CommonParams| params.transition_numbers.cip94n, is_active: |spec: &Spec| spec.cip94); } fn generate_fn_table() -> SolFnTable { make_function_table!( @@ -137,7 +137,8 @@ fn test_vote_abi_length() { pub const POW_BASE_REWARD_INDEX: u8 = 0; pub const POS_REWARD_INTEREST_RATE_INDEX: u8 = 1; pub const STORAGE_POINT_PROP_INDEX: u8 = 2; -pub const PARAMETER_INDEX_MAX: usize = 3; +pub const BASEFEE_PROP_INDEX: u8 = 3; +pub const PARAMETER_INDEX_MAX: usize = 4; pub const OPTION_UNCHANGE_INDEX: u8 = 0; pub const OPTION_INCREASE_INDEX: u8 = 1; diff --git a/crates/cfxcore/executor/src/internal_contract/contracts/system_storage.rs b/crates/cfxcore/executor/src/internal_contract/contracts/system_storage.rs index 28e72939f0..6fbd94545e 100644 --- a/crates/cfxcore/executor/src/internal_contract/contracts/system_storage.rs +++ b/crates/cfxcore/executor/src/internal_contract/contracts/system_storage.rs @@ -7,7 +7,7 @@ use cfx_parameters::internal_contract_addresses::SYSTEM_STORAGE_ADDRESS; use cfx_types::U256; make_solidity_contract! { - pub struct SystemStorage(SYSTEM_STORAGE_ADDRESS, SolFnTable::default, initialize: |params: &CommonParams| params.transition_numbers.cip94, is_active: |spec: &Spec| spec.cip94); + pub struct SystemStorage(SYSTEM_STORAGE_ADDRESS, SolFnTable::default, initialize: |params: &CommonParams| params.transition_numbers.cip94n, is_active: |spec: &Spec| spec.cip94); } pub fn base_slot(contract: Address) -> U256 { diff --git a/crates/cfxcore/executor/src/internal_contract/impls/context.rs b/crates/cfxcore/executor/src/internal_contract/impls/context.rs new file mode 100644 index 0000000000..a0eba69b28 --- /dev/null +++ b/crates/cfxcore/executor/src/internal_contract/impls/context.rs @@ -0,0 +1,32 @@ +use primitives::{block::BlockHeight, BlockNumber}; +use sha3_macro::keccak; + +const BLOCK_HASHES_START_SLOT: [u8; 32] = { + let mut hash = keccak!("CIP_133_BLOCK_HASHES_START_SLOT"); + hash[30] = 0; + hash[31] = 0; + hash +}; + +const EPOCH_HASHES_START_SLOT: [u8; 32] = { + let mut hash = keccak!("CIP_133_EPOCH_HASHES_START_SLOT"); + hash[30] = 0; + hash[31] = 0; + hash +}; + +pub const fn block_hash_slot(number: BlockNumber) -> [u8; 32] { + let mut answer = BLOCK_HASHES_START_SLOT; + let r = ((number & 0xffff) as u16).to_be_bytes(); + answer[30] = r[0]; + answer[31] = r[1]; + answer +} + +pub const fn epoch_hash_slot(height: BlockHeight) -> [u8; 32] { + let mut answer = EPOCH_HASHES_START_SLOT; + let r = ((height & 0xffff) as u16).to_be_bytes(); + answer[30] = r[0]; + answer[31] = r[1]; + answer +} diff --git a/crates/cfxcore/executor/src/internal_contract/impls/cross_space.rs b/crates/cfxcore/executor/src/internal_contract/impls/cross_space.rs index 1348f4e74a..267dd41c84 100644 --- a/crates/cfxcore/executor/src/internal_contract/impls/cross_space.rs +++ b/crates/cfxcore/executor/src/internal_contract/impls/cross_space.rs @@ -34,7 +34,7 @@ pub fn create_gas(context: &InternalRefContext, code: &[u8]) -> DbResult { let code_length = code.len(); let transaction_gas = - gas_required_for(/* is_create */ true, code, context.spec) + gas_required_for(/* is_create */ true, code, None, context.spec) + context.spec.tx_gas as u64; let create_gas = U256::from(context.spec.create_gas); @@ -70,7 +70,7 @@ pub fn call_gas( let data_length = data.len(); let transaction_gas = - gas_required_for(/* is_create */ false, data, context.spec) + gas_required_for(/* is_create */ false, data, None, context.spec) + context.spec.tx_gas as u64; let new_account = !context diff --git a/crates/cfxcore/executor/src/internal_contract/impls/mod.rs b/crates/cfxcore/executor/src/internal_contract/impls/mod.rs index 32817f9d2d..ed7b85acb3 100644 --- a/crates/cfxcore/executor/src/internal_contract/impls/mod.rs +++ b/crates/cfxcore/executor/src/internal_contract/impls/mod.rs @@ -3,6 +3,7 @@ // See http://www.gnu.org/licenses/ pub(super) mod admin; +pub(super) mod context; pub(super) mod cross_space; pub(super) mod params_control; pub(super) mod pos; diff --git a/crates/cfxcore/executor/src/internal_contract/impls/params_control.rs b/crates/cfxcore/executor/src/internal_contract/impls/params_control.rs index 3859124515..56d226c732 100644 --- a/crates/cfxcore/executor/src/internal_contract/impls/params_control.rs +++ b/crates/cfxcore/executor/src/internal_contract/impls/params_control.rs @@ -333,9 +333,9 @@ impl ParamVoteCount { } pub fn compute_next_params( - &self, old_value: U256, pos_staking_tokens: U256, + &self, old_value: U256, pos_staking_for_votes: U256, ) -> U256 { - if self.should_update(pos_staking_tokens) { + if self.should_update(pos_staking_for_votes) { let answer = self.compute_next_params_inner(old_value); // The return value should be in `[2^8, 2^192]` let min_value = U256::from(256u64); @@ -348,7 +348,7 @@ impl ParamVoteCount { answer } } else { - debug!("params unchanged with pos token {}", pos_staking_tokens); + debug!("params unchanged with pos token {}", pos_staking_for_votes); old_value } } @@ -387,9 +387,9 @@ impl ParamVoteCount { } } - fn should_update(&self, pos_staking_tokens: U256) -> bool { + fn should_update(&self, pos_staking_for_votes: U256) -> bool { (self.decrease + self.increase + self.unchange) - >= pos_staking_tokens * DAO_MIN_VOTE_PERCENTAGE / 100 + >= pos_staking_for_votes * DAO_MIN_VOTE_PERCENTAGE / 100 } } @@ -398,6 +398,7 @@ pub struct AllParamsVoteCount { pub pow_base_reward: ParamVoteCount, pub pos_reward_interest: ParamVoteCount, pub storage_point_prop: ParamVoteCount, + pub base_fee_prop: ParamVoteCount, } /// If the vote counts are not initialized, all counts will be zero, and the @@ -417,10 +418,15 @@ pub fn get_settled_param_vote_count( state, &SETTLED_VOTES_ENTRIES[STORAGE_POINT_PROP_INDEX as usize], )?; + let base_fee_prop = ParamVoteCount::from_state( + state, + &SETTLED_VOTES_ENTRIES[BASEFEE_PROP_INDEX as usize], + )?; Ok(AllParamsVoteCount { pow_base_reward, pos_reward_interest, storage_point_prop, + base_fee_prop, }) } @@ -430,9 +436,7 @@ pub fn get_settled_pos_staking_for_votes(state: &State) -> DbResult { /// Move the next vote counts into settled and reset the counts. /// `set_pos_staking` is for compatibility with the Testnet. -pub fn settle_current_votes( - state: &mut State, set_pos_staking: bool, -) -> DbResult<()> { +pub fn settle_current_votes(state: &mut State, cip105: bool) -> DbResult<()> { // Here using `PARAMETER_INDEX_MAX` without knowing the block_number is okay // because if the new parameters have not been enabled, their votes will // be zero and setting them will be no-op. @@ -450,7 +454,7 @@ pub fn settle_current_votes( )?; } } - if set_pos_staking { + if cip105 { let pos_staking = state.get_system_storage(¤t_pos_staking_for_votes())?; state.set_system_storage( @@ -466,11 +470,14 @@ pub fn settle_current_votes( } pub fn params_index_max(spec: &Spec) -> usize { - if spec.cip107 { - PARAMETER_INDEX_MAX - } else { - PARAMETER_INDEX_MAX - 1 + let mut max = PARAMETER_INDEX_MAX; + if !spec.cip1559 { + max -= 1; + } + if !spec.cip107 { + max -= 1; } + max } /// Solidity variable sequences. @@ -506,7 +513,7 @@ mod storage_key { pub fn votes( address: &Address, index: usize, opt_index: usize, ) -> [u8; 32] { - const TOPIC_OFFSET: [usize; 3] = [1, 2, 3]; + const TOPIC_OFFSET: [usize; 4] = [1, 2, 3, 4]; // Position of `votes` let base = U256::from(VOTES_SLOT); diff --git a/crates/cfxcore/executor/src/internal_contract/impls/pos.rs b/crates/cfxcore/executor/src/internal_contract/impls/pos.rs index 8eaf95210f..4ae0f3f6da 100644 --- a/crates/cfxcore/executor/src/internal_contract/impls/pos.rs +++ b/crates/cfxcore/executor/src/internal_contract/impls/pos.rs @@ -155,11 +155,19 @@ pub fn register( internal_bail!("can not change identifier"); } - let maybe_verified_bls_pubkey = - verify_bls_pubkey(bls_pubkey, bls_proof, !context.spec.cip_sigma_fix)?; - let verified_bls_pubkey = maybe_verified_bls_pubkey.ok_or( - vm::Error::InternalContract("Can not verify bls pubkey".into()), - )?; + let verified_bls_pubkey = match verify_bls_pubkey( + bls_pubkey, + bls_proof, + !context.spec.cip_sigma_fix, + ) { + Err(e) => { + internal_bail!("Crypto decoding error {:?}", e); + } + Ok(None) => { + internal_bail!("Can not verify bls pubkey"); + } + Ok(Some(key)) => key, + }; let mut hasher = Keccak::v256(); hasher.update(verified_bls_pubkey.as_slice()); diff --git a/crates/cfxcore/executor/src/internal_contract/mod.rs b/crates/cfxcore/executor/src/internal_contract/mod.rs index 850d78987a..6e979b19a5 100644 --- a/crates/cfxcore/executor/src/internal_contract/mod.rs +++ b/crates/cfxcore/executor/src/internal_contract/mod.rs @@ -20,6 +20,7 @@ pub use self::{ }, impls::{ admin::suicide, + context::{block_hash_slot, epoch_hash_slot}, cross_space::{evm_map, Resume}, params_control::{ get_settled_param_vote_count, get_settled_pos_staking_for_votes, diff --git a/crates/cfxcore/executor/src/machine/mod.rs b/crates/cfxcore/executor/src/machine/mod.rs index 97e596b06c..d116416a09 100644 --- a/crates/cfxcore/executor/src/machine/mod.rs +++ b/crates/cfxcore/executor/src/machine/mod.rs @@ -15,7 +15,7 @@ use crate::{ }; use cfx_types::{Address, AddressWithSpace, Space, H256}; use cfx_vm_types::Spec; -use primitives::BlockNumber; +use primitives::{block::BlockHeight, BlockNumber}; use std::{collections::BTreeMap, sync::Arc}; pub use vm_factory::VmFactory; @@ -23,7 +23,7 @@ pub type SpecCreationRules = dyn Fn(&mut Spec, BlockNumber) + Sync + Send; pub struct Machine { params: CommonParams, - vm: VmFactory, + vm_factory: VmFactory, builtins: Arc>, builtins_evm: Arc>, internal_contracts: Arc, @@ -55,32 +55,41 @@ impl Machine { /// Get the general parameters of the chain. pub fn params(&self) -> &CommonParams { &self.params } - pub fn spec(&self, number: BlockNumber) -> Spec { - let mut spec = self.params.spec(number); + pub fn spec(&self, number: BlockNumber, height: BlockHeight) -> Spec { + let mut spec = self.params.spec(number, height); if let Some(ref rules) = self.spec_rules { (rules)(&mut spec, number) } spec } + #[cfg(test)] + pub fn spec_for_test(&self, number: u64) -> Spec { + self.spec(number, number) + } + /// Builtin-contracts for the chain.. pub fn builtins(&self) -> &BTreeMap { &*self.builtins } + pub fn builtins_evm(&self) -> &BTreeMap { + &*self.builtins_evm + } + /// Builtin-contracts for the chain.. pub fn internal_contracts(&self) -> &InternalContractMap { &*self.internal_contracts } /// Get a VM factory that can execute on this state. - pub fn vm_factory(&self) -> VmFactory { self.vm.clone() } + pub fn vm_factory(&self) -> VmFactory { self.vm_factory.clone() } - pub fn vm_factory_ref(&self) -> &VmFactory { &self.vm } + pub fn vm_factory_ref(&self) -> &VmFactory { &self.vm_factory } } -pub fn new_machine(params: CommonParams, vm: VmFactory) -> Machine { +pub fn new_machine(params: CommonParams, vm_factory: VmFactory) -> Machine { Machine { params, - vm, + vm_factory, builtins: Arc::new(BTreeMap::new()), builtins_evm: Arc::new(Default::default()), internal_contracts: Arc::new(InternalContractMap::default()), @@ -168,11 +177,19 @@ fn new_builtin_map( params.transition_numbers.cip92, ), ); + btree.insert( + Address::from(H256::from_low_u64_be(10)), + Builtin::new( + Box::new(Linear::new(50000, 0)), + builtin_factory("kzg_point_eval"), + params.transition_numbers.cip144, + ), + ); btree } pub fn new_machine_with_builtin( - params: CommonParams, vm: VmFactory, + params: CommonParams, vm_factory: VmFactory, ) -> Machine { let builtin = new_builtin_map(¶ms, Space::Native); let builtin_evm = new_builtin_map(¶ms, Space::Ethereum); @@ -180,7 +197,7 @@ pub fn new_machine_with_builtin( let internal_contracts = InternalContractMap::new(¶ms); Machine { params, - vm, + vm_factory, builtins: Arc::new(builtin), builtins_evm: Arc::new(builtin_evm), internal_contracts: Arc::new(internal_contracts), diff --git a/crates/cfxcore/executor/src/machine/vm_factory.rs b/crates/cfxcore/executor/src/machine/vm_factory.rs index af623b7e6d..54024c53f2 100644 --- a/crates/cfxcore/executor/src/machine/vm_factory.rs +++ b/crates/cfxcore/executor/src/machine/vm_factory.rs @@ -8,23 +8,23 @@ use cfx_vm_types::{ActionParams, Exec, Spec}; /// Virtual machine factory #[derive(Default, Clone)] pub struct VmFactory { - evm: EvmFactory, + evm_factory: EvmFactory, } impl VmFactory { pub fn create( &self, params: ActionParams, spec: &Spec, depth: usize, ) -> Box { - self.evm.create(params, spec, depth) + self.evm_factory.create(params, spec, depth) } pub fn new(cache_size: usize) -> Self { VmFactory { - evm: EvmFactory::new(VMType::Interpreter, cache_size), + evm_factory: EvmFactory::new(VMType::Interpreter, cache_size), } } } impl From for VmFactory { - fn from(evm: EvmFactory) -> Self { VmFactory { evm } } + fn from(evm_factory: EvmFactory) -> Self { VmFactory { evm_factory } } } diff --git a/crates/cfxcore/executor/src/observer/internal_transfer_tracer.rs b/crates/cfxcore/executor/src/observer/internal_transfer_tracer.rs index e88f28cfee..ba594a9e94 100644 --- a/crates/cfxcore/executor/src/observer/internal_transfer_tracer.rs +++ b/crates/cfxcore/executor/src/observer/internal_transfer_tracer.rs @@ -20,7 +20,7 @@ pub trait InternalTransferTracer { ) { } - fn trace_convert_stroage_points( + fn trace_convert_storage_points( &mut self, addr: Address, from_balance: U256, from_collateral: U256, ) { if !from_balance.is_zero() { diff --git a/crates/cfxcore/executor/src/observer/opcode_tracer.rs b/crates/cfxcore/executor/src/observer/opcode_tracer.rs index 46ffb3aa88..5611da5935 100644 --- a/crates/cfxcore/executor/src/observer/opcode_tracer.rs +++ b/crates/cfxcore/executor/src/observer/opcode_tracer.rs @@ -1,3 +1,6 @@ +use cfx_types::{Address, H256, U256}; +use cfx_vm_types::InterpreterInfo; + use impl_tools::autoimpl; use impl_trait_for_tuples::impl_for_tuples; @@ -5,5 +8,36 @@ use impl_trait_for_tuples::impl_for_tuples; #[autoimpl(for &mut T)] pub trait OpcodeTracer { fn do_trace_opcode(&self, _enabled: &mut bool) {} - // TODO[geth-tracer]: Define your hook here for EVM opcode + + /// Called before the interpreter is initialized. + #[inline] + fn initialize_interp(&mut self, gas_limit: U256) { let _ = gas_limit; } + + /// Called on each step of the interpreter. + /// + /// Information about the current execution, including the memory, stack and + /// more is available on `interp` (see [Interpreter]). + fn step(&mut self, interp: &dyn InterpreterInfo) { let _ = interp; } + + /// Called after `step` when the instruction has been executed. + fn step_end(&mut self, interp: &dyn InterpreterInfo) { let _ = interp; } + + /// Called when a log is emitted. + #[inline] + fn log(&mut self, address: &Address, topics: &Vec, data: &[u8]) { + let _ = address; + let _ = topics; + let _ = data; + } + + /// Called when a contract has been self-destructed with funds transferred + /// to target. + #[inline] + fn selfdestruct( + &mut self, contract: &Address, target: &Address, value: U256, + ) { + let _ = contract; + let _ = target; + let _ = value; + } } diff --git a/crates/cfxcore/executor/src/observer/storage_tracer.rs b/crates/cfxcore/executor/src/observer/storage_tracer.rs index a2f16dbae3..50e595bff4 100644 --- a/crates/cfxcore/executor/src/observer/storage_tracer.rs +++ b/crates/cfxcore/executor/src/observer/storage_tracer.rs @@ -3,6 +3,4 @@ use impl_trait_for_tuples::impl_for_tuples; #[impl_for_tuples(3)] #[autoimpl(for &mut T)] -pub trait StorageTracer { - // TODO[geth-tracer]: Define your hook here for EVM opcode -} +pub trait StorageTracer {} diff --git a/crates/cfxcore/executor/src/observer/tracer_trait.rs b/crates/cfxcore/executor/src/observer/tracer_trait.rs index 129f796d60..45d0d3a2fd 100644 --- a/crates/cfxcore/executor/src/observer/tracer_trait.rs +++ b/crates/cfxcore/executor/src/observer/tracer_trait.rs @@ -1,15 +1,14 @@ use super::{ - call_tracer::CallTracer, checkpoint_tracer::CheckpointTracer, - internal_transfer_tracer::InternalTransferTracer, - opcode_tracer::OpcodeTracer, StorageTracer, + CallTracer, CheckpointTracer, InternalTransferTracer, OpcodeTracer, + StorageTracer, }; pub trait TracerTrait: CheckpointTracer + CallTracer + InternalTransferTracer - + OpcodeTracer + StorageTracer + + OpcodeTracer { } diff --git a/crates/cfxcore/executor/src/spec.rs b/crates/cfxcore/executor/src/spec.rs index a8b5862b9d..bafc485edd 100644 --- a/crates/cfxcore/executor/src/spec.rs +++ b/crates/cfxcore/executor/src/spec.rs @@ -8,7 +8,8 @@ use cfx_parameters::{ block::{EVM_TRANSACTION_BLOCK_RATIO, EVM_TRANSACTION_GAS_RATIO}, consensus::{ CIP112_HEADER_CUSTOM_FIRST_ELEMENT, - DAO_VOTE_HEADER_CUSTOM_FIRST_ELEMENT, ONE_UCFX_IN_DRIP, + DAO_VOTE_HEADER_CUSTOM_FIRST_ELEMENT, + NEXT_HARDFORK_HEADER_CUSTOM_FIRST_ELEMENT, ONE_UCFX_IN_DRIP, TANZANITE_HEADER_CUSTOM_FIRST_ELEMENT, }, consensus_internal::{ @@ -16,7 +17,7 @@ use cfx_parameters::{ INITIAL_BASE_MINING_REWARD_IN_UCFX, }, }; -use cfx_types::{AllChainID, Space, U256, U512}; +use cfx_types::{AllChainID, Space, SpaceMap, U256, U512}; use cfx_vm_types::Spec; use primitives::{block::BlockHeight, BlockNumber}; use std::collections::BTreeMap; @@ -53,6 +54,7 @@ pub struct CommonParams { /// transactions pub evm_transaction_gas_ratio: u64, pub params_dao_vote_period: u64, + pub min_base_price: SpaceMap, /// Set the internal contracts to state at the genesis blocks, even if it /// is not activated. @@ -76,14 +78,13 @@ pub struct TransitionsBlockNumber { pub cip71: BlockNumber, /// CIP-78: Correct `is_sponsored` Fields in Receipt pub cip78a: BlockNumber, - /// CIP-78: Correct `is_sponsored` Fields in Receipt pub cip78b: BlockNumber, /// CIP-90: Introduce a Fully EVM-Compatible Space pub cip90b: BlockNumber, /// CIP-92: Enable Blake2F Builtin Function pub cip92: BlockNumber, /// CIP-94: On-Chain DAO Vote for Chain Parameters - pub cip94: BlockNumber, + pub cip94n: BlockNumber, /// CIP-97: Clear Staking Lists pub cip97: BlockNumber, /// CIP-98: Fix BLOCKHASH Opcode Bug in eSpace @@ -98,6 +99,22 @@ pub struct TransitionsBlockNumber { pub cip118: BlockNumber, /// CIP-119: PUSH0 instruction pub cip119: BlockNumber, + /// CIP-131: Retain Whitelist on Contract Deletion + pub cip131: BlockNumber, + /// CIP-132: Fix Static Context Check for Internal Contracts + pub cip132: BlockNumber, + /// CIP-133: Enhanced Block Hash Query + pub cip133b: BlockNumber, + /// CIP-137: Base Fee Sharing in CIP-1559 + pub cip137: BlockNumber, + /// CIP-141: Disable Subroutine Opcodes + /// CIP-142: Transient Storage Opcodes + /// CIP-143: MCOPY (0x5e) Opcode for Efficient Memory Copy + pub cancun_opcodes: BlockNumber, + /// CIP-144: Point Evaluation Precompile from EIP-4844 + pub cip144: BlockNumber, + /// CIP-145: Fix Receipts upon `NotEnoughBalance` Error + pub cip145: BlockNumber, } #[derive(Default, Debug, Clone)] @@ -111,9 +128,14 @@ pub struct TransitionsEpochHeight { /// CIP-90: Introduce a Fully EVM-Compatible Space pub cip90a: BlockHeight, /// CIP-94: On-Chain DAO Vote for Chain Parameters - pub cip94: BlockHeight, + pub cip94h: BlockHeight, /// CIP-112: Fix Block Headers `custom` Field Serde pub cip112: BlockHeight, + /// CIP-130: Aligning Gas Limit with Transaction Size + pub cip130: BlockHeight, + /// CIP-133: Enhanced Block Hash Query + pub cip133e: BlockHeight, + pub cip1559: BlockHeight, } impl Default for CommonParams { @@ -136,12 +158,13 @@ impl Default for CommonParams { early_set_internal_contracts_states: false, transition_numbers: Default::default(), transition_heights: Default::default(), + min_base_price: SpaceMap::default(), } } } impl CommonParams { - pub fn spec(&self, number: BlockNumber) -> Spec { + pub fn spec(&self, number: BlockNumber, height: BlockHeight) -> Spec { let mut spec = Spec::genesis_spec(); spec.cip43_contract = number >= self.transition_numbers.cip43a; spec.cip43_init = number >= self.transition_numbers.cip43a @@ -152,8 +175,8 @@ impl CommonParams { spec.cip90 = number >= self.transition_numbers.cip90b; spec.cip78a = number >= self.transition_numbers.cip78a; spec.cip78b = number >= self.transition_numbers.cip78b; - spec.cip94 = number >= self.transition_numbers.cip94; - spec.cip94_activation_block_number = self.transition_numbers.cip94; + spec.cip94 = number >= self.transition_numbers.cip94n; + spec.cip94_activation_block_number = self.transition_numbers.cip94n; spec.cip97 = number >= self.transition_numbers.cip97; spec.cip98 = number >= self.transition_numbers.cip98; spec.cip105 = number >= self.transition_numbers.cip105; @@ -162,9 +185,27 @@ impl CommonParams { spec.cip107 = number >= self.transition_numbers.cip107; spec.cip118 = number >= self.transition_numbers.cip118; spec.cip119 = number >= self.transition_numbers.cip119; + spec.cip131 = number >= self.transition_numbers.cip131; + spec.cip132 = number >= self.transition_numbers.cip132; + spec.cip133_b = self.transition_numbers.cip133b; + spec.cip133_e = self.transition_heights.cip133e; + spec.cip133_core = number >= self.transition_numbers.cip133b; + spec.cip137 = number >= self.transition_numbers.cip137; + spec.cip144 = number >= self.transition_numbers.cip144; + spec.cip145 = number >= self.transition_numbers.cip145; + spec.cip1559 = height >= self.transition_heights.cip1559; + spec.cancun_opcodes = number >= self.transition_numbers.cancun_opcodes; + if spec.cancun_opcodes { + spec.sload_gas = 800; + } spec } + #[cfg(test)] + pub fn spec_for_test(&self, number: u64) -> Spec { + self.spec(number, number) + } + /// Return the base reward for a block. /// `past_block_count` may be used for reward decay again in the future. pub fn base_reward_in_ucfx( @@ -180,15 +221,19 @@ impl CommonParams { pub fn custom_prefix(&self, height: BlockHeight) -> Option> { if height >= self.transition_heights.cip40 - && height < self.transition_heights.cip94 + && height < self.transition_heights.cip94h { Some(vec![TANZANITE_HEADER_CUSTOM_FIRST_ELEMENT.to_vec()]) - } else if height >= self.transition_heights.cip94 + } else if height >= self.transition_heights.cip94h && height < self.transition_heights.cip112 { Some(vec![DAO_VOTE_HEADER_CUSTOM_FIRST_ELEMENT.to_vec()]) - } else if height >= self.transition_heights.cip112 { + } else if height >= self.transition_heights.cip112 + && height < self.transition_heights.cip1559 + { Some(vec![CIP112_HEADER_CUSTOM_FIRST_ELEMENT.to_vec()]) + } else if height >= self.transition_heights.cip1559 { + Some(vec![NEXT_HARDFORK_HEADER_CUSTOM_FIRST_ELEMENT.to_vec()]) } else { None } @@ -214,4 +259,8 @@ impl CommonParams { ), ]) } + + pub fn init_base_price(&self) -> SpaceMap { self.min_base_price } + + pub fn min_base_price(&self) -> SpaceMap { self.min_base_price } } diff --git a/crates/cfxcore/executor/src/stack/executable.rs b/crates/cfxcore/executor/src/stack/executable.rs index 90b5644a25..a1dc3a19d3 100644 --- a/crates/cfxcore/executor/src/stack/executable.rs +++ b/crates/cfxcore/executor/src/stack/executable.rs @@ -1,6 +1,6 @@ use super::{FrameLocal, Resumable}; use crate::{ - builtin::BuiltinExec, context::Context, + builtin::BuiltinExec, context::Context, executive_observer::TracerTrait, internal_contract::InternalContractExec, }; use cfx_statedb::Result as DbResult; @@ -46,6 +46,7 @@ use ExecutableOutcome::*; /// contracts, simple transfers, or the execution of EVM bytecode. pub fn make_executable<'a>( frame_local: &FrameLocal<'a>, params: ActionParams, + tracer: &mut dyn TracerTrait, ) -> Box { let is_create = frame_local.create_address.is_some(); let code_address = params.code_address.with_space(params.space); @@ -73,6 +74,10 @@ pub fn make_executable<'a>( if is_create || params.code.is_some() { trace!("CallCreate"); + + // call the initialize_interp hook to log gas_limit + tracer.initialize_interp(params.gas.clone()); + let factory = frame_local.machine.vm_factory_ref(); Box::new(factory.create(params, frame_local.spec, frame_local.depth)) } else { diff --git a/crates/cfxcore/executor/src/stack/frame_start.rs b/crates/cfxcore/executor/src/stack/frame_start.rs index 47d772cbe6..2541a39a51 100644 --- a/crates/cfxcore/executor/src/stack/frame_start.rs +++ b/crates/cfxcore/executor/src/stack/frame_start.rs @@ -113,7 +113,8 @@ impl<'a> FreshFrame<'a> { )? }; - let executable = make_executable(&frame_local, params); + let executable = + make_executable(&frame_local, params, resources.tracer); run_executable(executable, frame_local, resources) } } diff --git a/crates/cfxcore/executor/src/state/mod.rs b/crates/cfxcore/executor/src/state/mod.rs index a41a5ed431..d6d012989b 100644 --- a/crates/cfxcore/executor/src/state/mod.rs +++ b/crates/cfxcore/executor/src/state/mod.rs @@ -21,9 +21,10 @@ mod state_object; #[cfg(test)] pub use state_object::get_state_for_genesis_write; pub use state_object::{ - distribute_pos_interest, initialize_cip107, + distribute_pos_interest, initialize_cip107, initialize_cip137, initialize_or_update_dao_voted_params, settle_collateral_for_all, - update_pos_status, State, COMMISSION_PRIVILEGE_SPECIAL_KEY, + update_pos_status, State, StateCommitResult, + COMMISSION_PRIVILEGE_SPECIAL_KEY, }; use cfx_types::AddressWithSpace; diff --git a/crates/cfxcore/executor/src/state/overlay_account/factory.rs b/crates/cfxcore/executor/src/state/overlay_account/factory.rs index 5a4a0e78c3..415c9bd426 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/factory.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/factory.rs @@ -14,6 +14,7 @@ impl Default for OverlayAccount { sponsor_info: Default::default(), storage_read_cache: Default::default(), storage_write_cache: Default::default(), + transient_storage: Default::default(), storage_layout_change: None, staking_balance: 0.into(), collateral_for_storage: 0.into(), @@ -143,6 +144,7 @@ impl OverlayAccount { pending_db_clear: self.pending_db_clear, storage_write_cache: self.storage_write_cache.clone(), storage_read_cache: self.storage_read_cache.clone(), + transient_storage: self.transient_storage.clone(), storage_layout_change: self.storage_layout_change.clone(), } } diff --git a/crates/cfxcore/executor/src/state/overlay_account/mod.rs b/crates/cfxcore/executor/src/state/overlay_account/mod.rs index ca1ace208a..6c7a0041e8 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/mod.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/mod.rs @@ -134,6 +134,9 @@ pub struct OverlayAccount { /// changed values. storage_write_cache: Arc, StorageValue>>, + /// Transient storage from CIP-142 + transient_storage: Arc, U256>>, + /* --------------- - Special flags - --------------- */ diff --git a/crates/cfxcore/executor/src/state/overlay_account/storage.rs b/crates/cfxcore/executor/src/state/overlay_account/storage.rs index d51703630f..4c1f31d403 100644 --- a/crates/cfxcore/executor/src/state/overlay_account/storage.rs +++ b/crates/cfxcore/executor/src/state/overlay_account/storage.rs @@ -167,6 +167,10 @@ impl OverlayAccount { }) } + pub fn transient_storage_at(&self, key: &[u8]) -> U256 { + self.transient_storage.get(key).cloned().unwrap_or_default() + } + fn get_and_cache_storage( &self, db: &StateDbGeneric, key: &[u8], ) -> DbResult { @@ -185,6 +189,10 @@ impl OverlayAccount { Ok(storage_value) } + pub fn transient_set_storage(&mut self, key: Vec, value: U256) { + Arc::make_mut(&mut self.transient_storage).insert(key, value); + } + pub(super) fn should_have_owner(&self, _key: &[u8]) -> bool { self.address.space == Space::Native && self.address.address != SYSTEM_STORAGE_ADDRESS diff --git a/crates/cfxcore/executor/src/state/state_object/collateral.rs b/crates/cfxcore/executor/src/state/state_object/collateral.rs index b533a08d63..8e216715da 100644 --- a/crates/cfxcore/executor/src/state/state_object/collateral.rs +++ b/crates/cfxcore/executor/src/state/state_object/collateral.rs @@ -174,7 +174,7 @@ fn settle_collateral_for_address( && (!sub.is_zero() || !inc.is_zero()) { let (from_balance, from_collateral) = state.initialize_cip107(addr)?; - tracer.trace_convert_stroage_points( + tracer.trace_convert_storage_points( *addr, from_balance, from_collateral, diff --git a/crates/cfxcore/executor/src/state/state_object/global_statistics.rs b/crates/cfxcore/executor/src/state/state_object/global_statistics.rs index 3805b05c0e..a0a06312bc 100644 --- a/crates/cfxcore/executor/src/state/state_object/global_statistics.rs +++ b/crates/cfxcore/executor/src/state/state_object/global_statistics.rs @@ -1,41 +1,11 @@ use super::State; -use cfx_parameters::{ - genesis::{ - genesis_contract_address_four_year, genesis_contract_address_two_year, - }, - staking::INTEREST_RATE_PER_BLOCK_SCALE, +use cfx_parameters::genesis::{ + genesis_contract_address_four_year, genesis_contract_address_two_year, }; use cfx_statedb::{global_params::*, Result as DbResult}; use cfx_types::{Address, AddressSpaceUtil, U256}; impl State { - /// Calculate the secondary reward for the next block number. - pub fn bump_block_number_accumulate_interest(&mut self) { - assert!(self.checkpoints.get_mut().is_empty()); - let interset_rate_per_block = self.global_stat.get::(); - let accumulate_interest_rate = - self.global_stat.val::(); - *accumulate_interest_rate = *accumulate_interest_rate - * (*INTEREST_RATE_PER_BLOCK_SCALE + interset_rate_per_block) - / *INTEREST_RATE_PER_BLOCK_SCALE; - } - - pub fn secondary_reward(&self) -> U256 { - assert!(self.checkpoints.read().is_empty()); - let secondary_reward = *self.global_stat.refr::() - * *self.global_stat.refr::() - / *INTEREST_RATE_PER_BLOCK_SCALE; - // TODO: the interest from tokens other than storage and staking should - // send to public fund. - secondary_reward - } - - pub fn pow_base_reward(&self) -> U256 { - let base_reward = self.global_stat.get::(); - assert!(!base_reward.is_zero()); - base_reward - } - pub fn total_issued_tokens(&self) -> U256 { self.global_stat.get::() } @@ -94,14 +64,6 @@ impl State { self.global_stat.refr::().saturating_sub(v) } - pub fn distributable_pos_interest(&self) -> U256 { - self.global_stat.get::() - } - - pub fn last_distribute_block(&self) -> u64 { - self.global_stat.refr::().as_u64() - } - pub fn total_circulating_tokens(&self) -> DbResult { Ok(self.total_issued_tokens() - self.balance(&Address::zero().with_native_space())? @@ -109,12 +71,6 @@ impl State { - self.balance(&genesis_contract_address_two_year())?) } - pub fn reset_pos_distribute_info(&mut self, current_block_number: u64) { - *self.global_stat.val::() = U256::zero(); - *self.global_stat.val::() = - U256::from(current_block_number); - } - pub fn add_converted_storage_point( &mut self, from_balance: U256, from_collateral: U256, ) { diff --git a/crates/cfxcore/executor/src/state/state_object/mod.rs b/crates/cfxcore/executor/src/state/state_object/mod.rs index e4e9d50da4..a6b760c774 100644 --- a/crates/cfxcore/executor/src/state/state_object/mod.rs +++ b/crates/cfxcore/executor/src/state/state_object/mod.rs @@ -39,12 +39,16 @@ mod staking; /// Implements access functions for the account storage entries of `State`. mod storage_entry; +mod reward; + #[cfg(test)] mod tests; pub use self::{ collateral::{initialize_cip107, settle_collateral_for_all}, + commit::StateCommitResult, pos::{distribute_pos_interest, update_pos_status}, + reward::initialize_cip137, sponsor::COMMISSION_PRIVILEGE_SPECIAL_KEY, staking::initialize_or_update_dao_voted_params, }; diff --git a/crates/cfxcore/executor/src/state/state_object/reward.rs b/crates/cfxcore/executor/src/state/state_object/reward.rs new file mode 100644 index 0000000000..0e5f0cd3c7 --- /dev/null +++ b/crates/cfxcore/executor/src/state/state_object/reward.rs @@ -0,0 +1,80 @@ +use super::State; +use cfx_parameters::{ + consensus::ONE_CFX_IN_DRIP, consensus_internal::CIP137_BASEFEE_PROP_INIT, +}; +use cfx_types::U256; + +use cfx_parameters::staking::INTEREST_RATE_PER_BLOCK_SCALE; +use cfx_statedb::global_params::*; + +impl State { + /// Calculate the secondary reward for the next block number. + pub fn bump_block_number_accumulate_interest(&mut self) { + assert!(self.checkpoints.get_mut().is_empty()); + let interset_rate_per_block = self.global_stat.get::(); + let accumulate_interest_rate = + self.global_stat.val::(); + *accumulate_interest_rate = *accumulate_interest_rate + * (*INTEREST_RATE_PER_BLOCK_SCALE + interset_rate_per_block) + / *INTEREST_RATE_PER_BLOCK_SCALE; + } + + pub fn secondary_reward(&self) -> U256 { + assert!(self.checkpoints.read().is_empty()); + let secondary_reward = *self.global_stat.refr::() + * *self.global_stat.refr::() + / *INTEREST_RATE_PER_BLOCK_SCALE; + // TODO: the interest from tokens other than storage and staking should + // send to public fund. + secondary_reward + } + + pub fn pow_base_reward(&self) -> U256 { + let base_reward = self.global_stat.get::(); + assert!(!base_reward.is_zero()); + base_reward + } + + pub fn distributable_pos_interest(&self) -> U256 { + self.global_stat.get::() + } + + pub fn last_distribute_block(&self) -> u64 { + self.global_stat.refr::().as_u64() + } + + pub fn reset_pos_distribute_info(&mut self, current_block_number: u64) { + *self.global_stat.val::() = U256::zero(); + *self.global_stat.val::() = + U256::from(current_block_number); + } + + pub fn burn_by_cip1559(&mut self, by: U256) { + // This function is called after transaction exeuction. At this time, + // the paid transaction fee has already been in the core space. + *self.global_stat.val::() += by; + self.sub_total_issued(by); + } + + pub fn get_base_price_prop(&self) -> U256 { + self.global_stat.get::() + } + + pub fn set_base_fee_prop(&mut self, val: U256) { + *self.global_stat.val::() = val; + } + + pub fn burnt_gas_price(&self, base_price: U256) -> U256 { + if base_price.is_zero() { + return U256::zero(); + } + let prop = self.get_base_price_prop(); + base_price - base_price * prop / (U256::from(ONE_CFX_IN_DRIP) + prop) + } +} + +/// Initialize CIP-137 for the whole system. +pub fn initialize_cip137(state: &mut State) { + debug!("set base_fee_prop to {}", CIP137_BASEFEE_PROP_INIT); + state.set_base_fee_prop(CIP137_BASEFEE_PROP_INIT.into()); +} diff --git a/crates/cfxcore/executor/src/state/state_object/sponsor.rs b/crates/cfxcore/executor/src/state/state_object/sponsor.rs index 9a64ce047c..7903265e24 100644 --- a/crates/cfxcore/executor/src/state/state_object/sponsor.rs +++ b/crates/cfxcore/executor/src/state/state_object/sponsor.rs @@ -205,14 +205,16 @@ impl State { } pub fn record_storage_and_whitelist_entries_release( - &mut self, address: &Address, substate: &mut Substate, + &mut self, address: &Address, substate: &mut Substate, cip131: bool, ) -> DbResult<()> { - storage_range_deletion_for_account( - self, - &SPONSOR_WHITELIST_CONTROL_CONTRACT_ADDRESS, - address.as_ref(), - substate, - )?; + if !cip131 { + storage_range_deletion_for_account( + self, + &SPONSOR_WHITELIST_CONTROL_CONTRACT_ADDRESS, + address.as_ref(), + substate, + )?; + } storage_range_deletion_for_account(self, address, &vec![], substate)?; Ok(()) } diff --git a/crates/cfxcore/executor/src/state/state_object/staking.rs b/crates/cfxcore/executor/src/state/state_object/staking.rs index 0a3a8c375b..7d09b8aaea 100644 --- a/crates/cfxcore/executor/src/state/state_object/staking.rs +++ b/crates/cfxcore/executor/src/state/state_object/staking.rs @@ -129,7 +129,7 @@ impl State { } pub fn initialize_or_update_dao_voted_params( - state: &mut State, set_pos_staking: bool, + state: &mut State, cip105: bool, ) -> DbResult<()> { let vote_count = get_settled_param_vote_count(state).expect("db error"); debug!( @@ -139,7 +139,7 @@ pub fn initialize_or_update_dao_voted_params( debug!( "before pos interest: {} base_reward:{}", state.global_stat.refr::(), - state.global_stat.refr::() + state.global_stat.refr::(), ); // If pos_staking has not been set before, this will be zero and the @@ -180,13 +180,22 @@ pub fn initialize_or_update_dao_voted_params( ), )?; } + + let old_base_fee_prop = state.get_base_price_prop(); + if !old_base_fee_prop.is_zero() { + state.set_base_fee_prop( + vote_count + .base_fee_prop + .compute_next_params(old_base_fee_prop, pos_staking_for_votes), + ) + } debug!( "pos interest: {} base_reward: {}", state.global_stat.refr::(), state.global_stat.refr::() ); - settle_current_votes(state, set_pos_staking)?; + settle_current_votes(state, cip105)?; Ok(()) } diff --git a/crates/cfxcore/executor/src/state/state_object/storage_entry.rs b/crates/cfxcore/executor/src/state/state_object/storage_entry.rs index 5adc34f993..5fe9fa8e0a 100644 --- a/crates/cfxcore/executor/src/state/state_object/storage_entry.rs +++ b/crates/cfxcore/executor/src/state/state_object/storage_entry.rs @@ -44,6 +44,14 @@ impl State { acc.storage_at(&self.db, key) } + #[inline] + pub fn transient_storage_at( + &self, address: &AddressWithSpace, key: &[u8], + ) -> DbResult { + let acc = try_loaded!(self.read_account_lock(address)); + Ok(acc.transient_storage_at(key)) + } + #[inline] pub fn storage_entry_at( &self, address: &AddressWithSpace, key: &[u8], @@ -68,6 +76,15 @@ impl State { Ok(()) } + #[inline] + pub fn transient_set_storage( + &mut self, address: &AddressWithSpace, key: Vec, value: U256, + ) -> DbResult<()> { + Ok(self + .write_account_lock(address)? + .transient_set_storage(key, value)) + } + pub fn is_fresh_storage( &self, address: &AddressWithSpace, ) -> DbResult { diff --git a/crates/cfxcore/executor/src/state/state_object/tests.rs b/crates/cfxcore/executor/src/state/state_object/tests.rs index 133db72d4a..a7e13362be 100644 --- a/crates/cfxcore/executor/src/state/state_object/tests.rs +++ b/crates/cfxcore/executor/src/state/state_object/tests.rs @@ -39,6 +39,7 @@ fn get_state( .expect("Failed to initialize state") } +#[cfg(test)] pub fn get_state_for_genesis_write( storage_manager: &Arc, ) -> State { @@ -49,7 +50,8 @@ pub fn get_state_for_genesis_write( initialize_internal_contract_accounts( &mut state, InternalContractMap::initialize_for_test().as_slice(), - ); + ) + .expect("no db error"); let genesis_epoch_id = EpochId::default(); state .commit(genesis_epoch_id, /* debug_record = */ None) diff --git a/crates/cfxcore/geth-tracer/Cargo.toml b/crates/cfxcore/geth-tracer/Cargo.toml new file mode 100644 index 0000000000..46c964cae9 --- /dev/null +++ b/crates/cfxcore/geth-tracer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "geth-tracer" +edition = "2021" +version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +repository.workspace = true +license-file.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alloy-primitives = { workspace = true } +alloy-sol-types = "0.7.1" +revm = { version = "8.0", default-features = false, features = ["std"] } +alloy-rpc-types-trace = { workspace = true } +cfx-vm-types = { path = "../vm-types" } +cfx-types = { path = "../../cfx_types" } +cfx-executor = { path = "../executor" } +typemap = { package = "typemap-ors", version = "1.0"} +cfx-vm-interpreter = { path = "../vm-interpreter" } +primitives = { path = "../../primitives" } diff --git a/crates/cfxcore/geth-tracer/readme.md b/crates/cfxcore/geth-tracer/readme.md new file mode 100644 index 0000000000..a48a15384a --- /dev/null +++ b/crates/cfxcore/geth-tracer/readme.md @@ -0,0 +1,3 @@ +# geth-tracer + +This crate provides a geth style tracer for evm. \ No newline at end of file diff --git a/crates/cfxcore/geth-tracer/src/arena.rs b/crates/cfxcore/geth-tracer/src/arena.rs new file mode 100644 index 0000000000..a355bbccfe --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/arena.rs @@ -0,0 +1,131 @@ +// Copyright 2023-2024 Paradigm.xyz +// This file is part of reth. +// Reth is a modular, contributor-friendly and blazing-fast implementation of +// the Ethereum protocol + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: + +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use super::types::{CallTrace, CallTraceNode, LogCallOrder}; + +/// An arena of recorded traces. +/// +/// This type will be populated via the +/// [TracingInspector](super::TracingInspector). +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTraceArena { + /// The arena of recorded trace nodes + pub(crate) arena: Vec, +} + +impl CallTraceArena { + /// Pushes a new trace into the arena, returning the trace ID + /// + /// This appends a new trace to the arena, and also inserts a new entry in + /// the node's parent node children set if `attach_to_parent` is `true`. + /// E.g. if calls to precompiles should not be included in the call + /// graph this should be called with [PushTraceKind::PushOnly]. + pub(crate) fn push_trace( + &mut self, mut entry: usize, kind: PushTraceKind, new_trace: CallTrace, + ) -> usize { + loop { + match new_trace.depth { + // The entry node, just update it + 0 => { + self.arena[0].trace = new_trace; + return 0; + } + // We found the parent node, add the new trace as a child + _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { + let id = self.arena.len(); + let node = CallTraceNode { + parent: Some(entry), + trace: new_trace, + idx: id, + ..Default::default() + }; + self.arena.push(node); + + // also track the child in the parent node + if kind.is_attach_to_parent() { + let parent = &mut self.arena[entry]; + let trace_location = parent.children.len(); + parent + .ordering + .push(LogCallOrder::Call(trace_location)); + parent.children.push(id); + } + + return id; + } + _ => { + // We haven't found the parent node, go deeper + entry = *self.arena[entry] + .children + .last() + .expect("Disconnected trace"); + } + } + } + } + + /// Returns the nodes in the arena + pub fn nodes(&self) -> &[CallTraceNode] { &self.arena } + + /// Consumes the arena and returns the nodes + pub fn into_nodes(self) -> Vec { self.arena } + + /// Clears the arena + /// + /// Note that this method has no effect on the allocated capacity of the + /// arena. + #[inline] + pub fn clear(&mut self) { self.arena.clear(); } +} + +/// How to push a trace into the arena +pub(crate) enum PushTraceKind { + /// This will _only_ push the trace into the arena. + PushOnly, + /// This will push the trace into the arena, and also insert a new entry in + /// the node's parent node children set. + PushAndAttachToParent, +} + +impl PushTraceKind { + #[inline] + const fn is_attach_to_parent(&self) -> bool { + matches!(self, Self::PushAndAttachToParent) + } +} + +impl Default for CallTraceArena { + fn default() -> Self { + // The first node is the root node + Self { + arena: vec![Default::default()], + } + } +} diff --git a/crates/cfxcore/geth-tracer/src/config.rs b/crates/cfxcore/geth-tracer/src/config.rs new file mode 100644 index 0000000000..ba3ffcb141 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/config.rs @@ -0,0 +1,351 @@ +// Copyright 2023-2024 Paradigm.xyz +// This file is part of reth. +// Reth is a modular, contributor-friendly and blazing-fast implementation of +// the Ethereum protocol + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: + +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +use alloy_rpc_types_trace::{ + geth::{CallConfig, GethDefaultTracingOptions, PreStateConfig}, + parity::TraceType, +}; +use std::collections::HashSet; + +/// Gives guidance to the [TracingInspector](crate::tracing::TracingInspector). +/// +/// Use [TracingInspectorConfig::default_parity] or +/// [TracingInspectorConfig::default_geth] to get the default configs for +/// specific styles of traces. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TracingInspectorConfig { + /// Whether to record every individual opcode level step. + pub record_steps: bool, + /// Whether to record individual memory snapshots. + pub record_memory_snapshots: bool, + /// Whether to record individual stack snapshots. + pub record_stack_snapshots: StackSnapshotType, + /// Whether to record state diffs. + pub record_state_diff: bool, + /// Whether to ignore precompile calls. + pub exclude_precompile_calls: bool, + /// Whether to record logs + pub record_logs: bool, +} + +impl TracingInspectorConfig { + /// Returns a config with everything enabled. + pub const fn all() -> Self { + Self { + record_steps: true, + record_memory_snapshots: true, + record_stack_snapshots: StackSnapshotType::Full, + record_state_diff: false, + exclude_precompile_calls: false, + record_logs: true, + } + } + + /// Returns a config with everything is disabled. + pub const fn none() -> Self { + Self { + record_steps: false, + record_memory_snapshots: false, + record_stack_snapshots: StackSnapshotType::None, + record_state_diff: false, + exclude_precompile_calls: false, + record_logs: false, + } + } + + /// Returns a config for parity style traces. + /// + /// This config does _not_ record opcode level traces and is suited for + /// `trace_transaction` + pub const fn default_parity() -> Self { + Self { + record_steps: false, + record_memory_snapshots: false, + record_stack_snapshots: StackSnapshotType::None, + record_state_diff: false, + exclude_precompile_calls: true, + record_logs: false, + } + } + + /// Returns a config for geth style traces. + /// + /// This config does _not_ record opcode level traces and is suited for + /// `debug_traceTransaction` + /// + /// This will configure the default output of geth's default + /// [StructLogTracer](alloy_rpc_trace_types::geth::DefaultFrame). + pub const fn default_geth() -> Self { + Self { + record_steps: true, + record_memory_snapshots: false, + record_stack_snapshots: StackSnapshotType::Full, + record_state_diff: true, + exclude_precompile_calls: false, + record_logs: false, + } + } + + /// Returns the [TracingInspectorConfig] depending on the enabled + /// [TraceType]s + /// + /// Note: the parity statediffs can be populated entirely via the execution + /// result, so we don't need statediff recording + #[inline] + pub fn from_parity_config(trace_types: &HashSet) -> Self { + let needs_vm_trace = trace_types.contains(&TraceType::VmTrace); + let snap_type = if needs_vm_trace { + StackSnapshotType::Pushes + } else { + StackSnapshotType::None + }; + Self::default_parity() + .set_steps(needs_vm_trace) + .set_stack_snapshots(snap_type) + .set_memory_snapshots(needs_vm_trace) + } + + /// Returns a config for geth style traces based on the given + /// [GethDefaultTracingOptions]. + /// + /// This will configure the output of geth's default + /// [StructLogTracer](alloy_rpc_trace_types::geth::DefaultFrame) according + /// to the given config. + #[inline] + pub fn from_geth_config(config: &GethDefaultTracingOptions) -> Self { + Self { + record_memory_snapshots: config.enable_memory.unwrap_or_default(), + record_stack_snapshots: if config.disable_stack.unwrap_or_default() + { + StackSnapshotType::None + } else { + StackSnapshotType::Full + }, + record_state_diff: !config.disable_storage.unwrap_or_default(), + ..Self::default_geth() + } + } + + /// Returns a config for geth's + /// [CallTracer](alloy_rpc_trace_types::geth::CallFrame). + /// + /// This returns [Self::none] and enables + /// [TracingInspectorConfig::record_logs] if configured in + /// the given [CallConfig] + #[inline] + pub fn from_geth_call_config(config: &CallConfig) -> Self { + Self::none() + // call tracer is similar parity tracer with optional support for + // logs + .set_record_logs(config.with_log.unwrap_or_default()) + } + + /// Returns a config for geth's + /// [PrestateTracer](alloy_rpc_trace_types::geth::PreStateFrame). + /// + /// Note: This currently returns [Self::none] because the prestate tracer + /// result currently relies on the execution result entirely, see + /// [GethTraceBuilder::geth_prestate_traces](crate::tracing::geth::GethTraceBuilder::geth_prestate_traces) + #[inline] + pub const fn from_geth_prestate_config(_config: &PreStateConfig) -> Self { + Self::none() + } + + /// Configure whether calls to precompiles should be ignored. + /// + /// If set to `true`, calls to precompiles without value transfers will be + /// ignored. + pub const fn set_exclude_precompile_calls( + mut self, exclude_precompile_calls: bool, + ) -> Self { + self.exclude_precompile_calls = exclude_precompile_calls; + self + } + + /// Disable recording of individual opcode level steps + pub const fn disable_steps(self) -> Self { self.set_steps(false) } + + /// Enable recording of individual opcode level steps + pub const fn steps(self) -> Self { self.set_steps(true) } + + /// Configure whether individual opcode level steps should be recorded + pub const fn set_steps(mut self, record_steps: bool) -> Self { + self.record_steps = record_steps; + self + } + + /// Disable recording of individual memory snapshots + pub const fn disable_memory_snapshots(self) -> Self { + self.set_memory_snapshots(false) + } + + /// Enable recording of individual memory snapshots + pub const fn memory_snapshots(self) -> Self { + self.set_memory_snapshots(true) + } + + /// Configure whether the tracer should record memory snapshots + pub const fn set_memory_snapshots( + mut self, record_memory_snapshots: bool, + ) -> Self { + self.record_memory_snapshots = record_memory_snapshots; + self + } + + /// Disable recording of individual stack snapshots + pub const fn disable_stack_snapshots(self) -> Self { + self.set_stack_snapshots(StackSnapshotType::None) + } + + /// Enable recording of individual stack snapshots + pub const fn stack_snapshots(self) -> Self { + self.set_stack_snapshots(StackSnapshotType::Full) + } + + /// Configure how the tracer should record stack snapshots + pub const fn set_stack_snapshots( + mut self, record_stack_snapshots: StackSnapshotType, + ) -> Self { + self.record_stack_snapshots = record_stack_snapshots; + self + } + + /// Disable recording of state diffs + pub const fn disable_state_diffs(self) -> Self { + self.set_state_diffs(false) + } + + /// Configure whether the tracer should record state diffs + pub const fn set_state_diffs(mut self, record_state_diff: bool) -> Self { + self.record_state_diff = record_state_diff; + self + } + + /// Sets state diff recording to true. + /// + /// Also enables steps recording since state diff recording requires steps + /// recording. + pub const fn with_state_diffs(self) -> Self { + self.set_steps_and_state_diffs(true) + } + + /// Configure whether the tracer should record steps and state diffs. + /// + /// This is a convenience method for setting both + /// [TracingInspectorConfig::set_steps] and + /// [TracingInspectorConfig::set_state_diffs] since tracking state diffs + /// requires steps tracing. + pub const fn set_steps_and_state_diffs( + mut self, steps_and_diffs: bool, + ) -> Self { + self.record_steps = steps_and_diffs; + self.record_state_diff = steps_and_diffs; + self + } + + /// Disable recording of individual logs + pub const fn disable_record_logs(self) -> Self { + self.set_record_logs(false) + } + + /// Enable recording of individual logs + pub const fn record_logs(self) -> Self { self.set_record_logs(true) } + + /// Configure whether the tracer should record logs + pub const fn set_record_logs(mut self, record_logs: bool) -> Self { + self.record_logs = record_logs; + self + } +} + +/// How much of the stack to record. Nothing, just the items pushed, or the full +/// stack +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum StackSnapshotType { + /// Don't record stack snapshots + None, + /// Record only the items pushed to the stack + Pushes, + /// Record the full stack + Full, +} + +impl StackSnapshotType { + /// Returns true if this is the [StackSnapshotType::Full] variant + #[inline] + pub const fn is_full(self) -> bool { matches!(self, Self::Full) } + + /// Returns true if this is the [StackSnapshotType::Pushes] variant + #[inline] + pub const fn is_pushes(self) -> bool { matches!(self, Self::Pushes) } +} + +/// What kind of tracing style this is. +/// +/// This affects things like error messages. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum TraceStyle { + /// Parity style tracer + Parity, + /// Geth style tracer + Geth, +} + +impl TraceStyle { + /// Returns true if this is a parity style tracer. + pub(crate) const fn is_parity(self) -> bool { matches!(self, Self::Parity) } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parity_config() { + let mut s = HashSet::new(); + s.insert(TraceType::StateDiff); + let config = TracingInspectorConfig::from_parity_config(&s); + // not required + assert!(!config.record_steps); + assert!(!config.record_state_diff); + + let mut s = HashSet::new(); + s.insert(TraceType::VmTrace); + let config = TracingInspectorConfig::from_parity_config(&s); + assert!(config.record_steps); + assert!(!config.record_state_diff); + + let mut s = HashSet::new(); + s.insert(TraceType::VmTrace); + s.insert(TraceType::StateDiff); + let config = TracingInspectorConfig::from_parity_config(&s); + assert!(config.record_steps); + // not required for StateDiff + assert!(!config.record_state_diff); + } +} diff --git a/crates/cfxcore/geth-tracer/src/fourbyte.rs b/crates/cfxcore/geth-tracer/src/fourbyte.rs new file mode 100644 index 0000000000..57929b361b --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/fourbyte.rs @@ -0,0 +1,78 @@ +//! Fourbyte tracing inspector +//! +//! Solidity contract functions are addressed using the first four byte of the +//! Keccak-256 hash of their signature. Therefore when calling the function of a +//! contract, the caller must send this function selector as well as the +//! ABI-encoded arguments as call data. +//! +//! The 4byteTracer collects the function selectors of every function executed +//! in the lifetime of a transaction, along with the size of the supplied call +//! data. The result is a map of SELECTOR-CALLDATASIZE to number of occurrences +//! entries, where the keys are SELECTOR-CALLDATASIZE and the values are number +//! of occurrences of this key. For example: +//! +//! ```json +//! { +//! "0x27dc297e-128": 1, +//! "0x38cc4831-0": 2, +//! "0x524f3889-96": 1, +//! "0xadf59f99-288": 1, +//! "0xc281d19e-0": 1 +//! } +//! ``` + +use alloy_primitives::{hex, Selector}; +use alloy_rpc_types_trace::geth::{FourByteFrame, GethTrace}; +use cfx_vm_types::ActionParams; +use std::collections::HashMap; + +/// Fourbyte tracing inspector that records all function selectors and their +/// calldata sizes. +#[derive(Clone, Debug, Default)] +pub struct FourByteInspector { + /// The map of SELECTOR to number of occurrences entries + inner: HashMap<(Selector, usize), u64>, +} + +impl FourByteInspector { + pub fn new() -> Self { Self::default() } + + /// Returns the map of SELECTOR to number of occurrences entries + pub const fn inner(&self) -> &HashMap<(Selector, usize), u64> { + &self.inner + } + + pub fn drain(self) -> GethTrace { + GethTrace::FourByteTracer(FourByteFrame::from(self)) + } + + pub fn record_call(&mut self, params: &ActionParams) { + if let Some(input) = ¶ms.data { + if input.len() > 4 { + let selector = Selector::try_from(&input[..4]) + .expect("input is at least 4 bytes"); + let calldata_size = input[4..].len(); + *self.inner.entry((selector, calldata_size)).or_default() += 1; + } + } + } +} + +impl From for FourByteFrame { + fn from(value: FourByteInspector) -> Self { + Self( + value + .inner + .into_iter() + .map(|((selector, calldata_size), count)| { + let key = format!( + "0x{}-{}", + hex::encode(&selector[..]), + calldata_size + ); + (key, count) + }) + .collect(), + ) + } +} diff --git a/crates/cfxcore/geth-tracer/src/gas.rs b/crates/cfxcore/geth-tracer/src/gas.rs new file mode 100644 index 0000000000..0815a18040 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/gas.rs @@ -0,0 +1,20 @@ +/// Helper [Inspector] that keeps track of gas. +#[derive(Clone, Copy, Debug, Default)] +pub struct GasInspector { + gas_remaining: u64, + last_gas_cost: u64, +} + +impl GasInspector { + pub fn gas_remaining(&self) -> u64 { self.gas_remaining } + + pub fn last_gas_cost(&self) -> u64 { self.last_gas_cost } + + pub fn set_gas_remainning(&mut self, remainning: u64) { + self.gas_remaining = remainning; + } + + pub fn set_last_gas_cost(&mut self, gas_cost: u64) { + self.last_gas_cost = gas_cost; + } +} diff --git a/crates/cfxcore/geth-tracer/src/geth_builder.rs b/crates/cfxcore/geth-tracer/src/geth_builder.rs new file mode 100644 index 0000000000..d07282d560 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/geth_builder.rs @@ -0,0 +1,424 @@ +// Copyright 2023-2024 Paradigm.xyz +// This file is part of reth. +// Reth is a modular, contributor-friendly and blazing-fast implementation of +// the Ethereum protocol + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: + +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +use crate::{ + types::{CallTraceNode, CallTraceStepStackItem}, + TracingInspectorConfig, +}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_rpc_types_trace::geth::{ + AccountChangeKind, AccountState, CallConfig, CallFrame, DefaultFrame, + DiffMode, GethDefaultTracingOptions, PreStateConfig, PreStateFrame, + PreStateMode, StructLog, +}; +use revm::{ + db::DatabaseRef, + primitives::{AccountInfo, State, KECCAK_EMPTY}, +}; +use std::collections::{BTreeMap, HashMap, VecDeque}; + +/// A type for creating geth style traces +#[derive(Clone, Debug)] +pub struct GethTraceBuilder { + /// Recorded trace nodes. + nodes: Vec, + /// How the traces were recorded + _config: TracingInspectorConfig, +} + +impl GethTraceBuilder { + /// Returns a new instance of the builder + pub fn new( + nodes: Vec, _config: TracingInspectorConfig, + ) -> Self { + Self { nodes, _config } + } + + /// Fill in the geth trace with all steps of the trace and its children + /// traces in the order they appear in the transaction. + fn fill_geth_trace( + &self, main_trace_node: &CallTraceNode, + opts: &GethDefaultTracingOptions, + storage: &mut HashMap>, + struct_logs: &mut Vec, + ) { + // A stack with all the steps of the trace and all its children's steps. + // This is used to process the steps in the order they appear in the + // transactions. Steps are grouped by their Call Trace Node, in + // order to process them all in the order they appear in the + // transaction, we need to process steps of call nodes when they appear. + // When we find a call step, we push all the steps of the child trace on + // the stack, so they are processed next. The very next step is + // the last item on the stack + let mut step_stack = + VecDeque::with_capacity(main_trace_node.trace.steps.len()); + + main_trace_node.push_steps_on_stack(&mut step_stack); + + // Iterate over the steps inside the given trace + while let Some(CallTraceStepStackItem { + trace_node, + step, + call_child_id, + }) = step_stack.pop_back() + { + let mut log = step.convert_to_geth_struct_log(opts); + + // Fill in storage depending on the options + if opts.is_storage_enabled() { + let contract_storage = + storage.entry(step.contract).or_default(); + if let Some(change) = step.storage_change { + contract_storage + .insert(change.key.into(), change.value.into()); + log.storage = Some(contract_storage.clone()); + } + } + + if opts.is_return_data_enabled() { + log.return_data = Some(trace_node.trace.output.clone()); + } + + // Add step to geth trace + struct_logs.push(log); + + // If the step is a call, we first push all the steps of the child + // trace on the stack, so they are processed next + if let Some(call_child_id) = call_child_id { + let child_trace = &self.nodes[call_child_id]; + child_trace.push_steps_on_stack(&mut step_stack); + } + + // if we reached the limit, we stop + if let Some(limit) = opts.limit { + if limit > 0 && struct_logs.len() >= limit as usize { + break; + } + } + } + } + + /// Generate a geth-style trace e.g. for `debug_traceTransaction` + /// + /// This expects the gas used and return value for the + /// [ExecutionResult](revm::primitives::ExecutionResult) of the executed + /// transaction. + pub fn geth_traces( + &self, receipt_gas_used: u64, return_value: Bytes, + opts: GethDefaultTracingOptions, + ) -> DefaultFrame { + if self.nodes.is_empty() { + return Default::default(); + } + // Fetch top-level trace + let main_trace_node = &self.nodes[0]; + let main_trace = &main_trace_node.trace; + + let mut struct_logs = Vec::new(); + let mut storage = HashMap::new(); + self.fill_geth_trace( + main_trace_node, + &opts, + &mut storage, + &mut struct_logs, + ); + + DefaultFrame { + // If the top-level trace succeeded, then it was a success + failed: !main_trace.success, + gas: receipt_gas_used, + return_value, + struct_logs, + } + } + + /// Generate a geth-style traces for the call tracer. + /// + /// This decodes all call frames from the recorded traces. + /// + /// This expects the gas used for the + /// [ExecutionResult](revm::primitives::ExecutionResult) of the executed + /// transaction. + pub fn geth_call_traces( + &self, opts: CallConfig, gas_used: u64, + ) -> CallFrame { + if self.nodes.is_empty() { + return Default::default(); + } + + let include_logs = opts.with_log.unwrap_or_default(); + // first fill up the root + let main_trace_node = &self.nodes[0]; + let mut root_call_frame = + main_trace_node.geth_empty_call_frame(include_logs); + root_call_frame.gas_used = U256::from(gas_used); + + // selfdestructs are not recorded as individual call traces but are + // derived from the call trace and are added as additional + // `CallFrame` objects to the parent call + if let Some(selfdestruct) = + main_trace_node.geth_selfdestruct_call_trace() + { + root_call_frame.calls.push(selfdestruct); + } + + if opts.only_top_call.unwrap_or_default() { + return root_call_frame; + } + + // fill all the call frames in the root call frame with the recorded + // traces. traces are identified by their index in the arena + // so we can populate the call frame tree by walking up the call tree + let mut call_frames = Vec::with_capacity(self.nodes.len()); + call_frames.push((0, root_call_frame)); + + for (idx, trace) in self.nodes.iter().enumerate().skip(1) { + // selfdestructs are not recorded as individual call traces but are + // derived from the call trace and are added as + // additional `CallFrame` objects to the parent call + if let Some(selfdestruct) = trace.geth_selfdestruct_call_trace() { + call_frames + .last_mut() + .expect("not empty") + .1 + .calls + .push(selfdestruct); + } + + // include logs only if call and all its parents were successful + let include_logs = + include_logs && !self.call_or_parent_failed(trace); + call_frames.push((idx, trace.geth_empty_call_frame(include_logs))); + } + + // pop the _children_ calls frame and move it to the parent + // this will roll up the child frames to their parent; this works + // because `child idx > parent idx` + loop { + let (idx, call) = call_frames.pop().expect("call frames not empty"); + let node = &self.nodes[idx]; + if let Some(parent) = node.parent { + let parent_frame = &mut call_frames[parent]; + // we need to ensure that calls are in order they are called: + // the last child node is the last call, but + // since we walk up the tree, we need to always + // insert at position 0 + parent_frame.1.calls.insert(0, call); + } else { + debug_assert!( + call_frames.is_empty(), + "only one root node has no parent" + ); + return call; + } + } + } + + /// Returns true if the given trace or any of its parents failed. + fn call_or_parent_failed(&self, node: &CallTraceNode) -> bool { + if node.trace.is_error() { + return true; + } + + let mut parent_idx = node.parent; + while let Some(idx) = parent_idx { + let next = &self.nodes[idx]; + if next.trace.is_error() { + return true; + } + + parent_idx = next.parent; + } + false + } + + /// Returns the accounts necessary for transaction execution. + /// + /// The prestate mode returns the accounts necessary to execute a given + /// transaction. diff_mode returns the differences between the + /// transaction's pre and post-state. + /// + /// * `state` - The state post-transaction execution. + /// * `diff_mode` - if prestate is in diff or prestate mode. + /// * `db` - The database to fetch state pre-transaction execution. + pub fn geth_prestate_traces( + &self, state: State, prestate_config: PreStateConfig, db: DB, + ) -> Result { + let account_diffs = state.iter().map(|(addr, acc)| (*addr, acc)); + + if prestate_config.is_default_mode() { + let mut prestate = PreStateMode::default(); + // we only want changed accounts for things like balance changes etc + for (addr, changed_acc) in account_diffs { + let db_acc = db.basic_ref(addr)?.unwrap_or_default(); + let code = load_account_code(&db, &db_acc); + let mut acc_state = AccountState::from_account_info( + db_acc.nonce, + db_acc.balance, + code, + ); + + // insert the original value of all modified storage slots + for (key, slot) in changed_acc.storage.iter() { + acc_state.storage.insert( + (*key).into(), + slot.previous_or_original_value.into(), + ); + } + + prestate.0.insert(addr, acc_state); + } + + Ok(PreStateFrame::Default(prestate)) + } else { + let mut state_diff = DiffMode::default(); + let mut account_change_kinds = + HashMap::with_capacity(account_diffs.len()); + for (addr, changed_acc) in account_diffs { + let db_acc = db.basic_ref(addr)?.unwrap_or_default(); + + let pre_code = load_account_code(&db, &db_acc); + + let mut pre_state = AccountState::from_account_info( + db_acc.nonce, + db_acc.balance, + pre_code, + ); + + let mut post_state = AccountState::from_account_info( + changed_acc.info.nonce, + changed_acc.info.balance, + changed_acc + .info + .code + .as_ref() + .map(|code| code.original_bytes()), + ); + + // handle storage changes + for (key, slot) in changed_acc + .storage + .iter() + .filter(|(_, slot)| slot.is_changed()) + { + pre_state.storage.insert( + (*key).into(), + slot.previous_or_original_value.into(), + ); + post_state + .storage + .insert((*key).into(), slot.present_value.into()); + } + + state_diff.pre.insert(addr, pre_state); + state_diff.post.insert(addr, post_state); + + // determine the change type + let pre_change = if changed_acc.is_created() { + AccountChangeKind::Create + } else { + AccountChangeKind::Modify + }; + let post_change = if changed_acc.is_selfdestructed() { + AccountChangeKind::SelfDestruct + } else { + AccountChangeKind::Modify + }; + + account_change_kinds.insert(addr, (pre_change, post_change)); + } + + // ensure we're only keeping changed entries + state_diff.retain_changed().remove_zero_storage_values(); + + self.diff_traces( + &mut state_diff.pre, + &mut state_diff.post, + account_change_kinds, + ); + Ok(PreStateFrame::Diff(state_diff)) + } + } + + /// Returns the difference between the pre and post state of the transaction + /// depending on the kind of changes of that account (pre,post) + fn diff_traces( + &self, pre: &mut BTreeMap, + post: &mut BTreeMap, + change_type: HashMap, + ) { + post.retain(|addr, post_state| { + // Don't keep destroyed accounts in the post state + if change_type + .get(addr) + .map(|ty| ty.1.is_selfdestruct()) + .unwrap_or(false) + { + return false; + } + if let Some(pre_state) = pre.get(addr) { + // remove any unchanged account info + post_state.remove_matching_account_info(pre_state); + } + + true + }); + + // Don't keep created accounts the pre state + pre.retain(|addr, _pre_state| { + // only keep accounts that are not created + change_type + .get(addr) + .map(|ty| !ty.0.is_created()) + .unwrap_or(true) + }); + } +} + +/// Loads the code for the given account from the account itself or the database +/// +/// Returns None if the code hash is the KECCAK_EMPTY hash +#[inline] +pub(crate) fn load_account_code( + db: DB, db_acc: &AccountInfo, +) -> Option { + db_acc + .code + .as_ref() + .map(|code| code.original_bytes()) + .or_else(|| { + if db_acc.code_hash == KECCAK_EMPTY { + None + } else { + db.code_by_hash_ref(db_acc.code_hash) + .ok() + .map(|code| code.original_bytes()) + } + }) + .map(Into::into) +} diff --git a/crates/cfxcore/geth-tracer/src/geth_tracer.rs b/crates/cfxcore/geth-tracer/src/geth_tracer.rs new file mode 100644 index 0000000000..0f2ae40814 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/geth_tracer.rs @@ -0,0 +1,458 @@ +use crate::{ + config::TracingInspectorConfig, + fourbyte::FourByteInspector, + tracing_inspector::TracingInspector, + types::{LogCallOrder, TxExecContext}, + utils::{to_alloy_address, to_alloy_h256, to_alloy_u256}, +}; +use alloy_primitives::{Address, Bytes, LogData}; +use alloy_rpc_types_trace::geth::{ + CallConfig, GethDebugBuiltInTracerType, GethDebugBuiltInTracerType::*, + GethDebugTracerType, GethDebugTracingOptions, GethTrace, NoopFrame, + PreStateConfig, +}; +use cfx_executor::{ + machine::Machine, + observer::{ + CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer, + OpcodeTracer, StorageTracer, + }, + stack::{FrameResult, FrameReturn}, +}; +use cfx_types::H160; +use cfx_vm_types::{ActionParams, CallType, Error, InterpreterInfo}; +use revm::{ + db::InMemoryDB, + interpreter::{Gas, InstructionResult, InterpreterResult}, + primitives::State, +}; +use std::sync::Arc; + +pub struct GethTracer { + inner: TracingInspector, + // + fourbyte_inspector: FourByteInspector, + // + tx_gas_limit: u64, // tx level gas limit + // + gas_left: u64, // update in call_result/create_result + // call depth + depth: usize, + // + opts: GethDebugTracingOptions, + // gas stack, used to trace gas_spent in call_result/create_result + pub gas_stack: Vec, +} + +impl GethTracer { + pub fn new( + tx_exec_context: TxExecContext, machine: Arc, + opts: GethDebugTracingOptions, + ) -> Self { + let TxExecContext { tx_gas_limit, .. } = tx_exec_context; + let config = match opts.tracer { + Some(GethDebugTracerType::BuiltInTracer(builtin_tracer)) => { + match builtin_tracer { + FourByteTracer | NoopTracer | MuxTracer => { + TracingInspectorConfig::none() + } + CallTracer => { + let c = opts + .tracer_config + .clone() + .into_call_config() + .expect("should success"); + TracingInspectorConfig::from_geth_call_config(&c) + } + PreStateTracer => { + let c = opts + .tracer_config + .clone() + .into_pre_state_config() + .expect("should success"); + TracingInspectorConfig::from_geth_prestate_config(&c) + } + } + } + Some(GethDebugTracerType::JsTracer(_)) => { + TracingInspectorConfig::none() + } + None => TracingInspectorConfig::from_geth_config(&opts.config), + }; + + Self { + inner: TracingInspector::new(config, machine, tx_exec_context), + fourbyte_inspector: FourByteInspector::new(), + tx_gas_limit, + depth: 0, + gas_left: tx_gas_limit, + opts, + gas_stack: Vec::new(), + } + } + + fn tracer_type(&self) -> Option { + match self.opts.tracer.clone() { + Some(t) => match t { + GethDebugTracerType::BuiltInTracer(builtin_tracer) => { + Some(builtin_tracer) + } + GethDebugTracerType::JsTracer(_) => { + // not supported + Some(NoopTracer) + } + }, + None => None, + } + } + + fn call_config(&self) -> Option { + self.opts.tracer_config.clone().into_call_config().ok() + } + + fn prestate_config(&self) -> Option { + self.opts.tracer_config.clone().into_pre_state_config().ok() + } + + pub fn is_fourbyte_tracer(&self) -> bool { + self.tracer_type() == Some(FourByteTracer) + } + + pub fn gas_used(&self) -> u64 { self.tx_gas_limit - self.gas_left } + + pub fn drain(self) -> GethTrace { + let trace = match self.tracer_type() { + Some(t) => match t { + FourByteTracer => self.fourbyte_inspector.drain(), + CallTracer => { + let gas_used = self.gas_used(); + let opts = self.call_config().expect("should have config"); + let frame = self + .inner + .into_geth_builder() + .geth_call_traces(opts, gas_used); + GethTrace::CallTracer(frame) + } + PreStateTracer => { + // TODO replace the empty state and db with a real state + let opts = + self.prestate_config().expect("should have config"); + let state = State::default(); + let db = InMemoryDB::default(); + let frame = self + .inner + .into_geth_builder() + .geth_prestate_traces(state, opts, db) + .unwrap(); + GethTrace::PreStateTracer(frame) + } + NoopTracer | MuxTracer => { + GethTrace::NoopTracer(NoopFrame::default()) + } + }, + None => { + let gas_used = self.gas_used(); + let return_value = self + .inner + .last_call_return_data + .clone() + .unwrap_or_default(); + let opts = self.opts.config; + let frame = self.inner.into_geth_builder().geth_traces( + gas_used, + return_value, + opts, + ); + GethTrace::Default(frame) + } + }; + + trace + } +} + +impl DrainTrace for GethTracer { + fn drain_trace(self, map: &mut typemap::ShareDebugMap) { + map.insert::(self.drain()); + } +} + +pub struct GethTraceKey; + +impl typemap::Key for GethTraceKey { + type Value = GethTrace; +} + +impl CheckpointTracer for GethTracer {} + +impl InternalTransferTracer for GethTracer {} + +impl StorageTracer for GethTracer {} + +impl CallTracer for GethTracer { + fn record_call(&mut self, params: &ActionParams) { + if self.is_fourbyte_tracer() { + self.fourbyte_inspector.record_call(params); + return; + } + + let gas_limit = params.gas.as_u64(); + self.gas_stack.push(gas_limit); + + // determine correct `from` and `to` based on the call scheme + let (from, to) = match params.call_type { + CallType::DelegateCall | CallType::CallCode => { + (params.address, params.code_address) + } + _ => (params.sender, params.address), + }; + + let value = if matches!(params.call_type, CallType::DelegateCall) + && self.inner.active_trace().is_some() + { + // for delegate calls we need to use the value of the top trace + let parent = self.inner.active_trace().unwrap(); + parent.trace.value + } else { + to_alloy_u256(params.value.value()) + }; + + // if calls to precompiles should be excluded, check whether this is a + // call to a precompile + let maybe_precompile = + self.inner.config.exclude_precompile_calls.then(|| { + self.inner.is_precompile_call(&to, value, params.space) + }); + + let to = to_alloy_address(to); + let from = to_alloy_address(from); + self.inner.start_trace_on_call( + to, + params.data.clone().unwrap_or_default().into(), + value, + params.call_type.into(), + from, + params.gas.as_u64(), + maybe_precompile, + self.tx_gas_limit, + self.depth, + ); + + self.depth += 1; + } + + fn record_call_result(&mut self, result: &FrameResult) { + if self.is_fourbyte_tracer() { + return; + } + + self.depth -= 1; + let mut gas_spent = self.gas_stack.pop().expect("should have value"); + + if let Ok(r) = result { + gas_spent = gas_spent - r.gas_left.as_u64(); + self.gas_left = r.gas_left.as_u64(); + } + + let instruction_result = to_instruction_result(result); + + if instruction_result.is_error() { + self.inner.gas_inspector.set_gas_remainning(0); + } + + let output = result + .as_ref() + .map(|f| Bytes::from(f.return_data.to_vec())) + .unwrap_or_default(); + + let outcome = InterpreterResult { + result: instruction_result, + output, + gas: Gas::default(), + }; + + self.inner.fill_trace_on_call_end(outcome, None, gas_spent); + } + + fn record_create(&mut self, params: &ActionParams) { + if self.is_fourbyte_tracer() { + return; + } + + let gas_limit = params.gas.as_u64(); + self.gas_stack.push(gas_limit); + + let value = if matches!(params.call_type, CallType::DelegateCall) { + // for delegate calls we need to use the value of the top trace + if let Some(parent) = self.inner.active_trace() { + parent.trace.value + } else { + to_alloy_u256(params.value.value()) + } + } else { + to_alloy_u256(params.value.value()) + }; + + self.inner.start_trace_on_call( + Address::default(), // call_result will set this address + params.data.clone().unwrap_or_default().into(), + value, + params.call_type.into(), + to_alloy_address(params.sender), + params.gas.as_u64(), + Some(false), + params.gas.as_u64(), + self.depth, + ); + + self.depth += 1; + } + + fn record_create_result(&mut self, result: &FrameResult) { + if self.is_fourbyte_tracer() { + return; + } + + self.depth -= 1; + let mut gas_spent = self.gas_stack.pop().expect("should have value"); + + if let Ok(r) = result { + gas_spent = gas_spent - r.gas_left.as_u64(); + self.gas_left = r.gas_left.as_u64(); + } + + let instruction_result = to_instruction_result(result); + + if instruction_result.is_error() { + self.inner.gas_inspector.set_gas_remainning(0); + } + + let output = result + .as_ref() + .map(|f| Bytes::from(f.return_data.to_vec())) + .unwrap_or_default(); + + let outcome = InterpreterResult { + result: instruction_result, + output, + gas: Gas::default(), + }; + + let create_address = + if let Ok(FrameReturn { create_address, .. }) = result { + create_address.as_ref().map(|h| to_alloy_address(*h)) + } else { + None + }; + + self.inner + .fill_trace_on_call_end(outcome, create_address, gas_spent); + } +} + +impl OpcodeTracer for GethTracer { + fn do_trace_opcode(&self, enabled: &mut bool) { + if self.inner.config.record_steps { + *enabled |= true; + } + } + + fn initialize_interp(&mut self, gas_limit: cfx_types::U256) { + self.inner + .gas_inspector + .set_gas_remainning(gas_limit.as_u64()); + } + + fn step(&mut self, interp: &dyn InterpreterInfo) { + self.inner + .gas_inspector + .set_gas_remainning(interp.gas_remainning().as_u64()); + + if self.inner.config.record_steps { + self.inner.start_step(interp, self.depth as u64); + } + } + + fn step_end(&mut self, interp: &dyn InterpreterInfo) { + let remainning = interp.gas_remainning().as_u64(); + let last_gas_cost = self + .inner + .gas_inspector + .gas_remaining() + .saturating_sub(remainning); + self.inner.gas_inspector.set_gas_remainning(remainning); + self.inner.gas_inspector.set_last_gas_cost(last_gas_cost); + + // trace + if self.inner.config.record_steps { + self.inner.fill_step_on_step_end(interp); + } + } + + fn log( + &mut self, _address: &cfx_types::Address, + topics: &Vec, data: &[u8], + ) { + if self.inner.config.record_logs { + let trace_idx = self.inner.last_trace_idx(); + let trace = &mut self.inner.traces.arena[trace_idx]; + trace.ordering.push(LogCallOrder::Log(trace.logs.len())); + trace.logs.push(LogData::new_unchecked( + topics.iter().map(|f| to_alloy_h256(*f)).collect(), + Bytes::from(data.to_vec()), + )); + } + } + + fn selfdestruct( + &mut self, _contract: &cfx_types::Address, target: &cfx_types::Address, + _value: cfx_types::U256, + ) { + if self.is_fourbyte_tracer() { + return; + } + + let trace_idx = self.inner.last_trace_idx(); + let trace = &mut self.inner.traces.arena[trace_idx].trace; + trace.selfdestruct_refund_target = + Some(to_alloy_address(*target as H160)) + } +} + +pub fn to_instruction_result(frame_result: &FrameResult) -> InstructionResult { + let result = match frame_result { + Ok(r) => match r.apply_state { + true => InstructionResult::Return, + false => InstructionResult::Revert, + }, + Err(err) => match err { + Error::OutOfGas => InstructionResult::OutOfGas, + Error::BadJumpDestination { .. } => InstructionResult::InvalidJump, + Error::BadInstruction { .. } => InstructionResult::OpcodeNotFound, + Error::StackUnderflow { .. } => InstructionResult::StackUnderflow, + Error::OutOfStack { .. } => InstructionResult::StackOverflow, + Error::SubStackUnderflow { .. } => { + InstructionResult::StackUnderflow + } + Error::OutOfSubStack { .. } => InstructionResult::StackOverflow, + Error::InvalidSubEntry => InstructionResult::NotActivated, // + Error::NotEnoughBalanceForStorage { .. } => { + InstructionResult::OutOfFunds + } + Error::ExceedStorageLimit => InstructionResult::OutOfGas, /* treat storage as gas */ + Error::BuiltIn(_) => InstructionResult::PrecompileError, + Error::InternalContract(_) => InstructionResult::PrecompileError, /* treat internalContract as builtIn */ + Error::MutableCallInStaticContext => { + InstructionResult::StateChangeDuringStaticCall + } + Error::StateDbError(_) => InstructionResult::FatalExternalError, + Error::Wasm(_) => InstructionResult::NotActivated, + Error::OutOfBounds => InstructionResult::OutOfOffset, + Error::Reverted => InstructionResult::Revert, + Error::InvalidAddress(_) => todo!(), /* when selfdestruct refund */ + // address is invalid will emit this error + Error::ConflictAddress(_) => InstructionResult::CreateCollision, + }, + }; + result +} diff --git a/crates/cfxcore/geth-tracer/src/lib.rs b/crates/cfxcore/geth-tracer/src/lib.rs new file mode 100644 index 0000000000..176a85a02f --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/lib.rs @@ -0,0 +1,20 @@ +#![allow(unused)] +mod arena; +mod config; +mod fourbyte; +mod gas; +mod geth_builder; +mod geth_tracer; +mod tracing_inspector; +mod types; +mod utils; + +use arena::CallTraceArena; +use config::TracingInspectorConfig; +use geth_builder::GethTraceBuilder; + +pub use geth_tracer::{GethTraceKey, GethTracer}; +pub use types::{GethTraceWithHash, TxExecContext}; +pub use utils::{ + from_alloy_address, to_alloy_address, to_alloy_h256, to_alloy_u256, +}; diff --git a/crates/cfxcore/geth-tracer/src/tracing_inspector.rs b/crates/cfxcore/geth-tracer/src/tracing_inspector.rs new file mode 100644 index 0000000000..82a0c00768 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/tracing_inspector.rs @@ -0,0 +1,478 @@ +// Copyright 2023-2024 Paradigm.xyz +// This file is part of reth. +// Reth is a modular, contributor-friendly and blazing-fast implementation of +// the Ethereum protocol + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: + +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. + +use crate::TxExecContext; + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +use super::{ + arena::PushTraceKind, + gas::GasInspector, + types::{ + CallKind, CallTrace, CallTraceNode, CallTraceStep, RecordedMemory, + StorageChange, StorageChangeReason, + }, + utils::{gas_used, stack_push_count, to_alloy_address, to_alloy_u256}, + CallTraceArena, GethTraceBuilder, TracingInspectorConfig, +}; +use cfx_types::{Space, H160}; + +use alloy_primitives::{Address, Bytes, U256}; +use revm::interpreter::{opcode, InstructionResult, InterpreterResult, OpCode}; + +use cfx_executor::machine::Machine; + +use cfx_vm_types::InterpreterInfo; + +use std::sync::Arc; + +#[derive(Clone)] +pub struct TracingInspector { + /// Configures what and how the inspector records traces. + pub config: TracingInspectorConfig, + /// Records all call traces + pub traces: CallTraceArena, + /// Tracks active calls + trace_stack: Vec, + /// Tracks active steps + step_stack: Vec, + /// Tracks the return value of the last call + pub last_call_return_data: Option, + /// The gas inspector used to track remaining gas. + pub gas_inspector: GasInspector, + // + machine: Arc, + + tx_exec_context: TxExecContext, +} + +impl TracingInspector { + /// Returns a new instance for the given config + pub fn new( + config: TracingInspectorConfig, machine: Arc, + tx_exec_context: TxExecContext, + ) -> Self { + Self { + config, + traces: Default::default(), + trace_stack: vec![], + step_stack: vec![], + last_call_return_data: None, + gas_inspector: Default::default(), + machine, + tx_exec_context, + } + } + + /// Resets the inspector to its initial state of [Self::new]. + /// This makes the inspector ready to be used again. + /// + /// Note that this method has no effect on the allocated capacity of the + /// vector. + #[inline] + pub fn fuse(&mut self) { + let Self { + traces, + trace_stack, + step_stack, + last_call_return_data, + gas_inspector, + // kept + config: _, + machine: _, + .. + } = self; + traces.clear(); + trace_stack.clear(); + step_stack.clear(); + last_call_return_data.take(); + *gas_inspector = Default::default(); + } + + /// Resets the inspector to it's initial state of [Self::new]. + #[inline] + pub fn fused(mut self) -> Self { + self.fuse(); + self + } + + /// Returns the config of the inspector. + pub const fn config(&self) -> &TracingInspectorConfig { &self.config } + + /// Gets a reference to the recorded call traces. + pub const fn get_traces(&self) -> &CallTraceArena { &self.traces } + + /// Gets a mutable reference to the recorded call traces. + pub fn get_traces_mut(&mut self) -> &mut CallTraceArena { &mut self.traces } + + /// Manually the gas used of the root trace. + /// + /// This is useful if the root trace's gasUsed should mirror the actual gas + /// used by the transaction. + /// + /// This allows setting it manually by consuming the execution result's gas + /// for example. + #[inline] + pub fn set_transaction_gas_used(&mut self, gas_used: u64) { + if let Some(node) = self.traces.arena.first_mut() { + node.trace.gas_used = gas_used; + } + } + + /// Convenience function for [ParityTraceBuilder::set_transaction_gas_used] + /// that consumes the type. + #[inline] + pub fn with_transaction_gas_used(mut self, gas_used: u64) -> Self { + self.set_transaction_gas_used(gas_used); + self + } + + /// Consumes the Inspector and returns a [ParityTraceBuilder]. + // #[inline] + // pub fn into_parity_builder(self) -> ParityTraceBuilder { + // ParityTraceBuilder::new(self.traces.arena, self.spec_id, self.config) + // } + + /// Consumes the Inspector and returns a [GethTraceBuilder]. + #[inline] + pub fn into_geth_builder(self) -> GethTraceBuilder { + GethTraceBuilder::new(self.traces.arena, self.config) + } + + /// Returns true if we're no longer in the context of the root call. + fn is_deep(&self) -> bool { + // the root call will always be the first entry in the trace stack + !self.trace_stack.is_empty() + } + + /// Returns true if this a call to a precompile contract. + /// + /// Returns true if the `to` address is a precompile contract and the value + /// is zero. + #[inline] + pub fn is_precompile_call( + &self, to: &H160, value: U256, space: Space, + ) -> bool { + // TODO: check according block height + let is_precompile = match space { + Space::Native => self.machine.builtins().contains_key(&to), + Space::Ethereum => self.machine.builtins_evm().contains_key(&to), + }; + + if is_precompile { + // only if this is _not_ the root call + return self.is_deep() && value.is_zero(); + } + false + } + + /// Returns the currently active call trace. + /// + /// This will be the last call trace pushed to the stack: the call we + /// entered most recently. + #[track_caller] + #[inline] + pub fn active_trace(&self) -> Option<&CallTraceNode> { + self.trace_stack.last().map(|idx| &self.traces.arena[*idx]) + } + + /// Returns the last trace [CallTrace] index from the stack. + /// + /// This will be the currently active call trace. + /// + /// # Panics + /// + /// If no [CallTrace] was pushed + #[track_caller] + #[inline] + pub fn last_trace_idx(&self) -> usize { + self.trace_stack + .last() + .copied() + .expect("can't start step without starting a trace first") + } + + /// _Removes_ the last trace [CallTrace] index from the stack. + /// + /// # Panics + /// + /// If no [CallTrace] was pushed + #[track_caller] + #[inline] + fn pop_trace_idx(&mut self) -> usize { + self.trace_stack + .pop() + .expect("more traces were filled than started") + } + + /// Starts tracking a new trace. + /// + /// Invoked on [Inspector::call]. + #[allow(clippy::too_many_arguments)] + pub fn start_trace_on_call( + &mut self, address: Address, input_data: Bytes, value: U256, + kind: CallKind, caller: Address, mut gas_limit: u64, + maybe_precompile: Option, tx_gas_limit: u64, depth: usize, + ) { + // This will only be true if the inspector is configured to exclude + // precompiles and the call is to a precompile + let push_kind = if maybe_precompile.unwrap_or(false) { + // We don't want to track precompiles + PushTraceKind::PushOnly + } else { + PushTraceKind::PushAndAttachToParent + }; + + if self.trace_stack.is_empty() { + // this is the root call which should get the original gas limit of + // the transaction, because initialization costs are + // already subtracted from gas_limit For the root call + // this value should use the transaction's gas limit See and + gas_limit = tx_gas_limit; + } + self.trace_stack.push(self.traces.push_trace( + 0, + push_kind, + CallTrace { + depth, + address, + kind, + data: input_data, + value, + status: InstructionResult::Continue, + caller, + maybe_precompile, + gas_limit, + ..Default::default() + }, + )); + } + + /// Fills the current trace with the outcome of a call. + /// + /// Invoked on [Inspector::call_end]. + /// + /// # Panics + /// + /// This expects an existing trace [Self::start_trace_on_call] + pub fn fill_trace_on_call_end( + &mut self, result: InterpreterResult, created_address: Option
, + gas_spent: u64, + ) { + let InterpreterResult { + result, + output, + gas: _, + } = result; + + let trace_idx = self.pop_trace_idx(); + let trace = &mut self.traces.arena[trace_idx].trace; + + if trace_idx == 0 { + // this is the root call which should get the gas used of the + // transaction refunds are applied after execution, + // which is when the root call ends + // Conflux have no refund + trace.gas_used = gas_used(gas_spent, 0); + } else { + trace.gas_used = gas_spent; + } + + trace.status = result; + trace.success = trace.status.is_ok(); + trace.output = output.clone(); + + self.last_call_return_data = Some(output); + + if let Some(address) = created_address { + // A new contract was created via CREATE + trace.address = address; + } + } + + /// Starts tracking a step + /// + /// Invoked on [Inspector::step] + /// + /// # Panics + /// + /// This expects an existing [CallTrace], in other words, this panics if not + /// within the context of a call. + pub fn start_step(&mut self, interp: &dyn InterpreterInfo, depth: u64) { + let trace_idx = self.last_trace_idx(); + let trace = &mut self.traces.arena[trace_idx]; + + self.step_stack.push(StackStep { + trace_idx, + step_idx: trace.trace.steps.len(), + }); + + let memory = self + .config + .record_memory_snapshots + .then(|| RecordedMemory::new(interp.mem().to_vec())) + .unwrap_or_default(); + + let stack: Option> = + if self.config.record_stack_snapshots.is_full() { + Some( + interp + .stack() + .into_iter() + .map(|v| to_alloy_u256(*v)) + .collect(), + ) + } else { + None + }; + + let op = OpCode::new(interp.current_opcode()) + .or_else(|| { + // if the opcode is invalid, we'll use the invalid opcode to + // represent it because this is invoked before + // the opcode is executed, the evm will eventually return a + // `Halt` with invalid/unknown opcode as result + let invalid_opcode = 0xfe; + OpCode::new(invalid_opcode) + }) + .expect("is valid opcode;"); + + // if op is SLOAD or SSTORE, we need to record the storage change + let storage_change = match (op.get(), stack.clone()) { + (opcode::SLOAD, Some(s)) if s.len() >= 1 => { + let key = s[s.len() - 1]; + let change = StorageChange { + key, + value: U256::ZERO, + had_value: None, // not used for now + reason: StorageChangeReason::SLOAD, + }; + Some(change) + } + (opcode::SSTORE, Some(s)) if s.len() >= 2 => { + let key = s[s.len() - 1]; + let value = s[s.len() - 2]; + let change = StorageChange { + key, + value, + had_value: None, // not used for now + reason: StorageChangeReason::SSTORE, + }; + Some(change) + } + _ => None, + }; + + trace.trace.steps.push(CallTraceStep { + depth, + pc: interp.program_counter() as usize, + op, + contract: to_alloy_address(interp.contract_address()), + stack, + push_stack: None, + memory_size: memory.len(), + memory, + gas_remaining: self.gas_inspector.gas_remaining(), + gas_refund_counter: 0, // conflux has no gas refund + + // fields will be populated end of call + gas_cost: 0, + storage_change, + status: InstructionResult::Continue, + }); + } + + /// Fills the current trace with the output of a step. + /// + /// Invoked on [Inspector::step_end]. + pub fn fill_step_on_step_end(&mut self, interp: &dyn InterpreterInfo) { + let StackStep { + trace_idx, + step_idx, + } = self + .step_stack + .pop() + .expect("can't fill step without starting a step first"); + let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx]; + + if self.config.record_stack_snapshots.is_pushes() { + let spec = self.machine.spec( + self.tx_exec_context.block_number, + self.tx_exec_context.block_height, + ); + let num_pushed = + stack_push_count(step.op.get(), spec.cancun_opcodes); + let start = interp.stack().len() - num_pushed; + let push_stack = interp.stack()[start..].to_vec(); + step.push_stack = Some( + push_stack.into_iter().map(|v| to_alloy_u256(v)).collect(), + ); + } + + if self.config.record_memory_snapshots { + // resize memory so opcodes that allocated memory is correctly + // displayed + if interp.mem().len() > step.memory.len() { + step.memory.resize(interp.mem().len()); + } + } + + if self.config.record_state_diff { + let op = step.op.get(); + + // update value if it's a SLOAD + match (op, step.push_stack.clone(), step.storage_change) { + (opcode::SLOAD, Some(s), Some(change)) if s.len() >= 1 => { + let val = s.last().unwrap(); + step.storage_change = Some(StorageChange { + key: change.key, + value: *val, + had_value: None, // not used for now + reason: StorageChangeReason::SLOAD, + }); + } + _ => {} + } + } + + // The gas cost is the difference between the recorded gas remaining at + // the start of the step the remaining gas here, at the end of + // the step. + // todo(evm-inspector): Figure out why this can overflow. https://github.com/paradigmxyz/evm-inspectors/pull/38 + step.gas_cost = step + .gas_remaining + .saturating_sub(self.gas_inspector.gas_remaining()); + + // TODO set the status + // step.status = interp.instruction_result; + } +} + +#[derive(Clone, Copy, Debug)] +struct StackStep { + trace_idx: usize, + step_idx: usize, +} diff --git a/crates/cfxcore/geth-tracer/src/types.rs b/crates/cfxcore/geth-tracer/src/types.rs new file mode 100644 index 0000000000..8a5c5cddad --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/types.rs @@ -0,0 +1,642 @@ +// Copyright 2023-2024 Paradigm.xyz +// This file is part of reth. +// Reth is a modular, contributor-friendly and blazing-fast implementation of +// the Ethereum protocol + +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: + +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Types for representing call trace items. +use crate::{config::TraceStyle, utils, utils::convert_memory}; +use alloy_primitives::{Address, Bytes, LogData, U256}; +use alloy_rpc_types_trace::geth::{ + CallFrame, CallLogFrame, GethDefaultTracingOptions, GethTrace, StructLog, +}; +use cfx_types::{Space, H256}; +use cfx_vm_types::CallType as CfxCallType; +use primitives::{block::BlockHeight, BlockNumber}; +use revm::interpreter::{ + opcode, CallContext, CallScheme, CreateScheme, InstructionResult, OpCode, +}; +use std::collections::VecDeque; + +/// A trace of a call. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTrace { + /// The depth of the call + pub depth: usize, + /// Whether the call was successful + pub success: bool, + /// caller of this call + pub caller: Address, + /// The destination address of the call or the address from the created + /// contract. + /// + /// In other words, this is the callee if the [CallKind::Call] or the + /// address of the created contract if [CallKind::Create]. + pub address: Address, + /// Whether this is a call to a precompile + /// + /// Note: This is an Option because not all tracers make use of this + pub maybe_precompile: Option, + /// Holds the target for the __selfdestruct__ refund target + /// + /// This is only set if a selfdestruct was executed. + /// + /// Note: This not necessarily guarantees that the status is + /// [InstructionResult::SelfDestruct] There's an edge case where a new + /// created contract is immediately selfdestructed. + pub selfdestruct_refund_target: Option
, + /// The kind of call this is + pub kind: CallKind, + /// The value transferred in the call + pub value: U256, + /// The calldata for the call, or the init code for contract creations + pub data: Bytes, + /// The return data of the call if this was not a contract creation, + /// otherwise it is the runtime bytecode of the created contract + pub output: Bytes, + /// The gas cost of the call + pub gas_used: u64, + /// The gas limit of the call + pub gas_limit: u64, + /// The status of the trace's call + pub status: InstructionResult, + /// call context of the runtime + pub call_context: Option>, + /// Opcode-level execution steps + pub steps: Vec, +} + +impl CallTrace { + /// Returns true if the status code is an error or revert, See + /// [InstructionResult::Revert] + #[inline] + pub const fn is_error(&self) -> bool { !self.status.is_ok() } + + /// Returns true if the status code is a revert + #[inline] + pub fn is_revert(&self) -> bool { self.status == InstructionResult::Revert } + + /// Returns the error message if it is an erroneous result. + pub(crate) fn as_error_msg(&self, kind: TraceStyle) -> Option { + // See also + self.is_error().then(|| match self.status { + InstructionResult::Revert => if kind.is_parity() { + "Reverted" + } else { + "execution reverted" + } + .to_string(), + InstructionResult::OutOfGas | InstructionResult::MemoryOOG => { + if kind.is_parity() { + "Out of gas" + } else { + "out of gas" + } + .to_string() + } + InstructionResult::OpcodeNotFound => if kind.is_parity() { + "Bad instruction" + } else { + "invalid opcode" + } + .to_string(), + InstructionResult::StackOverflow => "Out of stack".to_string(), + InstructionResult::InvalidJump => if kind.is_parity() { + "Bad jump destination" + } else { + "invalid jump destination" + } + .to_string(), + InstructionResult::PrecompileError => if kind.is_parity() { + "Built-in failed" + } else { + "precompiled failed" + } + .to_string(), + status => format!("{:?}", status), + }) + } +} + +/// A node in the arena +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTraceNode { + /// Parent node index in the arena + pub parent: Option, + /// Children node indexes in the arena + pub children: Vec, + /// This node's index in the arena + pub idx: usize, + /// The call trace + pub trace: CallTrace, + /// Recorded logs, if enabled + pub logs: Vec, + /// Ordering of child calls and logs + pub ordering: Vec, +} + +impl CallTraceNode { + /// Returns the call context's execution address + /// + /// See `Inspector::call` impl of + /// [TracingInspector](crate::tracing::TracingInspector) + pub const fn execution_address(&self) -> Address { + if self.trace.kind.is_delegate() { + self.trace.caller + } else { + self.trace.address + } + } + + /// Pushes all steps onto the stack in reverse order + /// so that the first step is on top of the stack + pub(crate) fn push_steps_on_stack<'a>( + &'a self, stack: &mut VecDeque>, + ) { + stack.extend(self.call_step_stack().into_iter().rev()); + } + + /// Returns a list of all steps in this trace in the order they were + /// executed + /// + /// If the step is a call, the id of the child trace is set. + pub(crate) fn call_step_stack(&self) -> Vec> { + let mut stack = Vec::with_capacity(self.trace.steps.len()); + let mut child_id = 0; + for step in self.trace.steps.iter() { + let mut item = CallTraceStepStackItem { + trace_node: self, + step, + call_child_id: None, + }; + + // If the opcode is a call, put the child trace on the stack + if step.is_calllike_op() { + // The opcode of this step is a call but it's possible that this + // step resulted in a revert or out of gas error in which case there's no actual child call executed and recorded: + if let Some(call_id) = self.children.get(child_id).copied() { + item.call_child_id = Some(call_id); + child_id += 1; + } + } + stack.push(item); + } + stack + } + + /// Returns true if this is a call to a precompile + #[inline] + pub fn is_precompile(&self) -> bool { + self.trace.maybe_precompile.unwrap_or(false) + } + + /// Returns the kind of call the trace belongs to + #[inline] + pub const fn kind(&self) -> CallKind { self.trace.kind } + + /// Returns the status of the call + #[inline] + pub const fn status(&self) -> InstructionResult { self.trace.status } + + /// Returns true if the call was a selfdestruct + /// + /// A selfdestruct is marked by the refund target being set. + /// + /// See also `TracingInspector::selfdestruct` + /// + /// Note: We can't rely in the [Self::status] being + /// [InstructionResult::SelfDestruct] because there's an edge case where + /// a new created contract (CREATE) is immediately selfdestructed. + #[inline] + pub const fn is_selfdestruct(&self) -> bool { + self.trace.selfdestruct_refund_target.is_some() + } + + /// If the trace is a selfdestruct, returns the `CallFrame` for a geth call + /// trace + pub fn geth_selfdestruct_call_trace(&self) -> Option { + if self.is_selfdestruct() { + Some(CallFrame { + typ: "SELFDESTRUCT".to_string(), + from: self.trace.caller, + to: self.trace.selfdestruct_refund_target, + value: Some(self.trace.value), + ..Default::default() + }) + } else { + None + } + } + + /// Converts this call trace into an _empty_ geth [CallFrame] + pub fn geth_empty_call_frame(&self, include_logs: bool) -> CallFrame { + let mut call_frame = CallFrame { + typ: self.trace.kind.to_string(), + from: self.trace.caller, + to: Some(self.trace.address), + value: Some(self.trace.value), + gas: U256::from(self.trace.gas_limit), + gas_used: U256::from(self.trace.gas_used), + input: self.trace.data.clone(), + output: (!self.trace.output.is_empty()) + .then(|| self.trace.output.clone()), + error: None, + revert_reason: None, + calls: Default::default(), + logs: Default::default(), + }; + + if self.trace.kind.is_static_call() { + // STATICCALL frames don't have a value + call_frame.value = None; + } + + // we need to populate error and revert reason + if !self.trace.success { + call_frame.revert_reason = + utils::maybe_revert_reason(self.trace.output.as_ref()); + + // Note: the call tracer mimics parity's trace transaction and geth maps errors to parity style error messages, + call_frame.error = self.trace.as_error_msg(TraceStyle::Parity); + } + + if include_logs && !self.logs.is_empty() { + call_frame.logs = self + .logs + .iter() + .map(|log| CallLogFrame { + address: Some(self.execution_address()), + topics: Some(log.topics().to_vec()), + data: Some(log.data.clone()), + }) + .collect(); + } + + call_frame + } +} + +/// A unified representation of a call. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "UPPERCASE"))] +pub enum CallKind { + /// Represents a regular call. + #[default] + Call, + /// Represents a static call. + StaticCall, + /// Represents a call code operation. + CallCode, + /// Represents a delegate call. + DelegateCall, + /// Represents a contract creation operation. + Create, + /// Represents a contract creation operation using the CREATE2 opcode. + Create2, +} + +impl CallKind { + /// Returns true if the call is a create + #[inline] + pub const fn is_any_create(&self) -> bool { + matches!(self, Self::Create | Self::Create2) + } + + /// Returns true if the call is a delegate of some sorts + #[inline] + pub const fn is_delegate(&self) -> bool { + matches!(self, Self::DelegateCall | Self::CallCode) + } + + /// Returns true if the call is [CallKind::StaticCall]. + #[inline] + pub const fn is_static_call(&self) -> bool { + matches!(self, Self::StaticCall) + } +} + +impl std::fmt::Display for CallKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Call => { + write!(f, "CALL") + } + Self::StaticCall => { + write!(f, "STATICCALL") + } + Self::CallCode => { + write!(f, "CALLCODE") + } + Self::DelegateCall => { + write!(f, "DELEGATECALL") + } + Self::Create => { + write!(f, "CREATE") + } + Self::Create2 => { + write!(f, "CREATE2") + } + } + } +} + +impl From for CallKind { + fn from(scheme: CallScheme) -> Self { + match scheme { + CallScheme::Call => Self::Call, + CallScheme::StaticCall => Self::StaticCall, + CallScheme::CallCode => Self::CallCode, + CallScheme::DelegateCall => Self::DelegateCall, + } + } +} + +impl From for CallKind { + fn from(create: CreateScheme) -> Self { + match create { + CreateScheme::Create => Self::Create, + CreateScheme::Create2 { .. } => Self::Create2, + } + } +} + +impl From for CallKind { + fn from(ct: CfxCallType) -> Self { + match ct { + CfxCallType::None => Self::Create, + CfxCallType::Call => Self::Call, + CfxCallType::CallCode => Self::CallCode, + CfxCallType::DelegateCall => Self::DelegateCall, + CfxCallType::StaticCall => Self::StaticCall, + } + } +} + +pub(crate) struct CallTraceStepStackItem<'a> { + /// The trace node that contains this step + pub(crate) trace_node: &'a CallTraceNode, + /// The step that this stack item represents + pub(crate) step: &'a CallTraceStep, + /// The index of the child call in the CallArena if this step's opcode is a + /// call + pub(crate) call_child_id: Option, +} + +/// Ordering enum for calls and logs +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum LogCallOrder { + /// Contains the index of the corresponding log + Log(usize), + /// Contains the index of the corresponding trace node + Call(usize), +} + +/// Represents a tracked call step during execution +#[derive(Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CallTraceStep { + // Fields filled in `step` + /// Call depth + pub depth: u64, + /// Program counter before step execution + pub pc: usize, + /// Opcode to be executed + #[cfg_attr(feature = "serde", serde(with = "opcode_serde"))] + pub op: OpCode, + /// Current contract address + pub contract: Address, + /// Stack before step execution + pub stack: Option>, + /// The new stack items placed by this step if any + pub push_stack: Option>, + /// All allocated memory in a step + /// + /// This will be empty if memory capture is disabled + pub memory: RecordedMemory, + /// Size of memory at the beginning of the step + pub memory_size: usize, + /// Remaining gas before step execution + pub gas_remaining: u64, + /// Gas refund counter before step execution + pub gas_refund_counter: u64, + // Fields filled in `step_end` + /// Gas cost of step execution + pub gas_cost: u64, + /// Change of the contract state after step execution (effect of the + /// SLOAD/SSTORE instructions) + pub storage_change: Option, + /// Final status of the step + /// + /// This is set after the step was executed. + pub status: InstructionResult, +} + +// === impl CallTraceStep === + +impl CallTraceStep { + /// Converts this step into a geth [StructLog] + /// + /// This sets memory and stack capture based on the `opts` parameter. + pub(crate) fn convert_to_geth_struct_log( + &self, opts: &GethDefaultTracingOptions, + ) -> StructLog { + let mut log = StructLog { + depth: self.depth, + error: self.as_error(), + gas: self.gas_remaining, + gas_cost: self.gas_cost, + op: self.op.to_string(), + pc: self.pc as u64, + refund_counter: (self.gas_refund_counter > 0) + .then_some(self.gas_refund_counter), + // Filled, if not disabled manually + stack: None, + // Filled in `CallTraceArena::geth_trace` as a result of compounding + // all slot changes + return_data: None, + // Filled via trace object + storage: None, + // Only enabled if `opts.enable_memory` is true + memory: None, + // This is None in the rpc response + memory_size: None, + }; + + if opts.is_stack_enabled() { + log.stack.clone_from(&self.stack); + } + + if opts.is_memory_enabled() { + log.memory = Some(self.memory.memory_chunks()); + } + + log + } + + /// Returns true if the step is a STOP opcode + #[inline] + pub(crate) const fn is_stop(&self) -> bool { + matches!(self.op.get(), opcode::STOP) + } + + /// Returns true if the step is a call operation, any of + /// CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2 + #[inline] + pub(crate) const fn is_calllike_op(&self) -> bool { + matches!( + self.op.get(), + opcode::CALL + | opcode::DELEGATECALL + | opcode::STATICCALL + | opcode::CREATE + | opcode::CALLCODE + | opcode::CREATE2 + ) + } + + // Returns true if the status code is an error or revert, See + // [InstructionResult::Revert] + #[inline] + pub(crate) const fn is_error(&self) -> bool { + self.status as u8 >= InstructionResult::Revert as u8 + } + + /// Returns the error message if it is an erroneous result. + #[inline] + pub(crate) fn as_error(&self) -> Option { + self.is_error().then(|| format!("{:?}", self.status)) + } +} + +/// Represents the source of a storage change - e.g., whether it came +/// from an SSTORE or SLOAD instruction. +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum StorageChangeReason { + /// SLOAD opcode + SLOAD, + /// SSTORE opcode + SSTORE, +} + +/// Represents a storage change during execution. +/// +/// This maps to evm internals: +/// [JournalEntry::StorageChange](revm::JournalEntry::StorageChange) +/// +/// It is used to track both storage change and warm load of a storage slot. For +/// warm load in regard to EIP-2929 AccessList had_value will be None. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StorageChange { + /// key of the storage slot + pub key: U256, + /// Current value of the storage slot + pub value: U256, + /// The previous value of the storage slot, if any + pub had_value: Option, + /// How this storage was accessed + pub reason: StorageChangeReason, +} + +/// Represents the memory captured during execution +/// +/// This is a wrapper around the [SharedMemory](revm::interpreter::SharedMemory) +/// context memory. +#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct RecordedMemory(pub(crate) Vec); + +impl RecordedMemory { + #[inline] + pub(crate) fn new(mem: Vec) -> Self { Self(mem) } + + /// Returns the memory as a byte slice + #[inline] + pub fn as_bytes(&self) -> &[u8] { &self.0 } + + #[inline] + pub(crate) fn resize(&mut self, size: usize) { self.0.resize(size, 0); } + + /// Returns the size of the memory + #[inline] + pub fn len(&self) -> usize { self.0.len() } + + /// Returns whether the memory is empty + #[inline] + pub fn is_empty(&self) -> bool { self.0.is_empty() } + + /// Converts the memory into 32byte hex chunks + #[inline] + pub fn memory_chunks(&self) -> Vec { + convert_memory(self.as_bytes()) + } +} + +impl AsRef<[u8]> for RecordedMemory { + fn as_ref(&self) -> &[u8] { self.as_bytes() } +} + +pub struct GethTraceWithHash { + pub trace: GethTrace, + pub tx_hash: H256, + pub space: Space, +} + +#[derive(Clone)] +pub struct TxExecContext { + pub tx_gas_limit: u64, + pub block_number: BlockNumber, + pub block_height: BlockHeight, +} + +#[cfg(feature = "serde")] +mod opcode_serde { + use super::OpCode; + use serde::{Deserialize, Deserializer, Serializer}; + + pub(super) fn serialize( + op: &OpCode, serializer: S, + ) -> Result + where S: Serializer { + serializer.serialize_u8(op.get()) + } + + pub(super) fn deserialize<'de, D>( + deserializer: D, + ) -> Result + where D: Deserializer<'de> { + let op = u8::deserialize(deserializer)?; + Ok(OpCode::new(op).unwrap_or_else(|| { + OpCode::new(revm::interpreter::opcode::INVALID).unwrap() + })) + } +} diff --git a/crates/cfxcore/geth-tracer/src/utils.rs b/crates/cfxcore/geth-tracer/src/utils.rs new file mode 100644 index 0000000000..e33455c4d2 --- /dev/null +++ b/crates/cfxcore/geth-tracer/src/utils.rs @@ -0,0 +1,104 @@ +//! Util functions for revm related ops +use alloy_primitives::{hex, B256}; +use alloy_sol_types::{ContractError, GenericRevertReason}; +use cfx_types::{Address, H160, H256, U256}; +use cfx_vm_interpreter::instructions::{INSTRUCTIONS, INSTRUCTIONS_CANCUN}; +use revm::primitives::{Address as RAddress, U256 as RU256}; + +/// creates the memory data in 32byte chunks +/// see +#[inline] +pub(crate) fn convert_memory(data: &[u8]) -> Vec { + let mut memory = Vec::with_capacity((data.len() + 31) / 32); + for idx in (0..data.len()).step_by(32) { + let len = std::cmp::min(idx + 32, data.len()); + memory.push(hex::encode(&data[idx..len])); + } + memory +} + +/// Get the gas used, accounting for refunds +#[inline] +pub(crate) fn gas_used(spent: u64, refunded: u64) -> u64 { + let refund_quotient = 5; + spent - (refunded).min(spent / refund_quotient) +} + +/// Returns a non empty revert reason if the output is a revert/error. +#[inline] +pub(crate) fn maybe_revert_reason(output: &[u8]) -> Option { + let reason = match GenericRevertReason::decode(output)? { + GenericRevertReason::ContractError(err) => { + match err { + // return the raw revert reason and don't use the revert's + // display message + ContractError::Revert(revert) => revert.reason, + err => err.to_string(), + } + } + GenericRevertReason::RawString(err) => err, + }; + if reason.is_empty() { + None + } else { + Some(reason) + } +} + +/// Returns the number of items pushed on the stack by a given opcode. +/// This used to determine how many stack etries to put in the `push` element +/// in a parity vmTrace. +/// The value is obvious for most opcodes, but SWAP* and DUP* are a bit weird, +/// and we handle those as they are handled in parity vmtraces. +/// For reference: +pub(crate) fn stack_push_count(step_op: u8, cancun_enabled: bool) -> usize { + match cancun_enabled { + true => match INSTRUCTIONS_CANCUN.get(step_op as usize) { + Some(Some(instruct)) => instruct.ret, + _ => 0, + }, + false => match INSTRUCTIONS.get(step_op as usize) { + Some(Some(instruct)) => instruct.ret, + _ => 0, + }, + } +} + +// convert from cfx U256 to alloy U256 +pub fn to_alloy_u256(u: U256) -> RU256 { + let mut be_bytes: [u8; 32] = [0; 32]; + u.to_big_endian(&mut be_bytes); + RU256::from_be_bytes(be_bytes) +} + +pub fn to_alloy_address(h: H160) -> RAddress { + RAddress::from_slice(h.as_bytes()) +} + +pub fn to_alloy_h256(h: H256) -> B256 { B256::from(h.0) } + +pub fn from_alloy_address(address: RAddress) -> Address { + Address::from_slice(address.as_slice()) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_sol_types::{GenericContractError, SolInterface}; + + #[test] + fn decode_revert_reason() { + let err = GenericContractError::Revert("my revert".into()); + let encoded = err.abi_encode(); + let reason = maybe_revert_reason(&encoded).unwrap(); + assert_eq!(reason, "my revert"); + } + + // + #[test] + fn decode_revert_reason_with_error() { + let err = hex!("08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e5400000000000000000000000000000000000000000000000000000080"); + let reason = maybe_revert_reason(&err[..]).unwrap(); + assert_eq!(reason, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT"); + } +} diff --git a/crates/cfxcore/internal_common/Cargo.toml b/crates/cfxcore/internal_common/Cargo.toml index c5c9ba6186..34d0a56875 100644 --- a/crates/cfxcore/internal_common/Cargo.toml +++ b/crates/cfxcore/internal_common/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-internal-common" version = "1.0.0" -edition = "2018" +edition = "2021" [dependencies] cfx-bytes = { path = "../../cfx_bytes" } diff --git a/crates/cfxcore/packing-pool/Cargo.toml b/crates/cfxcore/packing-pool/Cargo.toml index 812a810b0a..32efa04bf6 100644 --- a/crates/cfxcore/packing-pool/Cargo.toml +++ b/crates/cfxcore/packing-pool/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cfx-packing-pool" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/crates/cfxcore/packing-pool/src/packing_batch.rs b/crates/cfxcore/packing-pool/src/packing_batch.rs index dcd90fb9ef..2ef33f53c7 100644 --- a/crates/cfxcore/packing-pool/src/packing_batch.rs +++ b/crates/cfxcore/packing-pool/src/packing_batch.rs @@ -266,6 +266,7 @@ impl PackingBatch { let loss_ratio = config.loss_ratio(sort_key); let weight = PackingPoolWeight { gas_limit: self.total_gas_limit, + min_gas_price: gas_price, weighted_loss_ratio: loss_ratio * self.total_gas_limit, max_loss_ratio: loss_ratio, }; diff --git a/crates/cfxcore/packing-pool/src/pool.rs b/crates/cfxcore/packing-pool/src/pool.rs index b8120ffd62..8268ad6c3f 100644 --- a/crates/cfxcore/packing-pool/src/pool.rs +++ b/crates/cfxcore/packing-pool/src/pool.rs @@ -12,6 +12,7 @@ use super::{ }; use cfx_types::U256; use malloc_size_of::MallocSizeOf; +use primitives::block_header::{compute_next_price, estimate_max_possible_gas}; use rand::RngCore; use treap_map::{ ApplyOpOutcome, ConsoliableWeight, Node, SearchDirection, SearchResult, @@ -181,6 +182,56 @@ impl PackingPool { } } + pub fn estimate_packing_gas_limit( + &self, gas_target: U256, parent_base_price: U256, min_base_price: U256, + ) -> U256 { + let ret = self.treap_map.search(|left_weight, node| { + let can_sample = |weight| { + can_sample_within_1559( + weight, + gas_target, + parent_base_price, + min_base_price, + ) + }; + + if !can_sample(&left_weight) { + return SearchDirection::Left; + } + let right_weight = + PackingPoolWeight::consolidate(left_weight, &node.weight); + if !can_sample(&right_weight) { + return SearchDirection::Stop; + } else { + return SearchDirection::Right(right_weight); + } + }); + match ret { + Some( + SearchResult::Found { base_weight, .. } + | SearchResult::RightMost(base_weight), + ) => { + let gas_limit = estimate_max_possible_gas( + gas_target, + base_weight.min_gas_price, + parent_base_price, + ); + if cfg!(test) { + // Guarantee the searched result can be packed + let next_price = compute_next_price( + gas_target, + gas_limit, + parent_base_price, + min_base_price, + ); + assert!(base_weight.min_gas_price >= next_price); + } + gas_limit + } + _ => U256::zero(), + } + } + #[cfg(test)] fn assert_consistency(&self) { self.treap_map.assert_consistency(); @@ -243,6 +294,34 @@ fn can_sample(weight: &PackingPoolWeight, gas_limit: U256) -> bool { < weight.weighted_loss_ratio } +fn can_sample_within_1559( + weight: &PackingPoolWeight, gas_target: U256, parent_base_price: U256, + min_base_price: U256, +) -> bool { + if weight.min_gas_price < min_base_price { + return false; + } + + let max_target_gas_used = estimate_max_possible_gas( + gas_target, + weight.min_gas_price, + parent_base_price, + ); + + if max_target_gas_used.is_zero() { + return false; + } + + if weight.gas_limit <= max_target_gas_used { + return true; + } + + weight + .max_loss_ratio + .saturating_mul(weight.gas_limit - max_target_gas_used) + < weight.weighted_loss_ratio +} + impl MallocSizeOf for PackingPool where TX: PackingPoolTransaction + MallocSizeOf, @@ -293,6 +372,25 @@ mod pool_tests { } } + #[allow(dead_code)] + fn same_price_txs() -> PackingPool { + let config = PackingPoolConfig::new_for_test(); + let mut pool = PackingPool::new(config); + + static ID: AtomicUsize = AtomicUsize::new(0); + for i in 1000..2000 { + let (_, res) = pool.insert(MockTransaction { + sender: i, + nonce: 0, + gas_price: 20, + gas_limit: 1, + id: ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), + }); + res.unwrap(); + } + pool + } + #[test] fn test_split_in_middle() { let mut pool = default_pool(5, 10); diff --git a/crates/cfxcore/packing-pool/src/weight.rs b/crates/cfxcore/packing-pool/src/weight.rs index c86ae62eed..4653f63043 100644 --- a/crates/cfxcore/packing-pool/src/weight.rs +++ b/crates/cfxcore/packing-pool/src/weight.rs @@ -5,17 +5,24 @@ use treap_map::ConsoliableWeight; #[derive(Default, Clone, Eq, PartialEq, MallocSizeOf, Debug)] pub struct PackingPoolWeight { pub gas_limit: U256, + pub min_gas_price: U256, pub weighted_loss_ratio: U256, pub max_loss_ratio: U256, } impl ConsoliableWeight for PackingPoolWeight { #[inline] - fn empty() -> Self { Self::default() } + fn empty() -> Self { + Self { + min_gas_price: U256::max_value(), + ..Default::default() + } + } fn consolidate(a: &Self, b: &Self) -> Self { Self { gas_limit: a.gas_limit + b.gas_limit, + min_gas_price: U256::min(a.min_gas_price, b.min_gas_price), weighted_loss_ratio: a.weighted_loss_ratio + b.weighted_loss_ratio, max_loss_ratio: U256::max(a.max_loss_ratio, b.max_loss_ratio), } @@ -25,6 +32,7 @@ impl ConsoliableWeight for PackingPoolWeight { self.gas_limit += other.gas_limit; self.weighted_loss_ratio += other.weighted_loss_ratio; self.max_loss_ratio = - U256::max(self.max_loss_ratio, other.max_loss_ratio) + U256::max(self.max_loss_ratio, other.max_loss_ratio); + self.min_gas_price = U256::min(self.min_gas_price, other.min_gas_price); } } diff --git a/crates/cfxcore/parameters/Cargo.toml b/crates/cfxcore/parameters/Cargo.toml index 69dac972b1..c92815550a 100644 --- a/crates/cfxcore/parameters/Cargo.toml +++ b/crates/cfxcore/parameters/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-parameters" version = "1.0.0" -edition = "2018" +edition = "2021" [dependencies] cfx-types = { path = "../../cfx_types" } diff --git a/crates/cfxcore/parameters/src/lib.rs b/crates/cfxcore/parameters/src/lib.rs index 89be79a0ce..077a1d25f2 100644 --- a/crates/cfxcore/parameters/src/lib.rs +++ b/crates/cfxcore/parameters/src/lib.rs @@ -50,10 +50,11 @@ pub mod consensus { pub const TANZANITE_HEADER_CUSTOM_FIRST_ELEMENT: [u8; 1] = [1]; pub const DAO_VOTE_HEADER_CUSTOM_FIRST_ELEMENT: [u8; 1] = [2]; pub const CIP112_HEADER_CUSTOM_FIRST_ELEMENT: [u8; 1] = [3]; + pub const NEXT_HARDFORK_HEADER_CUSTOM_FIRST_ELEMENT: [u8; 1] = [4]; } pub mod consensus_internal { - use crate::consensus::ONE_CFX_IN_DRIP; + use crate::consensus::{ONE_CFX_IN_DRIP, ONE_GDRIP_IN_DRIP}; /// `REWARD_EPOCH_COUNT` needs to be larger than /// `ANTICONE_PENALTY_UPPER_EPOCH_COUNT`. If we cannot cache receipts of @@ -116,6 +117,17 @@ pub mod consensus_internal { /// The initial storage point proportion after CIP107 is enabled. pub const CIP107_STORAGE_POINT_PROP_INIT: u64 = ONE_CFX_IN_DRIP; + + /// The initial base price share proportion after CIP137 is enabled. + pub const CIP137_BASEFEE_PROP_INIT: u64 = ONE_CFX_IN_DRIP; + + /// The initial and minimum base price + pub const INITIAL_1559_CORE_BASE_PRICE: u64 = ONE_GDRIP_IN_DRIP; + + pub const INITIAL_1559_ETH_BASE_PRICE: u64 = 20 * ONE_GDRIP_IN_DRIP; + + // Parameter specified in EIP-1559 + pub const ELASTICITY_MULTIPLIER: usize = 2; } pub mod rpc { diff --git a/crates/cfxcore/vm-interpreter/Cargo.toml b/crates/cfxcore/vm-interpreter/Cargo.toml index 07ab4e4b6b..a9758436cb 100644 --- a/crates/cfxcore/vm-interpreter/Cargo.toml +++ b/crates/cfxcore/vm-interpreter/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-vm-interpreter" version = "2.0.2" -edition = "2018" +edition = "2021" [dependencies] bit-set = "0.4" diff --git a/crates/cfxcore/vm-interpreter/src/factory.rs b/crates/cfxcore/vm-interpreter/src/factory.rs index 68b2cf5c2d..b868448a39 100644 --- a/crates/cfxcore/vm-interpreter/src/factory.rs +++ b/crates/cfxcore/vm-interpreter/src/factory.rs @@ -30,7 +30,8 @@ use std::sync::Arc; #[derive(Clone)] pub struct Factory { evm: VMType, - evm_cache: Arc, + evm_cache: Arc>, + evm_cache_cancun: Arc>, } impl Factory { @@ -39,24 +40,38 @@ impl Factory { pub fn create( &self, params: ActionParams, spec: &Spec, depth: usize, ) -> Box { + use super::interpreter::Interpreter; + // Assert there is only one type. Parity Ethereum is dead and no more + // types will be added. match self.evm { - VMType::Interpreter => { - if Self::can_fit_in_usize(¶ms.gas) { - Box::new(super::interpreter::Interpreter::::new( - params, - self.evm_cache.clone(), - spec, - depth, - )) - } else { - Box::new(super::interpreter::Interpreter::::new( - params, - self.evm_cache.clone(), - spec, - depth, - )) - } - } + VMType::Interpreter => {} + }; + + match (Self::can_fit_in_usize(¶ms.gas), spec.cancun_opcodes) { + (true, true) => Box::new(Interpreter::::new( + params, + self.evm_cache_cancun.clone(), + spec, + depth, + )), + (true, false) => Box::new(Interpreter::::new( + params, + self.evm_cache.clone(), + spec, + depth, + )), + (false, true) => Box::new(Interpreter::::new( + params, + self.evm_cache_cancun.clone(), + spec, + depth, + )), + (false, false) => Box::new(Interpreter::::new( + params, + self.evm_cache.clone(), + spec, + depth, + )), } } @@ -66,6 +81,7 @@ impl Factory { Factory { evm, evm_cache: Arc::new(SharedCache::new(cache_size)), + evm_cache_cancun: Arc::new(SharedCache::new(cache_size)), } } @@ -80,6 +96,7 @@ impl Default for Factory { Factory { evm: VMType::Interpreter, evm_cache: Arc::new(SharedCache::default()), + evm_cache_cancun: Arc::new(SharedCache::default()), } } } diff --git a/crates/cfxcore/vm-interpreter/src/instructions.rs b/crates/cfxcore/vm-interpreter/src/instructions.rs index 2fbeac1a7a..b1c6e67829 100644 --- a/crates/cfxcore/vm-interpreter/src/instructions.rs +++ b/crates/cfxcore/vm-interpreter/src/instructions.rs @@ -159,6 +159,11 @@ enum_with_from_u8! { CHAINID = 0x46, #[doc = "get balance of own account"] SELFBALANCE = 0x47, + #[doc = "base fee for EIP-1559 (EIP-3198)"] + BASEFEE = 0x48, + + // BLOBHASH=0x49 + // BLOBBASEFEE=0x4A #[doc = "remove item from stack"] POP = 0x50, @@ -184,12 +189,15 @@ enum_with_from_u8! { GAS = 0x5a, #[doc = "set a potential jump destination"] JUMPDEST = 0x5b, - #[doc = "Marks the entry point to a subroutine."] - BEGINSUB = 0x5c, - #[doc = "Returns from a subroutine."] - RETURNSUB = 0x5d, - #[doc = "Jumps to a defined BEGINSUB subroutine."] - JUMPSUB = 0x5e, + #[doc = "Marks the entry point to a subroutine (pre cip-142). load word from transient storage (after cip-142)"] + #[allow(non_camel_case_types)] + BEGINSUB_TLOAD = 0x5c, + #[doc = "Returns from a subroutine (pre cip-142). store word from transient storage (after cip-142)"] + #[allow(non_camel_case_types)] + RETURNSUB_TSTORE = 0x5d, + #[doc = "Jumps to a defined BEGINSUB subroutine (pre cip-143). copy data from one memory range to another (after cip-143)"] + #[allow(non_camel_case_types)] + JUMPSUB_MCOPY = 0x5e, #[doc = "place zero item on stack (EIP-3855/CIP-119)"] PUSH0 = 0x5f, @@ -347,10 +355,13 @@ enum_with_from_u8! { DELEGATECALL = 0xf4, #[doc = "create a new account and set creation address to sha3(sender + sha3(init code)) % 2**160"] CREATE2 = 0xf5, - #[doc = "stop execution and revert state changes. Return output data."] - REVERT = 0xfd, #[doc = "like CALL but it does not take value, nor modify the state"] STATICCALL = 0xfa, + #[doc = "stop execution and revert state changes. Return output data."] + REVERT = 0xfd, + + // INVALID = 0xfe + #[doc = "halt execution and register account for later deletion"] SUICIDE = 0xff, } @@ -365,9 +376,14 @@ impl Instruction { if instruction == Some(PUSH0) && !spec.cip119 { instruction = None; } + if instruction == Some(BASEFEE) && !spec.cip1559 { + instruction = None; + } return instruction; } + pub fn u8(self) -> u8 { self as u8 } + /// Returns number of bytes to read for `PUSHN` instruction /// PUSH1 -> 1 pub fn push_bytes(&self) -> Option { @@ -409,8 +425,14 @@ impl Instruction { } /// Returns the instruction info. - pub fn info(&self) -> &'static InstructionInfo { - INSTRUCTIONS[*self as usize].as_ref().expect("A instruction is defined in Instruction enum, but it is not found in InstructionInfo struct; this indicates a logic failure in the code.") + pub fn info(&self) -> &InstructionInfo { + let instrs = if !CANCUN { + &*INSTRUCTIONS + } else { + &*INSTRUCTIONS_CANCUN + }; + + instrs[*self as usize].as_ref().expect("A instruction is defined in Instruction enum, but it is not found in InstructionInfo struct; this indicates a logic failure in the code.") } } @@ -479,7 +501,7 @@ impl InstructionInfo { lazy_static! { /// Static instruction table. - static ref INSTRUCTIONS: [Option; 0x100] = { + pub static ref INSTRUCTIONS: [Option; 0x100] = { let mut arr = [None; 0x100]; arr[STOP as usize] = Some(InstructionInfo::new("STOP", 0, 0, GasPriceTier::Zero)); arr[ADD as usize] = Some(InstructionInfo::new("ADD", 2, 1, GasPriceTier::VeryLow)); @@ -532,6 +554,7 @@ lazy_static! { arr[GASLIMIT as usize] = Some(InstructionInfo::new("GASLIMIT", 0, 1, GasPriceTier::Base)); arr[CHAINID as usize] = Some(InstructionInfo::new("CHAINID", 0, 1, GasPriceTier::Base)); arr[SELFBALANCE as usize] = Some(InstructionInfo::new("SELFBALANCE", 0, 1, GasPriceTier::Low)); + arr[BASEFEE as usize] = Some(InstructionInfo::new("BASEFEE", 0, 1, GasPriceTier::VeryLow)); arr[POP as usize] = Some(InstructionInfo::new("POP", 1, 0, GasPriceTier::Base)); arr[MLOAD as usize] = Some(InstructionInfo::new("MLOAD", 1, 1, GasPriceTier::VeryLow)); arr[MSTORE as usize] = Some(InstructionInfo::new("MSTORE", 2, 0, GasPriceTier::VeryLow)); @@ -614,9 +637,9 @@ lazy_static! { arr[LOG2 as usize] = Some(InstructionInfo::new("LOG2", 4, 0, GasPriceTier::Special)); arr[LOG3 as usize] = Some(InstructionInfo::new("LOG3", 5, 0, GasPriceTier::Special)); arr[LOG4 as usize] = Some(InstructionInfo::new("LOG4", 6, 0, GasPriceTier::Special)); - arr[BEGINSUB as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base)); - arr[JUMPSUB as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High)); - arr[RETURNSUB as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low)); + arr[BEGINSUB_TLOAD as usize] = Some(InstructionInfo::new("BEGINSUB", 0, 0, GasPriceTier::Base)); + arr[JUMPSUB_MCOPY as usize] = Some(InstructionInfo::new("JUMPSUB", 1, 0, GasPriceTier::High)); + arr[RETURNSUB_TSTORE as usize] = Some(InstructionInfo::new("RETURNSUB", 0, 0, GasPriceTier::Low)); arr[CREATE as usize] = Some(InstructionInfo::new("CREATE", 3, 1, GasPriceTier::Special)); arr[CALL as usize] = Some(InstructionInfo::new("CALL", 7, 1, GasPriceTier::Special)); arr[CALLCODE as usize] = Some(InstructionInfo::new("CALLCODE", 7, 1, GasPriceTier::Special)); @@ -628,6 +651,14 @@ lazy_static! { arr[REVERT as usize] = Some(InstructionInfo::new("REVERT", 2, 0, GasPriceTier::Zero)); arr }; + + pub static ref INSTRUCTIONS_CANCUN: [Option; 0x100] = { + let mut arr = *INSTRUCTIONS; + arr[BEGINSUB_TLOAD as usize] = Some(InstructionInfo::new("TLOAD", 1, 1, GasPriceTier::Special)); + arr[JUMPSUB_MCOPY as usize] = Some(InstructionInfo::new("MCOPY", 3, 0, GasPriceTier::Special)); + arr[RETURNSUB_TSTORE as usize] = Some(InstructionInfo::new("TSTORE", 2, 0, GasPriceTier::Special)); + arr + }; } /// Maximal number of topics for log instructions diff --git a/crates/cfxcore/vm-interpreter/src/interpreter/gasometer.rs b/crates/cfxcore/vm-interpreter/src/interpreter/gasometer.rs index 7a3a9c2be1..ec8e5cb97a 100644 --- a/crates/cfxcore/vm-interpreter/src/interpreter/gasometer.rs +++ b/crates/cfxcore/vm-interpreter/src/interpreter/gasometer.rs @@ -21,6 +21,7 @@ use cfx_types::{Space, U256}; use cfx_vm_types::{self as vm, Spec}; use std::cmp; +use vm::BlockHashSource; use super::{ instructions::{self, Instruction, InstructionInfo}, @@ -206,6 +207,19 @@ impl Gasometer { mem_needed(stack.peek(0), stack.peek(2))?, Gas::from_u256(*stack.peek(2))?, ), + instructions::JUMPSUB_MCOPY if spec.cancun_opcodes => { + Request::GasMemCopy( + default_gas, + mem_needed(stack.peek(0), stack.peek(2))?, + Gas::from_u256(*stack.peek(2))?, + ) + } + instructions::BEGINSUB_TLOAD if spec.cancun_opcodes => { + Request::Gas(Gas::from(spec.tload_gas)) + } + instructions::RETURNSUB_TSTORE if spec.cancun_opcodes => { + Request::Gas(Gas::from(spec.tstore_gas)) + } instructions::EXTCODECOPY => Request::GasMemCopy( spec.extcodecopy_base_gas.into(), mem_needed(stack.peek(1), stack.peek(3))?, @@ -298,7 +312,11 @@ impl Gasometer { Request::Gas(gas) } instructions::BLOCKHASH => { - Request::Gas(Gas::from(spec.blockhash_gas)) + let gas = match context.blockhash_source() { + BlockHashSource::Env => spec.blockhash_gas, + BlockHashSource::State => spec.sload_gas, + }; + Request::Gas(Gas::from(gas)) } _ => Request::Gas(default_gas), }; diff --git a/crates/cfxcore/vm-interpreter/src/interpreter/mod.rs b/crates/cfxcore/vm-interpreter/src/interpreter/mod.rs index d31d61d028..81d792fe04 100644 --- a/crates/cfxcore/vm-interpreter/src/interpreter/mod.rs +++ b/crates/cfxcore/vm-interpreter/src/interpreter/mod.rs @@ -46,8 +46,8 @@ use cfx_bytes::Bytes; use cfx_types::{Address, BigEndianHash, Space, H256, U256, U512}; use cfx_vm_types::{ self as vm, ActionParams, ActionValue, CallType, ContractCreateResult, - CreateContractAddress, GasLeft, MessageCallResult, ParamsType, ReturnData, - Spec, TrapError, TrapKind, + CreateContractAddress, GasLeft, InstructionResult, InterpreterInfo, + MessageCallResult, ParamsType, ReturnData, Spec, TrapError, TrapKind, }; use keccak_hash::keccak; use std::{cmp, convert::TryFrom, marker::PhantomData, mem, sync::Arc}; @@ -92,26 +92,6 @@ impl CodeReader { fn len(&self) -> usize { self.code.len() } } -enum InstructionResult { - Ok, - UnusedGas(Gas), - JumpToPosition(U256), - JumpToSubroutine(U256), - ReturnFromSubroutine(usize), - StopExecutionNeedsReturn { - /// Gas left. - gas: Gas, - /// Return data offset. - init_off: U256, - /// Return data size. - init_size: U256, - /// Apply or revert state changes. - apply: bool, - }, - StopExecution, - Trap(TrapKind), -} - /// ActionParams without code, so that it can be feed into CodeReader. #[derive(Debug)] #[allow(dead_code)] @@ -178,9 +158,9 @@ impl From for InterpreterResult { } /// Interpreter EVM implementation -pub struct Interpreter { +pub struct Interpreter { mem: Vec, - cache: Arc, + cache: Arc>, params: InterpreterParams, reader: CodeReader, return_data: ReturnData, @@ -199,7 +179,9 @@ pub struct Interpreter { _type: PhantomData, } -impl vm::Exec for Interpreter { +impl vm::Exec + for Interpreter +{ fn exec( mut self: Box, context: &mut dyn vm::Context, ) -> vm::ExecTrapResult { @@ -231,7 +213,9 @@ impl vm::Exec for Interpreter { } } -impl vm::ResumeCall for Interpreter { +impl vm::ResumeCall + for Interpreter +{ fn resume_call( mut self: Box, result: MessageCallResult, ) -> Box { @@ -276,7 +260,9 @@ impl vm::ResumeCall for Interpreter { } } -impl vm::ResumeCreate for Interpreter { +impl vm::ResumeCreate + for Interpreter +{ fn resume_create( mut self: Box, result: ContractCreateResult, ) -> Box { @@ -305,12 +291,12 @@ impl vm::ResumeCreate for Interpreter { } } -impl Interpreter { +impl Interpreter { /// Create a new `Interpreter` instance with shared cache. pub fn new( - mut params: ActionParams, cache: Arc, spec: &Spec, + mut params: ActionParams, cache: Arc>, spec: &Spec, depth: usize, - ) -> Interpreter { + ) -> Interpreter { let reader = CodeReader::new( params.code.take().expect("VM always called with code; qed"), ); @@ -346,6 +332,7 @@ impl Interpreter { } /// Execute a single step on the VM. + /// First check gas and reader is ok, then will call step_inner #[inline(always)] pub fn step(&mut self, context: &mut dyn vm::Context) -> InterpreterResult { if self.done { @@ -381,117 +368,20 @@ impl Interpreter { let result = match self.resume_result.take() { Some(result) => result, None => { - let opcode = self.reader.code[self.reader.position]; - let instruction = - Instruction::from_u8_versioned(opcode, context.spec()); - self.reader.position += 1; - - // TODO: make compile-time removable if too much of a - // performance hit. - // self.do_trace = self.do_trace - // && context.trace_next_instruction( - // self.reader.position - 1, - // opcode, - // self.gasometer - // .as_mut() - // .expect(GASOMETER_PROOF) - // .current_gas - // .as_u256(), - // ); - - let instruction = match instruction { - Some(i) => i, - None => { - return InterpreterResult::Done(Err( - vm::Error::BadInstruction { - instruction: opcode, - }, - )); - } - }; - - let info = instruction.info(); - self.last_stack_ret_len = info.ret; - if let Err(e) = - self.verify_instruction(context, instruction, info) - { - return InterpreterResult::Done(Err(e)); + if self.do_trace { + context.trace_step(self); } - // Calculate gas cost - let requirements = match self - .gasometer - .as_mut() - .expect(GASOMETER_PROOF) - .requirements( - context, - instruction, - info, - &self.stack, - self.mem.size(), - ) { - Ok(t) => t, - Err(e) => return InterpreterResult::Done(Err(e)), - }; - // if self.do_trace { - // context.trace_prepare_execute( - // self.reader.position - 1, - // opcode, - // requirements.gas_cost.as_u256(), - // Self::mem_written(instruction, &self.stack), - // Self::store_written(instruction, &self.stack), - // ); - // } - - if let Err(e) = self - .gasometer - .as_mut() - .expect(GASOMETER_PROOF) - .verify_gas(&requirements.gas_cost) - { - return InterpreterResult::Done(Err(e)); - } - self.mem.expand(requirements.memory_required_size); - self.gasometer - .as_mut() - .expect(GASOMETER_PROOF) - .current_mem_gas = requirements.memory_total_gas; - self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas = - self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas - - requirements.gas_cost; - - evm_debug!({ - self.informant.before_instruction( - self.reader.position, - instruction, - info, - &self - .gasometer - .as_mut() - .expect(GASOMETER_PROOF) - .current_gas, - &self.stack, - ) - }); - - // Execute instruction - let current_gas = - self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas; - let result = match self.exec_instruction( - current_gas, - context, - instruction, - requirements.provide_gas, - ) { - Err(x) => { - return InterpreterResult::Done(Err(x)); - } - Ok(x) => x, - }; + let op_result = self.exec_instruction(context); - evm_debug!({ self.informant.after_instruction(instruction) }); + if self.do_trace { + context.trace_step_end(self); + } - result + match op_result { + Ok(result) => result, + Err(e) => return e, + } } }; @@ -639,6 +529,7 @@ impl Interpreter { instructions::CALLDATACOPY | instructions::CODECOPY | instructions::RETURNDATACOPY => Some((read(0), read(2))), + instructions::JUMPSUB_MCOPY if CANCUN => Some((read(0), read(2))), instructions::EXTCODECOPY => Some((read(1), read(3))), instructions::CALL | instructions::CALLCODE => { Some((read(5), read(6))) @@ -670,6 +561,116 @@ impl Interpreter { } fn exec_instruction( + &mut self, context: &mut dyn vm::Context, + ) -> Result, InterpreterResult> { + let opcode = self.reader.code[self.reader.position]; + let instruction = + Instruction::from_u8_versioned(opcode, context.spec()); + self.reader.position += 1; + + // TODO: make compile-time removable if too much of a + // performance hit. + // self.do_trace = self.do_trace + // && context.trace_next_instruction( + // self.reader.position - 1, + // opcode, + // self.gasometer + // .as_mut() + // .expect(GASOMETER_PROOF) + // .current_gas + // .as_u256(), + // ); + + let instruction = match instruction { + Some(i) => i, + None => { + return Err(InterpreterResult::Done(Err( + vm::Error::BadInstruction { + instruction: opcode, + }, + ))); + } + }; + + let info = instruction.info::(); + self.last_stack_ret_len = info.ret; + if let Err(e) = self.verify_instruction(context, instruction, info) { + return Err(InterpreterResult::Done(Err(e))); + } + + // Calculate gas cost + let requirements = match self + .gasometer + .as_mut() + .expect(GASOMETER_PROOF) + .requirements( + context, + instruction, + info, + &self.stack, + self.mem.size(), + ) { + Ok(t) => t, + Err(e) => return Err(InterpreterResult::Done(Err(e))), + }; + // if self.do_trace { + // context.trace_prepare_execute( + // self.reader.position - 1, + // opcode, + // requirements.gas_cost.as_u256(), + // Self::mem_written(instruction, &self.stack), + // Self::store_written(instruction, &self.stack), + // ); + // } + + if let Err(e) = self + .gasometer + .as_mut() + .expect(GASOMETER_PROOF) + .verify_gas(&requirements.gas_cost) + { + return Err(InterpreterResult::Done(Err(e))); + } + self.mem.expand(requirements.memory_required_size); + self.gasometer + .as_mut() + .expect(GASOMETER_PROOF) + .current_mem_gas = requirements.memory_total_gas; + self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas = + self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas + - requirements.gas_cost; + + evm_debug!({ + self.informant.before_instruction( + self.reader.position, + instruction, + info, + &self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas, + &self.stack, + ) + }); + + // Execute instruction + let current_gas = + self.gasometer.as_mut().expect(GASOMETER_PROOF).current_gas; + let result = match self.exec_instruction_inner( + current_gas, + context, + instruction, + requirements.provide_gas, + ) { + Err(x) => { + return Err(InterpreterResult::Done(Err(x))); + } + Ok(x) => x, + }; + + evm_debug!({ self.informant.after_instruction(instruction) }); + + Ok(result) + } + + fn exec_instruction_inner( &mut self, gas: Cost, context: &mut dyn vm::Context, instruction: Instruction, provided: Option, ) -> vm::Result> { @@ -689,31 +690,58 @@ impl Interpreter { instructions::JUMPDEST => { // ignore } - instructions::BEGINSUB => { - // BEGINSUB should not be executed. If so, returns - // InvalidSubEntry (EIP-2315). - return Err(vm::Error::InvalidSubEntry); - } - instructions::JUMPSUB => { - if self.return_stack.len() >= MAX_SUB_STACK_SIZE { - return Err(vm::Error::OutOfSubStack { - wanted: 1, - limit: MAX_SUB_STACK_SIZE, - }); + instructions::BEGINSUB_TLOAD => { + if !CANCUN { + // BEGINSUB + // BEGINSUB should not be executed. If so, returns + // InvalidSubEntry (EIP-2315). + return Err(vm::Error::InvalidSubEntry); + } else { + // TLOAD + let mut key = vec![0; 32]; + self.stack.pop_back().to_big_endian(key.as_mut()); + let word = context.storage_at(&key)?; + self.stack.push(word); + } + } + instructions::JUMPSUB_MCOPY => { + if !CANCUN { + // JUMPSUB + if self.return_stack.len() >= MAX_SUB_STACK_SIZE { + return Err(vm::Error::OutOfSubStack { + wanted: 1, + limit: MAX_SUB_STACK_SIZE, + }); + } + let sub_destination = self.stack.pop_back(); + return Ok(InstructionResult::JumpToSubroutine( + sub_destination, + )); + } else { + // MCOPY + Self::copy_memory_to_memory(&mut self.mem, &mut self.stack); } - let sub_destination = self.stack.pop_back(); - return Ok(InstructionResult::JumpToSubroutine( - sub_destination, - )); } - instructions::RETURNSUB => { - if let Some(pos) = self.return_stack.pop() { - return Ok(InstructionResult::ReturnFromSubroutine(pos)); + instructions::RETURNSUB_TSTORE => { + if !CANCUN { + // RETURNSUB + if let Some(pos) = self.return_stack.pop() { + return Ok(InstructionResult::ReturnFromSubroutine( + pos, + )); + } else { + return Err(vm::Error::SubStackUnderflow { + wanted: 1, + on_stack: 0, + }); + } } else { - return Err(vm::Error::SubStackUnderflow { - wanted: 1, - on_stack: 0, - }); + // TSTORE + let mut key = vec![0; 32]; + self.stack.pop_back().to_big_endian(key.as_mut()); + let val = self.stack.pop_back(); + + context.transient_set_storage(key, val)?; } } instructions::CREATE | instructions::CREATE2 => { @@ -1182,9 +1210,13 @@ impl Interpreter { instructions::GASPRICE => { self.stack.push(self.params.gas_price.clone()); } + instructions::BASEFEE => { + self.stack + .push(context.env().base_gas_price[context.space()]); + } instructions::BLOCKHASH => { let block_number = self.stack.pop_back(); - let block_hash = context.blockhash(&block_number); + let block_hash = context.blockhash(&block_number)?; self.stack.push(block_hash.into_uint()); } instructions::COINBASE => { @@ -1547,6 +1579,47 @@ impl Interpreter { let dest_offset = stack.pop_back(); let source_offset = stack.pop_back(); let size = stack.pop_back(); + + Self::copy_data_to_memory_inner( + mem, + source, + dest_offset, + source_offset, + size, + ); + } + + fn copy_memory_to_memory(mem: &mut Vec, stack: &mut dyn Stack) { + let dest_offset = stack.pop_back(); + let source_offset = stack.pop_back(); + let size = stack.pop_back(); + + let mem_size = U256::from(mem.len()); + + if source_offset >= mem_size { + return; + } + let source_offset_usize = source_offset.as_usize(); + let source = if size >= mem_size || source_offset + size >= mem_size { + mem[source_offset_usize..].to_vec() + } else { + let size = size.as_usize(); + mem[source_offset_usize..source_offset_usize + size].to_vec() + }; + + Self::copy_data_to_memory_inner( + mem, + &source, + dest_offset, + U256::zero(), + size, + ); + } + + fn copy_data_to_memory_inner( + mem: &mut Vec, source: &[u8], dest_offset: U256, + source_offset: U256, size: U256, + ) { let source_size = U256::from(source.len()); let output_end = match source_offset > source_size @@ -1598,6 +1671,37 @@ impl Interpreter { } } +impl InterpreterInfo + for Interpreter +{ + fn gas_remainning(&self) -> U256 { + self.gasometer + .as_ref() + .map(|gm| gm.current_gas.as_u256()) + .unwrap_or_default() + } + + fn opcode(&self, pc: u64) -> Option { + let pc = pc as usize; + if pc >= self.reader.len() { + return None; + } + Some(self.reader.code[pc]) + } + + fn current_opcode(&self) -> u8 { self.reader.code[self.reader.position] } + + fn program_counter(&self) -> u64 { self.reader.position as u64 } + + fn mem(&self) -> &Vec { &self.mem } + + fn return_stack(&self) -> &Vec { &self.return_stack } + + fn stack(&self) -> &Vec { self.stack.content() } + + fn contract_address(&self) -> Address { self.params.address } +} + fn get_and_reset_sign(value: U256) -> (U256, bool) { let U256(arr) = value; let sign = arr[3].leading_zeros() == 0; diff --git a/crates/cfxcore/vm-interpreter/src/interpreter/shared_cache.rs b/crates/cfxcore/vm-interpreter/src/interpreter/shared_cache.rs index 4812529fa0..ebdb761b1c 100644 --- a/crates/cfxcore/vm-interpreter/src/interpreter/shared_cache.rs +++ b/crates/cfxcore/vm-interpreter/src/interpreter/shared_cache.rs @@ -53,11 +53,11 @@ impl MallocSizeOf for CacheItem { } /// Global cache for EVM interpreter -pub struct SharedCache { +pub struct SharedCache { jump_destinations: Mutex>, } -impl SharedCache { +impl SharedCache { /// Create a jump destinations cache with a maximum size in bytes /// to cache. pub fn new(max_size: usize) -> Self { @@ -101,7 +101,7 @@ impl SharedCache { instructions::JUMPDEST => { jump_dests.insert(position); } - instructions::BEGINSUB => { + instructions::BEGINSUB_TLOAD if !CANCUN => { sub_entrypoints.insert(position); } _ => { @@ -122,7 +122,7 @@ impl SharedCache { } } -impl Default for SharedCache { +impl Default for SharedCache { fn default() -> Self { SharedCache::new(DEFAULT_CACHE_SIZE) } } @@ -141,7 +141,8 @@ fn test_find_jump_destinations() { let code: Vec = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff5b01600055".from_hex().unwrap(); // when - let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + let cache_item = + SharedCache::::find_jump_and_sub_destinations(&code); // then assert!(cache_item @@ -166,7 +167,8 @@ fn test_find_jump_destinations_not_in_data_segments() { let code: Vec = "600656605B565B6004".from_hex().unwrap(); // when - let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + let cache_item = + SharedCache::::find_jump_and_sub_destinations(&code); // then assert!(cache_item.jump_destination.0.iter().eq(vec![6].into_iter())); @@ -182,7 +184,8 @@ fn test_find_sub_entrypoints() { "6800000000000000000c5e005c60115e5d5c5d".from_hex().unwrap(); // when - let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + let cache_item = + SharedCache::::find_jump_and_sub_destinations(&code); // then assert!(cache_item.jump_destination.0.is_empty()); @@ -206,7 +209,8 @@ fn test_find_jump_and_sub_allowing_unknown_opcodes() { let code: Vec = "5BCC5C".from_hex().unwrap(); // when - let cache_item = SharedCache::find_jump_and_sub_destinations(&code); + let cache_item = + SharedCache::::find_jump_and_sub_destinations(&code); // then assert!(cache_item.jump_destination.0.iter().eq(vec![0].into_iter())); diff --git a/crates/cfxcore/vm-interpreter/src/interpreter/stack.rs b/crates/cfxcore/vm-interpreter/src/interpreter/stack.rs index 23a4e87e2f..b554319cc8 100644 --- a/crates/cfxcore/vm-interpreter/src/interpreter/stack.rs +++ b/crates/cfxcore/vm-interpreter/src/interpreter/stack.rs @@ -39,6 +39,7 @@ pub trait Stack { /// Get number of elements on Stack fn size(&self) -> usize; /// Returns all data on stack. + #[allow(dead_code)] fn peek_top(&self, no_of_elems: usize) -> &[T]; } @@ -54,6 +55,8 @@ impl VecStack { logs: [zero; instructions::MAX_NO_OF_TOPICS], } } + + pub fn content(&self) -> &Vec { &self.stack } } impl Stack for VecStack { diff --git a/crates/cfxcore/vm-interpreter/src/lib.rs b/crates/cfxcore/vm-interpreter/src/lib.rs index ec401ba6df..3937ef9f42 100644 --- a/crates/cfxcore/vm-interpreter/src/lib.rs +++ b/crates/cfxcore/vm-interpreter/src/lib.rs @@ -8,7 +8,7 @@ extern crate log; mod evm; #[macro_use] pub mod factory; -mod instructions; +pub mod instructions; mod interpreter; mod vmtype; #[macro_use] @@ -20,6 +20,6 @@ mod tests; pub use self::{ evm::{CostType, FinalizationResult, Finalize}, factory::Factory, - instructions::GasPriceTier, + instructions::{GasPriceTier, INSTRUCTIONS, INSTRUCTIONS_CANCUN}, vmtype::VMType, }; diff --git a/crates/cfxcore/vm-types/Cargo.toml b/crates/cfxcore/vm-types/Cargo.toml index fc82424730..942c1e22fc 100644 --- a/crates/cfxcore/vm-types/Cargo.toml +++ b/crates/cfxcore/vm-types/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-vm-types" version = "2.0.2" -edition = "2018" +edition = "2021" [dependencies] cfx-bytes = { path = "../../cfx_bytes" } @@ -17,7 +17,6 @@ rlp_derive = { git = "https://github.com/Conflux-Chain/conflux-parity-deps.git", serde = { version = "1.0", features = ["rc"] } serde_derive = "1.0" solidity-abi = {path= "../../util/solidity-abi" } -bls-signatures = {git = "https://github.com/Conflux-Chain/bls-signatures.git", rev = "fb52187df92d27c365642cb7e7b2aaf60437cf9c", default-features = false, features = ["multicore"]} [features] testonly_code = [] \ No newline at end of file diff --git a/crates/cfxcore/vm-types/src/call_create_type.rs b/crates/cfxcore/vm-types/src/call_create_type.rs index 037feeb9c9..c05c0e8b04 100644 --- a/crates/cfxcore/vm-types/src/call_create_type.rs +++ b/crates/cfxcore/vm-types/src/call_create_type.rs @@ -73,7 +73,7 @@ impl Decodable for CallType { } /// The type of the create-like instruction. -#[derive(Clone, Eq, PartialEq, Debug, Serialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize)] #[serde(rename_all = "lowercase")] pub enum CreateType { /// Not a create diff --git a/crates/cfxcore/vm-types/src/context.rs b/crates/cfxcore/vm-types/src/context.rs index 2d5aa8b6d4..65046dd2ca 100644 --- a/crates/cfxcore/vm-types/src/context.rs +++ b/crates/cfxcore/vm-types/src/context.rs @@ -26,7 +26,7 @@ use super::{ error::{Result, TrapKind}, return_data::ReturnData, spec::Spec, - Error, + Error, InterpreterInfo, }; use cfx_bytes::Bytes; use cfx_db_errors::statedb::Result as DbResult; @@ -79,6 +79,14 @@ pub enum CreateContractAddress { FromSenderSaltAndCodeHash(H256), } +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum BlockHashSource { + /// Before CIP-133, block hash is read from `Env`, same as the Ethereum + Env, + /// After CIP-133, block hash is read from `State` + State, +} + /// Calculate new contract address. pub fn contract_address( address_scheme: CreateContractAddress, _block_number: u64, @@ -155,6 +163,14 @@ pub trait Context { /// Stores a value for given key. fn set_storage(&mut self, key: Vec, value: U256) -> Result<()>; + /// Returns a value for given key. + fn transient_storage_at(&self, key: &Vec) -> Result; + + /// Stores a value for given key. + fn transient_set_storage( + &mut self, key: Vec, value: U256, + ) -> Result<()>; + /// Determine whether an account exists. fn exists(&self, address: &Address) -> Result; @@ -169,7 +185,7 @@ pub trait Context { fn balance(&self, address: &Address) -> Result; /// Returns the hash of one of the 256 most recent complete blocks. - fn blockhash(&mut self, number: &U256) -> H256; + fn blockhash(&mut self, number: &U256) -> Result; /// Creates new contract. /// @@ -253,13 +269,21 @@ pub trait Context { // ) { // } - fn opcode_trace_enabled(&self) -> bool { false } + fn trace_step(&mut self, interpreter: &dyn InterpreterInfo) { + let _ = interpreter; + } - // TODO[geth-tracer]: customize your tracer hook. + fn trace_step_end(&mut self, interpreter: &dyn InterpreterInfo) { + let _ = interpreter; + } + + fn opcode_trace_enabled(&self) -> bool { false } /// Check if running in static context. fn is_static(&self) -> bool; /// Check if running in static context or reentrancy context fn is_static_or_reentrancy(&self) -> bool; + + fn blockhash_source(&self) -> BlockHashSource; } diff --git a/crates/cfxcore/vm-types/src/env.rs b/crates/cfxcore/vm-types/src/env.rs index ab8ec6f4ca..8b3a29e084 100644 --- a/crates/cfxcore/vm-types/src/env.rs +++ b/crates/cfxcore/vm-types/src/env.rs @@ -26,7 +26,7 @@ use std::collections::BTreeMap; -use cfx_types::{Address, Space, H256, U256}; +use cfx_types::{Address, Space, SpaceMap, H256, U256}; use primitives::BlockNumber; /// Information concerning the execution environment for a @@ -58,6 +58,11 @@ pub struct Env { /// The transaction_epoch_bound used to verify if a transaction has /// expired. pub transaction_epoch_bound: u64, + /// Base gas price in CIP-1559, equals to 0 if CIP-1559 has not been + /// activated + pub base_gas_price: SpaceMap, + /// Base gas price to miner according to in CIP-137 + pub burnt_gas_price: SpaceMap, } #[cfg(test)] diff --git a/crates/cfxcore/vm-types/src/error.rs b/crates/cfxcore/vm-types/src/error.rs index d7e7f37989..ebf0fd49fe 100644 --- a/crates/cfxcore/vm-types/src/error.rs +++ b/crates/cfxcore/vm-types/src/error.rs @@ -21,13 +21,12 @@ //! VM errors module use super::{action_params::ActionParams, ResumeCall, ResumeCreate}; -use bls_signatures::Error as CryptoError; use cfx_db_errors::statedb::{Error as DbError, Result as DbResult}; use cfx_types::{Address, U256}; use solidity_abi::ABIDecodeError; use std::fmt; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TrapKind { Call(ActionParams), Create(ActionParams), @@ -136,14 +135,6 @@ impl From for Error { fn from(err: DbError) -> Self { Error::StateDbError(PartialEqWrapper(err)) } } -impl From for Error { - fn from(err: CryptoError) -> Self { - Error::InternalContract( - format!("Crypto decoding error {:?}", err).into(), - ) - } -} - impl From for Error { fn from(err: ABIDecodeError) -> Self { Error::InternalContract(format!("ABI decode error: {}", err.0)) diff --git a/crates/cfxcore/vm-types/src/instruction_result.rs b/crates/cfxcore/vm-types/src/instruction_result.rs new file mode 100644 index 0000000000..b20b7b0505 --- /dev/null +++ b/crates/cfxcore/vm-types/src/instruction_result.rs @@ -0,0 +1,23 @@ +use super::TrapKind; +use cfx_types::U256; + +#[derive(Clone)] +pub enum InstructionResult { + Ok, + UnusedGas(Gas), + JumpToPosition(U256), + JumpToSubroutine(U256), + ReturnFromSubroutine(usize), + StopExecutionNeedsReturn { + /// Gas left. + gas: Gas, + /// Return data offset. + init_off: U256, + /// Return data size. + init_size: U256, + /// Apply or revert state changes. + apply: bool, + }, + StopExecution, + Trap(TrapKind), +} diff --git a/crates/cfxcore/vm-types/src/interpreter_info.rs b/crates/cfxcore/vm-types/src/interpreter_info.rs new file mode 100644 index 0000000000..b1c81a295f --- /dev/null +++ b/crates/cfxcore/vm-types/src/interpreter_info.rs @@ -0,0 +1,19 @@ +use cfx_types::{Address, U256}; + +pub trait InterpreterInfo { + fn gas_remainning(&self) -> U256; + + fn program_counter(&self) -> u64; + + fn current_opcode(&self) -> u8; + + fn opcode(&self, pc: u64) -> Option; + + fn mem(&self) -> &Vec; + + fn stack(&self) -> &Vec; + + fn return_stack(&self) -> &Vec; + + fn contract_address(&self) -> Address; +} diff --git a/crates/cfxcore/vm-types/src/lib.rs b/crates/cfxcore/vm-types/src/lib.rs index 8a692c0973..17c96a9f49 100644 --- a/crates/cfxcore/vm-types/src/lib.rs +++ b/crates/cfxcore/vm-types/src/lib.rs @@ -7,6 +7,8 @@ mod call_create_type; mod context; mod env; mod error; +mod instruction_result; +mod interpreter_info; mod return_data; mod spec; @@ -17,14 +19,16 @@ pub use self::{ action_params::{ActionParams, ActionValue, ParamsType}, call_create_type::{CallType, CreateType}, context::{ - contract_address, Context, ContractCreateResult, CreateContractAddress, - MessageCallResult, + contract_address, BlockHashSource, Context, ContractCreateResult, + CreateContractAddress, MessageCallResult, }, env::Env, error::{ separate_out_db_error, Error, ExecTrapError, ExecTrapResult, Result, TrapError, TrapKind, TrapResult, }, + instruction_result::InstructionResult, + interpreter_info::InterpreterInfo, return_data::{GasLeft, ReturnData}, spec::{CleanDustMode, Spec, WasmCosts}, }; diff --git a/crates/cfxcore/vm-types/src/spec.rs b/crates/cfxcore/vm-types/src/spec.rs index e995c42bb4..7f268d6374 100644 --- a/crates/cfxcore/vm-types/src/spec.rs +++ b/crates/cfxcore/vm-types/src/spec.rs @@ -20,6 +20,7 @@ //! Cost spec and other parameterisations for the EVM. use cfx_types::{address_util::AddressUtil, Address}; +use primitives::{block::BlockHeight, BlockNumber}; /// Definition of the cost spec and other parameterisations for the VM. #[derive(Debug, Clone)] @@ -48,6 +49,10 @@ pub struct Spec { pub sstore_reset_gas: usize, /// Gas refund for `SSTORE` clearing (when `storage!=0`, `new==0`) pub sstore_refund_gas: usize, + /// Gas price for `TLOAD` + pub tload_gas: usize, + /// Gas price for `TSTORE` + pub tstore_gas: usize, /// Gas price for `JUMPDEST` opcode pub jumpdest_gas: usize, /// Gas price for `LOG*` @@ -100,6 +105,8 @@ pub struct Spec { pub retire_gas: usize, /// Price for deploying Eip-1820 contract. pub eip1820_gas: usize, + pub access_list_storage_key_gas: usize, + pub access_list_address_gas: usize, /// Amount of additional gas to pay when SUICIDE credits a non-existant /// account pub suicide_to_new_account_cost: usize, @@ -145,6 +152,7 @@ pub struct Spec { /// CIP-94: On-chain Parameter DAO Vote pub cip94: bool, pub cip94_activation_block_number: u64, + pub params_dao_vote_period: u64, /// CIP-97: Remove staking list pub cip97: bool, /// CIP-98: Fix espace bug @@ -158,7 +166,25 @@ pub struct Spec { pub cip118: bool, /// CIP-119: PUSH0 instruction pub cip119: bool, - pub params_dao_vote_period: u64, + /// CIP-131: Retain Whitelist on Contract Deletion + pub cip131: bool, + /// CIP-132: Fix Static Context Check for Internal Contracts + pub cip132: bool, + /// CIP-133: Enhanced Block Hash Query + pub cip133_b: BlockNumber, + pub cip133_e: BlockHeight, + pub cip133_core: bool, + /// CIP-137: Base Fee Sharing in CIP-1559 + pub cip137: bool, + pub cip1559: bool, + /// CIP-141: Disable Subroutine Opcodes + /// CIP-142: Transient Storage Opcodes + /// CIP-143: MCOPY (0x5e) Opcode for Efficient Memory Copy + pub cancun_opcodes: bool, + /// CIP-144: Point Evaluation Precompile from EIP-4844 + pub cip144: bool, + /// CIP-145: Fix Receipts upon `NotEnoughBalance` Error + pub cip145: bool, } /// Wasm cost table @@ -242,10 +268,13 @@ impl Spec { exp_byte_gas: 50, sha3_gas: 30, sha3_word_gas: 6, + // Become 800 after CIP-142 sload_gas: 200, sstore_set_gas: 20000, sstore_reset_gas: 5000, sstore_refund_gas: 15000, + tload_gas: 100, + tstore_gas: 100, jumpdest_gas: 1, log_gas: 375, log_data_gas: 8, @@ -272,6 +301,8 @@ impl Spec { suicide_gas: 5000, retire_gas: 5_000_000, eip1820_gas: 1_500_000, + access_list_storage_key_gas: 1900, + access_list_address_gas: 2400, suicide_to_new_account_cost: 25000, sub_gas_cap_divisor: Some(64), no_empty: true, @@ -299,6 +330,16 @@ impl Spec { cip107: false, cip118: false, cip119: false, + cip131: false, + cip132: false, + cip133_b: u64::MAX, + cip133_e: u64::MAX, + cip133_core: false, + cip137: false, + cip145: false, + cip1559: false, + cancun_opcodes: false, + cip144: false, } } diff --git a/crates/cfxcore/vm-types/src/tests.rs b/crates/cfxcore/vm-types/src/tests.rs index a326dfe03d..bb18cf835b 100644 --- a/crates/cfxcore/vm-types/src/tests.rs +++ b/crates/cfxcore/vm-types/src/tests.rs @@ -18,6 +18,8 @@ // Conflux is free software and distributed under GNU General Public License. // See http://www.gnu.org/licenses/ +use crate::BlockHashSource; + use super::{ error::TrapKind, CallType, Context, ContractCreateResult, CreateContractAddress, Env, Error, GasLeft, MessageCallResult, Result, @@ -121,6 +123,16 @@ impl Context for MockContext { Ok(()) } + fn transient_storage_at(&self, _key: &Vec) -> Result { + Ok(U256::zero()) + } + + fn transient_set_storage( + &mut self, _key: Vec, _value: U256, + ) -> Result<()> { + Ok(()) + } + fn exists(&self, address: &Address) -> Result { Ok(self.balances.contains_key(address)) } @@ -135,11 +147,12 @@ impl Context for MockContext { Ok(self.balances[address]) } - fn blockhash(&mut self, number: &U256) -> H256 { - self.blockhashes + fn blockhash(&mut self, number: &U256) -> Result { + Ok(self + .blockhashes .get(number) .unwrap_or(&H256::zero()) - .clone() + .clone()) } fn create( @@ -243,4 +256,6 @@ impl Context for MockContext { // } fn space(&self) -> Space { Space::Native } + + fn blockhash_source(&self) -> BlockHashSource { BlockHashSource::Env } } diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index a8114cc756..7f2abfb102 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "client" -version = "2.3.5" -edition = "2018" +version = "2.4.0" +edition = "2021" [dependencies] bigdecimal = "0.1.0" @@ -100,6 +100,9 @@ static_assertions = "1.1.0" parity-version = {path = "../util/version" } solidity-abi = {path= "../util/solidity-abi" } bls-signatures = {git = "https://github.com/Conflux-Chain/bls-signatures.git", rev = "fb52187df92d27c365642cb7e7b2aaf60437cf9c", default-features = false, features = ["multicore"]} +alloy-rpc-types-trace = { workspace = true } +geth-tracer = { path = "../cfxcore/geth-tracer" } +serde-utils = { path = "../serde_utils" } [dev-dependencies] criterion = "0.3" diff --git a/crates/client/benches/benchmark.rs b/crates/client/benches/benchmark.rs index d03528efab..7b3cddc096 100644 --- a/crates/client/benches/benchmark.rs +++ b/crates/client/benches/benchmark.rs @@ -17,7 +17,9 @@ use cfxkey::{Generator, KeyPair, Random}; use client::{archive::ArchiveClient, configuration::Configuration}; use criterion::{criterion_group, criterion_main, Criterion}; use parking_lot::{Condvar, Mutex}; -use primitives::{Action, NativeTransaction, Transaction}; +use primitives::{ + transaction::native_transaction::NativeTransaction, Action, Transaction, +}; use std::{sync::Arc, time::Duration}; fn txexe_benchmark(c: &mut Criterion) { @@ -60,6 +62,8 @@ fn txexe_benchmark(c: &mut Criterion) { pos_view: None, finalized_epoch: None, transaction_epoch_bound: TRANSACTION_DEFAULT_EPOCH_BOUND, + base_gas_price: Default::default(), + burnt_gas_price: Default::default(), }; let mut group = c.benchmark_group("Execute 1 transaction"); group @@ -85,7 +89,7 @@ fn txexe_benchmark(c: &mut Criterion) { )) .expect("Failed to initialize state"); - let spec = machine.spec(env.number); + let spec = machine.spec(env.number, env.epoch_height); b.iter(|| { state.clear(); diff --git a/crates/client/src/configuration.rs b/crates/client/src/configuration.rs index 8e20c285d4..8382249b22 100644 --- a/crates/client/src/configuration.rs +++ b/crates/client/src/configuration.rs @@ -20,7 +20,7 @@ use cfx_storage::{ defaults::DEFAULT_DEBUG_SNAPSHOT_CHECKER_THREADS, storage_dir, ConsensusParam, ProvideExtraSnapshotSyncConfig, StorageConfiguration, }; -use cfx_types::{Address, AllChainID, Space, H256, U256}; +use cfx_types::{Address, AllChainID, Space, SpaceMap, H256, U256}; use cfxcore::{ block_data_manager::{DataManagerConfiguration, DbType}, block_parameters::*, @@ -161,10 +161,16 @@ build_config! { (cip112_transition_height, (Option), Some(79050000)) (cip118_transition_number, (Option), Some(188900000)) (cip119_transition_number, (Option), Some(188900000)) + (next_hardfork_transition_number, (Option), Some(247480000)) + (next_hardfork_transition_height, (Option), Some(101900000)) + (cip1559_transition_height, (Option), None) + (cancun_opcodes_transition_number, (Option), None) (referee_bound, (usize), REFEREE_DEFAULT_BOUND) (params_dao_vote_period, (u64), DAO_PARAMETER_VOTE_PERIOD) (timer_chain_beta, (u64), TIMER_CHAIN_DEFAULT_BETA) (timer_chain_block_difficulty_ratio, (u64), TIMER_CHAIN_BLOCK_DEFAULT_DIFFICULTY_RATIO) + (min_native_base_price, (Option), None) + (min_eth_base_price, (Option), None) // FIXME: this is part of spec. (transaction_epoch_bound, (u64), TRANSACTION_DEFAULT_EPOCH_BOUND) @@ -349,6 +355,10 @@ build_config! { (pos_fix_cip99_in_queue_locked_views, (u64), 18720) (pos_fix_cip99_out_queue_locked_views, (u64), 1440) (nonce_limit_transition_view, (u64), u64::MAX) + (pos_cip136_transition_view, (u64), 1684080) + (pos_cip136_in_queue_locked_views, (u64), 18720 * 2) + (pos_cip136_out_queue_locked_views, (u64), 1440 * 2) + (pos_cip136_round_per_term, (u64), ROUND_PER_TERM * 2) (dev_pos_private_key_encryption_password, (Option), None) (pos_started_as_voter, (bool), true) @@ -413,6 +423,14 @@ build_config! { } } +macro_rules! set_conf { + ($src: expr; $dst: expr => {$($field: tt),* }) => { + { + let number = $src; + $($dst.$field = number;)* + } + }; +} pub struct Configuration { pub raw_conf: RawConfiguration, } @@ -1209,6 +1227,50 @@ impl Configuration { pub fn common_params(&self) -> CommonParams { let mut params = CommonParams::default(); + if self.is_test_or_dev_mode() { + params.early_set_internal_contracts_states = true; + } + + let non_test_default = SpaceMap::new( + INITIAL_1559_CORE_BASE_PRICE, + INITIAL_1559_ETH_BASE_PRICE, + ); + let test_default = SpaceMap::new(1u64, 1); + let config = SpaceMap::new( + self.raw_conf.min_native_base_price, + self.raw_conf.min_eth_base_price, + ); + let base_price = SpaceMap::zip3(non_test_default, test_default, config) + .map_all(|(non_test, test, config)| { + if let Some(x) = config { + x + } else if self.is_test_or_dev_mode() { + test + } else { + non_test + } + }); + params.min_base_price = base_price.map_all(U256::from); + + params.chain_id = self.chain_id_params(); + params.anticone_penalty_ratio = self.raw_conf.anticone_penalty_ratio; + params.evm_transaction_block_ratio = + self.raw_conf.evm_transaction_block_ratio; + params.evm_transaction_gas_ratio = + self.raw_conf.evm_transaction_gas_ratio; + + params.params_dao_vote_period = self.raw_conf.params_dao_vote_period; + + self.set_cips(&mut params); + + params + } + + pub fn node_type(&self) -> NodeType { + self.raw_conf.node_type.unwrap_or(NodeType::Full) + } + + fn set_cips(&self, params: &mut CommonParams) { let default_transition_time = if let Some(num) = self.raw_conf.default_transition_time { num @@ -1217,6 +1279,7 @@ impl Configuration { } else { u64::MAX }; + // This is to set the default transition time for the CIPs that cannot // be enabled in the genesis. let non_genesis_default_transition_time = @@ -1231,48 +1294,96 @@ impl Configuration { } }; - if self.is_test_or_dev_mode() { - params.early_set_internal_contracts_states = true; - } - - params.chain_id = self.chain_id_params(); - params.anticone_penalty_ratio = self.raw_conf.anticone_penalty_ratio; - params.evm_transaction_block_ratio = - self.raw_conf.evm_transaction_block_ratio; - params.evm_transaction_gas_ratio = - self.raw_conf.evm_transaction_gas_ratio; - + // + // Tanzanite hardfork + // params.transition_heights.cip40 = self.raw_conf.tanzanite_transition_height; - params.transition_numbers.cip43a = self + let mut base_block_rewards = BTreeMap::new(); + base_block_rewards.insert(0, INITIAL_BASE_MINING_REWARD_IN_UCFX.into()); + base_block_rewards.insert( + params.transition_heights.cip40, + MINING_REWARD_TANZANITE_IN_UCFX.into(), + ); + params.base_block_rewards = base_block_rewards; + + // + // Hydra hardfork (V2.0) + // + set_conf!( + self.raw_conf.hydra_transition_number.unwrap_or(default_transition_time); + params.transition_numbers => { cip43a, cip64, cip71, cip78a, cip92 } + ); + set_conf!( + self.raw_conf.hydra_transition_height.unwrap_or(default_transition_time); + params.transition_heights => { cip76, cip86 } + ); + params.transition_numbers.cip43b = + self.raw_conf.cip43_init_end_number.unwrap_or( + if self.is_test_or_dev_mode() { + u64::MAX + } else { + params.transition_numbers.cip43a + }, + ); + params.transition_numbers.cip62 = if self.is_test_or_dev_mode() { + 0u64 + } else { + BN128_ENABLE_NUMBER + }; + params.transition_numbers.cip78b = self .raw_conf - .hydra_transition_number - .unwrap_or(default_transition_time); - params.transition_numbers.cip94 = self + .cip78_patch_transition_number + .unwrap_or(params.transition_numbers.cip78a); + params.transition_heights.cip90a = self .raw_conf - .dao_vote_transition_number - .unwrap_or(non_genesis_default_transition_time); - params.transition_numbers.cip97 = self + .cip90_transition_height + .or(self.raw_conf.hydra_transition_height) + .unwrap_or(default_transition_time); + params.transition_numbers.cip90b = self .raw_conf - .dao_vote_transition_number + .cip90_transition_number + .or(self.raw_conf.hydra_transition_number) .unwrap_or(default_transition_time); - params.transition_numbers.cip98 = self + + // + // DAO vote hardfork (V2.1) + // + set_conf!( + self.raw_conf.dao_vote_transition_number.unwrap_or(default_transition_time); + params.transition_numbers => { cip97, cip98 } + ); + params.transition_numbers.cip94n = self .raw_conf .dao_vote_transition_number - .unwrap_or(default_transition_time); + .unwrap_or(non_genesis_default_transition_time); + params.transition_heights.cip94h = self + .raw_conf + .dao_vote_transition_height + .unwrap_or(non_genesis_default_transition_time); params.transition_numbers.cip105 = self .raw_conf .cip105_transition_number .or(self.raw_conf.dao_vote_transition_number) .unwrap_or(default_transition_time); + + // + // Sigma protocol fix hardfork (V2.2) + // params.transition_numbers.cip_sigma_fix = self .raw_conf .sigma_fix_transition_number .unwrap_or(default_transition_time); + + // + // Burn collateral hardfork (V2.3) + // params.transition_numbers.cip107 = self .raw_conf .cip107_transition_number .unwrap_or(default_transition_time); + params.transition_heights.cip112 = + *CIP112_TRANSITION_HEIGHT.get().expect("initialized"); params.transition_numbers.cip118 = self .raw_conf .cip118_transition_number @@ -1281,80 +1392,35 @@ impl Configuration { .raw_conf .cip119_transition_number .unwrap_or(default_transition_time); - if self.is_test_or_dev_mode() { - params.transition_numbers.cip43b = - self.raw_conf.cip43_init_end_number.unwrap_or(u64::MAX); - } else { - params.transition_numbers.cip43b = self - .raw_conf - .cip43_init_end_number - .unwrap_or(params.transition_numbers.cip43a); - } - params.transition_numbers.cip62 = if self.is_test_or_dev_mode() { - 0u64 - } else { - BN128_ENABLE_NUMBER - }; - params.transition_numbers.cip64 = self - .raw_conf - .hydra_transition_number - .unwrap_or(default_transition_time); - params.transition_numbers.cip71 = self - .raw_conf - .hydra_transition_number - .unwrap_or(default_transition_time); - params.transition_numbers.cip78a = self - .raw_conf - .hydra_transition_number - .unwrap_or(default_transition_time); - params.transition_numbers.cip78b = self - .raw_conf - .cip78_patch_transition_number - .unwrap_or(params.transition_numbers.cip78a); - params.transition_numbers.cip90b = self - .raw_conf - .cip90_transition_number - .or(self.raw_conf.hydra_transition_number) - .unwrap_or(default_transition_time); - params.transition_numbers.cip92 = self - .raw_conf - .hydra_transition_number - .unwrap_or(default_transition_time); - params.transition_heights.cip76 = self - .raw_conf - .hydra_transition_height - .unwrap_or(default_transition_time); - params.transition_heights.cip86 = self + // + // 1559 hardfork (V2.4) + // + set_conf!( + self.raw_conf.next_hardfork_transition_number.unwrap_or(default_transition_time); + params.transition_numbers => { cip131, cip132, cip133b, cip137, cip144, cip145 } + ); + set_conf!( + self.raw_conf.next_hardfork_transition_height.unwrap_or(default_transition_time); + params.transition_heights => { cip130, cip133e } + ); + // TODO: disable 1559 test during dev + params.transition_heights.cip1559 = self .raw_conf - .hydra_transition_height - .unwrap_or(default_transition_time); - params.transition_heights.cip90a = self + .cip1559_transition_height + .or(self.raw_conf.next_hardfork_transition_height) + .unwrap_or(non_genesis_default_transition_time); + params.transition_numbers.cancun_opcodes = self .raw_conf - .cip90_transition_height - .or(self.raw_conf.hydra_transition_height) + .cancun_opcodes_transition_number + .or(self.raw_conf.next_hardfork_transition_number) .unwrap_or(default_transition_time); - params.transition_heights.cip94 = self - .raw_conf - .dao_vote_transition_height - .unwrap_or(non_genesis_default_transition_time); - params.transition_heights.cip112 = - *CIP112_TRANSITION_HEIGHT.get().expect("initialized"); - params.params_dao_vote_period = self.raw_conf.params_dao_vote_period; - let mut base_block_rewards = BTreeMap::new(); - base_block_rewards.insert(0, INITIAL_BASE_MINING_REWARD_IN_UCFX.into()); - base_block_rewards.insert( - params.transition_heights.cip40, - MINING_REWARD_TANZANITE_IN_UCFX.into(), - ); - params.base_block_rewards = base_block_rewards; - - params - } - - pub fn node_type(&self) -> NodeType { - self.raw_conf.node_type.unwrap_or(NodeType::Full) + if params.transition_heights.cip1559 + < self.raw_conf.pos_reference_enable_height + { + panic!("1559 can not be activated earlier than pos reference: 1559 (epoch {}), pos (epoch {})", params.transition_heights.cip1559, self.raw_conf.pos_reference_enable_height); + } } pub fn pos_state_config(&self) -> PosStateConfig { @@ -1375,6 +1441,10 @@ impl Configuration { self.raw_conf.pos_fix_cip99_out_queue_locked_views, self.raw_conf.nonce_limit_transition_view, 20_000, // 2 * 10^7 CFX + self.raw_conf.pos_cip136_transition_view, + self.raw_conf.pos_cip136_in_queue_locked_views, + self.raw_conf.pos_cip136_out_queue_locked_views, + self.raw_conf.pos_cip136_round_per_term, ) } } diff --git a/crates/client/src/rpc.rs b/crates/client/src/rpc.rs index 44de09f122..5d3b0bee85 100644 --- a/crates/client/src/rpc.rs +++ b/crates/client/src/rpc.rs @@ -36,7 +36,8 @@ mod traits; pub mod types; pub use cfxcore::rpc_errors::{ - BoxFuture as RpcBoxFuture, Error as RpcError, ErrorKind as RpcErrorKind, + invalid_params, invalid_params_check, BoxFuture as RpcBoxFuture, + Error as RpcError, ErrorKind as RpcErrorKind, ErrorKind::JsonRpcError as JsonRpcErrorKind, Result as RpcResult, }; @@ -77,11 +78,14 @@ use crate::{ rpc::{ error_codes::request_rejected_too_many_request_error, impls::{ - eth::EthHandler, eth_filter::EthFilterClient, - trace::EthTraceHandler, RpcImplConfiguration, + eth::{EthHandler, GethDebugHandler}, + eth_filter::EthFilterClient, + trace::EthTraceHandler, + RpcImplConfiguration, }, interceptor::{RpcInterceptor, RpcProxy}, rpc_apis::{Api, ApiSet}, + traits::eth_space::debug::Debug, }, }; use futures01::lazy; @@ -344,6 +348,11 @@ fn setup_rpc_apis( throttling_section, ); } + Api::EthDebug => { + info!("Add geth debug method"); + let geth_debug = GethDebugHandler::new(rpc.consensus.clone()); + handler.extend_with(geth_debug.to_delegate()); + } Api::Test => { handler.extend_with( TestRpcImpl::new(common.clone(), rpc.clone()).to_delegate(), @@ -493,6 +502,9 @@ fn setup_rpc_apis_light( Api::Eth => { warn!("Light nodes do not support evm ports."); } + Api::EthDebug => { + warn!("Light nodes do not support evm ports."); + } Api::Debug => { handler.extend_with( LightDebugRpcImpl::new(common.clone(), rpc.clone()) diff --git a/crates/client/src/rpc/impls.rs b/crates/client/src/rpc/impls.rs index 1ff6421866..92442ac1b4 100644 --- a/crates/client/src/rpc/impls.rs +++ b/crates/client/src/rpc/impls.rs @@ -21,13 +21,9 @@ pub struct RpcImplConfiguration { } pub mod cfx; -pub mod cfx_filter; -pub mod common; pub mod eth; -pub mod eth_filter; -pub mod eth_pubsub; -pub mod light; -pub mod pool; pub mod pos; -pub mod pubsub; pub mod trace; + +pub use cfx::{cfx_filter, common, light, pool, pubsub}; +pub use eth::{debug, eth_filter, eth_handler::EthHandler, eth_pubsub}; diff --git a/crates/client/src/rpc/impls/cfx_filter.rs b/crates/client/src/rpc/impls/cfx/cfx_filter.rs similarity index 100% rename from crates/client/src/rpc/impls/cfx_filter.rs rename to crates/client/src/rpc/impls/cfx/cfx_filter.rs diff --git a/crates/client/src/rpc/impls/cfx.rs b/crates/client/src/rpc/impls/cfx/cfx_handler.rs similarity index 93% rename from crates/client/src/rpc/impls/cfx.rs rename to crates/client/src/rpc/impls/cfx/cfx_handler.rs index 25e5d6df1a..a28e1d5257 100644 --- a/crates/client/src/rpc/impls/cfx.rs +++ b/crates/client/src/rpc/impls/cfx/cfx_handler.rs @@ -6,9 +6,9 @@ use crate::rpc::{ error_codes::{internal_error_msg, invalid_params_msg}, types::{ call_request::rpc_call_request_network, - errors::check_rpc_address_network, pos::PoSEpochReward, PoSEconomics, - RpcAddress, SponsorInfo, StatOnGasLoad, TokenSupplyInfo, - VoteParamsInfo, WrapTransaction, + errors::check_rpc_address_network, pos::PoSEpochReward, CfxFeeHistory, + PoSEconomics, RpcAddress, SponsorInfo, StatOnGasLoad, TokenSupplyInfo, + VoteParamsInfo, WrapTransaction, U64 as HexU64, }, }; use blockgen::BlockGenerator; @@ -19,8 +19,9 @@ use cfx_executor::{ }; use cfx_statedb::{ global_params::{ - AccumulateInterestRate, DistributablePoSInterest, InterestRate, - LastDistributeBlock, PowBaseReward, TotalPosStaking, + AccumulateInterestRate, BaseFeeProp, DistributablePoSInterest, + InterestRate, LastDistributeBlock, PowBaseReward, TotalBurnt1559, + TotalPosStaking, }, StateDbExt, }; @@ -46,7 +47,7 @@ use network::{ }; use parking_lot::Mutex; use primitives::{ - filter::LogFilter, receipt::EVM_SPACE_SUCCESS, Account, Block, + filter::LogFilter, receipt::EVM_SPACE_SUCCESS, Account, Block, BlockHeader, BlockReceipts, DepositInfo, SignedTransaction, StorageKey, StorageRoot, StorageValue, Transaction, TransactionIndex, TransactionStatus, TransactionWithSignature, VoteStakeInfo, @@ -102,6 +103,10 @@ use cfxcore::{ consensus_parameters::DEFERRED_STATE_EPOCH_COUNT, }; use diem_types::account_address::AccountAddress; +use primitives::transaction::{ + eth_transaction::EthereumTransaction, + native_transaction::TypedNativeTransaction, +}; use serde::Serialize; #[derive(Debug)] @@ -110,7 +115,7 @@ pub(crate) struct BlockExecInfo { pub(crate) block: Arc, pub(crate) epoch_number: u64, pub(crate) maybe_state_root: Option, - pub(crate) pivot_hash: H256, + pub(crate) pivot_header: Arc, } pub struct RpcImpl { @@ -461,8 +466,10 @@ impl RpcImpl { info!("RPC Request: cfx_sendRawTransaction len={:?}", raw.0.len()); debug!("RawTransaction bytes={:?}", raw); - let tx: TransactionWithSignature = - invalid_params_check("raw", Rlp::new(&raw.into_vec()).as_val())?; + let tx: TransactionWithSignature = invalid_params_check( + "raw", + TransactionWithSignature::from_raw(&raw.into_vec()), + )?; if tx.recover_public().is_err() { bail!(invalid_params( @@ -697,6 +704,14 @@ impl RpcImpl { .get_data_manager() .get_executed_state_root(&tx_index.block_hash); + // Acutally, the return value of `block_header_by_hash` + // should not be none. + let maybe_base_price = self + .consensus + .get_data_manager() + .block_header_by_hash(&tx_index.block_hash) + .and_then(|x| x.base_price()); + PackedOrExecuted::Executed(RpcReceipt::new( tx.clone(), receipt, @@ -704,6 +719,7 @@ impl RpcImpl { prior_gas_used, epoch_number, block_number, + maybe_base_price, maybe_state_root, tx_exec_error_msg, *self.sync.network.get_network_type(), @@ -774,12 +790,23 @@ impl RpcImpl { bail!("Inconsistent state"); } + let pivot_header = if let Some(x) = self + .consensus + .get_data_manager() + .block_header_by_hash(&pivot_hash) + { + x + } else { + warn!("Cannot find pivot header when get block execution info: pivot hash {:?}", pivot_hash); + return Ok(None); + }; + Ok(Some(BlockExecInfo { block_receipts, block, epoch_number, maybe_state_root, - pivot_hash, + pivot_header, })) } @@ -824,6 +851,7 @@ impl RpcImpl { prior_gas_used, Some(exec_info.epoch_number), exec_info.block_receipts.block_number, + exec_info.pivot_header.base_price(), exec_info.maybe_state_root.clone(), tx_exec_error_msg, *self.sync.network.get_network_type(), @@ -883,10 +911,10 @@ impl RpcImpl { }; // pivot chain reorg - if pivot_assumption != exec_info.pivot_hash { + if pivot_assumption != exec_info.pivot_header.hash() { bail!(pivot_assumption_failed( pivot_assumption, - exec_info.pivot_hash + exec_info.pivot_header.hash() )); } @@ -959,6 +987,7 @@ impl RpcImpl { "RPC Request: generate_fixed_block({:?}, {:?}, {:?}, {:?}, {:?})", parent_hash, referee, num_txs, difficulty, pos_reference, ); + Ok(self.block_gen.generate_fixed_block( parent_hash, referee, @@ -1067,10 +1096,34 @@ impl RpcImpl { // set fake data for latency tests match signed_tx.transaction.transaction.unsigned { - Transaction::Native(ref mut unsigned) if tx_data_len > 0 => { + Transaction::Native(TypedNativeTransaction::Cip155( + ref mut unsigned, + )) if tx_data_len > 0 => { + unsigned.data = vec![0; tx_data_len]; + } + Transaction::Native(TypedNativeTransaction::Cip1559( + ref mut unsigned, + )) if tx_data_len > 0 => { + unsigned.data = vec![0; tx_data_len]; + } + Transaction::Native(TypedNativeTransaction::Cip2930( + ref mut unsigned, + )) if tx_data_len > 0 => { + unsigned.data = vec![0; tx_data_len]; + } + Transaction::Ethereum(EthereumTransaction::Eip155( + ref mut unsigned, + )) if tx_data_len > 0 => { + unsigned.data = vec![0; tx_data_len]; + } + Transaction::Ethereum(EthereumTransaction::Eip1559( + ref mut unsigned, + )) if tx_data_len > 0 => { unsigned.data = vec![0; tx_data_len]; } - Transaction::Ethereum(ref mut unsigned) if tx_data_len > 0 => { + Transaction::Ethereum(EthereumTransaction::Eip2930( + ref mut unsigned, + )) if tx_data_len > 0 => { unsigned.data = vec![0; tx_data_len]; } _ => {} @@ -1224,6 +1277,12 @@ impl RpcImpl { "Transaction can not be executed".into(), format! {"invalid recipient address {:?}", recipient} )), + ExecutionOutcome::NotExecutedDrop( + TxDropError::NotEnoughGasLimit { expected, got }, + ) => bail!(call_execution_error( + "Transaction can not be executed".into(), + format! {"not enough gas limit with respected to tx size: expected {:?} got {:?}", expected, got} + )), ExecutionOutcome::NotExecutedToReconsiderPacking(e) => { bail!(call_execution_error( "Transaction can not be executed".into(), @@ -1275,6 +1334,12 @@ impl RpcImpl { format! {"{:?}", e} )) } + ExecutionOutcome::NotExecutedDrop( + TxDropError::NotEnoughGasLimit { expected, got }, + ) => bail!(call_execution_error( + "Can not estimate: transaction can not be executed".into(), + format! {"not enough gas limit with respected to tx size: expected {:?} got {:?}", expected, got} + )), ExecutionOutcome::ExecutionErrorBumpNonce( ExecutionError::VmError(VmError::Reverted), executed, @@ -1305,18 +1370,13 @@ impl RpcImpl { }; let storage_collateralized = U64::from(estimation.estimated_storage_limit); - let estimated_gas_limit = estimation.estimated_gas_limit; + let estimated_gas_used = estimation.estimated_gas_limit; let response = EstimateGasAndCollateralResponse { - // We multiply the gas_used for 2 reasons: - // 1. In each EVM call, the gas passed is at most 63/64 of the - // remaining gas, so the gas_limit should be multiplied a factor so - // that the gas passed into the sub-call is sufficient. The 4 / 3 - // factor is sufficient for 18 level of calls. - // 2. In Conflux, we recommend setting the gas_limit to (gas_used * - // 4) / 3, because the extra gas will be refunded up to - // 1/4 of the gas limit. - gas_limit: estimation.estimated_gas_limit, - gas_used: estimated_gas_limit, + gas_limit: estimated_gas_used, /* gas_limit used to be 4/3 of + * gas_used due to inaccuracy, + * currently it's the same as gas + * used as it's more accurate */ + gas_used: estimated_gas_used, storage_collateralized, }; Ok(response) @@ -1391,7 +1451,8 @@ impl RpcImpl { let estimate_request = EstimateRequest { has_sender: request.from.is_some(), has_gas_limit: request.gas.is_some(), - has_gas_price: request.gas_price.is_some(), + has_gas_price: request.gas_price.is_some() + || request.max_priority_fee_per_gas.is_some(), has_nonce: request.nonce.is_some(), has_storage_limit: request.storage_limit.is_some(), }; @@ -1543,13 +1604,25 @@ impl RpcImpl { let storage_point_prop = state_db.get_system_storage(&storage_point_prop())?; + + let base_fee_share_prop = state_db.get_global_param::()?; Ok(VoteParamsInfo { pow_base_reward, interest_rate, storage_point_prop, + base_fee_share_prop, }) } + pub fn get_fee_burnt(&self, epoch: Option) -> RpcResult { + let epoch = epoch.unwrap_or(EpochNumber::LatestState).into(); + let state_db = self + .consensus + .get_state_db_by_epoch_number(epoch, "epoch_num")?; + + Ok(state_db.get_global_param::()?) + } + pub fn set_db_crash( &self, crash_probability: f64, crash_exit_code: i32, ) -> RpcResult<()> { @@ -2091,36 +2164,33 @@ impl RpcImpl { let tx_exec_error_msg = &execution_result .block_receipts .tx_execution_error_messages[id]; + let receipt = RpcReceipt::new( + (**tx).clone(), + receipt.clone(), + tx_index, + prior_gas_used, + Some(epoch_number), + execution_result + .block_receipts + .block_number, + b.block_header.base_price(), + maybe_state_root, + if tx_exec_error_msg.is_empty() { + None + } else { + Some(tx_exec_error_msg.clone()) + }, + network, + false, + false, + )?; res.push( WrapTransaction::NativeTransaction( RpcTransaction::from_signed( tx, Some( PackedOrExecuted::Executed( - RpcReceipt::new( - (**tx).clone(), - receipt.clone(), - tx_index, - prior_gas_used, - Some(epoch_number), - execution_result - .block_receipts - .block_number, - maybe_state_root, - if tx_exec_error_msg - .is_empty() - { - None - } else { - Some( - tx_exec_error_msg - .clone(), - ) - }, - network, - false, - false, - )?, + receipt, ), ), network, @@ -2213,6 +2283,8 @@ impl Cfx for CfxHandler { fn account_pending_info(&self, addr: RpcAddress) -> BoxFuture>; fn account_pending_transactions(&self, address: RpcAddress, maybe_start_nonce: Option, maybe_limit: Option) -> BoxFuture; fn get_pos_reward_by_epoch(&self, epoch: EpochNumber) -> JsonRpcResult>; + fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; + fn max_priority_fee_per_gas(&self) -> BoxFuture; } to self.rpc_impl { @@ -2251,6 +2323,7 @@ impl Cfx for CfxHandler { fn get_supply_info(&self, epoch_num: Option) -> JsonRpcResult; fn get_collateral_info(&self, epoch_num: Option) -> JsonRpcResult; fn get_vote_params(&self, epoch_num: Option) -> JsonRpcResult; + fn get_fee_burnt(&self, epoch_num: Option) -> JsonRpcResult; } } } diff --git a/crates/client/src/rpc/impls/common.rs b/crates/client/src/rpc/impls/cfx/common.rs similarity index 92% rename from crates/client/src/rpc/impls/common.rs rename to crates/client/src/rpc/impls/cfx/common.rs index af2c381492..c4939cfcf9 100644 --- a/crates/client/src/rpc/impls/common.rs +++ b/crates/client/src/rpc/impls/cfx/common.rs @@ -14,10 +14,10 @@ use crate::rpc::{ types::{ errors::check_rpc_address_network, pos::PoSEpochReward, AccountPendingInfo, AccountPendingTransactions, Block as RpcBlock, - BlockHashOrEpochNumber, Bytes, CheckBalanceAgainstTransactionResponse, - EpochNumber, RpcAddress, Status as RpcStatus, - Transaction as RpcTransaction, TxPoolPendingNonceRange, TxPoolStatus, - TxWithPoolInfo, + BlockHashOrEpochNumber, Bytes, CfxFeeHistory, + CheckBalanceAgainstTransactionResponse, EpochNumber, FeeHistory, + RpcAddress, Status as RpcStatus, Transaction as RpcTransaction, + TxPoolPendingNonceRange, TxPoolStatus, TxWithPoolInfo, U64 as HexU64, }, RpcErrorKind, RpcResult, }; @@ -528,6 +528,119 @@ impl RpcImpl { "num", ) } + + // TODO: cache the history to improve performance + pub fn fee_history( + &self, block_count: HexU64, newest_block: EpochNumber, + reward_percentiles: Vec, + ) -> RpcResult { + if newest_block == EpochNumber::LatestMined { + return Err(RpcError::invalid_params( + "newestBlock cannot be 'LatestMined'", + ) + .into()); + } + + info!( + "RPC Request: cfx_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}", + block_count, newest_block, reward_percentiles + ); + + if block_count.as_u64() == 0 { + return Ok(FeeHistory::new().to_cfx_fee_history()); + } + // keep read lock to ensure consistent view + let inner = self.consensus_graph().inner.read(); + + let fetch_block = |height| { + let pivot_hash = inner + .get_pivot_hash_from_epoch_number(height) + .map_err(RpcError::invalid_params)?; + + let maybe_block = self + .data_man + .block_by_hash(&pivot_hash, false /* update_cache */); + if let Some(block) = maybe_block { + // Internal error happens only if the fetch header has + // inconsistent block height + Ok(block) + } else { + Err(RpcError::invalid_params( + "Specified block header does not exist", + )) + } + }; + + let start_height: u64 = self + .consensus_graph() + .get_height_from_epoch_number(newest_block.into()) + .map_err(RpcError::invalid_params)?; + + let mut current_height = start_height; + + let mut fee_history = FeeHistory::new(); + while current_height + >= start_height.saturating_sub(block_count.as_u64() - 1) + { + let block = fetch_block(current_height)?; + + let transactions = block + .transactions + .iter() + .filter(|tx| tx.space() == Space::Native) + .map(|x| &**x); + + // Internal error happens only if the fetch header has inconsistent + // block height + fee_history + .push_front_block( + Space::Native, + &reward_percentiles, + &block.block_header, + transactions, + ) + .map_err(|_| RpcError::internal_error())?; + + if current_height == 0 { + break; + } else { + current_height -= 1; + } + } + + // Fetch the block after the last block in the history + let block = fetch_block(start_height + 1)?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; + fee_history.finish( + oldest_block, + block.block_header.base_price().as_ref(), + Space::Native, + ); + + Ok(fee_history.to_cfx_fee_history()) + } + + pub fn max_priority_fee_per_gas(&self) -> RpcResult { + info!("RPC Request: max_priority_fee_per_gas",); + + let fee_history = self.fee_history( + HexU64::from(300), + EpochNumber::LatestState, + vec![50f64], + )?; + + let total_reward: U256 = fee_history + .reward() + .iter() + .map(|x| x.first().unwrap()) + .fold(U256::zero(), |x, y| x + *y); + + Ok(total_reward / 300) + } } // Test RPC implementation @@ -995,7 +1108,7 @@ impl RpcImpl { })?; let required_storage_collateral = if let Transaction::Native(ref tx) = tx.unsigned { - U256::from(tx.storage_limit) + U256::from(*tx.storage_limit()) * *DRIPS_PER_STORAGE_COLLATERAL_UNIT } else { U256::zero() diff --git a/crates/client/src/rpc/impls/light.rs b/crates/client/src/rpc/impls/cfx/light.rs similarity index 90% rename from crates/client/src/rpc/impls/light.rs rename to crates/client/src/rpc/impls/cfx/light.rs index be7ab4e50f..3c3c210b81 100644 --- a/crates/client/src/rpc/impls/light.rs +++ b/crates/client/src/rpc/impls/cfx/light.rs @@ -7,12 +7,14 @@ use cfx_types::{ }; use cfxcore::{ block_data_manager::BlockDataManager, + consensus::ConsensusConfig, light_protocol::{ self, query_service::TxInfo, Error as LightError, ErrorKind, }, rpc_errors::{account_result_to_rpc_result, invalid_params_check}, verification::EpochReceiptProof, - ConsensusGraph, LightQueryService, PeerInfo, SharedConsensusGraph, + ConsensusGraph, ConsensusGraphTrait, LightQueryService, PeerInfo, + SharedConsensusGraph, }; use cfxcore_accounts::AccountProvider; use delegate::delegate; @@ -40,14 +42,15 @@ use crate::{ pos::{Block as PosBlock, PoSEpochReward}, Account as RpcAccount, AccountPendingInfo, AccountPendingTransactions, BlameInfo, Block as RpcBlock, - BlockHashOrEpochNumber, Bytes, CallRequest, CfxRpcLogFilter, - CheckBalanceAgainstTransactionResponse, ConsensusGraphStates, - EpochNumber, EstimateGasAndCollateralResponse, Log as RpcLog, + BlockHashOrEpochNumber, Bytes, CallRequest, CfxFeeHistory, + CfxRpcLogFilter, CheckBalanceAgainstTransactionResponse, + ConsensusGraphStates, EpochNumber, + EstimateGasAndCollateralResponse, FeeHistory, Log as RpcLog, PoSEconomics, Receipt as RpcReceipt, RewardInfo as RpcRewardInfo, RpcAddress, SendTxRequest, SponsorInfo, StatOnGasLoad, Status as RpcStatus, StorageCollateralInfo, SyncGraphStates, TokenSupplyInfo, Transaction as RpcTransaction, VoteParamsInfo, - WrapTransaction, + WrapTransaction, U64 as HexU64, }, RpcBoxFuture, RpcResult, }, @@ -501,9 +504,10 @@ impl RpcImpl { // decode tx so that we have its hash // this way we also avoid spamming peers with invalid txs - let tx: TransactionWithSignature = rlp::decode(&raw.clone()) - .map_err(|e| format!("Failed to decode tx: {:?}", e)) - .map_err(RpcError::invalid_params)?; + let tx: TransactionWithSignature = + TransactionWithSignature::from_raw(&raw.clone()) + .map_err(|e| format!("Failed to decode tx: {:?}", e)) + .map_err(RpcError::invalid_params)?; debug!("Deserialized tx: {:?}", tx); @@ -673,6 +677,7 @@ impl RpcImpl { // clone `self.light` to avoid lifetime issues due to capturing `self` let light = self.light.clone(); + let data_man = self.data_man.clone(); let fut = async move { // TODO: @@ -703,6 +708,10 @@ impl RpcImpl { return Ok(None); } + let maybe_base_price = data_man + .block_header_by_hash(&tx_index.block_hash) + .and_then(|x| x.base_price()); + let receipt = RpcReceipt::new( tx, receipt, @@ -710,6 +719,7 @@ impl RpcImpl { prior_gas_used, maybe_epoch, maybe_block_number.unwrap(), + maybe_base_price, maybe_state_root, // Can not offer error_message from light node. None, @@ -1080,6 +1090,113 @@ impl RpcImpl { Box::new(fut.boxed().compat()) } + + fn fee_history( + &self, block_count: HexU64, newest_block: EpochNumber, + reward_percentiles: Vec, + ) -> RpcBoxFuture { + info!( + "RPC Request: cfx_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}", + block_count, newest_block, reward_percentiles + ); + + if block_count.as_u64() == 0 { + return Box::new( + async { Ok(FeeHistory::new().to_cfx_fee_history()) } + .boxed() + .compat(), + ); + } + + // clone to avoid lifetime issues due to capturing `self` + let consensus_graph = self.consensus.clone(); + let light = self.light.clone(); + + let fut = async move { + let start_height: u64 = light + .get_height_from_epoch_number(newest_block.into()) + .map_err(|e| e.to_string()) + .map_err(RpcError::invalid_params)?; + + let mut current_height = start_height; + + let mut fee_history = FeeHistory::new(); + + while current_height + >= start_height.saturating_sub(block_count.as_u64() - 1) + { + let block = fetch_block_for_fee_history( + consensus_graph.clone(), + light.clone(), + current_height, + ) + .await?; + + let transactions = block + .transactions + .iter() + .filter(|tx| tx.space() == Space::Native) + .map(|x| &**x); + // Internal error happens only if the fetch header has + // inconsistent block height + fee_history + .push_front_block( + Space::Native, + &reward_percentiles, + &block.block_header, + transactions, + ) + .map_err(|_| RpcError::internal_error())?; + + if current_height == 0 { + break; + } else { + current_height -= 1; + } + } + + let block = fetch_block_for_fee_history( + consensus_graph.clone(), + light.clone(), + start_height + 1, + ) + .await?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; + fee_history.finish( + oldest_block, + block.block_header.base_price().as_ref(), + Space::Native, + ); + Ok(fee_history.to_cfx_fee_history()) + }; + + Box::new(fut.boxed().compat()) + } +} + +async fn fetch_block_for_fee_history( + consensus_graph: Arc< + dyn ConsensusGraphTrait, + >, + light: Arc, height: u64, +) -> cfxcore::rpc_errors::Result { + let hash = consensus_graph + .as_any() + .downcast_ref::() + .expect("downcast should succeed") + .inner + .read() + .get_pivot_hash_from_epoch_number(height) + .map_err(RpcError::invalid_params)?; + + match light.retrieve_block(hash).await? { + None => Err(RpcError::internal_error().into()), + Some(b) => Ok(b), + } } pub struct CfxHandler { @@ -1131,6 +1248,7 @@ impl Cfx for CfxHandler { fn transaction_by_hash(&self, hash: H256) -> BoxFuture>; fn transaction_receipt(&self, tx_hash: H256) -> BoxFuture>; fn vote_list(&self, address: RpcAddress, num: Option) -> BoxFuture>; + fn fee_history(&self, block_count: HexU64, newest_block: EpochNumber, reward_percentiles: Vec) -> BoxFuture; } } @@ -1145,6 +1263,8 @@ impl Cfx for CfxHandler { fn get_collateral_info(&self, epoch_num: Option) -> JsonRpcResult; fn get_vote_params(&self, epoch_num: Option) -> JsonRpcResult; fn get_pos_reward_by_epoch(&self, epoch: EpochNumber) -> JsonRpcResult>; + fn get_fee_burnt(&self, epoch: Option) -> JsonRpcResult; + fn max_priority_fee_per_gas(&self) -> BoxFuture; } } diff --git a/crates/client/src/rpc/impls/cfx/mod.rs b/crates/client/src/rpc/impls/cfx/mod.rs new file mode 100644 index 0000000000..b5eeb7e2ab --- /dev/null +++ b/crates/client/src/rpc/impls/cfx/mod.rs @@ -0,0 +1,8 @@ +pub mod cfx_filter; +pub mod cfx_handler; +pub mod common; +pub mod light; +pub mod pool; +pub mod pubsub; + +pub use cfx_handler::{CfxHandler, LocalRpcImpl, RpcImpl, TestRpcImpl}; diff --git a/crates/client/src/rpc/impls/pool.rs b/crates/client/src/rpc/impls/cfx/pool.rs similarity index 100% rename from crates/client/src/rpc/impls/pool.rs rename to crates/client/src/rpc/impls/cfx/pool.rs diff --git a/crates/client/src/rpc/impls/pubsub.rs b/crates/client/src/rpc/impls/cfx/pubsub.rs similarity index 100% rename from crates/client/src/rpc/impls/pubsub.rs rename to crates/client/src/rpc/impls/cfx/pubsub.rs diff --git a/crates/client/src/rpc/impls/eth/debug.rs b/crates/client/src/rpc/impls/eth/debug.rs new file mode 100644 index 0000000000..6725aa2183 --- /dev/null +++ b/crates/client/src/rpc/impls/eth/debug.rs @@ -0,0 +1,188 @@ +use std::convert::TryInto; + +use crate::rpc::{ + error_codes::invalid_params_msg, + traits::eth_space::debug::Debug, + types::eth::{BlockNumber, CallRequest}, +}; +use alloy_rpc_types_trace::geth::{ + GethDebugBuiltInTracerType, + GethDebugTracerType::{BuiltInTracer, JsTracer}, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, + TraceResult, +}; +use cfx_types::{Space, H256}; +use cfxcore::{ConsensusGraph, SharedConsensusGraph}; +use geth_tracer::to_alloy_h256; +use jsonrpc_core::Result as JsonRpcResult; + +pub struct GethDebugHandler { + consensus: SharedConsensusGraph, +} + +impl GethDebugHandler { + pub fn new(consensus: SharedConsensusGraph) -> Self { + GethDebugHandler { consensus } + } + + fn consensus_graph(&self) -> &ConsensusGraph { + self.consensus + .as_any() + .downcast_ref::() + .expect("downcast should succeed") + } +} + +impl Debug for GethDebugHandler { + fn db_get(&self, _key: String) -> JsonRpcResult> { + Ok(Some("To be implemented!".into())) + } + + fn debug_trace_transaction( + &self, hash: H256, opts: Option, + ) -> JsonRpcResult { + let opts = opts.unwrap_or_default(); + + // early return if tracer is not supported or NoopTracer is requested + if let Some(tracer_type) = &opts.tracer { + match tracer_type { + BuiltInTracer(builtin_tracer) => match builtin_tracer { + GethDebugBuiltInTracerType::FourByteTracer => (), + GethDebugBuiltInTracerType::CallTracer => { + // pre check config + let _ = opts + .tracer_config + .clone() + .into_call_config() + .map_err(|e| { + invalid_params_msg(&e.to_string()) + })?; + () + } + GethDebugBuiltInTracerType::PreStateTracer => { + // pre check config + let _ = opts + .tracer_config + .clone() + .into_pre_state_config() + .map_err(|e| invalid_params_msg(&e.to_string()))?; + () + } + GethDebugBuiltInTracerType::NoopTracer => { + return Ok(GethTrace::NoopTracer(NoopFrame::default())) + } + GethDebugBuiltInTracerType::MuxTracer => { + return Err(invalid_params_msg("not supported")) + } + }, + JsTracer(_) => return Err(invalid_params_msg("not supported")), + } + } + + let tx_index = self + .consensus + .get_data_manager() + .transaction_index_by_hash(&hash, false /* update_cache */) + .ok_or(invalid_params_msg("invalid tx hash"))?; + + let epoch_num = self + .consensus + .get_block_epoch_number(&tx_index.block_hash) + .ok_or(invalid_params_msg("invalid tx hash"))?; + + let epoch_traces = self + .consensus_graph() + .collect_epoch_geth_trace(epoch_num, Some(hash), opts) + .map_err(|e| { + invalid_params_msg(&format!("invalid tx hash: {e}")) + })?; + + // filter by tx hash + let trace = epoch_traces + .into_iter() + .find(|val| val.tx_hash == hash) + .map(|val| val.trace) + .ok_or(invalid_params_msg("trace generation failed")); + + trace + } + + fn debug_trace_block_by_hash( + &self, block_hash: H256, opts: Option, + ) -> JsonRpcResult> { + let opts = opts.unwrap_or_default(); + let epoch_num = self + .consensus_graph() + .get_block_epoch_number_with_pivot_check(&block_hash, false)?; + + let epoch_traces = self + .consensus_graph() + .collect_epoch_geth_trace(epoch_num, None, opts) + .map_err(|e| { + invalid_params_msg(&format!("invalid tx hash: {e}")) + })?; + + let result = epoch_traces + .into_iter() + .filter(|val| val.space == Space::Ethereum) + .map(|val| TraceResult::Success { + result: val.trace, + tx_hash: Some(to_alloy_h256(val.tx_hash)), + }) + .collect(); + Ok(result) + } + + fn debug_trace_block_by_number( + &self, block: BlockNumber, opts: Option, + ) -> JsonRpcResult> { + let opts = opts.unwrap_or_default(); + let num = match block { + BlockNumber::Num(block_number) => block_number, + BlockNumber::Latest + | BlockNumber::Safe + | BlockNumber::Finalized => { + let epoch_num = block.try_into().expect("should success"); + self.consensus_graph() + .get_height_from_epoch_number(epoch_num) + .map_err(|msg| invalid_params_msg(&msg))? + } + BlockNumber::Hash { + hash, + require_canonical, + } => self + .consensus_graph() + .get_block_epoch_number_with_pivot_check( + &hash, + require_canonical, + )?, + _ => return Err(invalid_params_msg("not supported")), + }; + let epoch_traces = self + .consensus_graph() + .collect_epoch_geth_trace(num, None, opts) + .map_err(|e| { + invalid_params_msg(&format!("invalid tx hash: {e}")) + })?; + + let result = epoch_traces + .into_iter() + .filter(|val| val.space == Space::Ethereum) + .map(|val| TraceResult::Success { + result: val.trace, + tx_hash: Some(to_alloy_h256(val.tx_hash)), + }) + .collect(); + Ok(result) + } + + fn debug_trace_call( + &self, request: CallRequest, block_number: Option, + opts: Option, + ) -> JsonRpcResult { + let _ = request; + let _ = block_number; + let _ = opts; + todo!("not implemented yet"); + } +} diff --git a/crates/client/src/rpc/impls/eth_filter.rs b/crates/client/src/rpc/impls/eth/eth_filter.rs similarity index 100% rename from crates/client/src/rpc/impls/eth_filter.rs rename to crates/client/src/rpc/impls/eth/eth_filter.rs diff --git a/crates/client/src/rpc/impls/eth.rs b/crates/client/src/rpc/impls/eth/eth_handler.rs similarity index 82% rename from crates/client/src/rpc/impls/eth.rs rename to crates/client/src/rpc/impls/eth/eth_handler.rs index 91545a4cab..79f94460a3 100644 --- a/crates/client/src/rpc/impls/eth.rs +++ b/crates/client/src/rpc/impls/eth/eth_handler.rs @@ -15,7 +15,7 @@ use crate::rpc::{ CallRequest, EthRpcLogFilter, Log, Receipt, SyncInfo, SyncStatus, Transaction, }, - Bytes, Index, MAX_GAS_CALL_REQUEST, + Bytes, FeeHistory, Index, MAX_GAS_CALL_REQUEST, U64 as HexU64, }, }; use cfx_execute_helper::estimation::{ @@ -41,11 +41,15 @@ use cfxcore::{ use clap::crate_version; use jsonrpc_core::{Error as RpcError, Result as RpcResult}; use primitives::{ - filter::LogFilter, receipt::EVM_SPACE_SUCCESS, Action, - BlockHashOrEpochNumber, Eip155Transaction, EpochNumber, SignedTransaction, - StorageKey, StorageValue, TransactionStatus, TransactionWithSignature, + filter::LogFilter, + receipt::EVM_SPACE_SUCCESS, + transaction::{ + Eip1559Transaction, Eip155Transaction, Eip2930Transaction, + EthereumTransaction::*, EIP1559_TYPE, EIP2930_TYPE, LEGACY_TX_TYPE, + }, + Action, BlockHashOrEpochNumber, EpochNumber, SignedTransaction, StorageKey, + StorageValue, TransactionStatus, TransactionWithSignature, }; -use rlp::Rlp; use rustc_hex::ToHex; use std::{cmp::min, convert::TryInto}; @@ -82,18 +86,74 @@ pub fn sign_call( ) -> RpcResult { let max_gas = U256::from(MAX_GAS_CALL_REQUEST); let gas = min(request.gas.unwrap_or(max_gas), max_gas); - let from = request.from.unwrap_or_else(|| Address::zero()); - - Ok(Eip155Transaction { - nonce: request.nonce.unwrap_or_default(), - action: request.to.map_or(Action::Create, |addr| Action::Call(addr)), - gas, - gas_price: request.gas_price.unwrap_or(1.into()), - value: request.value.unwrap_or_default(), - chain_id: Some(chain_id), - data: request.data.unwrap_or_default().into_vec(), - } - .fake_sign_rpc(from.with_evm_space())) + let nonce = request.nonce.unwrap_or_default(); + let action = request.to.map_or(Action::Create, |addr| Action::Call(addr)); + let value = request.value.unwrap_or_default(); + + let default_type_id = if request.max_fee_per_gas.is_some() + || request.max_priority_fee_per_gas.is_some() + { + EIP1559_TYPE + } else if request.access_list.is_some() { + EIP2930_TYPE + } else { + LEGACY_TX_TYPE + }; + let transaction_type = request + .transaction_type + .unwrap_or(U64::from(default_type_id)); + + let gas_price = request.gas_price.unwrap_or(1.into()); + let max_fee_per_gas = request + .max_fee_per_gas + .or(request.max_priority_fee_per_gas) + .unwrap_or(gas_price); + let max_priority_fee_per_gas = + request.max_priority_fee_per_gas.unwrap_or(U256::zero()); + let access_list = request.access_list.unwrap_or(vec![]); + let data = request.data.unwrap_or_default().into_vec(); + + let transaction = match transaction_type.as_usize() as u8 { + LEGACY_TX_TYPE => Eip155(Eip155Transaction { + nonce, + gas_price, + gas, + action, + value, + chain_id: Some(chain_id), + data, + }), + EIP2930_TYPE => Eip2930(Eip2930Transaction { + chain_id, + nonce, + gas_price, + gas, + action, + value, + data, + access_list, + }), + EIP1559_TYPE => Eip1559(Eip1559Transaction { + chain_id, + nonce, + max_priority_fee_per_gas, + max_fee_per_gas, + gas, + action, + value, + data, + access_list, + }), + x => { + return Err( + invalid_params("Unrecognized transaction type", x).into() + ); + } + }; + + let from = request.from.unwrap_or(Address::zero()); + + Ok(transaction.fake_sign_rpc(from.with_evm_space())) } fn block_tx_by_index( @@ -127,7 +187,8 @@ fn block_tx_by_index( impl EthHandler { fn exec_transaction( - &self, request: CallRequest, block_number_or_hash: Option, + &self, mut request: CallRequest, + block_number_or_hash: Option, ) -> CfxRpcResult<(ExecutionOutcome, EstimateExt)> { let consensus_graph = self.consensus_graph(); @@ -153,6 +214,9 @@ impl EthHandler { epoch => epoch.try_into()?, }; + // if gas_price is zero, it is considered as not set + request.unset_zero_gas_price(); + let estimate_request = EstimateRequest { has_sender: request.from.is_some(), has_gas_limit: request.gas.is_some(), @@ -238,7 +302,7 @@ impl EthHandler { let transaction_hash = tx.hash(); let transaction_index: U256 = idx.into(); let block_hash = b.pivot_header.hash(); - let block_number: U256 = b.pivot_header.height().into(); + let block_height: U256 = b.pivot_header.height().into(); let logs: Vec<_> = receipt .logs @@ -250,7 +314,7 @@ impl EthHandler { topics: log.topics, data: Bytes(log.data), block_hash, - block_number, + block_number: block_height, transaction_hash, transaction_index, log_index: Some((*prior_log_index + idx).into()), @@ -275,6 +339,18 @@ impl EthHandler { Some(b.errors[idx].clone()) }; + let effective_gas_price = + if let Some(base_price) = b.pivot_header.base_price() { + let base_price = base_price[tx.space()]; + if *tx.gas_price() < base_price { + *tx.gas_price() + } else { + tx.effective_gas_price(&base_price) + } + } else { + *tx.gas_price() + }; + Ok(Receipt { transaction_hash, transaction_index, @@ -284,7 +360,7 @@ impl EthHandler { Action::Create => None, Action::Call(addr) => Some(*addr), }, - block_number, + block_number: block_height, cumulative_gas_used: receipt.accumulated_gas_used, gas_used, contract_address, @@ -294,8 +370,13 @@ impl EthHandler { .outcome_status .in_space(Space::Ethereum) .into(), - effective_gas_price: *tx.gas_price(), + effective_gas_price, tx_exec_error_msg, + transaction_type: receipt + .burnt_gas_fee + .is_some() + .then_some(U64::from(tx.type_id())), + burnt_gas_fee: receipt.burnt_gas_fee, }) } @@ -377,6 +458,12 @@ impl Eth for EthHandler { fn gas_price(&self) -> jsonrpc_core::Result { info!("RPC Request: eth_gasPrice"); + let (_, maybe_base_price) = + self.tx_pool.get_best_info_with_parent_base_price(); + if let Some(base_price) = maybe_base_price { + return Ok(base_price[Space::Ethereum]); + } + let consensus_gas_price = self .consensus_graph() .gas_price(Space::Ethereum) @@ -389,8 +476,23 @@ impl Eth for EthHandler { fn max_priority_fee_per_gas(&self) -> jsonrpc_core::Result { info!("RPC Request: eth_maxPriorityFeePerGas"); - // TODO: Change this - Ok(U256::from(20000000000u64)) + let evm_ratio = + self.tx_pool.machine().params().evm_transaction_block_ratio + as usize; + + let fee_history = self.fee_history( + HexU64::from(300), + BlockNumber::Latest, + vec![50f64], + )?; + + let total_reward: U256 = fee_history + .reward() + .iter() + .map(|x| x.first().unwrap()) + .fold(U256::zero(), |x, y| x + *y); + + Ok(total_reward * evm_ratio / 300) } fn accounts(&self) -> jsonrpc_core::Result> { @@ -662,8 +764,10 @@ impl Eth for EthHandler { "RPC Request: eth_sendRawTransaction / eth_submitTransaction raw={:?}", raw, ); - let tx: TransactionWithSignature = - invalid_params_check("raw", Rlp::new(&raw.into_vec()).as_val())?; + let tx: TransactionWithSignature = invalid_params_check( + "raw", + TransactionWithSignature::from_raw(&raw.into_vec()), + )?; if tx.space() != Space::Ethereum { bail!(invalid_params("tx", "Incorrect transaction space")); @@ -709,6 +813,12 @@ impl Eth for EthHandler { "Transaction can not be executed".into(), format! {"invalid recipient address {:?}", recipient} )), + ExecutionOutcome::NotExecutedDrop( + TxDropError::NotEnoughGasLimit { expected, got }, + ) => bail!(call_execution_error( + "Can not estimate: transaction can not be executed".into(), + format! {"not enough gas limit with respected to tx size: expected {:?} got {:?}", expected, got} + )), ExecutionOutcome::NotExecutedToReconsiderPacking(e) => { bail!(call_execution_error( "Transaction can not be executed".into(), @@ -759,6 +869,12 @@ impl Eth for EthHandler { "Can not estimate: transaction can not be executed".into(), format! {"invalid recipient address {:?}", recipient} )), + ExecutionOutcome::NotExecutedDrop( + TxDropError::NotEnoughGasLimit { expected, got }, + ) => bail!(call_execution_error( + "Can not estimate: transaction can not be executed".into(), + format! {"not enough gas limit with respected to tx size: expected {:?} got {:?}", expected, got} + )), ExecutionOutcome::NotExecutedToReconsiderPacking(e) => { bail!(call_execution_error( "Can not estimate: transaction can not be executed".into(), @@ -794,6 +910,87 @@ impl Eth for EthHandler { Ok(estimation.estimated_gas_limit) } + fn fee_history( + &self, block_count: HexU64, newest_block: BlockNumber, + reward_percentiles: Vec, + ) -> jsonrpc_core::Result { + info!( + "RPC Request: eth_feeHistory: block_count={}, newest_block={:?}, reward_percentiles={:?}", + block_count, newest_block, reward_percentiles + ); + + if block_count.as_u64() == 0 { + return Ok(FeeHistory::new()); + } + + // keep read lock to ensure consistent view + let _consensus = self.consensus_graph().inner.read(); + + let fetch_block = |height| { + let maybe_block = self + .consensus_graph() + .get_phantom_block_pivot_by_number( + EpochNumber::Number(height), + None, + false, + ) + .map_err(RpcError::invalid_params)?; + if let Some(block) = maybe_block { + // Internal error happens only if the fetch header has + // inconsistent block height + Ok(block) + } else { + Err(RpcError::invalid_params( + "Specified block header does not exist", + )) + } + }; + + let start_height: u64 = self + .consensus_graph() + .get_height_from_epoch_number(newest_block.try_into()?) + .map_err(RpcError::invalid_params)?; + + let mut current_height = start_height; + + let mut fee_history = FeeHistory::new(); + while current_height + >= start_height.saturating_sub(block_count.as_u64() - 1) + { + let block = fetch_block(current_height)?; + + // Internal error happens only if the fetch header has inconsistent + // block height + fee_history + .push_front_block( + Space::Ethereum, + &reward_percentiles, + &block.pivot_header, + block.transactions.iter().map(|x| &**x), + ) + .map_err(|_| RpcError::internal_error())?; + + if current_height == 0 { + break; + } else { + current_height -= 1; + } + } + + let block = fetch_block(start_height + 1)?; + let oldest_block = if current_height == 0 { + 0 + } else { + current_height + 1 + }; + fee_history.finish( + oldest_block, + block.pivot_header.base_price().as_ref(), + Space::Ethereum, + ); + Ok(fee_history) + } + fn transaction_by_hash( &self, hash: H256, ) -> jsonrpc_core::Result> { diff --git a/crates/client/src/rpc/impls/eth_pubsub.rs b/crates/client/src/rpc/impls/eth/eth_pubsub.rs similarity index 100% rename from crates/client/src/rpc/impls/eth_pubsub.rs rename to crates/client/src/rpc/impls/eth/eth_pubsub.rs diff --git a/crates/client/src/rpc/impls/eth/mod.rs b/crates/client/src/rpc/impls/eth/mod.rs new file mode 100644 index 0000000000..b0ee412335 --- /dev/null +++ b/crates/client/src/rpc/impls/eth/mod.rs @@ -0,0 +1,7 @@ +pub mod debug; +pub mod eth_filter; +pub mod eth_handler; +pub mod eth_pubsub; + +pub use debug::GethDebugHandler; +pub use eth_handler::EthHandler; diff --git a/crates/client/src/rpc/impls/pos.rs b/crates/client/src/rpc/impls/pos.rs index 53a3ce8d43..d9498373a0 100644 --- a/crates/client/src/rpc/impls/pos.rs +++ b/crates/client/src/rpc/impls/pos.rs @@ -179,17 +179,12 @@ impl PosHandler { call_data.extend_from_slice(&account_addr.abi_encode()); let call_request = CallRequest { - from: None, to: Some(RpcAddress::try_from_h160( POS_REGISTER_CONTRACT_ADDRESS, self.network_type, )?), - gas_price: None, - gas: None, - value: None, data: Some(Bytes(call_data)), - nonce: None, - storage_limit: None, + ..Default::default() }; let epoch = match view { diff --git a/crates/client/src/rpc/rpc_apis.rs b/crates/client/src/rpc/rpc_apis.rs index 11502f2aac..c62f0ce61c 100644 --- a/crates/client/src/rpc/rpc_apis.rs +++ b/crates/client/src/rpc/rpc_apis.rs @@ -12,13 +12,14 @@ use std::{ pub enum Api { Cfx, Eth, - Debug, + Debug, // core space parity style debug Pubsub, Test, Trace, TxPool, Pos, EthPubsub, + EthDebug, } impl FromStr for Api { @@ -36,6 +37,7 @@ impl FromStr for Api { "txpool" => Ok(TxPool), "pos" => Ok(Pos), "ethpubsub" => Ok(EthPubsub), + "ethdebug" => Ok(EthDebug), _ => Err("Unknown api type".into()), } } @@ -53,13 +55,14 @@ impl Display for Api { Api::TxPool => write!(f, "txpool"), Api::Pos => write!(f, "pos"), Api::EthPubsub => write!(f, "ethpubsub"), + Api::EthDebug => write!(f, "ethdebug"), } } } #[derive(Debug, Clone, Eq, PartialEq)] pub enum ApiSet { - All, + All, // core space all apis Safe, Evm, // Ethereum api set List(HashSet), diff --git a/crates/client/src/rpc/traits/cfx_space/cfx.rs b/crates/client/src/rpc/traits/cfx_space/cfx.rs index dd2da4566d..5fe7e9071e 100644 --- a/crates/client/src/rpc/traits/cfx_space/cfx.rs +++ b/crates/client/src/rpc/traits/cfx_space/cfx.rs @@ -5,12 +5,12 @@ use crate::rpc::types::{ pos::PoSEpochReward, Account as RpcAccount, AccountPendingInfo, AccountPendingTransactions, Block, BlockHashOrEpochNumber, Bytes, - CallRequest, CfxFilterChanges, CfxRpcLogFilter, + CallRequest, CfxFeeHistory, CfxFilterChanges, CfxRpcLogFilter, CheckBalanceAgainstTransactionResponse, EpochNumber, EstimateGasAndCollateralResponse, Log as RpcLog, PoSEconomics, Receipt as RpcReceipt, RewardInfo as RpcRewardInfo, RpcAddress, SponsorInfo, Status as RpcStatus, StorageCollateralInfo, TokenSupplyInfo, - Transaction, VoteParamsInfo, + Transaction, VoteParamsInfo, U64 as HexU64, }; use cfx_types::{H128, H256, U256, U64}; use jsonrpc_core::{BoxFuture, Result as JsonRpcResult}; @@ -40,6 +40,10 @@ pub trait Cfx { #[rpc(name = "cfx_gasPrice")] fn gas_price(&self) -> BoxFuture; + /// Returns current max_priority_fee + #[rpc(name = "cfx_maxPriorityFeePerGas")] + fn max_priority_fee_per_gas(&self) -> BoxFuture; + /// Returns highest epoch number. #[rpc(name = "cfx_epochNumber")] fn epoch_number( @@ -197,6 +201,12 @@ pub trait Cfx { &self, request: CallRequest, epoch_number: Option, ) -> JsonRpcResult; + #[rpc(name = "cfx_feeHistory")] + fn fee_history( + &self, block_count: HexU64, newest_block: EpochNumber, + reward_percentiles: Vec, + ) -> BoxFuture; + /// Check if user balance is enough for the transaction. #[rpc(name = "cfx_checkBalanceAgainstTransaction")] fn check_balance_against_transaction( @@ -274,6 +284,11 @@ pub trait Cfx { &self, epoch_number: Option, ) -> JsonRpcResult; + #[rpc(name = "cfx_getFeeBurnt")] + fn get_fee_burnt( + &self, epoch_number: Option, + ) -> JsonRpcResult; + #[rpc(name = "cfx_getPoSRewardByEpoch")] fn get_pos_reward_by_epoch( &self, epoch: EpochNumber, diff --git a/crates/client/src/rpc/traits/eth_space/debug.rs b/crates/client/src/rpc/traits/eth_space/debug.rs new file mode 100644 index 0000000000..ce76c24e03 --- /dev/null +++ b/crates/client/src/rpc/traits/eth_space/debug.rs @@ -0,0 +1,41 @@ +use crate::rpc::types::eth::{BlockNumber, CallRequest}; +use alloy_rpc_types_trace::geth::{ + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, + TraceResult, +}; +use cfx_types::H256; +use jsonrpc_core::Result as JsonRpcResult; +use jsonrpc_derive::rpc; + +/// methods compatible with geth debug namespace methods https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug +#[rpc(server)] +pub trait Debug { + #[rpc(name = "debug_dbGet")] + fn db_get(&self, key: String) -> JsonRpcResult>; + + /// The `debug_traceTransaction` debugging method will attempt to run the + /// transaction in the exact same manner as it was executed on the + /// network. It will replay any transaction that may have been executed + /// prior to this one before it will finally attempt to execute the + /// transaction that corresponds to the given hash. + #[rpc(name = "debug_traceTransaction")] + fn debug_trace_transaction( + &self, tx_hash: H256, opts: Option, + ) -> JsonRpcResult; + + #[rpc(name = "debug_traceBlockByHash")] + fn debug_trace_block_by_hash( + &self, block: H256, opts: Option, + ) -> JsonRpcResult>; + + #[rpc(name = "debug_traceBlockByNumber")] + fn debug_trace_block_by_number( + &self, block: BlockNumber, opts: Option, + ) -> JsonRpcResult>; + + #[rpc(name = "debug_traceCall")] + fn debug_trace_call( + &self, request: CallRequest, block_number: Option, + opts: Option, + ) -> JsonRpcResult; +} diff --git a/crates/client/src/rpc/traits/eth_space/eth.rs b/crates/client/src/rpc/traits/eth_space/eth.rs index 99e02efabb..8155088f6f 100644 --- a/crates/client/src/rpc/traits/eth_space/eth.rs +++ b/crates/client/src/rpc/traits/eth_space/eth.rs @@ -19,6 +19,7 @@ // along with OpenEthereum. If not, see . //! Eth rpc interface. +use crate::rpc::types::U64 as HexU64; use cfx_types::{H128, H160, H256, U256, U64}; use jsonrpc_core::Result; use jsonrpc_derive::rpc; @@ -28,7 +29,7 @@ use crate::rpc::types::{ AccountPendingTransactions, Block, BlockNumber, CallRequest, EthRpcLogFilter, FilterChanges, Log, Receipt, SyncStatus, Transaction, }, - Bytes, Index, + Bytes, FeeHistory, Index, }; /// Eth rpc interface. @@ -74,10 +75,11 @@ pub trait Eth { #[rpc(name = "eth_maxPriorityFeePerGas")] fn max_priority_fee_per_gas(&self) -> Result; - // /// Returns transaction fee history. - // #[rpc(name = "eth_feeHistory")] - // fn fee_history(&self, _: U256, _: BlockNumber, _: Option>) - // -> BoxFuture; + #[rpc(name = "eth_feeHistory")] + fn fee_history( + &self, block_count: HexU64, newest_block: BlockNumber, + reward_percentiles: Vec, + ) -> Result; /// Returns accounts list. #[rpc(name = "eth_accounts")] @@ -89,7 +91,9 @@ pub trait Eth { /// Returns balance of the given account. #[rpc(name = "eth_getBalance")] - fn balance(&self, _: H160, _: Option) -> Result; + fn balance( + &self, address: H160, block: Option, + ) -> Result; // /// Returns the account- and storage-values of the specified account // including the Merkle-proof #[rpc(name = "eth_getProof")] @@ -99,97 +103,112 @@ pub trait Eth { /// Returns content of the storage at given address. #[rpc(name = "eth_getStorageAt")] fn storage_at( - &self, _: H160, _: U256, _: Option, + &self, address: H160, storage_slot: U256, block: Option, ) -> jsonrpc_core::Result; /// Returns block with given hash. #[rpc(name = "eth_getBlockByHash")] - fn block_by_hash(&self, _: H256, _: bool) -> Result>; + fn block_by_hash( + &self, block_hash: H256, hydrated_transactions: bool, + ) -> Result>; /// Returns block with given number. #[rpc(name = "eth_getBlockByNumber")] - fn block_by_number(&self, _: BlockNumber, _: bool) - -> Result>; + fn block_by_number( + &self, block: BlockNumber, hydrated_transactions: bool, + ) -> Result>; /// Returns the number of transactions sent from given address at given time /// (block number). #[rpc(name = "eth_getTransactionCount")] fn transaction_count( - &self, _: H160, _: Option, + &self, address: H160, block: Option, ) -> Result; /// Returns the number of transactions in a block with given hash. #[rpc(name = "eth_getBlockTransactionCountByHash")] - fn block_transaction_count_by_hash(&self, _: H256) -> Result>; + fn block_transaction_count_by_hash( + &self, block_hash: H256, + ) -> Result>; /// Returns the number of transactions in a block with given block number. #[rpc(name = "eth_getBlockTransactionCountByNumber")] fn block_transaction_count_by_number( - &self, _: BlockNumber, + &self, block: BlockNumber, ) -> Result>; /// Returns the number of uncles in a block with given hash. #[rpc(name = "eth_getUncleCountByBlockHash")] - fn block_uncles_count_by_hash(&self, _: H256) -> Result>; + fn block_uncles_count_by_hash( + &self, block_hash: H256, + ) -> Result>; /// Returns the number of uncles in a block with given block number. #[rpc(name = "eth_getUncleCountByBlockNumber")] fn block_uncles_count_by_number( - &self, _: BlockNumber, + &self, block: BlockNumber, ) -> Result>; /// Returns the code at given address at given time (block number). #[rpc(name = "eth_getCode")] - fn code_at(&self, _: H160, _: Option) -> Result; + fn code_at( + &self, address: H160, block: Option, + ) -> Result; /// Sends signed transaction, returning its hash. #[rpc(name = "eth_sendRawTransaction")] - fn send_raw_transaction(&self, _: Bytes) -> Result; + fn send_raw_transaction(&self, transaction: Bytes) -> Result; /// @alias of `eth_sendRawTransaction`. #[rpc(name = "eth_submitTransaction")] - fn submit_transaction(&self, _: Bytes) -> Result; + fn submit_transaction(&self, transaction: Bytes) -> Result; /// Call contract, returning the output data. #[rpc(name = "eth_call")] - fn call(&self, _: CallRequest, _: Option) -> Result; + fn call( + &self, transaction: CallRequest, block: Option, + ) -> Result; /// Estimate gas needed for execution of given contract. #[rpc(name = "eth_estimateGas")] fn estimate_gas( - &self, _: CallRequest, _: Option, + &self, transaction: CallRequest, block: Option, ) -> Result; /// Get transaction by its hash. #[rpc(name = "eth_getTransactionByHash")] - fn transaction_by_hash(&self, _: H256) -> Result>; + fn transaction_by_hash( + &self, transaction_hash: H256, + ) -> Result>; /// Returns transaction at given block hash and index. #[rpc(name = "eth_getTransactionByBlockHashAndIndex")] fn transaction_by_block_hash_and_index( - &self, _: H256, _: Index, + &self, block_hash: H256, transaction_index: Index, ) -> Result>; /// Returns transaction by given block number and index. #[rpc(name = "eth_getTransactionByBlockNumberAndIndex")] fn transaction_by_block_number_and_index( - &self, _: BlockNumber, _: Index, + &self, block: BlockNumber, transaction_index: Index, ) -> Result>; /// Returns transaction receipt by transaction hash. #[rpc(name = "eth_getTransactionReceipt")] - fn transaction_receipt(&self, _: H256) -> Result>; + fn transaction_receipt( + &self, transaction_hash: H256, + ) -> Result>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockHashAndIndex")] fn uncle_by_block_hash_and_index( - &self, _: H256, _: Index, + &self, block_hash: H256, _: Index, ) -> Result>; /// Returns an uncles at given block and index. #[rpc(name = "eth_getUncleByBlockNumberAndIndex")] fn uncle_by_block_number_and_index( - &self, _: BlockNumber, _: Index, + &self, block: BlockNumber, _: Index, ) -> Result>; // /// Returns available compilers. @@ -214,7 +233,7 @@ pub trait Eth { /// Returns logs matching given filter object. #[rpc(name = "eth_getLogs")] - fn logs(&self, _: EthRpcLogFilter) -> Result>; + fn logs(&self, filter: EthRpcLogFilter) -> Result>; // /// Returns the hash of the current block, the seedHash, and the boundary // condition to be met. #[rpc(name = "eth_getWork")] @@ -229,11 +248,13 @@ pub trait Eth { fn submit_hashrate(&self, _: U256, _: H256) -> Result; #[rpc(name = "parity_getBlockReceipts")] - fn block_receipts(&self, _: Option) -> Result>; + fn block_receipts( + &self, block: Option, + ) -> Result>; #[rpc(name = "eth_getAccountPendingTransactions")] fn account_pending_transactions( - &self, _: H160, maybe_start_nonce: Option, + &self, address: H160, maybe_start_nonce: Option, maybe_limit: Option, ) -> Result; } @@ -243,7 +264,7 @@ pub trait Eth { pub trait EthFilter { /// Returns id of new filter. #[rpc(name = "eth_newFilter")] - fn new_filter(&self, _: EthRpcLogFilter) -> Result; + fn new_filter(&self, filter: EthRpcLogFilter) -> Result; /// Returns id of new block filter. #[rpc(name = "eth_newBlockFilter")] @@ -255,13 +276,13 @@ pub trait EthFilter { /// Returns filter changes since last poll. #[rpc(name = "eth_getFilterChanges")] - fn filter_changes(&self, _: H128) -> Result; + fn filter_changes(&self, identifier: H128) -> Result; /// Returns all logs matching given filter (in a range 'from' - 'to'). #[rpc(name = "eth_getFilterLogs")] - fn filter_logs(&self, _: H128) -> Result>; + fn filter_logs(&self, identifier: H128) -> Result>; /// Uninstalls filter. #[rpc(name = "eth_uninstallFilter")] - fn uninstall_filter(&self, _: H128) -> Result; + fn uninstall_filter(&self, identifier: H128) -> Result; } diff --git a/crates/client/src/rpc/traits/eth_space/mod.rs b/crates/client/src/rpc/traits/eth_space/mod.rs index 7a1d873fd8..27d7f2f5a5 100644 --- a/crates/client/src/rpc/traits/eth_space/mod.rs +++ b/crates/client/src/rpc/traits/eth_space/mod.rs @@ -1,3 +1,4 @@ +pub mod debug; pub mod eth; pub mod eth_pubsub; pub mod trace; diff --git a/crates/client/src/rpc/types.rs b/crates/client/src/rpc/types.rs index e9a4871ca9..02405f90d2 100644 --- a/crates/client/src/rpc/types.rs +++ b/crates/client/src/rpc/types.rs @@ -3,15 +3,15 @@ // See http://www.gnu.org/licenses/ mod account; -pub mod address; mod blame_info; mod block; mod bytes; -pub mod call_request; +pub mod cfx; mod consensus_graph_states; mod epoch_number; pub mod errors; pub mod eth; +mod fee_history; mod filter; mod index; mod log; @@ -31,20 +31,28 @@ mod trace; mod trace_filter; mod transaction; mod tx_pool; +mod variadic_u64; mod vote_params_info; pub use self::{ account::Account, - address::RpcAddress, blame_info::BlameInfo, block::{Block, BlockTransactions, Header}, bytes::Bytes, - call_request::{ - sign_call, CallRequest, CheckBalanceAgainstTransactionResponse, - EstimateGasAndCollateralResponse, SendTxRequest, MAX_GAS_CALL_REQUEST, + cfx::{ + address, + address::RpcAddress, + call_request::{ + self, sign_call, CallRequest, + CheckBalanceAgainstTransactionResponse, + EstimateGasAndCollateralResponse, SendTxRequest, + MAX_GAS_CALL_REQUEST, + }, + CfxFeeHistory, }, consensus_graph_states::ConsensusGraphStates, epoch_number::{BlockHashOrEpochNumber, EpochNumber}, + fee_history::FeeHistory, filter::{CfxFilterChanges, CfxFilterLog, CfxRpcLogFilter, RevertTo}, index::Index, log::Log, @@ -68,5 +76,6 @@ pub use self::{ AccountPendingInfo, AccountPendingTransactions, TxPoolPendingNonceRange, TxPoolStatus, TxWithPoolInfo, }, + variadic_u64::U64, vote_params_info::VoteParamsInfo, }; diff --git a/crates/client/src/rpc/types/block.rs b/crates/client/src/rpc/types/block.rs index aae0676c72..d8fab80a58 100644 --- a/crates/client/src/rpc/types/block.rs +++ b/crates/client/src/rpc/types/block.rs @@ -113,6 +113,9 @@ pub struct Block { pub gas_limit: U256, /// Gas used pub gas_used: Option, + /// Base fee + #[serde(skip_serializing_if = "Option::is_none")] + pub base_fee_per_gas: Option, /// Timestamp pub timestamp: U256, /// Difficulty @@ -240,6 +243,7 @@ impl Block { prior_gas_used, epoch_number, execution_result.block_receipts.block_number, + b.block_header.base_price(), maybe_state_root, if tx_exec_error_msg.is_empty() { None @@ -304,6 +308,11 @@ impl Block { // fee system gas_used, gas_limit: b.block_header.gas_limit().into(), + base_fee_per_gas: b + .block_header + .base_price() + .map(|x| x[Space::Native]) + .into(), timestamp: b.block_header.timestamp().into(), difficulty: b.block_header.difficulty().clone().into(), pow_quality: b @@ -538,6 +547,7 @@ mod tests { epoch_number: None, block_number: None, gas_limit: U256::default(), + base_fee_per_gas: None, gas_used: None, timestamp: 0.into(), difficulty: U256::default(), @@ -573,6 +583,7 @@ mod tests { transactions_root: KECCAK_EMPTY_LIST_RLP.into(), epoch_number: Some(0.into()), block_number: Some(0.into()), + base_fee_per_gas: None, gas_limit: U256::default(), gas_used: None, timestamp: 0.into(), diff --git a/crates/client/src/rpc/types/cfx/access_list.rs b/crates/client/src/rpc/types/cfx/access_list.rs new file mode 100644 index 0000000000..1e0295669d --- /dev/null +++ b/crates/client/src/rpc/types/cfx/access_list.rs @@ -0,0 +1,38 @@ +use crate::rpc::types::address::RpcAddress; +use cfx_addr::Network; +use cfx_types::H256; +use primitives::{AccessList, AccessListItem}; +use std::convert::Into; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CfxAccessListItem { + pub address: RpcAddress, + pub storage_keys: Vec, +} + +impl Into for CfxAccessListItem { + fn into(self) -> AccessListItem { + AccessListItem { + address: self.address.hex_address, + storage_keys: self.storage_keys, + } + } +} + +pub type CfxAccessList = Vec; + +pub fn to_primitive_access_list(list: CfxAccessList) -> AccessList { + list.into_iter().map(|item| item.into()).collect() +} + +pub fn from_primitive_access_list( + list: AccessList, network: Network, +) -> CfxAccessList { + list.into_iter() + .map(|item| CfxAccessListItem { + address: RpcAddress::try_from_h160(item.address, network).unwrap(), + storage_keys: item.storage_keys, + }) + .collect() +} diff --git a/crates/client/src/rpc/types/address.rs b/crates/client/src/rpc/types/cfx/address.rs similarity index 100% rename from crates/client/src/rpc/types/address.rs rename to crates/client/src/rpc/types/cfx/address.rs diff --git a/crates/client/src/rpc/types/call_request.rs b/crates/client/src/rpc/types/cfx/call_request.rs similarity index 73% rename from crates/client/src/rpc/types/call_request.rs rename to crates/client/src/rpc/types/cfx/call_request.rs index a9f07397b2..fb9fa5bb62 100644 --- a/crates/client/src/rpc/types/call_request.rs +++ b/crates/client/src/rpc/types/cfx/call_request.rs @@ -3,8 +3,10 @@ // See http://www.gnu.org/licenses/ use crate::rpc::{ + error_codes::invalid_params, types::{ address::RpcAddress, + cfx::{to_primitive_access_list, CfxAccessList}, errors::{check_rpc_address_network, RcpAddressNetworkInconsistent}, Bytes, }, @@ -16,15 +18,19 @@ use cfxcore::rpc_errors::invalid_params_check; use cfxcore_accounts::AccountProvider; use cfxkey::Password; use primitives::{ - transaction::Action, NativeTransaction as PrimitiveTransaction, + transaction::{ + native_transaction::NativeTransaction as PrimitiveTransaction, Action, + Cip1559Transaction, Cip2930Transaction, NativeTransaction, + TypedNativeTransaction::*, CIP1559_TYPE, CIP2930_TYPE, LEGACY_TX_TYPE, + }, SignedTransaction, Transaction, TransactionWithSignature, }; -use std::{cmp::min, sync::Arc}; - -// use serde_json::de::ParserNumber::U64; +use std::{cmp::min, convert::Into, sync::Arc}; -/// The MAX_GAS_CALL_REQUEST is one magnitude higher than block gas limit and -/// not too high that a call_virtual consumes too much resource. +/// The MAX_GAS_CALL_REQUEST is used as max value of cfx_call or cfx_estimate's +/// gas value to prevent call_virtual consumes too much resource. +/// The tx_pool will reject the tx if the gas is larger than half of the block +/// gas limit. which is 30_000_000 before 1559, and 60_000_000 after 1559. pub const MAX_GAS_CALL_REQUEST: u64 = 15_000_000; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] @@ -46,6 +52,12 @@ pub struct CallRequest { pub nonce: Option, /// StorageLimit pub storage_limit: Option, + /// Access list in EIP-2930 + pub access_list: Option, + pub max_fee_per_gas: Option, + pub max_priority_fee_per_gas: Option, + #[serde(rename = "type")] + pub transaction_type: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -145,27 +157,90 @@ pub fn sign_call( ) -> RpcResult { let max_gas = U256::from(MAX_GAS_CALL_REQUEST); let gas = min(request.gas.unwrap_or(max_gas), max_gas); + + let nonce = request.nonce.unwrap_or_default(); + let action = request.to.map_or(Action::Create, |rpc_addr| { + Action::Call(rpc_addr.hex_address) + }); + + let value = request.value.unwrap_or_default(); + let storage_limit = request + .storage_limit + .map(|v| v.as_u64()) + .unwrap_or(std::u64::MAX); + let data = request.data.unwrap_or_default().into_vec(); + + let default_type_id = if request.max_fee_per_gas.is_some() + || request.max_priority_fee_per_gas.is_some() + { + CIP1559_TYPE + } else if request.access_list.is_some() { + CIP2930_TYPE + } else { + LEGACY_TX_TYPE + }; + let transaction_type = request + .transaction_type + .unwrap_or(U64::from(default_type_id)); + + let gas_price = request.gas_price.unwrap_or(1.into()); + let max_fee_per_gas = request + .max_fee_per_gas + .or(request.max_priority_fee_per_gas) + .unwrap_or(gas_price); + let max_priority_fee_per_gas = + request.max_priority_fee_per_gas.unwrap_or(U256::zero()); + let access_list = request.access_list.unwrap_or(vec![]); + + let transaction = match transaction_type.as_usize() as u8 { + LEGACY_TX_TYPE => Cip155(NativeTransaction { + nonce, + action, + gas, + gas_price, + value, + storage_limit, + epoch_height, + chain_id, + data, + }), + CIP2930_TYPE => Cip2930(Cip2930Transaction { + nonce, + gas_price, + gas, + action, + value, + storage_limit, + epoch_height, + chain_id, + data, + access_list: to_primitive_access_list(access_list), + }), + CIP1559_TYPE => Cip1559(Cip1559Transaction { + nonce, + action, + gas, + value, + max_fee_per_gas, + max_priority_fee_per_gas, + storage_limit, + epoch_height, + chain_id, + data, + access_list: to_primitive_access_list(access_list), + }), + x => { + return Err( + invalid_params("Unrecognized transaction type", x).into() + ); + } + }; + let from = request .from .map_or_else(|| Address::zero(), |rpc_addr| rpc_addr.hex_address); - Ok(PrimitiveTransaction { - nonce: request.nonce.unwrap_or_default(), - action: request.to.map_or(Action::Create, |rpc_addr| { - Action::Call(rpc_addr.hex_address) - }), - gas, - gas_price: request.gas_price.unwrap_or(1.into()), - value: request.value.unwrap_or_default(), - storage_limit: request - .storage_limit - .map(|v| v.as_u64()) - .unwrap_or(std::u64::MAX), - epoch_height, - chain_id, - data: request.data.unwrap_or_default().into_vec(), - } - .fake_sign(from.with_native_space())) + Ok(transaction.fake_sign_rpc(from.with_native_space())) } pub fn rpc_call_request_network( @@ -222,6 +297,7 @@ mod tests { data: Some(vec![0x12, 0x34, 0x56].into()), storage_limit: Some(U64::from_str("7b").unwrap()), nonce: Some(U256::from(4)), + ..Default::default() }; let s = r#"{ @@ -253,7 +329,8 @@ mod tests { value: Some(U256::from_str("9184e72a").unwrap()), storage_limit: Some(U64::from_str("3344adf").unwrap()), data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex::>().unwrap().into()), - nonce: None + nonce: None, + ..Default::default() }; let s = r#"{ @@ -291,6 +368,7 @@ mod tests { data: None, storage_limit: None, nonce: None, + ..Default::default() }; let s = r#"{"from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6"}"#; diff --git a/crates/client/src/rpc/types/cfx/fee_history.rs b/crates/client/src/rpc/types/cfx/fee_history.rs new file mode 100644 index 0000000000..9a0696597d --- /dev/null +++ b/crates/client/src/rpc/types/cfx/fee_history.rs @@ -0,0 +1,35 @@ +use cfx_types::U256; +use std::collections::VecDeque; + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct CfxFeeHistory { + /// Oldest epoch + oldest_epoch: U256, + /// An array of pivot block base fees per gas. This includes one block + /// earlier than the oldest block. Zeroes are returned for pre-EIP-1559 + /// blocks. + base_fee_per_gas: VecDeque, + /// In Conflux, 1559 is adjusted by the current block's gas limit of total + /// transactions, instead of parent's gas used + gas_used_ratio: VecDeque, + /// A two-dimensional array of effective priority fees per gas at the + /// requested block percentiles. + reward: VecDeque>, +} + +impl CfxFeeHistory { + pub fn new( + oldest_epoch: U256, base_fee_per_gas: VecDeque, + gas_used_ratio: VecDeque, reward: VecDeque>, + ) -> Self { + CfxFeeHistory { + oldest_epoch, + base_fee_per_gas, + gas_used_ratio, + reward, + } + } + + pub fn reward(&self) -> &VecDeque> { &self.reward } +} diff --git a/crates/client/src/rpc/types/cfx/mod.rs b/crates/client/src/rpc/types/cfx/mod.rs new file mode 100644 index 0000000000..4a15fecffb --- /dev/null +++ b/crates/client/src/rpc/types/cfx/mod.rs @@ -0,0 +1,8 @@ +mod access_list; +pub mod address; +pub mod call_request; +mod fee_history; + +pub use access_list::*; +pub use address::RpcAddress; +pub use fee_history::*; diff --git a/crates/client/src/rpc/types/eth/block.rs b/crates/client/src/rpc/types/eth/block.rs index 090865325a..a63f7ffe84 100644 --- a/crates/client/src/rpc/types/eth/block.rs +++ b/crates/client/src/rpc/types/eth/block.rs @@ -146,8 +146,7 @@ impl Block { pub fn from_phantom(pb: &PhantomBlock, full: bool) -> Self { let transactions = if full { BlockTransactions::Full( - pb - .transactions + pb.transactions .iter() .enumerate() .map(|(idx, t)| { @@ -155,15 +154,18 @@ impl Block { .outcome_status .in_space(Space::Ethereum); - let contract_address = match Transaction::deployed_contract_address(&**t) { - Some(a) if status == EVM_SPACE_SUCCESS => Some(a), - _ => None, - }; + let contract_address = + match Transaction::deployed_contract_address(&**t) { + Some(a) if status == EVM_SPACE_SUCCESS => { + Some(a) + } + _ => None, + }; Transaction::from_signed( &**t, ( - Some(pb.pivot_header.hash()), // block_hash + Some(pb.pivot_header.hash()), // block_hash Some(pb.pivot_header.height().into()), // block_number Some(idx.into()), // transaction_index ), @@ -201,7 +203,10 @@ impl Block { timestamp: pb.pivot_header.timestamp().into(), difficulty: pb.pivot_header.difficulty().into(), total_difficulty: 0.into(), - base_fee_per_gas: None, + base_fee_per_gas: pb + .pivot_header + .base_price() + .map(|x| x[Space::Ethereum]), uncles: vec![], // Note: we allow U256 nonce in Stratum and in the block. // However, most mining clients use U64. Here we truncate diff --git a/crates/client/src/rpc/types/eth/call_request.rs b/crates/client/src/rpc/types/eth/call_request.rs index 9171f24d8f..3d694209b1 100644 --- a/crates/client/src/rpc/types/eth/call_request.rs +++ b/crates/client/src/rpc/types/eth/call_request.rs @@ -19,7 +19,8 @@ // along with OpenEthereum. If not, see . use crate::rpc::types::Bytes; -use cfx_types::{H160, U256}; +use cfx_types::{H160, U256, U64}; +use primitives::AccessList; /// Call request #[derive(Debug, Default, PartialEq, Deserialize)] @@ -30,10 +31,8 @@ pub struct CallRequest { /// To pub to: Option, /// Gas Price - #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, /// Max fee per gas - #[serde(skip_serializing_if = "Option::is_none")] pub max_fee_per_gas: Option, /// Gas pub gas: Option, @@ -44,115 +43,16 @@ pub struct CallRequest { /// Nonce pub nonce: Option, /// Miner bribe - #[serde(skip_serializing_if = "Option::is_none")] pub max_priority_fee_per_gas: Option, + pub access_list: Option, + #[serde(rename = "type")] + pub transaction_type: Option, } -// impl Into for CallRequest { -// fn into(self) -> Request { -// Request { -// transaction_type: self.transaction_type, -// from: self.from.map(Into::into), -// to: self.to.map(Into::into), -// gas_price: self.gas_price.map(Into::into), -// max_fee_per_gas: self.max_fee_per_gas, -// gas: self.gas.map(Into::into), -// value: self.value.map(Into::into), -// data: self.data.map(Into::into), -// nonce: self.nonce.map(Into::into), -// access_list: self.access_list.map(Into::into), -// max_priority_fee_per_gas: -// self.max_priority_fee_per_gas.map(Into::into), } -// } -// } -// -// #[cfg(test)] -// mod tests { -// use super::CallRequest; -// use ethereum_types::{H160, U256}; -// use rustc_hex::FromHex; -// use serde_json; -// use std::str::FromStr; -// -// #[test] -// fn call_request_deserialize() { -// let s = r#"{ -// "from":"0x0000000000000000000000000000000000000001", -// "to":"0x0000000000000000000000000000000000000002", -// "gasPrice":"0x1", -// "gas":"0x2", -// "value":"0x3", -// "data":"0x123456", -// "nonce":"0x4" -// }"#; -// let deserialized: CallRequest = serde_json::from_str(s).unwrap(); -// -// assert_eq!( -// deserialized, -// CallRequest { -// transaction_type: Default::default(), -// from: Some(H160::from_low_u64_be(1)), -// to: Some(H160::from_low_u64_be(2)), -// gas_price: Some(U256::from(1)), -// max_fee_per_gas: None, -// gas: Some(U256::from(2)), -// value: Some(U256::from(3)), -// data: Some(vec![0x12, 0x34, 0x56].into()), -// nonce: Some(U256::from(4)), -// access_list: None, -// max_priority_fee_per_gas: None, -// } -// ); -// } -// -// #[test] -// fn call_request_deserialize2() { -// let s = r#"{ -// "from": "0xb60e8dd61c5d32be8058bb8eb970870f07233155", -// "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567", -// "gas": "0x76c0", -// "gasPrice": "0x9184e72a000", -// "value": "0x9184e72a", -// "data": -// "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675" -// }"#; -// let deserialized: CallRequest = serde_json::from_str(s).unwrap(); -// -// assert_eq!(deserialized, CallRequest { -// transaction_type: Default::default(), -// from: Some(H160::from_str("b60e8dd61c5d32be8058bb8eb970870f07233155"). -// unwrap()), to: Some(H160::from_str("d46e8dd67c5d32be8058bb8eb970870f07244567" -// ).unwrap()), gas_price: Some(U256::from_str("9184e72a000").unwrap()), -// max_fee_per_gas: None, -// gas: Some(U256::from_str("76c0").unwrap()), -// value: Some(U256::from_str("9184e72a").unwrap()), -// data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex().unwrap().into()), -// nonce: None, -// access_list: None, -// max_priority_fee_per_gas: None, -// }); -// } -// -// #[test] -// fn call_request_deserialize_empty() { -// let s = r#"{"from":"0x0000000000000000000000000000000000000001"}"#; -// let deserialized: CallRequest = serde_json::from_str(s).unwrap(); -// -// assert_eq!( -// deserialized, -// CallRequest { -// transaction_type: Default::default(), -// from: Some(H160::from_low_u64_be(1)), -// to: None, -// gas_price: None, -// max_fee_per_gas: None, -// gas: None, -// value: None, -// data: None, -// nonce: None, -// access_list: None, -// max_priority_fee_per_gas: None, -// } -// ); -// } -// } +impl CallRequest { + pub fn unset_zero_gas_price(&mut self) { + if self.gas_price == Some(U256::zero()) { + self.gas_price = None; + } + } +} diff --git a/crates/client/src/rpc/types/eth/receipt.rs b/crates/client/src/rpc/types/eth/receipt.rs index aaefae0934..8e800f3215 100644 --- a/crates/client/src/rpc/types/eth/receipt.rs +++ b/crates/client/src/rpc/types/eth/receipt.rs @@ -25,6 +25,8 @@ use cfx_types::{Bloom as H2048, H160, H256, U256, U64}; #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct Receipt { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, /// Transaction Hash pub transaction_hash: H256, /// Transaction index @@ -56,171 +58,6 @@ pub struct Receipt { /// is None if tx execution is successful or it can not be offered. /// Error message can not be offered by light client. pub tx_exec_error_msg: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub burnt_gas_fee: Option, } - -// impl Receipt { -// fn outcome_to_state_root(outcome: TransactionOutcome) -> Option { -// match outcome { -// TransactionOutcome::Unknown | TransactionOutcome::StatusCode(_) -// => None, TransactionOutcome::StateRoot(root) => Some(root), -// } -// } -// -// fn outcome_to_status_code(outcome: &TransactionOutcome) -> Option { -// match *outcome { -// TransactionOutcome::Unknown | TransactionOutcome::StateRoot(_) => -// None, TransactionOutcome::StatusCode(ref code) => Some((*code as -// u64).into()), } -// } -// } -// -// impl From for Receipt { -// fn from(r: LocalizedReceipt) -> Self { -// Receipt { -// to: r.to.map(Into::into), -// from: Some(r.from), -// transaction_type: r.transaction_type.to_U64_option_id(), -// transaction_hash: Some(r.transaction_hash), -// transaction_index: Some(r.transaction_index.into()), -// block_hash: Some(r.block_hash), -// block_number: Some(r.block_number.into()), -// cumulative_gas_used: r.cumulative_gas_used, -// gas_used: Some(r.gas_used), -// contract_address: r.contract_address.map(Into::into), -// logs: r.logs.into_iter().map(Into::into).collect(), -// status_code: Self::outcome_to_status_code(&r.outcome), -// state_root: Self::outcome_to_state_root(r.outcome), -// logs_bloom: r.log_bloom, -// effective_gas_price: r.effective_gas_price, -// } -// } -// } -// -// impl From for Receipt { -// fn from(r: RichReceipt) -> Self { -// Receipt { -// from: Some(r.from), -// to: r.to.map(Into::into), -// transaction_type: r.transaction_type.to_U64_option_id(), -// transaction_hash: Some(r.transaction_hash), -// transaction_index: Some(r.transaction_index.into()), -// block_hash: None, -// block_number: None, -// cumulative_gas_used: r.cumulative_gas_used, -// gas_used: Some(r.gas_used), -// contract_address: r.contract_address.map(Into::into), -// logs: r.logs.into_iter().map(Into::into).collect(), -// status_code: Self::outcome_to_status_code(&r.outcome), -// state_root: Self::outcome_to_state_root(r.outcome), -// logs_bloom: r.log_bloom, -// effective_gas_price: r.effective_gas_price, -// } -// } -// } -// -// impl From for Receipt { -// fn from(r: TypedReceipt) -> Self { -// let transaction_type = r.tx_type().to_U64_option_id(); -// let legacy_receipt = r.receipt().clone(); -// Receipt { -// from: None, -// to: None, -// transaction_type, -// transaction_hash: None, -// transaction_index: None, -// block_hash: None, -// block_number: None, -// cumulative_gas_used: legacy_receipt.gas_used, -// gas_used: None, -// contract_address: None, -// logs: legacy_receipt.logs.into_iter().map(Into::into).collect(), -// status_code: -// Self::outcome_to_status_code(&legacy_receipt.outcome), -// state_root: Self::outcome_to_state_root(legacy_receipt.outcome), -// logs_bloom: legacy_receipt.log_bloom, effective_gas_price: -// Default::default(), } -// } -// } -// -// #[cfg(test)] -// mod tests { -// use ethereum_types::{Bloom, H256}; -// use serde_json; -// use types::transaction::TypedTxId; -// use v1::types::{Log, Receipt}; -// -// #[test] -// fn receipt_serialization() { -// let s = -// r#"{"type":"0x1","transactionHash":" -// 0x0000000000000000000000000000000000000000000000000000000000000000"," -// transactionIndex":"0x0","blockHash":" -// 0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5","from": -// null,"to":null,"blockNumber":"0x4510c","cumulativeGasUsed":"0x20","gasUsed":" -// 0x10","contractAddress":null,"logs":[{"address":" -// 0x33990122638b9132ca29c723bdf037f1a891a70c","topics":[" -// 0xa6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc"," -// 0x4861736852656700000000000000000000000000000000000000000000000000"],"data":" -// 0x","blockHash":" -// 0xed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5"," -// blockNumber":"0x4510c","transactionHash":" -// 0x0000000000000000000000000000000000000000000000000000000000000000"," -// transactionIndex":"0x0","logIndex":"0x1","transactionLogIndex":null,"type":" -// mined","removed":false}],"root":" -// 0x000000000000000000000000000000000000000000000000000000000000000a"," -// logsBloom":" -// 0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f" -// ,"status":"0x1","effectiveGasPrice":"0x0"}"#; -// -// let receipt = Receipt { -// from: None, -// to: None, -// transaction_type: TypedTxId::AccessList.to_U64_option_id(), -// transaction_hash: Some(H256::zero()), -// transaction_index: Some(0.into()), -// block_hash: Some( -// -// "ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5" -// .parse() -// .unwrap(), -// ), -// block_number: Some(0x4510c.into()), -// cumulative_gas_used: 0x20.into(), -// gas_used: Some(0x10.into()), -// contract_address: None, -// logs: vec![Log { -// address: -// "33990122638b9132ca29c723bdf037f1a891a70c".parse().unwrap(), -// topics: vec![ -// "a6697e974e6a320f454390be03f74955e8978f1a6971ea6730542e37b66179bc" -// .parse() -// .unwrap(), -// -// "4861736852656700000000000000000000000000000000000000000000000000" -// .parse() -// .unwrap(), -// ], -// data: vec![].into(), -// block_hash: Some( -// -// "ed76641c68a1c641aee09a94b3b471f4dc0316efe5ac19cf488e2674cf8d05b5" -// .parse() -// .unwrap(), -// ), -// block_number: Some(0x4510c.into()), -// transaction_hash: Some(H256::zero()), -// transaction_index: Some(0.into()), -// transaction_log_index: None, -// log_index: Some(1.into()), -// removed: false, -// }], -// logs_bloom: Bloom::from_low_u64_be(15), -// state_root: Some(H256::from_low_u64_be(10)), -// status_code: Some(1u64.into()), -// effective_gas_price: Default::default(), -// }; -// -// let serialized = serde_json::to_string(&receipt).unwrap(); -// assert_eq!(serialized, s); -// } -// } diff --git a/crates/client/src/rpc/types/eth/transaction.rs b/crates/client/src/rpc/types/eth/transaction.rs index 989e287994..ed847c9f83 100644 --- a/crates/client/src/rpc/types/eth/transaction.rs +++ b/crates/client/src/rpc/types/eth/transaction.rs @@ -21,7 +21,10 @@ use crate::rpc::types::Bytes; use cfx_types::{H160, H256, H512, U256, U64}; use cfx_vm_types::{contract_address, CreateContractAddress}; -use primitives::{transaction::eip155_signature, Action, SignedTransaction}; +use primitives::{ + transaction::eth_transaction::eip155_signature, AccessList, Action, + SignedTransaction, +}; use rlp::Encodable; use serde::Serialize; @@ -29,9 +32,9 @@ use serde::Serialize; #[derive(Debug, Default, Clone, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Transaction { - // /// transaction type - // #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - // pub transaction_type: Option, + /// transaction type + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, /// Hash pub hash: H256, /// Nonce @@ -77,14 +80,16 @@ pub struct Transaction { pub s: U256, // Whether tx is success pub status: Option, + /// Optional access list + #[serde(skip_serializing_if = "Option::is_none")] + pub access_list: Option, + /// miner bribe + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub y_parity: Option, /* /// Transaction activates at specified block. - * pub condition: Option, - * /// optional access list - * #[serde(skip_serializing_if = "Option::is_none")] - * pub access_list: Option, - * /// miner bribe - * #[serde(skip_serializing_if = "Option::is_none")] - * pub max_priority_fee_per_gas: Option, */ + * pub condition: Option, */ } impl Transaction { @@ -95,21 +100,6 @@ impl Transaction { exec_info: (Option, Option), ) -> Transaction { let signature = t.signature(); - // We only support EIP-155 - // let access_list = match t.as_unsigned() { - // TypedTransaction::AccessList(tx) => { - // Some(tx.access_list.clone().into_iter().map(Into::into). - // collect()) } - // TypedTransaction::EIP1559Transaction(tx) => Some( - // tx.transaction - // .access_list - // .clone() - // .into_iter() - // .map(Into::into) - // .collect(), - // ), - // TypedTransaction::Legacy(_) => None, - // }; Transaction { hash: t.hash(), @@ -124,7 +114,6 @@ impl Transaction { }, value: *t.value(), gas_price: *t.gas_price(), - max_fee_per_gas: None, gas: *t.gas(), input: Bytes::new(t.data().clone()), creates: exec_info.1, @@ -140,6 +129,13 @@ impl Transaction { r: signature.r().into(), s: signature.s().into(), status: exec_info.0, + access_list: t.access_list().cloned(), + max_fee_per_gas: t.after_1559().then_some(*t.gas_price()), + max_priority_fee_per_gas: t + .after_1559() + .then_some(*t.max_priority_gas_price()), + y_parity: t.is_2718().then_some(U64::from(signature.v())), + transaction_type: Some(U64::from(t.type_id())), } } diff --git a/crates/client/src/rpc/types/fee_history.rs b/crates/client/src/rpc/types/fee_history.rs new file mode 100644 index 0000000000..d530701597 --- /dev/null +++ b/crates/client/src/rpc/types/fee_history.rs @@ -0,0 +1,125 @@ +use std::collections::VecDeque; + +use cfx_types::{Space, SpaceMap, U256}; +use primitives::{transaction::SignedTransaction, BlockHeader}; + +use super::CfxFeeHistory; + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct FeeHistory { + /// Oldest Block + oldest_block: U256, + /// An array of block base fees per gas. This includes one block earlier + /// than the oldest block. Zeroes are returned for pre-EIP-1559 blocks. + base_fee_per_gas: VecDeque, + /// In Conflux, 1559 is adjusted by the current block's gas limit of total + /// transactions, instead of parent's gas used + gas_used_ratio: VecDeque, + /// A two-dimensional array of effective priority fees per gas at the + /// requested block percentiles. + reward: VecDeque>, +} + +impl FeeHistory { + pub fn new() -> Self { Default::default() } + + pub fn reward(&self) -> &VecDeque> { &self.reward } + + pub fn to_cfx_fee_history(self) -> CfxFeeHistory { + CfxFeeHistory::new( + self.oldest_block, + self.base_fee_per_gas, + self.gas_used_ratio, + self.reward, + ) + } + + pub fn push_front_block<'a, I>( + &mut self, space: Space, percentiles: &Vec, + pivot_header: &BlockHeader, transactions: I, + ) -> Result<(), String> + where + I: Clone + Iterator, + { + let base_price = if let Some(base_price) = pivot_header.base_price() { + base_price[space] + } else { + self.base_fee_per_gas.push_front(U256::zero()); + self.gas_used_ratio.push_front(0.0); + self.reward + .push_front(vec![U256::zero(); percentiles.len()]); + return Ok(()); + }; + + self.base_fee_per_gas.push_front(base_price); + + let gas_limit: U256 = match space { + Space::Native => pivot_header.gas_limit() * 9 / 10, + Space::Ethereum => pivot_header.gas_limit() * 5 / 10, + }; + + let gas_used = transactions + .clone() + .map(|x| *x.gas_limit()) + .reduce(|x, y| x + y) + .unwrap_or_default(); + + let gas_used_ratio = if gas_limit >= U256::from(u128::MAX) + || gas_used >= U256::from(u128::MAX) + { + // Impossible path. + 1.0 + } else { + gas_used.as_u128() as f64 / gas_limit.as_u128() as f64 + }; + + self.gas_used_ratio.push_front(gas_used_ratio); + + let reward = compute_reward(percentiles, transactions, base_price); + self.reward.push_front(reward); + + Ok(()) + } + + pub fn finish( + &mut self, oldest_block: u64, last_base_price: Option<&SpaceMap>, + space: Space, + ) { + self.oldest_block = oldest_block.into(); + self.base_fee_per_gas + .push_back(last_base_price.map_or(U256::zero(), |x| x[space])); + } +} + +fn compute_reward<'a, I>( + percentiles: &Vec, transactions: I, base_price: U256, +) -> Vec +where I: Iterator { + let mut rewards: Vec<_> = transactions + .map(|tx| { + if *tx.gas_price() < base_price { + U256::zero() + } else { + tx.effective_gas_price(&base_price) + } + }) + .collect(); + + if rewards.is_empty() { + return vec![U256::zero(); percentiles.len()]; + } + + rewards.sort_unstable(); + let n = rewards.len(); + percentiles + .into_iter() + .map(|per| { + let mut index = ((*per) * (n as f64) / 100f64) as usize; + if index >= n { + index = n - 1; + } + rewards[index] + }) + .collect() +} diff --git a/crates/client/src/rpc/types/receipt.rs b/crates/client/src/rpc/types/receipt.rs index e8bf52312d..85fa0e18e3 100644 --- a/crates/client/src/rpc/types/receipt.rs +++ b/crates/client/src/rpc/types/receipt.rs @@ -4,7 +4,9 @@ use crate::rpc::types::{Log, RpcAddress}; use cfx_addr::Network; -use cfx_types::{address_util::AddressUtil, Bloom, Space, H256, U256, U64}; +use cfx_types::{ + address_util::AddressUtil, Bloom, Space, SpaceMap, H256, U256, U64, +}; use cfx_vm_types::{contract_address, CreateContractAddress}; use primitives::{ receipt::{ @@ -36,6 +38,8 @@ impl StorageChange { #[derive(Debug, Serialize, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Receipt { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, /// Transaction hash. pub transaction_hash: H256, /// Transaction index within the block. @@ -57,6 +61,7 @@ pub struct Receipt { pub accumulated_gas_used: Option, /// The gas fee charged in the execution of the transaction. pub gas_fee: U256, + pub effective_gas_price: U256, /// Address of contract created if the transaction action is create. pub contract_created: Option, /// Array of log objects, which this transaction generated. @@ -82,6 +87,8 @@ pub struct Receipt { /// Transaction space. #[serde(skip_serializing_if = "Option::is_none")] pub space: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub burnt_gas_fee: Option, } impl Receipt { @@ -89,6 +96,7 @@ impl Receipt { transaction: PrimitiveTransaction, receipt: PrimitiveReceipt, transaction_index: TransactionIndex, prior_gas_used: U256, epoch_number: Option, block_number: u64, + maybe_base_price: Option>, maybe_state_root: Option, tx_exec_error_msg: Option, network: Network, include_eth_receipt: bool, include_accumulated_gas_used: bool, @@ -103,49 +111,50 @@ impl Receipt { storage_collateralized, storage_released, storage_sponsor_paid, + .. } = receipt; let (address, action, space) = match transaction.unsigned { Transaction::Native(ref unsigned) => { - if Action::Create == unsigned.action + if Action::Create == *unsigned.action() && outcome_status == TransactionStatus::Success { let (mut created_address, _) = contract_address( CreateContractAddress::FromSenderNonceAndCodeHash, block_number.into(), &transaction.sender, - &unsigned.nonce, - &unsigned.data, + unsigned.nonce(), + unsigned.data(), ); created_address.set_contract_type_bits(); let address = Some(RpcAddress::try_from_h160( created_address, network, )?); - (address, unsigned.action.clone(), Space::Native) + (address, unsigned.action().clone(), Space::Native) } else { - (None, unsigned.action.clone(), Space::Native) + (None, unsigned.action().clone(), Space::Native) } } Transaction::Ethereum(ref unsigned) => { if include_eth_receipt { - if Action::Create == unsigned.action + if Action::Create == *unsigned.action() && outcome_status == TransactionStatus::Success { let (created_address, _) = contract_address( CreateContractAddress::FromSenderNonce, 0, &transaction.sender, - &unsigned.nonce, - &unsigned.data, + unsigned.nonce(), + unsigned.data(), ); let address = Some(RpcAddress::try_from_h160( created_address, network, )?); - (address, unsigned.action.clone(), Space::Ethereum) + (address, unsigned.action().clone(), Space::Ethereum) } else { - (None, unsigned.action.clone(), Space::Ethereum) + (None, unsigned.action().clone(), Space::Ethereum) } } else { bail!(format!("Does not support EIP-155 transaction in Conflux space RPC. get_receipt for tx: {:?}",transaction)); @@ -161,7 +170,19 @@ impl Receipt { .map(Into::into) .unwrap_or_default(); + let effective_gas_price = if let Some(base_price) = maybe_base_price { + let base_price = base_price[transaction.space()]; + if *transaction.gas_price() < base_price { + *transaction.gas_price() + } else { + transaction.effective_gas_price(&base_price) + } + } else { + *transaction.gas_price() + }; + Ok(Receipt { + transaction_type: Some(U64::from(transaction.type_id())), transaction_hash: transaction.hash.into(), index: U64::from( transaction_index @@ -179,6 +200,8 @@ impl Receipt { None }, gas_fee: gas_fee.into(), + burnt_gas_fee: receipt.burnt_gas_fee, + effective_gas_price, from: RpcAddress::try_from_h160(transaction.sender, network)?, to: match &action { Action::Create => None, diff --git a/crates/client/src/rpc/types/transaction.rs b/crates/client/src/rpc/types/transaction.rs index e7b084e5fc..a5795a1aaa 100644 --- a/crates/client/src/rpc/types/transaction.rs +++ b/crates/client/src/rpc/types/transaction.rs @@ -3,13 +3,19 @@ // See http://www.gnu.org/licenses/ use crate::rpc::types::{ - eth::Transaction as ETHTransaction, receipt::Receipt, Bytes, RpcAddress, + cfx::{from_primitive_access_list, CfxAccessList}, + eth::Transaction as ETHTransaction, + receipt::Receipt, + Bytes, RpcAddress, }; use cfx_addr::Network; use cfx_types::{Space, H256, U256, U64}; use cfxkey::Error; use primitives::{ - transaction::Action, Eip155Transaction, NativeTransaction, + transaction::{ + eth_transaction::Eip155Transaction, + native_transaction::NativeTransaction, Action, + }, SignedTransaction, Transaction as PrimitiveTransaction, TransactionIndex, TransactionWithSignature, TransactionWithSignatureSerializePart, }; @@ -24,6 +30,8 @@ pub enum WrapTransaction { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Transaction { + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub transaction_type: Option, #[serde(skip_serializing_if = "Option::is_none")] pub space: Option, pub hash: H256, @@ -41,12 +49,23 @@ pub struct Transaction { pub epoch_height: U256, pub chain_id: Option, pub status: Option, + /// Optional access list + #[serde(skip_serializing_if = "Option::is_none")] + pub access_list: Option, + /// miner bribe + #[serde(skip_serializing_if = "Option::is_none")] + pub max_priority_fee_per_gas: Option, + /// Max fee per gas + #[serde(skip_serializing_if = "Option::is_none")] + pub max_fee_per_gas: Option, /// The standardised V field of the signature. pub v: U256, /// The R field of the signature. pub r: U256, /// The S field of the signature. pub s: U256, + #[serde(skip_serializing_if = "Option::is_none")] + pub y_parity: Option, } pub enum PackedOrExecuted { @@ -76,6 +95,11 @@ impl Transaction { v: Default::default(), r: Default::default(), s: Default::default(), + access_list: Default::default(), + max_priority_fee_per_gas: Default::default(), + max_fee_per_gas: Default::default(), + y_parity: Default::default(), + transaction_type: Default::default(), }) } @@ -106,7 +130,7 @@ impl Transaction { } let (storage_limit, epoch_height) = if let PrimitiveTransaction::Native(ref tx) = t.unsigned { - (tx.storage_limit, tx.epoch_height) + (*tx.storage_limit(), *tx.epoch_height()) } else { (0, 0) }; @@ -136,6 +160,16 @@ impl Transaction { storage_limit: storage_limit.into(), epoch_height: epoch_height.into(), chain_id: t.chain_id().map(|x| U256::from(x as u64)), + access_list: t + .access_list() + .cloned() + .map(|list| from_primitive_access_list(list, network)), + max_fee_per_gas: t.after_1559().then_some(*t.gas_price()), + max_priority_fee_per_gas: t + .after_1559() + .then_some(*t.max_priority_gas_price()), + y_parity: t.is_2718().then_some(t.transaction.v.into()), + transaction_type: Some(U64::from(t.type_id())), v: t.transaction.v.into(), r: t.transaction.r.into(), s: t.transaction.s.into(), diff --git a/crates/client/src/rpc/types/variadic_u64.rs b/crates/client/src/rpc/types/variadic_u64.rs new file mode 100644 index 0000000000..0f9011b21f --- /dev/null +++ b/crates/client/src/rpc/types/variadic_u64.rs @@ -0,0 +1,44 @@ +use serde::{Deserialize, Deserializer}; +use serde_utils::num::deserialize_u64_from_num_or_hex; +use std::fmt; + +// support both hex strings and number deserialization +#[derive(Debug)] +pub struct U64(u64); + +impl fmt::Display for U64 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for U64 { + fn from(value: u64) -> Self { U64(value) } +} + +impl U64 { + pub fn as_u64(&self) -> u64 { self.0 } +} + +impl<'de> Deserialize<'de> for U64 { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> { + Ok(U64(deserialize_u64_from_num_or_hex(deserializer)?)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_u64_or_hex() { + let json_data = r#""0x1a""#; + let my_struct: U64 = serde_json::from_str(json_data).unwrap(); + assert_eq!(my_struct.as_u64(), 26); + + let json_data = r#"26"#; + let my_struct: U64 = serde_json::from_str(json_data).unwrap(); + assert_eq!(my_struct.as_u64(), 26); + } +} diff --git a/crates/client/src/rpc/types/vote_params_info.rs b/crates/client/src/rpc/types/vote_params_info.rs index 7ae5c2d7af..e6703d080d 100644 --- a/crates/client/src/rpc/types/vote_params_info.rs +++ b/crates/client/src/rpc/types/vote_params_info.rs @@ -6,4 +6,5 @@ pub struct VoteParamsInfo { pub(crate) pow_base_reward: U256, pub(crate) interest_rate: U256, pub(crate) storage_point_prop: U256, + pub(crate) base_fee_share_prop: U256, } diff --git a/crates/dbs/db-errors/Cargo.toml b/crates/dbs/db-errors/Cargo.toml index eb3c3fdf25..8a5be605c1 100644 --- a/crates/dbs/db-errors/Cargo.toml +++ b/crates/dbs/db-errors/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-db-errors" version = "2.0.2" -edition = "2018" +edition = "2021" [dependencies] primitives = { path = "../../primitives" } diff --git a/crates/dbs/db/Cargo.toml b/crates/dbs/db/Cargo.toml index 741428a262..b44d9f2f8c 100644 --- a/crates/dbs/db/Cargo.toml +++ b/crates/dbs/db/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "db" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] log = "0.4" diff --git a/crates/dbs/kvdb-rocksdb/Cargo.toml b/crates/dbs/kvdb-rocksdb/Cargo.toml index affb1e4b74..32f843de8e 100644 --- a/crates/dbs/kvdb-rocksdb/Cargo.toml +++ b/crates/dbs/kvdb-rocksdb/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] repository = "https://github.com/paritytech/parity-common" description = "kvdb implementation backed by rocksDB" license = "GPL-3.0" -edition = "2018" +edition = "2021" [dependencies] cfx-types = { path = "../../cfx_types" } @@ -27,4 +27,4 @@ tempdir = "0.3.7" [dependencies.rocksdb] git = "https://github.com/Conflux-Chain/rust-rocksdb.git" -rev = "a1ce5bd3322a7b732dfb300c2571dc4d99f1edae" +rev = "3773afe5b953997188f37c39308105b5deb0faac" diff --git a/crates/dbs/statedb/Cargo.toml b/crates/dbs/statedb/Cargo.toml index 78e4a8ff48..d5024b8d8f 100644 --- a/crates/dbs/statedb/Cargo.toml +++ b/crates/dbs/statedb/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-statedb" version = "1.0.0" -edition = "2018" +edition = "2021" [dependencies] cfx-internal-common = { path = "../../cfxcore/internal_common" } diff --git a/crates/dbs/statedb/src/global_params.rs b/crates/dbs/statedb/src/global_params.rs index 7dcd9031ee..7cbd209e9a 100644 --- a/crates/dbs/statedb/src/global_params.rs +++ b/crates/dbs/statedb/src/global_params.rs @@ -42,6 +42,8 @@ macro_rules! for_all_global_param_keys { $f::($($args),*); $f::($($args),*); $f::($($args),*); + $f::($($args),*); + $f::($($args),*); }; ($f:ident::($($args:expr),*)?;) => { $f::($($args),*)?; @@ -56,6 +58,8 @@ macro_rules! for_all_global_param_keys { $f::($($args),*)?; $f::($($args),*)?; $f::($($args),*)?; + $f::($($args),*)?; + $f::($($args),*)?; }; } @@ -139,4 +143,16 @@ impl GlobalParamKey for PowBaseReward { const KEY: &'static [u8] = b"pow_base_reward"; } -pub const TOTAL_GLOBAL_PARAMS: usize = PowBaseReward::ID + 1; +pub struct TotalBurnt1559; +impl GlobalParamKey for TotalBurnt1559 { + const ID: usize = PowBaseReward::ID + 1; + const KEY: &'static [u8] = b"total_burnt_tokens_by_cip1559"; +} + +pub struct BaseFeeProp; +impl GlobalParamKey for BaseFeeProp { + const ID: usize = TotalBurnt1559::ID + 1; + const KEY: &'static [u8] = b"base_fee_prop"; +} + +pub const TOTAL_GLOBAL_PARAMS: usize = BaseFeeProp::ID + 1; diff --git a/crates/dbs/storage/Cargo.toml b/crates/dbs/storage/Cargo.toml index d533ff2c01..219092b067 100644 --- a/crates/dbs/storage/Cargo.toml +++ b/crates/dbs/storage/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "cfx-storage" version = "1.0.0" -edition = "2018" +edition = "2021" [dependencies] cfg-if = "0.1" diff --git a/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/recent_lfu.rs b/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/recent_lfu.rs index 89812e5b2e..4c82fe4533 100644 --- a/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/recent_lfu.rs +++ b/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/recent_lfu.rs @@ -436,7 +436,7 @@ impl CacheAlgorithm evicted_r_lfu_metadata.cache_index; hole.pointer_pos.write(evicted_r_lfu_metadata); - // The caller should read the the returned + // The caller should read the returned // CacheAccessResult and // removes the evicted keys. // set_removed isn't necessary but prevent diff --git a/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/removable_heap.rs b/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/removable_heap.rs index c61b807fdd..2109978e0d 100644 --- a/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/removable_heap.rs +++ b/crates/dbs/storage/src/impls/delta_mpt/cache/algorithm/removable_heap.rs @@ -631,7 +631,13 @@ impl RemovableHeap { &mut self, pos: PosT, hole: Hole, replaced: *mut ValueType, value_util: &mut ValueUtilT, ) { - ptr::copy_nonoverlapping(self.get_unchecked_mut(pos), replaced, 1); + { + let src = self.get_unchecked_mut(pos) as *mut ValueType; + let dst = replaced; + if src != dst { + ptr::copy_nonoverlapping(src, dst, 1); + } + } if value_util.get_key_for_comparison(self.get_unchecked_mut(pos)) < value_util.get_key_for_comparison(&hole.value) diff --git a/crates/dbs/storage/src/impls/delta_mpt/node_ref_map.rs b/crates/dbs/storage/src/impls/delta_mpt/node_ref_map.rs index d2f8623f57..52410ca76c 100644 --- a/crates/dbs/storage/src/impls/delta_mpt/node_ref_map.rs +++ b/crates/dbs/storage/src/impls/delta_mpt/node_ref_map.rs @@ -149,13 +149,6 @@ impl NodeRefMapDeltaMpts { pub const DEFAULT_NODE_MAP_SIZE: u32 = 200_000_000; -// Type alias for clarity. -pub trait NodeRefMapTrait { - type StorageAccessKey; - type NodeRef; - type MaybeCacheSlotIndex; -} - use super::{ super::errors::*, cache::algorithm::CacheAlgoDataTrait, node_memory_manager::ActualSlabIndex, row_number::RowNumberUnderlyingType, diff --git a/crates/dbs/storage/src/impls/merkle_patricia_trie/mpt_cursor.rs b/crates/dbs/storage/src/impls/merkle_patricia_trie/mpt_cursor.rs index ebe25a82e5..63bb3b33c4 100644 --- a/crates/dbs/storage/src/impls/merkle_patricia_trie/mpt_cursor.rs +++ b/crates/dbs/storage/src/impls/merkle_patricia_trie/mpt_cursor.rs @@ -1467,6 +1467,7 @@ impl Drop for ReadWritePathNode { } pub trait GetReadMpt { + #[allow(unused)] fn get_merkle_root(&self) -> MerkleHash; fn get_read_mpt(&mut self) -> &mut dyn SnapshotMptTraitRead; diff --git a/crates/dbs/storage/src/utils/tuple.rs b/crates/dbs/storage/src/utils/tuple.rs index 53030ee154..633d00e18e 100644 --- a/crates/dbs/storage/src/utils/tuple.rs +++ b/crates/dbs/storage/src/utils/tuple.rs @@ -37,6 +37,7 @@ pub trait TupleIndexExt: Sized { /// We don't support generics and lifetime yet because it will take some time to /// update the macro implementation. +#[cfg(test)] macro_rules! make_tuple_with_index_ext { ( $tuple_struct_name:ident($($element_type:ty$(: $pub_vis:tt)*),*) ) => { #[derive(Default, Clone)] @@ -281,6 +282,7 @@ mod macros { } } + #[cfg(test)] macro_rules! make_get_index_ext { ( $tuple_type:ty, @@ -318,6 +320,7 @@ mod macros { }; } + #[cfg(test)] macro_rules! make_get_index_ext_all { ( $tuple_struct_name:ident(), @@ -454,7 +457,7 @@ pub mod placeholders { } } -//#[cfg(test)] +#[cfg(test)] mod test { use super::*; diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index fcc5910fb7..682b073f01 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "network" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] cfx-addr = { path = "../cfx_addr" } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 330dad862d..254d0a6981 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "primitives" version = "0.2.0" -edition = "2018" +edition = "2021" [dependencies] byteorder = "1.2.7" @@ -25,6 +25,7 @@ once_cell = "1.17.1" [dev-dependencies] criterion = "0.3" serde_json = "1.0" +itertools = "0.10" [[bench]] name = "benchmark" diff --git a/crates/primitives/src/block_header.rs b/crates/primitives/src/block_header.rs index a24970934a..357555cc1b 100644 --- a/crates/primitives/src/block_header.rs +++ b/crates/primitives/src/block_header.rs @@ -2,14 +2,23 @@ // Conflux is free software and distributed under GNU General Public License. // See http://www.gnu.org/licenses/ +mod base_price; +pub use base_price::{ + compute_next_price, compute_next_price_tuple, estimate_gas_used_boundary, + estimate_max_possible_gas, +}; + use crate::{ block::BlockHeight, bytes::Bytes, hash::keccak, pos::PosBlockId, receipt::BlockReceipts, MERKLE_NULL_NODE, NULL_EPOCH, }; -use cfx_types::{Address, Bloom, H256, KECCAK_EMPTY_BLOOM, U256}; +use cfx_types::{ + Address, Bloom, Space, SpaceMap, H256, KECCAK_EMPTY_BLOOM, U256, +}; use malloc_size_of::{new_malloc_size_ops, MallocSizeOf, MallocSizeOfOps}; use once_cell::sync::OnceCell; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; +use rlp_derive::{RlpDecodable, RlpEncodable}; use std::{ mem, ops::{Deref, DerefMut}, @@ -21,6 +30,8 @@ const HEADER_LIST_MIN_LEN: usize = 13; /// field. pub static CIP112_TRANSITION_HEIGHT: OnceCell = OnceCell::new(); +const BASE_PRICE_CHANGE_DENOMINATOR: usize = 8; + #[derive(Clone, Debug, Eq)] pub struct BlockHeaderRlpPart { /// Parent hash. @@ -58,6 +69,8 @@ pub struct BlockHeaderRlpPart { nonce: U256, /// Referred PoS block ID. pos_reference: Option, + /// `[core_space_base_price, espace_base_price]`. + base_price: Option, } impl PartialEq for BlockHeaderRlpPart { @@ -76,6 +89,8 @@ impl PartialEq for BlockHeaderRlpPart { && self.gas_limit == o.gas_limit && self.referee_hashes == o.referee_hashes && self.custom == o.custom + && self.pos_reference == o.pos_reference + && self.base_price == o.base_price } } @@ -167,6 +182,15 @@ impl BlockHeader { /// Get the PoS reference. pub fn pos_reference(&self) -> &Option { &self.pos_reference } + pub fn base_price(&self) -> Option> { + self.base_price.map( + |BasePrice { + core_base_price, + espace_base_price, + }| SpaceMap::new(core_base_price, espace_base_price), + ) + } + /// Set the nonce field of the header. pub fn set_nonce(&mut self, nonce: U256) { self.nonce = nonce; } @@ -212,6 +236,7 @@ impl BlockHeader { let adaptive_n = if self.adaptive { 1 as u8 } else { 0 as u8 }; let list_len = HEADER_LIST_MIN_LEN + self.pos_reference.is_some() as usize + + self.base_price.is_some() as usize + self.custom.len(); stream .begin_list(list_len) @@ -231,6 +256,9 @@ impl BlockHeader { if self.pos_reference.is_some() { stream.append(&self.pos_reference); } + if self.base_price.is_some() { + stream.append(&self.base_price); + } for b in &self.custom { if self.height @@ -249,6 +277,7 @@ impl BlockHeader { let list_len = HEADER_LIST_MIN_LEN + 1 + self.pos_reference.is_some() as usize + + self.base_price.is_some() as usize + self.custom.len(); stream .begin_list(list_len) @@ -269,6 +298,9 @@ impl BlockHeader { if self.pos_reference.is_some() { stream.append(&self.pos_reference); } + if self.base_price.is_some() { + stream.append(&self.base_price); + } for b in &self.custom { if self.height >= *CIP112_TRANSITION_HEIGHT.get().expect("initialized") @@ -286,6 +318,7 @@ impl BlockHeader { let list_len = HEADER_LIST_MIN_LEN + 2 + self.pos_reference.is_some() as usize + + self.base_price.is_some() as usize + self.custom.len(); stream .begin_list(list_len) @@ -309,6 +342,9 @@ impl BlockHeader { if self.pos_reference.is_some() { stream.append(&self.pos_reference); } + if self.base_price.is_some() { + stream.append(&self.base_price); + } for b in &self.custom { if self.height @@ -340,11 +376,14 @@ impl BlockHeader { custom: vec![], nonce: r.val_at(13)?, pos_reference: r.val_at(15).unwrap_or(None), + base_price: r.val_at(16).unwrap_or(None), }; let pow_hash = r.val_at(14)?; - for i in - (15 + rlp_part.pos_reference.is_some() as usize)..r.item_count()? + for i in (15 + + rlp_part.pos_reference.is_some() as usize + + rlp_part.base_price.is_some() as usize) + ..r.item_count()? { if rlp_part.height >= *CIP112_TRANSITION_HEIGHT.get().expect("initialized") @@ -389,6 +428,7 @@ pub struct BlockHeaderBuilder { custom: Vec, nonce: U256, pos_reference: Option, + base_price: Option, } impl BlockHeaderBuilder { @@ -410,6 +450,7 @@ impl BlockHeaderBuilder { custom: Vec::new(), nonce: U256::zero(), pos_reference: None, + base_price: None, } } @@ -505,6 +546,16 @@ impl BlockHeaderBuilder { self } + pub fn with_base_price( + &mut self, maybe_base_price: Option>, + ) -> &mut Self { + self.base_price = maybe_base_price.map(|x| BasePrice { + core_base_price: x[Space::Native], + espace_base_price: x[Space::Ethereum], + }); + self + } + pub fn build(&self) -> BlockHeader { let mut block_header = BlockHeader { rlp_part: BlockHeaderRlpPart { @@ -524,6 +575,7 @@ impl BlockHeaderBuilder { custom: self.custom.clone(), nonce: self.nonce, pos_reference: self.pos_reference, + base_price: self.base_price.clone(), }, hash: None, pow_hash: None, @@ -606,9 +658,12 @@ impl Decodable for BlockHeader { custom: vec![], nonce: r.val_at(13)?, pos_reference: r.val_at(14).unwrap_or(None), + base_price: r.val_at(15).unwrap_or(None), }; - for i in - (14 + rlp_part.pos_reference.is_some() as usize)..r.item_count()? + for i in (14 + + rlp_part.pos_reference.is_some() as usize + + rlp_part.base_price.is_some() as usize) + ..r.item_count()? { if rlp_part.height >= *CIP112_TRANSITION_HEIGHT.get().expect("initialized") @@ -631,6 +686,11 @@ impl Decodable for BlockHeader { } } +#[derive(Clone, Copy, Debug, Eq, RlpDecodable, RlpEncodable, PartialEq)] +pub struct BasePrice { + pub core_base_price: U256, + pub espace_base_price: U256, +} #[cfg(test)] mod tests { use super::BlockHeaderBuilder; @@ -674,6 +734,7 @@ mod tests { storage_sponsor_paid: false, storage_collateralized: vec![], storage_released: vec![], + burnt_gas_fee: None, }; // 10 blocks with 10 empty receipts each @@ -723,6 +784,7 @@ mod tests { storage_sponsor_paid: false, storage_collateralized: vec![], storage_released: vec![], + burnt_gas_fee: None, }, Receipt { accumulated_gas_used: U256::zero(), @@ -752,6 +814,7 @@ mod tests { storage_sponsor_paid: false, storage_collateralized: vec![], storage_released: vec![], + burnt_gas_fee: None, }, ], block_number: 0, @@ -788,6 +851,7 @@ mod tests { storage_sponsor_paid: false, storage_collateralized: vec![], storage_released: vec![], + burnt_gas_fee: None, }], block_number: 0, secondary_reward: U256::zero(), diff --git a/crates/primitives/src/block_header/base_price.rs b/crates/primitives/src/block_header/base_price.rs new file mode 100644 index 0000000000..02b9550c9b --- /dev/null +++ b/crates/primitives/src/block_header/base_price.rs @@ -0,0 +1,201 @@ +use super::BASE_PRICE_CHANGE_DENOMINATOR as DENOM; +use cfx_types::U256; +use log::warn; + +pub fn compute_next_price( + gas_target: U256, gas_actual: U256, last_base_price: U256, + min_base_price: U256, +) -> U256 { + let gas_actual = if gas_actual > gas_target * 2 { + warn!("gas target is larger than expected"); + gas_target * 2 + } else { + gas_actual + }; + + let next_base_price = if gas_target.is_zero() || gas_target == gas_actual { + last_base_price + } else { + let (gas_delta, delta_sign) = if gas_actual > gas_target { + (gas_actual - gas_target, true) + } else { + (gas_target - gas_actual, false) + }; + + assert!(gas_delta <= gas_target); + + let mut price_delta = gas_delta * last_base_price / gas_target / DENOM; + if price_delta.is_zero() { + price_delta = U256::one() + } + + if delta_sign { + last_base_price + price_delta + } else if !last_base_price.is_zero() { + last_base_price - price_delta + } else { + U256::zero() + } + }; + + next_base_price.max(min_base_price) +} + +pub fn estimate_max_possible_gas( + gas_target: U256, current_base_price: U256, last_base_price: U256, +) -> U256 { + let (_, upper_boundary) = estimate_gas_used_boundary( + gas_target, + current_base_price, + last_base_price, + ); + match upper_boundary { + None => gas_target * 2, + Some(U256([0, 0, 0, 0])) => U256::zero(), + Some(x) => x - 1, + } +} + +// Returns the outside boundary gas usage values that define the range +// +// The first item represents the maximum value that the next_price < +// current_base_price. +// +// The second item represents the minimum value that the +// next_price > current_base_price. +pub fn estimate_gas_used_boundary( + gas_target: U256, current_base_price: U256, last_base_price: U256, +) -> (Option, Option) { + if gas_target.is_zero() { + return (None, Some(1.into())); + } + + if last_base_price.is_zero() { + return if current_base_price == U256::zero() { + (None, Some(gas_target + 1)) + } else if current_base_price == U256::one() { + (Some(gas_target), Some(gas_target * 2 + 1)) + } else { + (Some(gas_target * 2), None) + }; + } + + let max_price_delta = U256::max(U256::one(), last_base_price / DENOM); + let upper_base_price = last_base_price + max_price_delta; + let lower_base_price = last_base_price - max_price_delta; + + if current_base_price > upper_base_price { + (Some(gas_target * 2), None) + } else if current_base_price < lower_base_price { + (None, Some(U256::zero())) + } else if current_base_price == last_base_price { + (Some(gas_target - 1), Some(gas_target + 1)) + } else { + let (price_delta, delta_sign) = if current_base_price > last_base_price + { + (current_base_price - last_base_price, true) + } else { + (last_base_price - current_base_price, false) + }; + + assert!(!price_delta.is_zero()); + + let lower_bound = if price_delta == U256::one() { + U256::zero() + } else { + (price_delta * gas_target * DENOM - 1) / last_base_price + }; + + let upper_bound = + ((price_delta + 1) * gas_target * DENOM + last_base_price - 1) + / last_base_price; + + if delta_sign { + ( + Some(gas_target + lower_bound), + Some(U256::min(gas_target + upper_bound, gas_target * 2 + 1)), + ) + } else { + ( + // Underflow could happen + gas_target.checked_sub(upper_bound), + Some(gas_target - lower_bound), + ) + } + } +} + +/// A helper function for `compute_next_price` which takes a typle as input +pub fn compute_next_price_tuple(x: (U256, U256, U256, U256)) -> U256 { + compute_next_price(x.0, x.1, x.2, x.3) +} + +#[cfg(test)] +mod tests { + use crate::block_header::compute_next_price; + + use super::{estimate_gas_used_boundary, DENOM, U256}; + use itertools::Itertools; + + fn test_boundary(gas_target: usize, last_base_price: usize) { + let max_price_delta = usize::max(1, last_base_price / DENOM); + + let start = last_base_price.saturating_sub(max_price_delta); + let end = last_base_price.saturating_add(max_price_delta); + + let mut next_start = 0usize; + + for price in start..=end { + // dbg!(gas_target, price, last_base_price); + let (lo, up) = estimate_gas_used_boundary( + gas_target.into(), + price.into(), + last_base_price.into(), + ); + let s = lo.map_or(0, |x| x.as_usize() + 1); + let e = up.unwrap().as_usize(); + assert_eq!(s, next_start); + next_start = e; + for gas_used in s..e { + let next_price = compute_next_price( + gas_target.into(), + gas_used.into(), + last_base_price.into(), + U256::zero(), + ) + .as_usize(); + assert_eq!(next_price, price); + } + } + assert_eq!(next_start, gas_target * 2 + 1); + + if start > 0 { + let res = estimate_gas_used_boundary( + gas_target.into(), + (start - 1).into(), + last_base_price.into(), + ); + assert_eq!(res, (None, Some(U256::zero()))) + } + + let res = estimate_gas_used_boundary( + gas_target.into(), + (end + 1).into(), + last_base_price.into(), + ); + assert_eq!(res, (Some((gas_target * 2).into()), None)); + } + + #[test] + fn test_gas_used_estimation() { + let tasks = [1, 2, 3, 4, 5, 8, 10, 991, 1000, 1019, 9949, 10000, 10067] + .into_iter() + .cartesian_product([ + 0, 1, 2, 3, 4, 5, 8, 10, 991, 1000, 1019, 9949, 10000, 10067, + ]); + + for (gas_target, last_base_price) in tasks.into_iter() { + test_boundary(gas_target, last_base_price); + } + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b140404938..3738b291d1 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -50,9 +50,9 @@ pub use crate::{ }, storage_key::*, transaction::{ - Action, Eip155Transaction, NativeTransaction, SignedTransaction, - Transaction, TransactionWithSignature, - TransactionWithSignatureSerializePart, TxPropagateId, + AccessList, AccessListItem, Action, SignedTransaction, Transaction, + TransactionWithSignature, TransactionWithSignatureSerializePart, + TxPropagateId, }, transaction_index::TransactionIndex, zero::Zero, diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 193579450d..54ef026d9c 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -91,7 +91,7 @@ pub struct SortedStorageChanges { } /// Information describing execution of a transaction. -#[derive(Debug, Clone, PartialEq, Eq, RlpDecodable, RlpEncodable)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct Receipt { /// The total gas used (not gas charged) in the block following execution /// of the transaction. @@ -110,6 +110,51 @@ pub struct Receipt { pub storage_sponsor_paid: bool, pub storage_collateralized: Vec, pub storage_released: Vec, + pub burnt_gas_fee: Option, +} + +impl Encodable for Receipt { + fn rlp_append(&self, s: &mut RlpStream) { + let length = if self.burnt_gas_fee.is_none() { 9 } else { 10 }; + s.begin_list(length) + .append(&self.accumulated_gas_used) + .append(&self.gas_fee) + .append(&self.gas_sponsor_paid) + .append(&self.log_bloom) + .append_list(&self.logs) + .append(&self.outcome_status) + .append(&self.storage_sponsor_paid) + .append_list(&self.storage_collateralized) + .append_list(&self.storage_released); + if let Some(burnt_gas_fee) = self.burnt_gas_fee { + s.append(&burnt_gas_fee); + } + } +} + +impl Decodable for Receipt { + fn decode(rlp: &Rlp) -> Result { + let item_count = rlp.item_count()?; + if !matches!(item_count, 9..=10) { + return Err(DecoderError::RlpIncorrectListLen); + } + Ok(Receipt { + accumulated_gas_used: rlp.val_at(0)?, + gas_fee: rlp.val_at(1)?, + gas_sponsor_paid: rlp.val_at(2)?, + log_bloom: rlp.val_at(3)?, + logs: rlp.list_at(4)?, + outcome_status: rlp.val_at(5)?, + storage_sponsor_paid: rlp.val_at(6)?, + storage_collateralized: rlp.list_at(7)?, + storage_released: rlp.list_at(8)?, + burnt_gas_fee: if item_count == 9 { + None + } else { + Some(rlp.val_at(9)?) + }, + }) + } } impl Receipt { @@ -117,7 +162,7 @@ impl Receipt { outcome: TransactionStatus, accumulated_gas_used: U256, gas_fee: U256, gas_sponsor_paid: bool, logs: Vec, log_bloom: Bloom, storage_sponsor_paid: bool, storage_collateralized: Vec, - storage_released: Vec, + storage_released: Vec, burnt_gas_fee: Option, ) -> Self { Self { accumulated_gas_used, @@ -129,6 +174,7 @@ impl Receipt { storage_sponsor_paid, storage_collateralized, storage_released, + burnt_gas_fee, } } @@ -184,3 +230,17 @@ fn test_transaction_outcome_rlp() { assert_eq!(rlp::encode(&TransactionStatus::Failure), rlp::encode(&1u8)); assert_eq!(rlp::encode(&TransactionStatus::Skipped), rlp::encode(&2u8)); } + +#[test] +fn test_receipt_rlp_serde() { + let mut receipt = Receipt { + accumulated_gas_used: 189000.into(), + gas_fee: 60054.into(), + burnt_gas_fee: Some(30027.into()), + ..Default::default() + }; + assert_eq!(receipt, Rlp::new(&receipt.rlp_bytes()).as_val().unwrap()); + + receipt.burnt_gas_fee = None; + assert_eq!(receipt, Rlp::new(&receipt.rlp_bytes()).as_val().unwrap()); +} diff --git a/crates/primitives/src/transaction/eth_transaction.rs b/crates/primitives/src/transaction/eth_transaction.rs new file mode 100644 index 0000000000..e83c321d56 --- /dev/null +++ b/crates/primitives/src/transaction/eth_transaction.rs @@ -0,0 +1,287 @@ +use crate::{ + transaction::AccessList, Action, SignedTransaction, Transaction, + TransactionWithSignature, TransactionWithSignatureSerializePart, +}; +use bytes::Bytes; +use cfx_types::{AddressWithSpace, H256, U256}; +use rlp::{Encodable, RlpStream}; +use serde_derive::{Deserialize, Serialize}; + +impl Eip155Transaction { + /// Fake sign phantom transactions. + // The signature is part of the hash input. This implementation + // ensures that phantom transactions whose fields are identical + // will have different hashes. + pub fn fake_sign_phantom( + self, from: AddressWithSpace, + ) -> SignedTransaction { + SignedTransaction { + transaction: TransactionWithSignature { + transaction: TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum( + EthereumTransaction::Eip155(self), + ), + // we use sender address for `r` and `s` so that phantom + // transactions with matching fields from different senders + // will have different hashes + r: U256::from(from.address.as_ref()), + s: U256::from(from.address.as_ref()), + v: 0, + }, + hash: H256::zero(), + rlp_size: None, + } + .compute_hash(), + sender: from.address, + public: None, + } + } + + /// Fake sign call requests in `eth_call`. + // `fake_sign_phantom` will use zero signature when the sender is the + // zero address, and that will fail basic signature verification. + pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction { + SignedTransaction { + transaction: TransactionWithSignature { + transaction: TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum( + EthereumTransaction::Eip155(self), + ), + r: U256::one(), + s: U256::one(), + v: 0, + }, + hash: H256::zero(), + rlp_size: None, + } + .compute_hash(), + sender: from.address, + public: None, + } + } +} + +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Eip155Transaction { + /// Nonce. + pub nonce: U256, + /// Gas price. + pub gas_price: U256, + /// Gas paid up front for transaction execution. + pub gas: U256, + /// Action, can be either call or contract create. + pub action: Action, + /// Transferred value. + pub value: U256, + /// The chain id of the transaction + pub chain_id: Option, + /// Transaction data. + pub data: Bytes, +} + +impl Encodable for Eip155Transaction { + fn rlp_append(&self, s: &mut RlpStream) { + match self.chain_id { + Some(chain_id) => { + s.begin_list(9); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + s.append(&self.action); + s.append(&self.value); + s.append(&self.data); + s.append(&chain_id); + s.append(&0u8); + s.append(&0u8); + } + None => { + s.begin_list(6); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + s.append(&self.action); + s.append(&self.value); + s.append(&self.data); + } + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Eip2930Transaction { + pub chain_id: u32, + pub nonce: U256, + pub gas_price: U256, + pub gas: U256, + pub action: Action, + pub value: U256, + pub data: Bytes, + pub access_list: AccessList, +} + +impl Encodable for Eip2930Transaction { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(8); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.gas_price); + s.append(&self.gas); + s.append(&self.action); + s.append(&self.value); + s.append(&self.data); + s.append_list(&self.access_list); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Eip1559Transaction { + pub chain_id: u32, + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas: U256, + pub action: Action, + pub value: U256, + pub data: Bytes, + pub access_list: AccessList, +} + +impl Encodable for Eip1559Transaction { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(9); + s.append(&self.chain_id); + s.append(&self.nonce); + s.append(&self.max_priority_fee_per_gas); + s.append(&self.max_fee_per_gas); + s.append(&self.gas); + s.append(&self.action); + s.append(&self.value); + s.append(&self.data); + s.append_list(&self.access_list); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum EthereumTransaction { + Eip155(Eip155Transaction), + Eip1559(Eip1559Transaction), + Eip2930(Eip2930Transaction), +} +use EthereumTransaction::*; + +impl EthereumTransaction { + pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction { + SignedTransaction { + transaction: TransactionWithSignature { + transaction: TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum(self), + r: U256::one(), + s: U256::one(), + v: 0, + }, + hash: H256::zero(), + rlp_size: None, + } + .compute_hash(), + sender: from.address, + public: None, + } + } +} + +macro_rules! eth_access_common_ref { + ($field:ident, $ty:ty) => { + pub fn $field(&self) -> &$ty { + match self { + EthereumTransaction::Eip155(tx) => &tx.$field, + EthereumTransaction::Eip2930(tx) => &tx.$field, + EthereumTransaction::Eip1559(tx) => &tx.$field, + } + } + }; +} + +impl EthereumTransaction { + eth_access_common_ref!(gas, U256); + + eth_access_common_ref!(data, Bytes); + + eth_access_common_ref!(nonce, U256); + + eth_access_common_ref!(action, Action); + + eth_access_common_ref!(value, U256); + + pub fn gas_price(&self) -> &U256 { + match self { + Eip155(tx) => &tx.gas_price, + Eip1559(tx) => &tx.max_fee_per_gas, + Eip2930(tx) => &tx.gas_price, + } + } + + pub fn max_priority_gas_price(&self) -> &U256 { + match self { + Eip155(tx) => &tx.gas_price, + Eip1559(tx) => &tx.max_priority_fee_per_gas, + Eip2930(tx) => &tx.gas_price, + } + } + + pub fn chain_id(&self) -> Option { + match self { + Eip155(tx) => tx.chain_id, + Eip1559(tx) => Some(tx.chain_id), + Eip2930(tx) => Some(tx.chain_id), + } + } + + pub fn nonce_mut(&mut self) -> &mut U256 { + match self { + Eip155(tx) => &mut tx.nonce, + Eip2930(tx) => &mut tx.nonce, + Eip1559(tx) => &mut tx.nonce, + } + } + + pub fn access_list(&self) -> Option<&AccessList> { + match self { + Eip155(_tx) => None, + Eip2930(tx) => Some(&tx.access_list), + Eip1559(tx) => Some(&tx.access_list), + } + } +} + +/// Replay protection logic for v part of transaction's signature +pub mod eip155_signature { + /// Adds chain id into v + pub fn add_chain_replay_protection(v: u8, chain_id: Option) -> u64 { + v as u64 + + if let Some(n) = chain_id { + 35 + n * 2 + } else { + 27 + } + } + + /// Returns refined v + /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if + /// invalid. + pub fn extract_standard_v(v: u64) -> u8 { + match v { + v if v == 27 => 0, + v if v == 28 => 1, + v if v >= 35 => ((v - 1) % 2) as u8, + _ => 4, + } + } + + pub fn extract_chain_id_from_legacy_v(v: u64) -> Option { + if v >= 35 { + Some((v - 35) / 2 as u64) + } else { + None + } + } +} diff --git a/crates/primitives/src/transaction.rs b/crates/primitives/src/transaction/mod.rs similarity index 53% rename from crates/primitives/src/transaction.rs rename to crates/primitives/src/transaction/mod.rs index ea7d72d971..eb1528ace7 100644 --- a/crates/primitives/src/transaction.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -2,17 +2,29 @@ // Conflux is free software and distributed under GNU General Public License. // See http://www.gnu.org/licenses/ +pub mod eth_transaction; +pub mod native_transaction; + +pub use eth_transaction::{ + Eip1559Transaction, Eip155Transaction, Eip2930Transaction, + EthereumTransaction, +}; +pub use native_transaction::{ + Cip1559Transaction, Cip2930Transaction, NativeTransaction, + TypedNativeTransaction, +}; + use crate::{bytes::Bytes, hash::keccak}; use cfx_types::{ Address, AddressSpaceUtil, AddressWithSpace, BigEndianHash, Space, H160, H256, U256, }; +use eth_transaction::eip155_signature; use keylib::{ self, public_to_address, recover, verify_public, Public, Secret, Signature, }; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use rlp::{self, Decodable, DecoderError, Encodable, Rlp, RlpStream}; -use rlp_derive::{RlpDecodable, RlpEncodable}; use serde::{Deserialize, Serialize}; use std::{ error, fmt, @@ -23,6 +35,14 @@ use unexpected::OutOfBounds; /// Fake address for unsigned transactions. pub const UNSIGNED_SENDER: Address = H160([0xff; 20]); +pub const TYPED_NATIVE_TX_PREFIX: &[u8; 3] = b"cfx"; +pub const TYPED_NATIVE_TX_PREFIX_BYTE: u8 = TYPED_NATIVE_TX_PREFIX[0]; +pub const LEGACY_TX_TYPE: u8 = 0x00; +pub const EIP2930_TYPE: u8 = 0x01; +pub const EIP1559_TYPE: u8 = 0x02; +pub const CIP2930_TYPE: u8 = 0x01; +pub const CIP1559_TYPE: u8 = 0x02; + /// Shorter id for transactions in compact blocks // TODO should be u48 pub type TxShortId = u64; @@ -102,8 +122,8 @@ pub enum TransactionError { /// Invalid RLP encoding InvalidRlp(String), ZeroGasPrice, - /// Ethereum-like transaction with invalid storage limit. - InvalidEthereumLike, + /// Transaction types have not been activated + FutureTransactionType, /// Receiver with invalid type bit. InvalidReceiver, /// Transaction nonce exceeds local limit. @@ -169,7 +189,7 @@ impl fmt::Display for TransactionError { format!("Transaction has invalid RLP structure: {}.", err) } ZeroGasPrice => "Zero gas price is not allowed".into(), - InvalidEthereumLike => "Ethereum like transaction should have u64::MAX storage limit".into(), + FutureTransactionType => "Ethereum like transaction should have u64::MAX storage limit".into(), InvalidReceiver => "Sending transaction to invalid address. The first four bits of address must be 0x0, 0x1, or 0x8.".into(), TooLargeNonce => "Transaction nonce is too large.".into(), }; @@ -214,234 +234,62 @@ impl Encodable for Action { } } -#[derive( - Default, - Debug, - Clone, - Eq, - PartialEq, - RlpEncodable, - RlpDecodable, - Serialize, - Deserialize, -)] -pub struct NativeTransaction { - /// Nonce. - pub nonce: U256, - /// Gas price. - pub gas_price: U256, - /// Gas paid up front for transaction execution. - pub gas: U256, - /// Action, can be either call or contract create. - pub action: Action, - /// Transferred value. - pub value: U256, - /// Maximum storage increasement in this execution. - pub storage_limit: u64, - /// The epoch height of the transaction. A transaction - /// can only be packed between the epochs of [epoch_height - - /// TRANSACTION_EPOCH_BOUND, epoch_height + TRANSACTION_EPOCH_BOUND] - pub epoch_height: u64, - /// The chain id of the transaction - pub chain_id: u32, - /// Transaction data. - pub data: Bytes, -} - -impl NativeTransaction { - /// Specify the sender; this won't survive the serialize/deserialize - /// process, but can be cloned. - pub fn fake_sign(self, from: AddressWithSpace) -> SignedTransaction { - SignedTransaction { - transaction: TransactionWithSignature { - transaction: TransactionWithSignatureSerializePart { - unsigned: Transaction::Native(self), - r: U256::one(), - s: U256::one(), - v: 0, - }, - hash: H256::zero(), - rlp_size: None, - } - .compute_hash(), - sender: from.address, - public: None, - } - } -} - -/// Replay protection logic for v part of transaction's signature -pub mod eip155_signature { - /// Adds chain id into v - pub fn add_chain_replay_protection(v: u8, chain_id: Option) -> u64 { - v as u64 - + if let Some(n) = chain_id { - 35 + n * 2 - } else { - 27 - } - } - - /// Returns refined v - /// 0 if `v` would have been 27 under "Electrum" notation, 1 if 28 or 4 if - /// invalid. - pub fn extract_standard_v(v: u64) -> u8 { - match v { - v if v == 27 => 0, - v if v == 28 => 1, - v if v >= 35 => ((v - 1) % 2) as u8, - _ => 4, - } - } - - pub fn extract_chain_id_from_legacy_v(v: u64) -> Option { - if v >= 35 { - Some((v - 35) / 2 as u64) - } else { - None - } - } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AccessListItem { + pub address: Address, + pub storage_keys: Vec, } -impl Eip155Transaction { - /// Fake sign phantom transactions. - // The signature is part of the hash input. This implementation - // ensures that phantom transactions whose fields are identical - // will have different hashes. - pub fn fake_sign_phantom( - self, from: AddressWithSpace, - ) -> SignedTransaction { - SignedTransaction { - transaction: TransactionWithSignature { - transaction: TransactionWithSignatureSerializePart { - unsigned: Transaction::Ethereum(self), - // we use sender address for `r` and `s` so that phantom - // transactions with matching fields from different senders - // will have different hashes - r: U256::from(from.address.as_ref()), - s: U256::from(from.address.as_ref()), - v: 0, - }, - hash: H256::zero(), - rlp_size: None, - } - .compute_hash(), - sender: from.address, - public: None, - } - } - - /// Fake sign call requests in `eth_call`. - // `fake_sign_phantom` will use zero signature when the sender is the - // zero address, and that will fail basic signature verification. - pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction { - SignedTransaction { - transaction: TransactionWithSignature { - transaction: TransactionWithSignatureSerializePart { - unsigned: Transaction::Ethereum(self), - r: U256::one(), - s: U256::one(), - v: 0, - }, - hash: H256::zero(), - rlp_size: None, - } - .compute_hash(), - sender: from.address, - public: None, - } +impl Encodable for AccessListItem { + fn rlp_append(&self, s: &mut RlpStream) { + s.begin_list(2); + s.append(&self.address); + s.append_list(&self.storage_keys); } } -#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct Eip155Transaction { - /// Nonce. - pub nonce: U256, - /// Gas price. - pub gas_price: U256, - /// Gas paid up front for transaction execution. - pub gas: U256, - /// Action, can be either call or contract create. - pub action: Action, - /// Transferred value. - pub value: U256, - /// The chain id of the transaction - pub chain_id: Option, - /// Transaction data. - pub data: Bytes, -} - -impl Encodable for Eip155Transaction { - fn rlp_append(&self, s: &mut RlpStream) { - match self.chain_id { - Some(chain_id) => { - s.begin_list(9); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - s.append(&self.action); - s.append(&self.value); - s.append(&self.data); - s.append(&chain_id); - s.append(&0u8); - s.append(&0u8); - } - None => { - s.begin_list(6); - s.append(&self.nonce); - s.append(&self.gas_price); - s.append(&self.gas); - s.append(&self.action); - s.append(&self.value); - s.append(&self.data); - } - } +impl Decodable for AccessListItem { + fn decode(rlp: &Rlp) -> Result { + Ok(Self { + address: rlp.val_at(0)?, + storage_keys: rlp.list_at(1)?, + }) } } -// impl Decodable for Eip155Transaction { -// fn decode(rlp: &Rlp) -> Result { -// if !(rlp.at(7)?.is_empty() && rlp.at(8)?.is_empty()) { -// return Err(DecoderError::Custom( -// "The last two items should be empty", -// )); -// } -// Ok(Self { -// nonce: rlp.val_at(0)?, -// gas_price: rlp.val_at(1)?, -// gas: rlp.val_at(2)?, -// action: rlp.val_at(3)?, -// value: rlp.val_at(4)?, -// chain_id: rlp.val_at(5)?, -// data: rlp.val_at(6)?, -// }) -// } -// } +pub type AccessList = Vec; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Transaction { - Native(NativeTransaction), - Ethereum(Eip155Transaction), + Native(TypedNativeTransaction), + Ethereum(EthereumTransaction), } impl Default for Transaction { - fn default() -> Self { Transaction::Native(Default::default()) } + fn default() -> Self { + Transaction::Native(TypedNativeTransaction::Cip155(Default::default())) + } } impl From for Transaction { - fn from(tx: NativeTransaction) -> Self { Self::Native(tx) } + fn from(tx: NativeTransaction) -> Self { + Self::Native(TypedNativeTransaction::Cip155(tx)) + } } impl From for Transaction { - fn from(tx: Eip155Transaction) -> Self { Self::Ethereum(tx) } + fn from(tx: Eip155Transaction) -> Self { + Self::Ethereum(EthereumTransaction::Eip155(tx)) + } } macro_rules! access_common_ref { - ($field:ident, $ty:ident) => { + ($field:ident, $ty:ty) => { pub fn $field(&self) -> &$ty { match self { - Transaction::Native(tx) => &tx.$field, - Transaction::Ethereum(tx) => &tx.$field, + Transaction::Native(tx) => tx.$field(), + Transaction::Ethereum(tx) => tx.$field(), } } }; @@ -463,6 +311,8 @@ impl Transaction { access_common_ref!(gas_price, U256); + access_common_ref!(max_priority_gas_price, U256); + access_common_ref!(data, Bytes); access_common_ref!(nonce, U256); @@ -473,41 +323,113 @@ impl Transaction { pub fn chain_id(&self) -> Option { match self { - Transaction::Native(tx) => Some(tx.chain_id), - Transaction::Ethereum(tx) => tx.chain_id, + Transaction::Native(tx) => Some(*tx.chain_id()), + Transaction::Ethereum(tx) => tx.chain_id().clone(), } } pub fn storage_limit(&self) -> Option { match self { - Transaction::Native(tx) => Some(tx.storage_limit), + Transaction::Native(tx) => Some(*tx.storage_limit()), Transaction::Ethereum(_tx) => None, } } pub fn nonce_mut(&mut self) -> &mut U256 { match self { - Transaction::Native(tx) => &mut tx.nonce, - Transaction::Ethereum(tx) => &mut tx.nonce, + Transaction::Native(tx) => tx.nonce_mut(), + Transaction::Ethereum(tx) => tx.nonce_mut(), + } + } + + pub fn type_id(&self) -> u8 { + match self { + Transaction::Native(TypedNativeTransaction::Cip155(_)) + | Transaction::Ethereum(EthereumTransaction::Eip155(_)) => 0, + + Transaction::Native(TypedNativeTransaction::Cip2930(_)) + | Transaction::Ethereum(EthereumTransaction::Eip2930(_)) => 1, + + Transaction::Native(TypedNativeTransaction::Cip1559(_)) + | Transaction::Ethereum(EthereumTransaction::Eip1559(_)) => 2, + } + } + + pub fn is_legacy(&self) -> bool { + matches!( + self, + Transaction::Native(TypedNativeTransaction::Cip155(_)) + | Transaction::Ethereum(EthereumTransaction::Eip155(_)) + ) + } + + pub fn is_2718(&self) -> bool { !self.is_legacy() } + + pub fn after_1559(&self) -> bool { + matches!( + self, + Transaction::Native(TypedNativeTransaction::Cip1559(_)) + | Transaction::Ethereum(EthereumTransaction::Eip1559(_)) + ) + } + + pub fn access_list(&self) -> Option<&AccessList> { + match self { + Transaction::Native(tx) => tx.access_list(), + Transaction::Ethereum(tx) => tx.access_list(), } } } impl Transaction { + pub fn priority_gas_price(&self, base_price: &U256) -> U256 { + std::cmp::min( + *self.max_priority_gas_price(), + self.gas_price() - base_price, + ) + } + + pub fn effective_gas_price(&self, base_price: &U256) -> U256 { + base_price + self.priority_gas_price(base_price) + } + // This function returns the hash value used in transaction signature. It is // different from transaction hash. The transaction hash also contains // signatures. pub fn signature_hash(&self) -> H256 { let mut s = RlpStream::new(); + let mut type_prefix = vec![]; match self { - Transaction::Native(tx) => { + Transaction::Native(TypedNativeTransaction::Cip155(tx)) => { s.append(tx); } - Transaction::Ethereum(tx) => { + Transaction::Native(TypedNativeTransaction::Cip1559(tx)) => { s.append(tx); + type_prefix.extend_from_slice(TYPED_NATIVE_TX_PREFIX); + type_prefix.push(CIP1559_TYPE); } - } - keccak(s.as_raw()) + Transaction::Native(TypedNativeTransaction::Cip2930(tx)) => { + s.append(tx); + type_prefix.extend_from_slice(TYPED_NATIVE_TX_PREFIX); + type_prefix.push(CIP2930_TYPE); + } + Transaction::Ethereum(EthereumTransaction::Eip155(tx)) => { + s.append(tx); + } + Transaction::Ethereum(EthereumTransaction::Eip1559(tx)) => { + s.append(tx); + type_prefix.push(EIP1559_TYPE); + } + Transaction::Ethereum(EthereumTransaction::Eip2930(tx)) => { + s.append(tx); + type_prefix.push(EIP2930_TYPE); + } + }; + let encoded = s.as_raw(); + let mut out = vec![0; type_prefix.len() + encoded.len()]; + out[0..type_prefix.len()].copy_from_slice(&type_prefix); + out[type_prefix.len()..].copy_from_slice(&encoded); + keccak(&out) } pub fn space(&self) -> Space { @@ -566,14 +488,14 @@ pub struct TransactionWithSignatureSerializePart { impl Encodable for TransactionWithSignatureSerializePart { fn rlp_append(&self, s: &mut RlpStream) { match self.unsigned { - Transaction::Native(ref tx) => { + Transaction::Native(TypedNativeTransaction::Cip155(ref tx)) => { s.begin_list(4); s.append(tx); s.append(&self.v); s.append(&self.r); s.append(&self.s); } - Transaction::Ethereum(ref tx) => { + Transaction::Ethereum(EthereumTransaction::Eip155(ref tx)) => { let Eip155Transaction { nonce, gas_price, @@ -598,65 +520,232 @@ impl Encodable for TransactionWithSignatureSerializePart { s.append(&self.r); s.append(&self.s); } + Transaction::Ethereum(EthereumTransaction::Eip2930(ref tx)) => { + s.append_raw(&[EIP2930_TYPE], 0); + s.begin_list(11); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.gas_price); + s.append(&tx.gas); + s.append(&tx.action); + s.append(&tx.value); + s.append(&tx.data); + s.append_list(&tx.access_list); + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } + Transaction::Ethereum(EthereumTransaction::Eip1559(ref tx)) => { + s.append_raw(&[EIP1559_TYPE], 0); + s.begin_list(12); + s.append(&tx.chain_id); + s.append(&tx.nonce); + s.append(&tx.max_priority_fee_per_gas); + s.append(&tx.max_fee_per_gas); + s.append(&tx.gas); + s.append(&tx.action); + s.append(&tx.value); + s.append(&tx.data); + s.append_list(&tx.access_list); + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } + Transaction::Native(TypedNativeTransaction::Cip2930(ref tx)) => { + s.append_raw(TYPED_NATIVE_TX_PREFIX, 0); + s.append_raw(&[CIP2930_TYPE], 0); + s.begin_list(4); + s.append(tx); + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } + Transaction::Native(TypedNativeTransaction::Cip1559(ref tx)) => { + s.append_raw(TYPED_NATIVE_TX_PREFIX, 0); + s.append_raw(&[CIP1559_TYPE], 0); + s.begin_list(4); + s.append(tx); + s.append(&self.v); + s.append(&self.r); + s.append(&self.s); + } } } } impl Decodable for TransactionWithSignatureSerializePart { fn decode(rlp: &Rlp) -> Result { - match rlp.item_count()? { - 4 => { - let unsigned: NativeTransaction = rlp.val_at(0)?; - let v: u8 = rlp.val_at(1)?; - let r: U256 = rlp.val_at(2)?; - let s: U256 = rlp.val_at(3)?; - Ok(TransactionWithSignatureSerializePart { - unsigned: Transaction::Native(unsigned), - v, - r, - s, - }) + if rlp.as_raw().len() == 0 { + return Err(DecoderError::RlpInvalidLength); + } + if rlp.is_list() { + match rlp.item_count()? { + 4 => { + let unsigned: NativeTransaction = rlp.val_at(0)?; + let v: u8 = rlp.val_at(1)?; + let r: U256 = rlp.val_at(2)?; + let s: U256 = rlp.val_at(3)?; + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Native( + TypedNativeTransaction::Cip155(unsigned), + ), + v, + r, + s, + }) + } + 9 => { + let nonce: U256 = rlp.val_at(0)?; + let gas_price: U256 = rlp.val_at(1)?; + let gas: U256 = rlp.val_at(2)?; + let action: Action = rlp.val_at(3)?; + let value: U256 = rlp.val_at(4)?; + let data: Vec = rlp.val_at(5)?; + let legacy_v: u64 = rlp.val_at(6)?; + let r: U256 = rlp.val_at(7)?; + let s: U256 = rlp.val_at(8)?; + + let v = eip155_signature::extract_standard_v(legacy_v); + let chain_id = + match eip155_signature::extract_chain_id_from_legacy_v( + legacy_v, + ) { + Some(chain_id) if chain_id > (u32::MAX as u64) => { + return Err(DecoderError::Custom( + "Does not support chain_id >= 2^32", + )); + } + chain_id => chain_id.map(|x| x as u32), + }; + + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum( + EthereumTransaction::Eip155(Eip155Transaction { + nonce, + gas_price, + gas, + action, + value, + chain_id, + data, + }), + ), + v, + r, + s, + }) + } + _ => Err(DecoderError::RlpInvalidLength), } - 9 => { - let nonce: U256 = rlp.val_at(0)?; - let gas_price: U256 = rlp.val_at(1)?; - let gas: U256 = rlp.val_at(2)?; - let action: Action = rlp.val_at(3)?; - let value: U256 = rlp.val_at(4)?; - let data: Vec = rlp.val_at(5)?; - let legacy_v: u64 = rlp.val_at(6)?; - let r: U256 = rlp.val_at(7)?; - let s: U256 = rlp.val_at(8)?; - - let v = eip155_signature::extract_standard_v(legacy_v); - let chain_id = - match eip155_signature::extract_chain_id_from_legacy_v( - legacy_v, - ) { - Some(chain_id) if chain_id > (u32::MAX as u64) => { - return Err(DecoderError::Custom( - "Does not support chain_id >= 2^32", - )); + } else { + match rlp.as_raw()[0] { + TYPED_NATIVE_TX_PREFIX_BYTE => { + if rlp.as_raw().len() <= 4 + || rlp.as_raw()[0..3] != *TYPED_NATIVE_TX_PREFIX + { + return Err(DecoderError::RlpInvalidLength); + } + match rlp.as_raw()[3] { + CIP2930_TYPE => { + let rlp = Rlp::new(&rlp.as_raw()[4..]); + if rlp.item_count()? != 4 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let tx = rlp.val_at(0)?; + let v = rlp.val_at(1)?; + let r = rlp.val_at(2)?; + let s = rlp.val_at(3)?; + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Native( + TypedNativeTransaction::Cip2930(tx), + ), + v, + r, + s, + }) + } + CIP1559_TYPE => { + let rlp = Rlp::new(&rlp.as_raw()[4..]); + if rlp.item_count()? != 4 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let tx = rlp.val_at(0)?; + let v = rlp.val_at(1)?; + let r = rlp.val_at(2)?; + let s = rlp.val_at(3)?; + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Native( + TypedNativeTransaction::Cip1559(tx), + ), + v, + r, + s, + }) } - chain_id => chain_id.map(|x| x as u32), + _ => Err(DecoderError::RlpInvalidLength), + } + } + EIP2930_TYPE => { + let rlp = Rlp::new(&rlp.as_raw()[1..]); + if rlp.item_count()? != 11 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let tx = Eip2930Transaction { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + gas_price: rlp.val_at(2)?, + gas: rlp.val_at(3)?, + action: rlp.val_at(4)?, + value: rlp.val_at(5)?, + data: rlp.val_at(6)?, + access_list: rlp.list_at(7)?, }; - - Ok(TransactionWithSignatureSerializePart { - unsigned: Transaction::Ethereum(Eip155Transaction { - nonce, - gas_price, - gas, - action, - value, - chain_id, - data, - }), - v, - r, - s, - }) + let v = rlp.val_at(8)?; + let r = rlp.val_at(9)?; + let s = rlp.val_at(10)?; + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum( + EthereumTransaction::Eip2930(tx), + ), + v, + r, + s, + }) + } + EIP1559_TYPE => { + let rlp = Rlp::new(&rlp.as_raw()[1..]); + if rlp.item_count()? != 12 { + return Err(DecoderError::RlpIncorrectListLen); + } + + let tx = Eip1559Transaction { + chain_id: rlp.val_at(0)?, + nonce: rlp.val_at(1)?, + max_priority_fee_per_gas: rlp.val_at(2)?, + max_fee_per_gas: rlp.val_at(3)?, + gas: rlp.val_at(4)?, + action: rlp.val_at(5)?, + value: rlp.val_at(6)?, + data: rlp.val_at(7)?, + access_list: rlp.list_at(8)?, + }; + let v = rlp.val_at(9)?; + let r = rlp.val_at(10)?; + let s = rlp.val_at(11)?; + Ok(TransactionWithSignatureSerializePart { + unsigned: Transaction::Ethereum( + EthereumTransaction::Eip1559(tx), + ), + v, + r, + s, + }) + } + _ => Err(DecoderError::RlpInvalidLength), } - _ => Err(DecoderError::RlpInvalidLength), } } } @@ -695,14 +784,22 @@ impl DerefMut for TransactionWithSignature { } impl Decodable for TransactionWithSignature { - fn decode(d: &Rlp) -> Result { - let hash = keccak(d.as_raw()); - let rlp_size = Some(d.as_raw().len()); - // Check item count of TransactionWithSignatureSerializePart - if d.item_count()? != 4 && d.item_count()? != 9 { - return Err(DecoderError::RlpIncorrectListLen); - } - let transaction = d.as_val()?; + fn decode(tx_rlp: &Rlp) -> Result { + let rlp_size = Some(tx_rlp.as_raw().len()); + // The item count of TransactionWithSignatureSerializePart is checked in + // its decoding. + let hash; + let transaction; + if tx_rlp.is_list() { + hash = keccak(tx_rlp.as_raw()); + // Vanilla tx encoding. + transaction = tx_rlp.as_val()?; + } else { + // Typed tx encoding is wrapped as an RLP string. + let b: Vec = tx_rlp.as_val()?; + hash = keccak(&b); + transaction = rlp::decode(&b)?; + }; Ok(TransactionWithSignature { transaction, hash, @@ -713,7 +810,16 @@ impl Decodable for TransactionWithSignature { impl Encodable for TransactionWithSignature { fn rlp_append(&self, s: &mut RlpStream) { - s.append_internal(&self.transaction); + match &self.transaction.unsigned { + Transaction::Native(TypedNativeTransaction::Cip155(_)) + | Transaction::Ethereum(EthereumTransaction::Eip155(_)) => { + s.append_internal(&self.transaction); + } + _ => { + // Typed tx encoding is wrapped as an RLP string. + s.append_internal(&rlp::encode(&self.transaction)); + } + } } } @@ -757,6 +863,16 @@ impl TransactionWithSignature { } } + pub fn check_y_parity(&self) -> Result<(), keylib::Error> { + if self.is_2718() && self.v > 1 { + // In Typed transactions (EIP-2718), v means y_parity, which must be + // 0 or 1 + Err(keylib::Error::InvalidYParity) + } else { + Ok(()) + } + } + pub fn hash(&self) -> H256 { self.hash } /// Recovers the public key of the sender. @@ -767,6 +883,14 @@ impl TransactionWithSignature { pub fn rlp_size(&self) -> usize { self.rlp_size.unwrap_or_else(|| self.rlp_bytes().len()) } + + pub fn from_raw(raw: &[u8]) -> Result { + Ok(TransactionWithSignature { + transaction: Rlp::new(raw).as_val()?, + hash: keccak(raw), + rlp_size: Some(raw.len()), + }) + } } impl MallocSizeOf for TransactionWithSignature { @@ -783,6 +907,7 @@ pub struct SignedTransaction { pub public: Option, } +// The default encoder for local storage. impl Encodable for SignedTransaction { fn rlp_append(&self, s: &mut RlpStream) { s.begin_list(3); diff --git a/crates/primitives/src/transaction/native_transaction.rs b/crates/primitives/src/transaction/native_transaction.rs new file mode 100644 index 0000000000..35c5edc381 --- /dev/null +++ b/crates/primitives/src/transaction/native_transaction.rs @@ -0,0 +1,210 @@ +use crate::{ + transaction::AccessListItem, Action, SignedTransaction, Transaction, + TransactionWithSignature, TransactionWithSignatureSerializePart, +}; +use bytes::Bytes; +use cfx_types::{AddressWithSpace, H256, U256}; +use rlp_derive::{RlpDecodable, RlpEncodable}; +use serde_derive::{Deserialize, Serialize}; + +use super::AccessList; + +#[derive( + Default, + Debug, + Clone, + Eq, + PartialEq, + RlpEncodable, + RlpDecodable, + Serialize, + Deserialize, +)] +pub struct NativeTransaction { + /// Nonce. + pub nonce: U256, + /// Gas price. + pub gas_price: U256, + /// Gas paid up front for transaction execution. + pub gas: U256, + /// Action, can be either call or contract create. + pub action: Action, + /// Transferred value. + pub value: U256, + /// Maximum storage increasement in this execution. + pub storage_limit: u64, + /// The epoch height of the transaction. A transaction + /// can only be packed between the epochs of [epoch_height - + /// TRANSACTION_EPOCH_BOUND, epoch_height + TRANSACTION_EPOCH_BOUND] + pub epoch_height: u64, + /// The chain id of the transaction + pub chain_id: u32, + /// Transaction data. + pub data: Bytes, +} + +impl NativeTransaction { + /// Specify the sender; this won't survive the serialize/deserialize + /// process, but can be cloned. + pub fn fake_sign(self, from: AddressWithSpace) -> SignedTransaction { + SignedTransaction { + transaction: TransactionWithSignature { + transaction: TransactionWithSignatureSerializePart { + unsigned: Transaction::Native( + TypedNativeTransaction::Cip155(self), + ), + r: U256::one(), + s: U256::one(), + v: 0, + }, + hash: H256::zero(), + rlp_size: None, + } + .compute_hash(), + sender: from.address, + public: None, + } + } +} + +#[derive( + Default, + Debug, + Clone, + Eq, + PartialEq, + RlpEncodable, + RlpDecodable, + Serialize, + Deserialize, +)] +pub struct Cip2930Transaction { + pub nonce: U256, + pub gas_price: U256, + pub gas: U256, + pub action: Action, + pub value: U256, + pub storage_limit: u64, + pub epoch_height: u64, + pub chain_id: u32, + pub data: Bytes, + // We do not use `AccessList` here because we need `Vec` for rlp derive. + pub access_list: Vec, +} + +#[derive( + Default, + Debug, + Clone, + Eq, + PartialEq, + RlpEncodable, + RlpDecodable, + Serialize, + Deserialize, +)] +pub struct Cip1559Transaction { + pub nonce: U256, + pub max_priority_fee_per_gas: U256, + pub max_fee_per_gas: U256, + pub gas: U256, + pub action: Action, + pub value: U256, + pub storage_limit: u64, + pub epoch_height: u64, + pub chain_id: u32, + pub data: Bytes, + // We do not use `AccessList` here because we need `Vec` for rlp derive. + pub access_list: Vec, +} + +macro_rules! access_common_ref { + ($field:ident, $ty:ty) => { + pub fn $field(&self) -> &$ty { + match self { + TypedNativeTransaction::Cip155(tx) => &tx.$field, + TypedNativeTransaction::Cip2930(tx) => &tx.$field, + TypedNativeTransaction::Cip1559(tx) => &tx.$field, + } + } + }; +} + +impl TypedNativeTransaction { + access_common_ref!(gas, U256); + + access_common_ref!(data, Bytes); + + access_common_ref!(nonce, U256); + + access_common_ref!(action, Action); + + access_common_ref!(value, U256); + + access_common_ref!(chain_id, u32); + + access_common_ref!(epoch_height, u64); + + access_common_ref!(storage_limit, u64); + + pub fn gas_price(&self) -> &U256 { + match self { + Cip155(tx) => &tx.gas_price, + Cip1559(tx) => &tx.max_fee_per_gas, + Cip2930(tx) => &tx.gas_price, + } + } + + pub fn max_priority_gas_price(&self) -> &U256 { + match self { + Cip155(tx) => &tx.gas_price, + Cip1559(tx) => &tx.max_priority_fee_per_gas, + Cip2930(tx) => &tx.gas_price, + } + } + + pub fn nonce_mut(&mut self) -> &mut U256 { + match self { + Cip155(tx) => &mut tx.nonce, + Cip2930(tx) => &mut tx.nonce, + Cip1559(tx) => &mut tx.nonce, + } + } + + pub fn access_list(&self) -> Option<&AccessList> { + match self { + Cip155(_tx) => None, + Cip2930(tx) => Some(&tx.access_list), + Cip1559(tx) => Some(&tx.access_list), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TypedNativeTransaction { + Cip155(NativeTransaction), + Cip2930(Cip2930Transaction), + Cip1559(Cip1559Transaction), +} + +impl TypedNativeTransaction { + pub fn fake_sign_rpc(self, from: AddressWithSpace) -> SignedTransaction { + SignedTransaction { + transaction: TransactionWithSignature { + transaction: TransactionWithSignatureSerializePart { + unsigned: Transaction::Native(self), + r: U256::one(), + s: U256::one(), + v: 0, + }, + hash: H256::zero(), + rlp_size: None, + } + .compute_hash(), + sender: from.address, + public: None, + } + } +} + +use TypedNativeTransaction::*; diff --git a/crates/secret_store/Cargo.toml b/crates/secret_store/Cargo.toml index e2a510a31b..8e8b1ddf93 100644 --- a/crates/secret_store/Cargo.toml +++ b/crates/secret_store/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "secret-store" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] cfx-types = { path = "../cfx_types" } diff --git a/crates/serde_utils/Cargo.toml b/crates/serde_utils/Cargo.toml new file mode 100644 index 0000000000..247a95fb07 --- /dev/null +++ b/crates/serde_utils/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "serde-utils" +edition = "2021" +version.workspace = true +authors.workspace = true +description.workspace = true +documentation.workspace = true +homepage.workspace = true +keywords.workspace = true +repository.workspace = true +license-file.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +alloy-primitives = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true, features = ["alloc"] } +cfx-types = { path = "../cfx_types" } diff --git a/crates/serde_utils/src/lib.rs b/crates/serde_utils/src/lib.rs new file mode 100644 index 0000000000..5d04f39733 --- /dev/null +++ b/crates/serde_utils/src/lib.rs @@ -0,0 +1,18 @@ +pub mod num; + +pub use num::*; + +use serde::Serializer; + +/// Serialize a byte vec as a hex string _without_ the "0x" prefix. +/// +/// This behaves the same as [`hex::encode`](alloy_primitives::hex::encode). +pub fn serialize_hex_string_no_prefix( + x: T, s: S, +) -> Result +where + S: Serializer, + T: AsRef<[u8]>, +{ + s.serialize_str(&alloy_primitives::hex::encode(x.as_ref())) +} diff --git a/crates/serde_utils/src/num.rs b/crates/serde_utils/src/num.rs new file mode 100644 index 0000000000..d1f4b4d91f --- /dev/null +++ b/crates/serde_utils/src/num.rs @@ -0,0 +1,123 @@ +use cfx_types::{U256, U64}; +use core::str::FromStr; +use serde::{de, Deserialize, Deserializer}; +use std::fmt; + +/// An enum that represents either a [serde_json::Number] integer, or a hex +/// [U256]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU256 { + /// An integer + Int(serde_json::Number), + /// A hex U256 + Hex(U256), +} + +impl NumberOrHexU256 { + /// Tries to convert this into a [U256]]. + pub fn try_into_u256(self) -> Result { + match self { + NumberOrHexU256::Int(num) => { + U256::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU256::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U256, accepting both 0x-prefixed hex and +/// decimal strings with arbitrary precision, defined by serde_json's +/// [`Number`](serde_json::Number). +pub fn from_int_or_hex_to_u256<'de, D>( + deserializer: D, +) -> Result +where D: Deserializer<'de> { + NumberOrHexU256::deserialize(deserializer)?.try_into_u256() +} + +/// Deserializes the input into an `Option`, using [`from_int_or_hex`] to +/// deserialize the inner value. +pub fn from_int_or_hex_to_u256_opt<'de, D>( + deserializer: D, +) -> Result, D::Error> +where D: Deserializer<'de> { + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u256().map(Some), + None => Ok(None), + } +} + +/// An enum that represents either a [serde_json::Number] integer, or a hex +/// [U64]. +#[derive(Debug, Deserialize)] +#[serde(untagged)] +pub enum NumberOrHexU64 { + /// An integer + Int(serde_json::Number), + /// A hex U64 + Hex(U64), +} + +impl NumberOrHexU64 { + /// Tries to convert this into a [U64]]. + pub fn try_into_u64(self) -> Result { + match self { + NumberOrHexU64::Int(num) => { + U64::from_str(num.to_string().as_str()).map_err(E::custom) + } + NumberOrHexU64::Hex(val) => Ok(val), + } + } +} + +/// Deserializes the input into a U64, accepting both 0x-prefixed hex and +/// decimal strings with arbitrary precision, defined by serde_json's +/// [`Number`](serde_json::Number). +pub fn from_int_or_hex_to_u64<'de, D>( + deserializer: D, +) -> Result +where D: Deserializer<'de> { + NumberOrHexU64::deserialize(deserializer)?.try_into_u64() +} + +/// Deserializes the input into an `Option`, using +/// [`from_int_or_hex_to_u64`] to deserialize the inner value. +pub fn from_int_or_hex_to_u64_opt<'de, D>( + deserializer: D, +) -> Result, D::Error> +where D: Deserializer<'de> { + match Option::::deserialize(deserializer)? { + Some(val) => val.try_into_u64().map(Some), + None => Ok(None), + } +} + +pub fn deserialize_u64_from_num_or_hex<'de, D>( + deserializer: D, +) -> Result +where D: Deserializer<'de> { + struct U64OrHexVisitor; + + impl<'de> serde::de::Visitor<'de> for U64OrHexVisitor { + type Value = u64; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter + .write_str("a u64 integer or a hex string representing a u64") + } + + fn visit_u64(self, value: u64) -> Result { Ok(value) } + + fn visit_str(self, value: &str) -> Result + where E: serde::de::Error { + if let Some(stripped) = value.strip_prefix("0x") { + u64::from_str_radix(stripped, 16).map_err(E::custom) + } else { + Err(E::custom("expected hex string to start with '0x'")) + } + } + } + + deserializer.deserialize_any(U64OrHexVisitor) +} diff --git a/crates/stratum/Cargo.toml b/crates/stratum/Cargo.toml index dbdf1ab411..b19ebad8ca 100644 --- a/crates/stratum/Cargo.toml +++ b/crates/stratum/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" name = "cfx-stratum" version = "1.12.0" license = "GPL-3.0" -edition = "2018" +edition = "2021" [dependencies] cfx-types = { path = "../cfx_types" } diff --git a/crates/transactiongen/Cargo.toml b/crates/transactiongen/Cargo.toml index 42bc5e8439..f483931279 100644 --- a/crates/transactiongen/Cargo.toml +++ b/crates/transactiongen/Cargo.toml @@ -4,7 +4,7 @@ homepage = "https://www.confluxnetwork.org" license = "GPL-3.0" name = "txgen" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] clap = "2" diff --git a/crates/transactiongen/src/lib.rs b/crates/transactiongen/src/lib.rs index 021ea5ba5b..0cecdc27ab 100644 --- a/crates/transactiongen/src/lib.rs +++ b/crates/transactiongen/src/lib.rs @@ -28,7 +28,7 @@ use lazy_static::lazy_static; use metrics::{register_meter_with_group, Meter}; use parking_lot::RwLock; use primitives::{ - transaction::{Action, NativeTransaction}, + transaction::{native_transaction::NativeTransaction, Action}, Account, SignedTransaction, Transaction, }; use rand::prelude::*; diff --git a/crates/util/cfx-vm-tracer-derive/Cargo.toml b/crates/util/cfx-vm-tracer-derive/Cargo.toml index ca849a830e..f1d62a4ca3 100644 --- a/crates/util/cfx-vm-tracer-derive/Cargo.toml +++ b/crates/util/cfx-vm-tracer-derive/Cargo.toml @@ -2,7 +2,7 @@ name = "cfx-vm-tracer-derive" version = "0.1.0" authors = ["Your Name "] -edition = "2018" +edition = "2021" # Set the crate type to "proc-macro" [lib] diff --git a/crates/util/treap-map/src/tests.rs b/crates/util/treap-map/src/tests.rs index 06bb992a48..814a1dca23 100644 --- a/crates/util/treap-map/src/tests.rs +++ b/crates/util/treap-map/src/tests.rs @@ -7,7 +7,10 @@ use crate::{node::Node, update::ApplyOpOutcome, KeyMngTrait, TreapMapConfig}; use super::{SharedKeyTreapMapConfig, TreapMap}; use cfx_types::{Address, Public, H256, U256, U512}; use cfxkey::Signature; -use primitives::{Action, NativeTransaction, SignedTransaction, Transaction}; +use primitives::{ + transaction::native_transaction::NativeTransaction, Action, + SignedTransaction, Transaction, +}; use rand::{seq::SliceRandom, thread_rng, Rng, RngCore, SeedableRng}; use rand_chacha::ChaChaRng; use rand_xorshift::XorShiftRng; diff --git a/dev-support/dep_pip3.sh b/dev-support/dep_pip3.sh index d54b482bfa..62559d494d 100755 --- a/dev-support/dep_pip3.sh +++ b/dev-support/dep_pip3.sh @@ -8,10 +8,11 @@ function install() { fi } +install git+https://github.com/conflux-fans/cfx-account.git@v1.1.0-beta.2 # install cfx-account lib and prepare for CIP-1559 tests install eth-utils install rlp==1.2.0 install py-ecc==5.2.0 -install coincurve==15.0.1 +install coincurve==19.0.1 install pysha3 install trie==1.4.0 install web3==5.31.1 diff --git a/docs/transaction-trace/geth-style-trace.md b/docs/transaction-trace/geth-style-trace.md new file mode 100644 index 0000000000..a9b579c028 --- /dev/null +++ b/docs/transaction-trace/geth-style-trace.md @@ -0,0 +1,76 @@ +# Geth Style Trace + +Ethereum geth client provides [a way to trace transactions](https://geth.ethereum.org/docs/developers/evm-tracing). The trace is useful for debugging and understanding the transaction execution. Geth's trace related RPC methods are under [`debug`](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug#debugtracetransaction) namespace, include: + +- `debug_traceTransaction` +- `debug_traceBlock` +- `debug_traceBlockByNumber` +- `debug_traceBlockByHash` +- `debug_traceCall` + +Geth support different kind of traces, include: + +- opcode +- prestateTracer +- 4byteTracer +- noopTracer +- callTracer + +And serveral builtin JS tracers, include: + +- bigram +- evmdis +- unigram +- opcount +- trigram +- unigram + +Geth also support writing custom tracer in [Go and Js](https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer). + +debug_traceCall method suport [state override](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#state-overrides). + +## Conflux Implementation + +Conflux eSpace has implemented main features of geth style trace, include: + +- `debug_traceTransaction` +- `debug_traceBlockByNumber` +- `debug_traceBlockByHash` +- `debug_traceCall` (Working) + +Currently supported tracers: + +- opcode +- prestateTracer (Working) +- 4byteTracer +- noopTracer +- callTracer + +We will support writing custom tracer with Js in the future. + +To use eSpace trace RPC methods, you need to enable `ethdebug` API module in the config file. + +```toml +public_evm_rpc_apis = "eth,ethdebug" +``` + +### Opcode Tracer + +Currently the opcode trace's structLogs `error` field is not implemented. + +Conflux does not have refund mechanism, so the `refund` field is omitted. + +## FAQs + +1. Does trace RPC methods support eSpace PhantomTransaction traces? + + Currently, no. We will support it in the future. + +## Resources + +1. [Geth EVM Tracing](https://geth.ethereum.org/docs/developers/evm-tracing) +2. [Geth Debug RPC](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug) +3. [Geth Custom Tracer](https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer) +4. [Geth State Override](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers#state-overrides) +5. [Geth Tracer](https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers) +6. [Paradigmxyz's ultimate_evm_tracing_reference](https://github.com/paradigmxyz/ultimate_evm_tracing_reference) \ No newline at end of file diff --git a/docs/transaction-trace/parity-style-trace.md b/docs/transaction-trace/parity-style-trace.md new file mode 100644 index 0000000000..df3f266b2a --- /dev/null +++ b/docs/transaction-trace/parity-style-trace.md @@ -0,0 +1,3 @@ +# Parity Trace Style + +Both Conflux Core Space and eSpace support parity style trace RPC methods, but with some differences, check [this doc](https://doc.confluxnetwork.org/docs/core/build/json-rpc/trace_rpc) for details \ No newline at end of file diff --git a/internal_contract/contracts/ConfluxContext.sol b/internal_contract/contracts/ConfluxContext.sol index 3acd917d14..f59e2245dc 100644 --- a/internal_contract/contracts/ConfluxContext.sol +++ b/internal_contract/contracts/ConfluxContext.sol @@ -17,4 +17,6 @@ contract ConfluxContext { * @return the finalized epoch number */ function finalizedEpochNumber() public view returns (uint256) {} + + function epochHash(uint256) external view returns (bytes32) {} } diff --git a/run/hydra.toml b/run/hydra.toml index 6c88c8c80e..08e3e3d67e 100644 --- a/run/hydra.toml +++ b/run/hydra.toml @@ -155,10 +155,13 @@ jsonrpc_local_http_port=12539 # Specify the APIs available through the public JSON-RPC interfaces (HTTP, TCP, WebSocket) # using a comma-delimited list of API names. -# Possible names are: all, safe, cfx, pos, debug, pubsub, test, trace, txpool. + +# Possible Core space names are: all, safe, cfx, pos, debug, pubsub, test, trace, txpool. # `safe` only includes `cfx` and `pubsub`, `txpool`. -# # public_rpc_apis = "safe" + +# Possible eSpace names are: eth, ethpubsub, ethdebug. +# `evm` only includes `eth` and `ethpubsub` # public_evm_rpc_apis = "evm" # --------------- Performance-related Network Parameters ---------------------- @@ -194,7 +197,7 @@ jsonrpc_local_http_port=12539 # # egress_max_throttle = 10 -# Time interval to to garbage-collect not block-graph-ready blocks periodically. +# Time interval to garbage-collect not block-graph-ready blocks periodically. # # expire_block_gc_period_s = 900 diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 5e3a425662..0000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.73.0 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..fb0f94604f --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.77.2" \ No newline at end of file diff --git a/tests/blockhash_test.py b/tests/blockhash_test.py new file mode 100644 index 0000000000..3eb9c6b2ac --- /dev/null +++ b/tests/blockhash_test.py @@ -0,0 +1,45 @@ +from web3 import Web3 +from web3.contract import ContractFunction, Contract + +from conflux.rpc import RpcClient +from conflux.utils import * +from test_framework.contracts import ConfluxTestFrameworkForContract, ZERO_ADDRESS +from test_framework.util import * +from test_framework.mininode import * + + +class BlockHashFromStateTest(ConfluxTestFrameworkForContract): + def run_test(self): + genesis_hash = self.client.block_by_epoch(0)["hash"] + for _ in range(5): + self.client.generate_block_with_parent(genesis_hash) + + self.wait_for_block(1000) + + test_contract = self.cfx_contract("BlockHash").deploy() + context_contract = self.internal_contract("ConfluxContext"); + for i in range(100, 1001, 100): + assert_equal(test_contract.functions.getBlockHash(i).cfx_call().hex(), self.client.block_by_block_number(i)["hash"][2:]) + assert_equal(context_contract.functions.epochHash(i).cfx_call().hex(), self.client.block_by_epoch(i)["hash"][2:]) + + self.log.info("Generate 65536+ blocks") + for i in range(5000, 66000, 5000): + self.wait_for_block(i) + self.wait_for_block(66000) + + assert_equal(test_contract.functions.getBlockHash(100).cfx_call().hex(), "0" * 64) + assert_equal(context_contract.functions.epochHash(100).cfx_call().hex(), "0" * 64) + + + def wait_for_block(self, block_number, have_not_reach=False): + if have_not_reach: + assert_greater_than_or_equal( + block_number, self.client.epoch_number()) + while self.client.epoch_number() < block_number: + self.client.generate_blocks( + block_number - self.client.epoch_number()) + time.sleep(0.1) + self.log.info(f"block_number: {self.client.epoch_number()}") + +if __name__ == "__main__": + BlockHashFromStateTest().main() diff --git a/tests/cip137_test.py b/tests/cip137_test.py new file mode 100644 index 0000000000..687f5ddec2 --- /dev/null +++ b/tests/cip137_test.py @@ -0,0 +1,212 @@ +import math +from typing import Union, Tuple +from conflux.rpc import RpcClient, default_config +from test_framework.util import ( + assert_equal, +) + +from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount + +from test_framework.test_framework import ConfluxTestFramework +from test_framework.util import ( + generate_blocks_for_base_fee_manipulation, + generate_single_block_for_base_fee_manipulation, + assert_correct_fee_computation_for_core_tx, +) + +MIN_NATIVE_BASE_PRICE = 10000 +BURNT_RATIO = 0.5 + + +class CIP137Test(ConfluxTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE + self.conf_parameters["next_hardfork_transition_height"] = 1 + self.conf_parameters["next_hardfork_transition_number"] = 1 + + def setup_network(self): + self.add_nodes(self.num_nodes) + self.start_node(0, ["--archive"]) + self.rpc = RpcClient(self.nodes[0]) + + # We need to ensure that the tx in B block + # B and ending block will be in the same epoch + # --- --- --- --- --- --- + # .- | A | <--- | C | <--- | D | <--- | E | <--- | F | <--- | G | ... + # --- | --- --- --- --- --- --- + # ... <--- | P | <-* . + # --- | --- . + # .- | B | <................................................... + # --- + # ensures txs to be included in B block and the ending block (e.g. F) base gas price is greater than the specified target_minimum_base_fee (not guaranteed to be the first block) + # returns the ending block hash + def construct_non_pivot_block( + self, + acct: CfxLocalAccount, + txs: list, + starting_block_hash: str = None, + epoch_delta: int = 6, # 1.125^6 -> 2.027 which would make the initial tx invalid + ) -> Tuple[str, str]: + + if epoch_delta <= 0: + raise ValueError("epoch_delta must be positive") + + if starting_block_hash is None: + starting_block_hash = self.rpc.block_by_epoch("latest_mined")["hash"] + + # create the non-pivot block + non_pivot_block = self.rpc.generate_custom_block( + parent_hash=starting_block_hash, txs=txs, referee=[] + ) + ending_but_two_block, account_next_nonce = ( + generate_blocks_for_base_fee_manipulation( + self.rpc, acct, epoch_delta-1, initial_parent_hash=starting_block_hash + ) + ) + ending_block, _ = generate_single_block_for_base_fee_manipulation( + self.rpc, + acct, + [non_pivot_block], + parent_hash=ending_but_two_block, + starting_nonce=account_next_nonce, + ) + return non_pivot_block, ending_block + + def init_acct_with_cfx(self, drip: int = 10**21) -> CfxLocalAccount: + self.rpc.send_tx( + self.rpc.new_tx( + receiver=(acct := CfxAccount.create()).address, + value=drip, + gas_price=max( + self.rpc.base_fee_per_gas() * 2, MIN_NATIVE_BASE_PRICE + ), # avoid genisis zero gas price + ), + True, + ) + return acct + + def get_gas_charged(self, tx_hash: str) -> int: + gas_limit = int(self.rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(self.rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) + + def run_test(self): + + acct1 = self.init_acct_with_cfx() + acct2 = self.init_acct_with_cfx() + + block_p = self.rpc.block_by_epoch("latest_mined")["hash"] + + gas_price_level_1 = MIN_NATIVE_BASE_PRICE + gas_price_level_1_5 = int(MIN_NATIVE_BASE_PRICE * 1.5) + gas_price_level_2 = self.rpc.base_fee_per_gas() * 10 + + acct1_txs = [ + self.rpc.new_typed_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=0, + max_fee_per_gas=gas_price_level_2, + ), # expected to succeed + self.rpc.new_typed_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=1, + max_fee_per_gas=gas_price_level_1_5, + ), # expected to succeed with max fee less than epoch base gas fee + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=2, + gas_price=gas_price_level_1, + ), # expected to be ignored and can be resend later + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct1.key, + nonce=3, + gas_price=gas_price_level_2, + ), # expected to be ignored + ] + + acct2_txs = [ + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=0, + gas_price=gas_price_level_2, + ), # expected to succeed + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=1, + gas_price=gas_price_level_2, + ), # expected to succeed + self.rpc.new_tx( + receiver=self.rpc.rand_addr(), + priv_key=acct2.key, + nonce=2, + gas_price=gas_price_level_2, + ), # expected to succeed + ] + + block_b, block_f = self.construct_non_pivot_block( + CfxAccount.from_key(default_config["GENESIS_PRI_KEY"]), + [*acct1_txs, *acct2_txs], + starting_block_hash=block_p, + epoch_delta=6, # 1.125^6 -> 2.03 + ) + + self.log.info(f"current base fee per gas: {self.rpc.base_fee_per_gas()}") + + # we are ensuring the gas price order: + # gas_price_level_1 < current_base_fee * burnt_ratio < gas_price_level_1_5 < current_base_fee < gas_price_level_2 + assert gas_price_level_2 > self.rpc.base_fee_per_gas() * BURNT_RATIO + assert ( + gas_price_level_1 < self.rpc.base_fee_per_gas() * BURNT_RATIO + ), f"gas_price_level_1 {gas_price_level_1} should be less than {self.rpc.base_fee_per_gas() * BURNT_RATIO}" + + # wait for epoch of block f executed + parent_block = block_f + for _ in range(30): + block = self.rpc.generate_custom_block( + parent_hash=parent_block, referee=[], txs=[] + ) + parent_block = block + + assert_equal(self.rpc.get_nonce(acct1.address), 2) + assert_equal(self.rpc.get_nonce(acct2.address), 3) + focusing_block = self.rpc.block_by_hash(block_b, True) + epoch = int(focusing_block["epochNumber"],16) + + self.log.info(f"epoch of block b: {epoch}") + self.log.info(f"heigth of block b: {int(focusing_block['height'], 16)}") + self.log.info(f"base_fee_per_gas for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch)}") + self.log.info(f"burnt_fee_per_gas for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * 0.5}") + self.log.info(f"least base fee for epoch {epoch}: {self.rpc.base_fee_per_gas(epoch) * BURNT_RATIO}") + self.log.info(f"transactions in block b: {self.rpc.block_by_hash(block_b)['transactions']}") + + assert_equal(focusing_block["transactions"][0]["status"], "0x0") + assert_equal(focusing_block["transactions"][1]["status"], "0x0") + assert_equal(focusing_block["transactions"][2]["status"], None) + assert_equal(focusing_block["transactions"][2]["blockHash"], None) + assert_equal(focusing_block["transactions"][3]["status"], None) + assert_equal(focusing_block["transactions"][3]["blockHash"], None) + + # as comparison + assert_equal(focusing_block["transactions"][4]["status"], "0x0") + assert_equal(focusing_block["transactions"][5]["status"], "0x0") + assert_equal(focusing_block["transactions"][6]["status"], "0x0") + + for tx_hash in self.rpc.block_by_hash(block_b)['transactions']: + assert_correct_fee_computation_for_core_tx(self.rpc, tx_hash, BURNT_RATIO) + + self.rpc.generate_blocks(20, 5) + + # transactions shall be sent back to txpool and then get packed + assert_equal(self.rpc.get_nonce(acct1.address), 4) + + +if __name__ == "__main__": + CIP137Test().main() diff --git a/tests/cip1559_test.py b/tests/cip1559_test.py new file mode 100644 index 0000000000..1c08166e27 --- /dev/null +++ b/tests/cip1559_test.py @@ -0,0 +1,213 @@ +from conflux.rpc import RpcClient +from test_framework.util import ( + assert_equal, +) +from decimal import Decimal +from typing import Literal + +from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount + +from test_framework.test_framework import ConfluxTestFramework +from test_framework.util import generate_blocks_for_base_fee_manipulation, assert_correct_fee_computation_for_core_tx + +CORE_BLOCK_GAS_TARGET = 270000 +BURNT_RATIO = 0.5 +MIN_NATIVE_BASE_PRICE = 10000 + +class CIP1559Test(ConfluxTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.conf_parameters["min_native_base_price"] = MIN_NATIVE_BASE_PRICE + self.conf_parameters["next_hardfork_transition_height"] = 1 + self.conf_parameters["next_hardfork_transition_number"] = 1 + + + def setup_network(self): + self.add_nodes(self.num_nodes) + self.start_node(0, ["--archive"]) + self.rpc = RpcClient(self.nodes[0]) + + # acct should have cfx + def change_base_fee(self, acct: CfxLocalAccount=None, block_count=10, tx_per_block=4, gas_per_tx=13500000): + if acct is None: + acct = self.init_acct_with_cfx() + generate_blocks_for_base_fee_manipulation(self.rpc, acct, block_count, tx_per_block, gas_per_tx) + + + def test_block_base_fee_change(self, acct: CfxLocalAccount, epoch_to_test:int, tx_per_block=4, gas_per_tx=13500000): + starting_epoch = self.rpc.epoch_number() + self.change_base_fee(acct, epoch_to_test, tx_per_block, gas_per_tx) + expected_base_fee_change_delta_rate = self.get_expected_base_fee_change_delta_rate(tx_per_block * gas_per_tx, 27000000) + + for i in range(starting_epoch+1, self.rpc.epoch_number()): + expected_current_base_fee = self.rpc.base_fee_per_gas(i-1) + int(self.rpc.base_fee_per_gas(i-1) * expected_base_fee_change_delta_rate) + assert_equal(self.rpc.base_fee_per_gas(i), expected_current_base_fee) + + + def get_expected_base_fee_change_delta_rate(self, sum_tx_gas_limit: int, block_target_gas_limit: int = None) -> Decimal: + if block_target_gas_limit is None: + block_target_gas_limit = CORE_BLOCK_GAS_TARGET + BASE_FEE_MAX_CHANGE_DENOMINATOR = 8 + return ((sum_tx_gas_limit-block_target_gas_limit) / Decimal(block_target_gas_limit)) / BASE_FEE_MAX_CHANGE_DENOMINATOR + + # default to 1000 CFX + def init_acct_with_cfx(self, drip: int=10**21) -> CfxLocalAccount: + self.rpc.send_tx( + self.rpc.new_tx( + receiver=(acct:=CfxAccount.create()).address, + value=drip, + gas_price=max(self.rpc.base_fee_per_gas()*2,MIN_NATIVE_BASE_PRICE) # avoid genisis zero gas price + ), + True, + ) + return acct + + def get_gas_charged(self, tx_hash: str) -> int: + gas_limit = int(self.rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(self.rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) + + + def test_balance_change(self, acct: CfxLocalAccount): + acct_balance = self.rpc.get_balance(acct.address) + h = self.rpc.send_tx( + self.rpc.new_typed_tx( + priv_key=acct.key.hex(), + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + value=100, + ), + wait_for_receipt=True, + ) + receipt = self.rpc.get_transaction_receipt(h) + acct_new_balance = self.rpc.get_balance(acct.address) + assert_equal(acct_new_balance, acct_balance - int(receipt["gasFee"], 16) - 100) + + # this tests the case for pivot blocks + # as for non-pivot blocks, the tests are in ./cip137_test.py + def test_max_fee_not_enough_for_current_base_fee(self): + self.change_base_fee(block_count=10) + initial_base_fee = self.rpc.base_fee_per_gas() + + self.log.info(f"initla base fee: {initial_base_fee}") + + # 112.5% ^ 10 + self.change_base_fee(block_count=10) + self.log.info(f"increase base fee by 112.5% ^ 10") + self.log.info(f"new base fee: {self.rpc.base_fee_per_gas()}") + assert self.rpc.base_fee_per_gas() > initial_base_fee + self.log.info(f"sending new transaction with max_fee_per_gas: {initial_base_fee}") + # as the transaction's max fee per gas is not enough for current base fee, + # the transaction will become pending until the base fee drops + # we will observe the base fee of the block the transaction is in + h = self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=initial_base_fee, + ), + wait_for_receipt=True, + ) + + tx_base_fee = self.rpc.base_fee_per_gas(self.rpc.get_transaction_receipt(h)["epochNumber"]) + self.log.info(f"epoch base fee for transaction accepted: {tx_base_fee}") + assert tx_base_fee <= initial_base_fee + + def test_type_2_tx_fees(self): + + assert_correct_fee_computation_for_core_tx(self.rpc, self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + ), + wait_for_receipt=True, + )) + assert_correct_fee_computation_for_core_tx(self.rpc, self.rpc.send_tx( + self.rpc.new_typed_tx( + receiver=CfxAccount.create().address, + max_fee_per_gas=self.rpc.base_fee_per_gas(), + max_priority_fee_per_gas=0, + ), + wait_for_receipt=True, + )) + self.test_balance_change(self.init_acct_with_cfx()) + + def test_balance_not_enough_for_base_fee(self): + # ensuring acct does not have enough balance to pay for base fee + initial_value = 21000*(MIN_NATIVE_BASE_PRICE-1) + acct = self.init_acct_with_cfx(initial_value) + block = self.rpc.generate_custom_block(parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], referee=[], txs=[ + self.rpc.new_typed_tx(value=0, gas=21000, priv_key=acct.key.hex()) + ]) + self.rpc.generate_blocks(20, 5) + # self. + # h = self.rpc.send_tx( + # self.rpc.new_typed_tx( + # priv_key=acct.key.hex(), + # max_fee_per_gas=self.rpc.base_fee_per_gas(), + # max_priority_fee_per_gas=self.rpc.base_fee_per_gas(), + # value=0, + # ), + # wait_for_receipt=True, + # ) + tx_data = self.rpc.block_by_hash(block, True)["transactions"][0] + tx_receipt = self.rpc.get_transaction_receipt(tx_data["hash"]) + gas_fee = int(tx_receipt["gasFee"],16) + assert_equal(gas_fee, initial_value) + assert_equal(tx_data["status"],"0x1") + # account balance is all consumed + assert_equal(self.rpc.get_balance(acct.address),0) + + # two cases to test based on balance enough for max priority fee per gas + # maxPriorityFeePerGas = maxFeePerGas <- will fail because balance is not enough for effective_gas_price * gas_charged + # maxPriorityFeePerGas = 0 <- succeed + def test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas(self, priority_fee_setting: Literal["MAX", "ZERO"]): + # ensuring acct does not have enough balance to pay for base fee + self.log.info(f"current base fee: {self.rpc.base_fee_per_gas()}") + assert_equal(self.rpc.base_fee_per_gas(), MIN_NATIVE_BASE_PRICE) + # allow extra 1 priority fee + initial_value = 21000*(MIN_NATIVE_BASE_PRICE+1) + acct = self.init_acct_with_cfx(initial_value) + max_fee_per_gas = MIN_NATIVE_BASE_PRICE+2 + max_priority_fee: int + if priority_fee_setting == "MAX": + max_priority_fee = max_fee_per_gas + elif priority_fee_setting == "ZERO": + max_priority_fee = 0 + block = self.rpc.generate_custom_block(parent_hash=self.rpc.block_by_epoch("latest_mined")["hash"], referee=[], txs=[ + self.rpc.new_typed_tx(value=0, gas=21000, priv_key=acct.key.hex(), max_fee_per_gas=max_fee_per_gas, max_priority_fee_per_gas=max_priority_fee) + ]) + self.rpc.generate_blocks(20, 5) + + tx_data = self.rpc.block_by_hash(block, True)["transactions"][0] + assert_correct_fee_computation_for_core_tx(self.rpc, tx_data["hash"], BURNT_RATIO) + + if priority_fee_setting == "MAX": + # extra test to assert gas fee equal to all of the balance + tx_receipt = self.rpc.get_transaction_receipt(tx_data["hash"]) + gas_fee = int(tx_receipt["gasFee"],16) + assert_equal(gas_fee, initial_value) + + + def run_test(self): + self.rpc.generate_blocks(5) + + # test fee increasing + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 4, 13500000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 6, 8000000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 20, 3, 13500000) + # note: as min base fee is provided, we use less epochs + self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 1, 13500000) + self.test_block_base_fee_change(self.init_acct_with_cfx(), 10, 2, 10000000) + + self.test_type_2_tx_fees() + self.test_max_fee_not_enough_for_current_base_fee() + self.test_balance_not_enough_for_base_fee() + self.test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas("ZERO") + self.test_balance_enough_for_base_fee_but_not_for_max_fee_per_gas("MAX") + + +if __name__ == "__main__": + CIP1559Test().main() diff --git a/tests/cip97_test.py b/tests/cip97_test.py index 324ec2db90..8d510df4c1 100644 --- a/tests/cip97_test.py +++ b/tests/cip97_test.py @@ -14,6 +14,7 @@ def set_test_params(self): self.num_nodes = 1 self.conf_parameters["dao_vote_transition_number"] = 100 self.conf_parameters["hydra_transition_number"] = 90 + self.conf_parameters["cancun_opcodes_transition_number"] = 99999999 def run_test(self): priv = default_config["GENESIS_PRI_KEY"] diff --git a/tests/cip98_test.py b/tests/cip98_test.py index 22bd2d4d68..80c1137de6 100644 --- a/tests/cip98_test.py +++ b/tests/cip98_test.py @@ -18,6 +18,9 @@ def set_test_params(self): self.conf_parameters["evm_chain_id"] = str(EVM_CHAIN_ID) self.conf_parameters["evm_transaction_block_ratio"] = str(1) self.conf_parameters["dao_vote_transition_number"] = "100" + # Disable CIP-133 on test + self.conf_parameters["next_hardfork_transition_number"] = str(9999999) + self.conf_parameters["next_hardfork_transition_height"] = str(9999999) def run_test(self): rpc = self.nodes[0].rpc diff --git a/tests/conflux/rpc.py b/tests/conflux/rpc.py index 68d7d8fd5b..e5ba8fc4bb 100644 --- a/tests/conflux/rpc.py +++ b/tests/conflux/rpc.py @@ -1,13 +1,15 @@ import os import random -from typing import Optional, Union +from typing import cast, Optional, Union, TypedDict, Any from web3 import Web3 import eth_utils +from cfx_account import Account as CfxAccount +from eth_account.datastructures import SignedTransaction import rlp import json -from .address import hex_to_b32_address, b32_address_to_hex +from .address import hex_to_b32_address, b32_address_to_hex, DEFAULT_PY_TEST_CHAIN_ID from .config import DEFAULT_PY_TEST_CHAIN_ID, default_config from .transactions import CONTRACT_DEFAULT_GAS, Transaction, UnsignedTransaction from .filter import Filter @@ -25,6 +27,7 @@ assert_equal, wait_until, checktx, get_contract_instance ) +from test_framework.test_node import TestNode file_dir = os.path.dirname(os.path.realpath(__file__)) REQUEST_BASE = { @@ -34,6 +37,11 @@ "to": b'', } +class CfxFeeHistoryResponse(TypedDict): + baseFeePerGas: list[int] + gasUsedRatio: list[float] + reward: list[list[str]] # does not convert it currently + def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): if original_dict is not None and field_name in original_dict and original_dict[field_name] not in [None, "null"]: @@ -41,8 +49,8 @@ def convert_b32_address_field_to_hex(original_dict: dict, field_name: str): class RpcClient: - def __init__(self, node=None, auto_restart=False, log=None): - self.node = node + def __init__(self, node: Optional[TestNode]=None, auto_restart=False, log=None): + self.node: TestNode = node # type: ignore self.auto_restart = auto_restart self.log = log @@ -129,13 +137,22 @@ def generate_block_with_parent(self, parent_hash: str, referee: list = None, num assert_is_hash_string(block_hash) return block_hash - def generate_custom_block(self, parent_hash: str, referee: list, txs: list) -> str: + def generate_custom_block(self, parent_hash: str, referee: list, txs: list[Union[Transaction, SignedTransaction]]) -> str: assert_is_hash_string(parent_hash) for r in referee: assert_is_hash_string(r) - encoded_txs = eth_utils.encode_hex(rlp.encode(txs)) + raw_txs = [] + for tx in txs: + if isinstance(tx, SignedTransaction): + raw_txs.append(tx.rawTransaction) + elif isinstance(tx, Transaction): + raw_txs.append(rlp.encode(tx)) + else: + raw_txs.append(rlp.encode(tx)) + + encoded_txs = eth_utils.encode_hex(rlp.encode(raw_txs)) block_hash = self.node.test_generatecustomblock(parent_hash, referee, encoded_txs) assert_is_hash_string(block_hash) @@ -188,6 +205,9 @@ def get_code(self, address: str, epoch: Union[str, dict] = None) -> str: def gas_price(self) -> int: return int(self.node.cfx_gasPrice(), 0) + def base_fee_per_gas(self, epoch: Union[int,str] = "latest_mined"): + return int(self.block_by_epoch(epoch).get("baseFeePerGas", "0x0"), 16) + def get_block_reward_info(self, epoch: str): reward = self.node.cfx_getBlockRewardInfo(epoch) convert_b32_address_field_to_hex(reward, "author") @@ -296,8 +316,12 @@ def send_raw_tx(self, raw_tx: str, wait_for_catchup=True) -> str: def clear_tx_pool(self): self.node.txpool_clear() - def send_tx(self, tx: Transaction, wait_for_receipt=False, wait_for_catchup=True) -> str: - encoded = eth_utils.encode_hex(rlp.encode(tx)) + # a temporary patch for transaction compatibity + def send_tx(self, tx: Union[Transaction, SignedTransaction], wait_for_receipt=False, wait_for_catchup=True) -> str: + if isinstance(tx, SignedTransaction): + encoded = cast(str, tx.rawTransaction.hex()) + else: + encoded = eth_utils.encode_hex(rlp.encode(tx)) tx_hash = self.send_raw_tx(encoded, wait_for_catchup=wait_for_catchup) if wait_for_receipt: @@ -334,12 +358,18 @@ def block_by_hash_with_pivot_assumption(self, block_hash: str, pivot_hash: str, convert_b32_address_field_to_hex(block, "miner") return block - def block_by_epoch(self, epoch: str, include_txs: bool = False) -> dict: + def block_by_epoch(self, epoch: Union[str, int], include_txs: bool = False) -> dict: + if type(epoch) is int: + epoch = hex(epoch) + block = self.node.cfx_getBlockByEpochNumber(epoch, include_txs) convert_b32_address_field_to_hex(block, "miner") return block - def block_by_block_number(self, block_number: str, include_txs: bool = False) -> dict: + def block_by_block_number(self, block_number: Union[str, int], include_txs: bool = False) -> dict: + if type(block_number) is int: + block_number = hex(block_number) + block = self.node.cfx_getBlockByBlockNumber(block_number, include_txs) convert_b32_address_field_to_hex(block, "miner") return block @@ -354,7 +384,7 @@ def get_tx(self, tx_hash: str) -> dict: convert_b32_address_field_to_hex(tx, "contractCreated") return tx - def new_tx(self, sender=None, receiver=None, nonce=None, gas_price=1, gas=21000, value=100, data=b'', sign=True, + def new_tx(self, *, sender=None, receiver=None, nonce=None, gas_price=1, gas=21000, value=100, data=b'', sign=True, priv_key=None, storage_limit=None, epoch_height=None, chain_id=DEFAULT_PY_TEST_CHAIN_ID): if priv_key is None: priv_key = default_config["GENESIS_PRI_KEY"] @@ -380,6 +410,49 @@ def new_tx(self, sender=None, receiver=None, nonce=None, gas_price=1, gas=21000, return tx.sign(priv_key) else: return tx + + def new_typed_tx(self, *, type_=2, receiver=None, nonce=None, max_fee_per_gas=None,max_priority_fee_per_gas=0, access_list=[], gas=21000, value=100, data=b'', + priv_key=None, storage_limit=0, epoch_height=None, chain_id=DEFAULT_PY_TEST_CHAIN_ID + ) -> SignedTransaction: + + if priv_key: + acct = CfxAccount.from_key(priv_key, DEFAULT_PY_TEST_CHAIN_ID) + else: + acct = CfxAccount.from_key(default_config["GENESIS_PRI_KEY"], DEFAULT_PY_TEST_CHAIN_ID) + if receiver is None: + receiver = self.COINBASE_ADDR + tx = {} + tx["type"] = type_ + tx["gas"] = gas + tx["storageLimit"] = storage_limit + tx["value"] = value + tx["data"] = data + tx["maxPriorityFeePerGas"] = max_priority_fee_per_gas + tx["chainId"] = chain_id + tx["to"] = receiver + + if nonce is None: + nonce = self.get_nonce(acct.hex_address) + tx["nonce"] = nonce + + if access_list != []: + def format_access_list(a_list): + rtn = [] + for item in a_list: + rtn.append({"address": item['address'], "storageKeys": item['storage_keys']}) + + access_list = format_access_list(access_list) + tx["accessList"] = access_list + + if epoch_height is None: + epoch_height = self.epoch_number() + tx["epochHeight"] = epoch_height + + # ensuring transaction can be sent + if max_fee_per_gas is None: + max_fee_per_gas = self.base_fee_per_gas('latest_mined') + 1 + tx["maxFeePerGas"] = max_fee_per_gas + return acct.sign_transaction(tx) def new_contract_tx(self, receiver: Optional[str], data_hex: str = None, sender=None, priv_key=None, nonce=None, gas_price=1, @@ -444,7 +517,7 @@ def disconnect_peer(self, node_id: str, node_op: str = None) -> int: def chain(self) -> list: return self.node.cfx_getChain() - def get_transaction_receipt(self, tx_hash: str) -> dict: + def get_transaction_receipt(self, tx_hash: str) -> dict[str, Any]: assert_is_hash_string(tx_hash) r = self.node.cfx_getTransactionReceipt(tx_hash) if r is None: @@ -573,6 +646,18 @@ def get_transaction_trace(self, tx_hash: str): def filter_trace(self, filter: dict): return self.node.trace_filter(filter) + + def fee_history(self, epoch_count: int, last_epoch: Union[int, str], reward_percentiles: Optional[list[float]]=None) -> CfxFeeHistoryResponse: + if reward_percentiles is None: + reward_percentiles = [50] + if isinstance(last_epoch, int): + last_epoch = hex(last_epoch) + rtn = self.node.cfx_feeHistory(hex(epoch_count), last_epoch, reward_percentiles) + rtn[ + 'baseFeePerGas' + ] = [ int(v, 16) for v in rtn['baseFeePerGas'] ] + return rtn + def wait_for_pos_register(self, priv_key=None, stake_value=2_000_000, voting_power=None, legacy=True, should_fail=False): if priv_key is None: diff --git a/tests/contract_bench_test.py b/tests/contract_bench_test.py index 1dc9f00513..db71e62820 100755 --- a/tests/contract_bench_test.py +++ b/tests/contract_bench_test.py @@ -21,6 +21,7 @@ class ContractBenchTest(SmartContractBenchBase): def set_test_params(self): self.num_nodes = 1 self.conf_parameters["execute_genesis"] = "true" + self.conf_parameters["cancun_opcodes_transition_number"] = 99999999 def setup_network(self): self.setup_nodes() diff --git a/tests/contract_remove_test.py b/tests/contract_remove_test.py index 4c2e9050ac..bcab2ea14e 100644 --- a/tests/contract_remove_test.py +++ b/tests/contract_remove_test.py @@ -36,6 +36,9 @@ def set_test_params(self): self.conf_parameters["dev_snapshot_epoch_count"] = str(SNAPSHOT_EPOCH) self.conf_parameters["anticone_penalty_ratio"] = "10" + # Disable CIP-131 on test + self.conf_parameters["next_hardfork_transition_number"] = 9999999 + def run_test(self): accounts: List[Account] = self.initialize_accounts(2, value = 1000) self.genesis_addr3 = accounts[0].address diff --git a/tests/evm_space/debug_trace_tx_test.py b/tests/evm_space/debug_trace_tx_test.py new file mode 100644 index 0000000000..26dea61e6c --- /dev/null +++ b/tests/evm_space/debug_trace_tx_test.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +# import os, sys +# sys.path.insert(1, os.path.join(sys.path[0], '..')) + +from base import Web3Base +from conflux.config import default_config +from test_framework.util import * +from web3 import Web3 + +toHex = Web3.toHex + +class DebugTraceTxTest(Web3Base): + def set_test_params(self): + super().set_test_params() + self.conf_parameters["public_evm_rpc_apis"] = "\"eth,ethdebug\"" + + def run_test(self): + self.cfxPrivkey = default_config['GENESIS_PRI_KEY'] + self.cfxAccount = self.rpc.GENESIS_ADDR + print(f'Using Conflux account {self.cfxAccount}') + # initialize EVM account + self.evmAccount = self.w3.eth.account.privateKeyToAccount(self.DEFAULT_TEST_ACCOUNT_KEY) + print(f'Using EVM account {self.evmAccount.address}') + self.cross_space_transfer(self.evmAccount.address, 100 * 10 ** 18) + assert_equal(self.nodes[0].eth_getBalance(self.evmAccount.address), hex(100 * 10 ** 18)) + + # self.common_cfx_transfer_tx_trace() + erc20_addr = self.contract_deploy_tx_trace() + erc20_transfer_hash = self.erc20_transfer_tx_trace(erc20_addr) + self.noop_tracer(erc20_transfer_hash) + self.four_byte_tracer(erc20_transfer_hash) + self.call_tracer(erc20_transfer_hash) + self.check_opcode_trace_with_config(erc20_transfer_hash) + + def trace_tx(self, tx_hash, opts = None): + trace = self.nodes[0].ethrpc.debug_traceTransaction(toHex(tx_hash), opts) + return trace + + def common_cfx_transfer_tx_trace(self): + nonce = self.w3.eth.getTransactionCount(self.evmAccount.address) + + signed = self.evmAccount.signTransaction({ + "to": self.evmAccount.address, + "value": 1, + "gasPrice": 1, + "gas": 210000, + "nonce": nonce, + "chainId": self.w3.eth.chainId, + }) + + return_tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + self.rpc.generate_blocks(20, 1) + trace = self.nodes[0].ethrpc.debug_traceTransaction(toHex(return_tx_hash)) + + assert_equal(trace["failed"], False) + assert_equal(trace["gas"], 21000) + assert_equal(trace["returnValue"], "") + assert_equal(trace["structLogs"], []) + + def contract_deploy_tx_trace(self): + bytecode_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../contracts/erc20_bytecode.dat") + assert(os.path.isfile(bytecode_file)) + bytecode = open(bytecode_file).read() + + nonce = self.w3.eth.getTransactionCount(self.evmAccount.address) + signed = self.evmAccount.signTransaction({ + "to": None, + "value": 0, + "gasPrice": 1, + "gas": 10000000, + "nonce": nonce, + "chainId": self.w3.eth.chainId, + "data": bytecode, + }) + + return_tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + + self.rpc.generate_block(1) + self.rpc.generate_blocks(20, 1) + + receipt = self.w3.eth.get_transaction_receipt(return_tx_hash) + assert_equal(receipt["status"], 1) + + trace = self.nodes[0].ethrpc.debug_traceTransaction(toHex(return_tx_hash)) + assert_equal(trace["failed"], False) + oplog_len = len(trace["structLogs"]) + assert_equal(oplog_len > 0, True) + # print(trace["structLogs"][oplog_len-1]) + assert_equal(trace["structLogs"][oplog_len-1]["op"], "RETURN") + + return receipt["contractAddress"] + + def erc20_transfer_tx_trace(self, erc20_address): + abi = self.load_abi_from_contracts_folder("erc20") + erc20 = self.w3.eth.contract(address=erc20_address, abi=abi) + + # balance = erc20.functions.balanceOf(self.evmAccount.address).call() + target_addr = Web3.toChecksumAddress("0x8b14d287b4150ff22ac73df8be720e933f659abc") + + data = erc20.encodeABI(fn_name="transfer", args=[target_addr, 100]) + + nonce = self.w3.eth.getTransactionCount(self.evmAccount.address) + signed = self.evmAccount.signTransaction({ + "to": erc20_address, + "value": 0, + "gasPrice": 1, + "gas": 1000000, + "nonce": nonce, + "chainId": self.w3.eth.chainId, + "data": data, + }) + + tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + + # why this method is not working? + # tx_hash = erc20.functions.transfer(target_addr, 100).transact() + + self.rpc.generate_block(1) + self.rpc.generate_blocks(20, 1) + # receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) + + trace = self.trace_tx(tx_hash) + + assert_equal(trace["failed"], False) + oplog_len = len(trace["structLogs"]) + assert_equal(oplog_len > 0, True) + assert_equal(trace["structLogs"][oplog_len-1]["op"], "RETURN") + + return tx_hash + + def noop_tracer(self, tx_hash): + noop_trace = self.trace_tx(tx_hash, {"tracer": "noopTracer"}) + assert_equal(noop_trace, {}) + + def four_byte_tracer(self, tx_hash): + four_byte_trace = self.trace_tx(tx_hash, {"tracer": "4byteTracer"}) + assert_equal(four_byte_trace, {'0xa9059cbb-64': 1}) + + def call_tracer(self, tx_hash): + call_trace = self.trace_tx(tx_hash, {"tracer": "callTracer"}) + assert_equal(call_trace["from"], "0xfcad0b19bb29d4674531d6f115237e16afce377c") + assert_equal(call_trace["to"], "0x8bfc6fd9437cf1879fb84aade867b6e81efb5631") + assert_equal(call_trace["type"], 'CALL') + assert_equal(call_trace["value"], "0x0") + assert_equal(call_trace["output"], "0x0000000000000000000000000000000000000000000000000000000000000001") + + def check_opcode_trace_with_config(self, tx_hash): + trace = self.trace_tx(tx_hash, { + "enableMemory": True, + "disableStack": False, + "disableStorage": False, + "enableReturnData": True + }) + + oplog_len = len(trace["structLogs"]) + assert_equal(trace["failed"], False) + assert_equal(oplog_len, 231) + # print(len(trace["structLogs"])) + + # limit parameter test + limited_trace = self.trace_tx(tx_hash, { + "enableMemory": True, + "disableStack": False, + "disableStorage": False, + "enableReturnData": True, + "limit": 10 + }) + assert_equal(len(limited_trace["structLogs"]), 10) + + no_stack_storage_trace = self.trace_tx(tx_hash, { + "enableMemory": True, + "disableStack": True, + "disableStorage": True, + "enableReturnData": True + }) + + disable_all_trace = self.trace_tx(tx_hash, { + "enableMemory": False, + "disableStack": True, + "disableStorage": True, + "enableReturnData": False + }) + + for i, oplog in enumerate(trace["structLogs"]): + oplog = trace["structLogs"][i] + + if "memory" in oplog: + assert_equal("memory" in disable_all_trace["structLogs"][i], False) + + if "returnData" in oplog: + assert_equal("returnData" in disable_all_trace["structLogs"][i], False) + + if "stack" in oplog: + assert_equal("stack" in no_stack_storage_trace["structLogs"][i], False) + + if "storage" in oplog: + assert_equal("storage" in no_stack_storage_trace["structLogs"][i], False) + + + + def load_abi_from_contracts_folder(self, name): + abi_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "contracts", name + "_abi.json") + with open(abi_file, 'r') as abi_file: + abi = json.loads(abi_file.read()) + return abi + +if __name__ == "__main__": + DebugTraceTxTest().main() \ No newline at end of file diff --git a/tests/evm_space/eip1559_test.py b/tests/evm_space/eip1559_test.py new file mode 100755 index 0000000000..cd7c3498aa --- /dev/null +++ b/tests/evm_space/eip1559_test.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python3 +import os, sys +sys.path.insert(1, os.path.join(sys.path[0], '..')) + +from test_framework.util import * +from conflux.config import default_config +from base import Web3Base +from test_framework.blocktools import encode_hex_0x +from conflux.address import b32_address_to_hex +from conflux.rpc import RpcClient +from web3 import Web3 + +BASE_PRICE = 20 * (10 ** 9) +class Eip1559Test(Web3Base): + def set_test_params(self): + self.num_nodes = 2 + self.conf_parameters["evm_chain_id"] = str(10) + self.conf_parameters["evm_transaction_block_ratio"] = str(1) + self.conf_parameters["executive_trace"] = "true" + self.conf_parameters["cip1559_transition_height"] = str(1) + self.conf_parameters["min_eth_base_price"] = 20 * (10**9) + + def setup_network(self): + self.add_nodes(self.num_nodes) + self.start_node(0, ["--archive"]) + self.start_node(1, ["--archive"]) + connect_nodes(self.nodes, 0 , 1) + self.rpc = RpcClient(self.nodes[0]) + ip = self.nodes[0].ip + port = self.nodes[0].ethrpcport + self.w3 = Web3(Web3.HTTPProvider(f'http://{ip}:{port}/')) + assert_equal(self.w3.isConnected(), True) + + + def run_test(self): + self.cfxPrivkey = default_config['GENESIS_PRI_KEY'] + self.cfxAccount = self.rpc.GENESIS_ADDR + self.log.info(f'Using Conflux account {self.cfxAccount}') + + # initialize EVM account + self.evmAccount = self.w3.eth.account.privateKeyToAccount(self.DEFAULT_TEST_ACCOUNT_KEY) + self.log.info(f'Using EVM account {self.evmAccount.address}') + self.cross_space_transfer(self.evmAccount.address, 100 * 10 ** 18) + assert_equal(self.nodes[0].eth_getBalance(self.evmAccount.address), hex(100 * 10 ** 18)) + + # x = b32_address_to_hex("NET10:TYPE.USER:AAR8JZYBZV0FHZREAV49SYXNZUT8S0JT1ASMXX99XH") + # y = b32_address_to_hex('NET10:TYPE.BUILTIN:AAEJUAAAAAAAAAAAAAAAAAAAAAAAAAAAA27GYVFYR7') + ret = self.nodes[0].debug_getTransactionsByEpoch("0x1") + assert_equal(len(ret), 1) + + self.nonce = self.w3.eth.getTransactionCount(self.evmAccount.address) + + tx, receipt = self.send_large_transaction() + self.check_node_sync(tx, receipt) + + tx, receipt = self.send_large_cheap_transactions() + self.check_node_sync(tx, receipt) + + tx, receipt = self.send_many_transactions_in_one_block() + self.check_node_sync(tx, receipt, tx_num = 10) + + self.check_fee_history() + + + def send_large_transaction(self): + signed = self.evmAccount.signTransaction({ + "type": "0x2", + "to": self.evmAccount.address, + "value": 1, + "gas": 30000000, + 'maxFeePerGas': 10 * BASE_PRICE, + 'maxPriorityFeePerGas': 1, + "nonce": self.nonce, + "chainId": 10, + }) + self.nonce += 1 + + return_tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + self.rpc.generate_block(1) + self.rpc.generate_blocks(20, 1) + receipt = self.w3.eth.waitForTransactionReceipt(return_tx_hash) + + assert_equal(receipt["status"], 1) + # TODO check EIP1559 gas usage + # assert_equal(receipt["gasUsed"], 210000 / 4 * 3) + assert_equal(receipt["txExecErrorMsg"], None) + + tx = self.w3.eth.get_transaction(return_tx_hash) + + return tx, receipt + + def send_large_cheap_transactions(self): + for i in range(0, 5): + signed = self.evmAccount.signTransaction({ + "type": "0x2", + "to": self.evmAccount.address, + "value": 1, + "gas": 7_500_000, + 'maxFeePerGas': BASE_PRICE, + 'maxPriorityFeePerGas': 1, + "nonce": self.nonce + i, + "chainId": 10, + }) + return_tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + + self.nonce += 5 + + self.rpc.generate_blocks(20, 5) + receipt = self.w3.eth.waitForTransactionReceipt(return_tx_hash) + + assert_equal(receipt["status"], 1) + assert_equal(receipt["txExecErrorMsg"], None) + + tx = self.w3.eth.get_transaction(return_tx_hash) + + return tx, receipt + + def check_node_sync(self, tx, receipt, tx_num = 1): + # Check if another node can decode EIP1559 transactions + sync_blocks(self.nodes) + ret1 = self.nodes[0].debug_getTransactionsByEpoch(hex(receipt["blockNumber"])) + ret2 = self.nodes[1].debug_getTransactionsByBlock(encode_hex_0x(tx["blockHash"])) + assert_equal(len(ret1), tx_num) + assert_equal(len(ret2), tx_num) + assert_equal(ret1[0], ret2[0]) + + def check_fee_history(self): + fee_history = self.nodes[0].eth_feeHistory("0x5", "latest", [21, 75]) + assert_equal(len(fee_history['baseFeePerGas']), 6) + assert_equal(len(fee_history['gasUsedRatio']), 5) + assert_equal(len(fee_history['reward']), 5) + + assert_greater_than(int(self.nodes[0].cfx_getFeeBurnt(), 0), 0) + + def send_many_transactions_in_one_block(self): + for i in range(0, 10): + signed = self.evmAccount.signTransaction({ + "type": "0x2", + "to": self.evmAccount.address, + "value": 1, + "gas": 21000, + 'maxFeePerGas': BASE_PRICE, + 'maxPriorityFeePerGas': 1, + "nonce": self.nonce + i, + "chainId": 10, + }) + return_tx_hash = self.w3.eth.sendRawTransaction(signed["rawTransaction"]) + self.nonce += 10 + + self.rpc.generate_block(10) + self.rpc.generate_blocks(20, 0) + receipt = self.w3.eth.waitForTransactionReceipt(return_tx_hash) + assert_equal(receipt["cumulativeGasUsed"], 21000 * 10) + assert_equal(receipt["gasUsed"], 21000) + + assert_equal(self.w3.eth.estimate_gas({"to": self.evmAccount.address}), 21000) + + tx = self.w3.eth.get_transaction(return_tx_hash) + return tx, receipt + + +if __name__ == "__main__": + Eip1559Test().main() \ No newline at end of file diff --git a/tests/evm_space/filter_fork_finalize_state_after_fork_test.py b/tests/evm_space/filter_fork_finalize_state_after_fork_test.py index e51cdbfe85..b9c432f372 100644 --- a/tests/evm_space/filter_fork_finalize_state_after_fork_test.py +++ b/tests/evm_space/filter_fork_finalize_state_after_fork_test.py @@ -35,6 +35,7 @@ def set_test_params(self): # No auto timeout. self.pos_parameters["round_time_ms"] = 1000000000 self.conf_parameters["pos_reference_enable_height"] = 10 + self.conf_parameters["cip1559_transition_height"] = 10 async def run_async(self): clients = [] diff --git a/tests/evm_space/filter_fork_finalize_state_in_fork_test.py b/tests/evm_space/filter_fork_finalize_state_in_fork_test.py index 98e03d9ee0..708078bbcb 100644 --- a/tests/evm_space/filter_fork_finalize_state_in_fork_test.py +++ b/tests/evm_space/filter_fork_finalize_state_in_fork_test.py @@ -36,6 +36,7 @@ def set_test_params(self): # No auto timeout. self.pos_parameters["round_time_ms"] = 1000000000 self.conf_parameters["pos_reference_enable_height"] = 10 + self.conf_parameters["cip1559_transition_height"] = 10 self.conf_parameters["adaptive_weight_beta"] = "1" async def run_async(self): diff --git a/tests/evm_space/tx_and_receipt_test.py b/tests/evm_space/tx_and_receipt_test.py index 273442ca77..4a1547e67a 100755 --- a/tests/evm_space/tx_and_receipt_test.py +++ b/tests/evm_space/tx_and_receipt_test.py @@ -41,7 +41,7 @@ def run_test(self): self.rpc.generate_blocks(20, 1) receipt = self.w3.eth.waitForTransactionReceipt(return_tx_hash) assert_equal(receipt["status"], 1) - assert_equal(receipt["gasUsed"], 210000 / 4 * 3) + assert_equal(receipt["gasUsed"], 21000) assert_equal(receipt["txExecErrorMsg"], None) tx = self.w3.eth.get_transaction(return_tx_hash) diff --git a/tests/expire_block_test.py b/tests/expire_block_test.py index 3df08f4b11..b926a9345a 100755 --- a/tests/expire_block_test.py +++ b/tests/expire_block_test.py @@ -16,6 +16,8 @@ def set_test_params(self): self.conf_parameters["era_epoch_count"] = "100" self.conf_parameters["dev_snapshot_epoch_count"] = "50" self.conf_parameters["anticone_penalty_ratio"] = "10" + # Disable 1559 as it uses an incompatible old format. + self.conf_parameters["cip1559_transition_height"] = str(99999999) self.num_nodes = 2 def setup_network(self): diff --git a/tests/extra-test-toolkits b/tests/extra-test-toolkits index 043d9c8185..f0a4feafd7 160000 --- a/tests/extra-test-toolkits +++ b/tests/extra-test-toolkits @@ -1 +1 @@ -Subproject commit 043d9c8185fdc62080cbb4f554a5a0a98b08cf1e +Subproject commit f0a4feafd70334a0d34f6e49ba2973715fb66666 diff --git a/tests/fork_same_height_hiding_test.py b/tests/fork_same_height_hiding_test.py index ca7e1d1335..cc061b621e 100755 --- a/tests/fork_same_height_hiding_test.py +++ b/tests/fork_same_height_hiding_test.py @@ -17,6 +17,7 @@ def set_test_params(self): self.num_nodes = 2 # Disable pos reference because pow blocks are generated too fast. self.conf_parameters["pos_reference_enable_height"] = '100000' + self.conf_parameters["cip1559_transition_height"] = '100000' def setup_network(self): self.setup_nodes() diff --git a/tests/full_node_tests/mpt_snapshot_null_epoch_test.py b/tests/full_node_tests/mpt_snapshot_null_epoch_test.py index a8d46e17a3..1984632193 100644 --- a/tests/full_node_tests/mpt_snapshot_null_epoch_test.py +++ b/tests/full_node_tests/mpt_snapshot_null_epoch_test.py @@ -26,6 +26,7 @@ def set_test_params(self): "dev_allow_phase_change_without_peer": "false", # Disable pos reference because pow blocks are generated too fast. "pos_reference_enable_height": "10000", + "cip1559_transition_height": "10000", "node_type": "\"archive\"", "use_isolated_db_for_mpt_table": "true", } diff --git a/tests/full_node_tests/mpt_snapshot_test.py b/tests/full_node_tests/mpt_snapshot_test.py index 27216566e8..a928ce61fb 100644 --- a/tests/full_node_tests/mpt_snapshot_test.py +++ b/tests/full_node_tests/mpt_snapshot_test.py @@ -26,6 +26,7 @@ def set_test_params(self): "dev_allow_phase_change_without_peer": "false", # Disable pos reference because pow blocks are generated too fast. "pos_reference_enable_height": "10000", + "cip1559_transition_height": "10000", "use_isolated_db_for_mpt_table": "false", } diff --git a/tests/full_node_tests/p2p_era_test.py b/tests/full_node_tests/p2p_era_test.py index 0be10f066a..1219e2e3de 100755 --- a/tests/full_node_tests/p2p_era_test.py +++ b/tests/full_node_tests/p2p_era_test.py @@ -30,6 +30,7 @@ def set_test_params(self): # it goes through all the phases to download data as a normal node. self.conf_parameters["dev_allow_phase_change_without_peer"] = "false" self.conf_parameters["pos_reference_enable_height"] = 600 + self.conf_parameters["cip1559_transition_height"] = 600 self.stop_probability = 0.01 self.clean_probability = 0.5 diff --git a/tests/full_node_tests/reboot_test.py b/tests/full_node_tests/reboot_test.py index 165b0defc6..ef1682e49d 100644 --- a/tests/full_node_tests/reboot_test.py +++ b/tests/full_node_tests/reboot_test.py @@ -24,6 +24,7 @@ def set_test_params(self): "dev_allow_phase_change_without_peer": "false", # Disable pos reference because pow blocks are generated too fast. "pos_reference_enable_height": "10000", + "cip1559_transition_height": "10000", "keep_snapshot_before_stable_checkpoint": "false", # force recompute with parent snapshot doesn't exist "force_recompute_height_during_construct_pivot": "1501", diff --git a/tests/full_node_tests/sync_checkpoint_snapshot_before_stable_test.py b/tests/full_node_tests/sync_checkpoint_snapshot_before_stable_test.py index bb804792bd..c45ddb483c 100755 --- a/tests/full_node_tests/sync_checkpoint_snapshot_before_stable_test.py +++ b/tests/full_node_tests/sync_checkpoint_snapshot_before_stable_test.py @@ -26,6 +26,7 @@ def set_test_params(self): "dev_allow_phase_change_without_peer": "false", # Disable pos reference because pow blocks are generated too fast. "pos_reference_enable_height": "10000", + "cip1559_transition_height": "10000", "keep_snapshot_before_stable_checkpoint": "false", } diff --git a/tests/full_node_tests/sync_checkpoint_test.py b/tests/full_node_tests/sync_checkpoint_test.py index 5634360033..9f290bd800 100755 --- a/tests/full_node_tests/sync_checkpoint_test.py +++ b/tests/full_node_tests/sync_checkpoint_test.py @@ -26,7 +26,8 @@ def set_test_params(self): # Make sure checkpoint synchronization is triggered during phase change. "dev_allow_phase_change_without_peer": "false", # Disable pos reference because pow blocks are generated too fast. - "pos_reference_enable_height": "10000" + "pos_reference_enable_height": "10000", + "cip1559_transition_height": "10000", } def setup_network(self): @@ -36,7 +37,7 @@ def setup_network(self): connect_sample_nodes(self.nodes[:-1], self.log, latency_max=1) for i in range(self.num_nodes - 1): self.nodes[i].wait_for_recovery(["NormalSyncPhase"], 10) - + def _generate_txs(self, peer, num): client = RpcClient(self.nodes[peer]) txs = [] diff --git a/tests/invalid_block_sync_test.py b/tests/invalid_block_sync_test.py index a005b5d4f9..705b98bbc1 100755 --- a/tests/invalid_block_sync_test.py +++ b/tests/invalid_block_sync_test.py @@ -69,6 +69,8 @@ class InvalidBodySyncTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 2 self.conf_parameters["dev_allow_phase_change_without_peer"] = "false" + # Disable 1559 because the client submit old format data + self.conf_parameters["cip1559_transition_height"] = str(99999999) def setup_network(self): self.add_nodes(self.num_nodes) diff --git a/tests/invalid_message_test.py b/tests/invalid_message_test.py index cee7212686..bac7272b61 100755 --- a/tests/invalid_message_test.py +++ b/tests/invalid_message_test.py @@ -14,6 +14,9 @@ class InvalidMessageTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 4 + # Disable 1559 for RPC tests temporarily + self.conf_parameters["cip1559_transition_height"] = str(99999999) + def setup_network(self): self.setup_nodes() diff --git a/tests/light/log_filtering_test.py b/tests/light/log_filtering_test.py index 31f646c6ca..d7b03a5ca8 100755 --- a/tests/light/log_filtering_test.py +++ b/tests/light/log_filtering_test.py @@ -28,6 +28,8 @@ class LogFilteringTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 3 + # Disable 1559 for RPC tests temporarily + self.conf_parameters["cip1559_transition_height"] = str(99999999) def setup_network(self): self.add_nodes(self.num_nodes) diff --git a/tests/light/rpc_test.py b/tests/light/rpc_test.py index 97da74c086..6f2216a345 100755 --- a/tests/light/rpc_test.py +++ b/tests/light/rpc_test.py @@ -37,6 +37,8 @@ def set_test_params(self): self.conf_parameters["timer_chain_beta"] = "20" self.conf_parameters["timer_chain_block_difficulty_ratio"] = "3" self.conf_parameters["block_cache_gc_period_ms"] = "10" + # Disable 1559 for RPC tests temporarily + self.conf_parameters["cip1559_transition_height"] = str(99999999) def deploy_contract(self, data_hex): tx = self.rpc[FULLNODE0].new_contract_tx(receiver="", data_hex=data_hex, storage_limit=2000) diff --git a/tests/log_filtering_test.py b/tests/log_filtering_test.py index 9357eba0e0..d092cd4c66 100755 --- a/tests/log_filtering_test.py +++ b/tests/log_filtering_test.py @@ -21,6 +21,8 @@ class LogFilteringTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 1 + # Disable 1559 because it has hardcore execution result not compatible with 1559 + self.conf_parameters["cip1559_transition_height"] = str(99999999) def setup_network(self): self.setup_nodes() @@ -140,11 +142,11 @@ def run_test(self): parent_hash = self.rpc.block_by_epoch("latest_mined")['hash'] start_nonce = self.rpc.get_nonce(sender) - txs1 = [self.rpc.new_contract_tx(receiver=contractAddr, data_hex=encode_hex_0x(keccak(b"bar()")), sender=sender, priv_key=priv_key, storage_limit=64, nonce = start_nonce + ii) for ii in range(0, NUM_CALLS)] + txs1 = [self.rpc.new_contract_tx(receiver=contractAddr, data_hex=encode_hex_0x(keccak(b"bar()")), sender=sender, priv_key=priv_key, storage_limit=64, nonce = start_nonce + ii, gas=1_500_000) for ii in range(0, NUM_CALLS)] block_hash_1 = self.rpc.generate_custom_block(parent_hash = parent_hash, referee = [], txs = txs1) epoch_1 = self.rpc.block_by_hash(block_hash_1)["epochNumber"] - txs2 = [self.rpc.new_contract_tx(receiver=contractAddr, data_hex=encode_hex_0x(keccak(b"bar()")), sender=sender, priv_key=priv_key, storage_limit=64, nonce = start_nonce + NUM_CALLS + ii) for ii in range(0, NUM_CALLS)] + txs2 = [self.rpc.new_contract_tx(receiver=contractAddr, data_hex=encode_hex_0x(keccak(b"bar()")), sender=sender, priv_key=priv_key, storage_limit=64, nonce = start_nonce + NUM_CALLS + ii, gas=1_500_000) for ii in range(0, NUM_CALLS)] block_hash_2 = self.rpc.generate_custom_block(parent_hash = block_hash_1, referee = [], txs = txs2) epoch_2 = self.rpc.block_by_hash(block_hash_2)["epochNumber"] diff --git a/tests/message_test.py b/tests/message_test.py index 8582725358..ef8fd81f36 100755 --- a/tests/message_test.py +++ b/tests/message_test.py @@ -13,6 +13,8 @@ class MessageTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 4 + # Disable 1559 for RPC tests temporarily + self.conf_parameters["cip1559_transition_height"] = str(99999999) def setup_network(self): self.setup_nodes() diff --git a/tests/pos/hard_fork_test.py b/tests/pos/hard_fork_test.py index 1b5e9f6e6d..df906446cc 100755 --- a/tests/pos/hard_fork_test.py +++ b/tests/pos/hard_fork_test.py @@ -43,6 +43,7 @@ def set_test_params(self): self.conf_parameters["hydra_transition_number"] = 300 self.conf_parameters["cip43_init_end_number"] = 500 self.conf_parameters["pos_reference_enable_height"] = 1000 + self.conf_parameters["cip1559_transition_height"] = 1000 self.conf_parameters["era_epoch_count"] = 200 self.conf_parameters["pos_round_per_term"] = 10 self.conf_parameters["pos_term_max_size"] = 100 @@ -51,6 +52,7 @@ def set_test_params(self): self.conf_parameters["sigma_fix_transition_number"] = 1000000 self.conf_parameters["tanzanite_transition_height"] = 100 self.conf_parameters["cip112_transition_height"] = 100 + self.rpc_timewait = 6000 def setup_nodes(self): diff --git a/tests/pos/retire_param_hard_fork_test.py b/tests/pos/retire_param_hard_fork_test.py index aebe59ef50..ffab85c3ea 100755 --- a/tests/pos/retire_param_hard_fork_test.py +++ b/tests/pos/retire_param_hard_fork_test.py @@ -44,6 +44,10 @@ def set_test_params(self): self.conf_parameters["pos_cip99_transition_view"] = 72 self.conf_parameters["pos_cip99_in_queue_locked_views"] = 66 self.conf_parameters["pos_cip99_out_queue_locked_views"] = 6 + self.conf_parameters["pos_cip136_transition_view"] = 144 + self.conf_parameters["pos_cip136_round_per_term"] = 12 + self.conf_parameters["pos_cip136_in_queue_locked_views"] = 132 + self.conf_parameters["pos_cip136_out_queue_locked_views"] = 12 self.rpc_timewait = 6000 def run_test(self): @@ -86,6 +90,17 @@ def wait(): print(new_epoch, old_epoch) assert_greater_than_or_equal(new_epoch - old_epoch, 3) + wait_until(lambda: int(client.pos_status()["latestCommitted"], 0) > self.conf_parameters["pos_cip136_transition_view"], timeout=120) + old_epoch = int(client.pos_status()["epoch"], 0) + check_view = self.conf_parameters["pos_cip136_transition_view"] + self.conf_parameters["pos_cip136_round_per_term"] + wait_until(lambda: int(client.pos_status()["latestCommitted"], 0) > check_view, timeout=120) + status = client.pos_status() + assert_greater_than(check_view + self.conf_parameters["pos_cip136_round_per_term"], int(status["latestCommitted"], 0)) + assert_equal(int(status["epoch"], 0), old_epoch + 1) + wait_until(lambda: int(client.pos_status()["latestCommitted"], 0) > check_view + 7 * self.conf_parameters["pos_cip136_round_per_term"], timeout=240) + status = client.pos_status() + assert_equal(int(status["epoch"], 0), old_epoch + 8) + if __name__ == '__main__': RetireParamHardforkTest().main() diff --git a/tests/rpc_test.py b/tests/rpc_test.py index 4b4e6fac2b..61c4b180d8 100755 --- a/tests/rpc_test.py +++ b/tests/rpc_test.py @@ -19,6 +19,8 @@ def set_test_params(self): self.conf_parameters = { "executive_trace": "true", "public_rpc_apis": "\"cfx,debug,test,pubsub,trace\"", + # Disable 1559 for RPC tests temporarily + "cip1559_transition_height": str(99999999), } def setup_network(self): diff --git a/tests/secondary_reward_test.py b/tests/secondary_reward_test.py index 72f19b1d00..4dc0e58aed 100755 --- a/tests/secondary_reward_test.py +++ b/tests/secondary_reward_test.py @@ -16,6 +16,8 @@ def set_test_params(self): self.conf_parameters = {"mining_author": "\"10000000000000000000000000000000000000aa\"", "mining_type": "'disable'" } + # Disable 1559 because it has hardcore execution result not compatible with 1559 + self.conf_parameters["cip1559_transition_height"] = str(99999999) self.gasPrice = 1 def setup_network(self): diff --git a/tests/sync_test.py b/tests/sync_test.py index f0b92281f1..8fcf61a718 100755 --- a/tests/sync_test.py +++ b/tests/sync_test.py @@ -10,6 +10,8 @@ class SyncTest(ConfluxTestFramework): def set_test_params(self): self.num_nodes = 2 + # Disable 1559 because it has hardcore execution result not compatible with 1559 + self.conf_parameters["cip1559_transition_height"] = str(99999999) def setup_network(self): self.add_nodes(self.num_nodes) diff --git a/tests/test_contracts b/tests/test_contracts index 38535cde36..ca89c563bc 160000 --- a/tests/test_contracts +++ b/tests/test_contracts @@ -1 +1 @@ -Subproject commit 38535cde36c40ad154f4e62da6e33c4383903824 +Subproject commit ca89c563bcbd86289b87a48970bb4b8b09bebd11 diff --git a/tests/test_framework/simple_rpc_proxy.py b/tests/test_framework/simple_rpc_proxy.py index 6105f26032..71cd2d7bb1 100644 --- a/tests/test_framework/simple_rpc_proxy.py +++ b/tests/test_framework/simple_rpc_proxy.py @@ -1,4 +1,5 @@ import time +from typing import Any import jsonrpcclient.client from jsonrpcclient.exceptions import ReceivedErrorResponseError @@ -25,7 +26,7 @@ def __init__(self, client, method, timeout, node): self.timeout = timeout self.node = node - def __call__(self, *args, **argsn): + def __call__(self, *args, **argsn) -> Any: if argsn: raise ValueError('json rpc 2 only supports array arguments') from jsonrpcclient.requests import Request diff --git a/tests/test_framework/test_framework.py b/tests/test_framework/test_framework.py index 2abc46690c..b66c0957fa 100644 --- a/tests/test_framework/test_framework.py +++ b/tests/test_framework/test_framework.py @@ -73,7 +73,7 @@ class ConfluxTestFramework: def __init__(self): """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method""" self.setup_clean_chain = True - self.nodes = [] + self.nodes: list[TestNode] = [] self.network_thread = None self.mocktime = 0 self.rpc_timewait = CONFLUX_RPC_WAIT_TIMEOUT diff --git a/tests/test_framework/test_node.py b/tests/test_framework/test_node.py index 05a240739d..2cbac1ce5b 100644 --- a/tests/test_framework/test_node.py +++ b/tests/test_framework/test_node.py @@ -74,7 +74,7 @@ def __init__(self, index, datadir, rpchost, confluxd, rpc_timeout=None, remote=F self.running = False self.process = None self.rpc_connected = False - self.rpc = None + self.rpc: SimpleRpcProxy = None # type: ignore self.ethrpc = None self.ethrpc_connected = False self.log = logging.getLogger('TestFramework.node%d' % index) diff --git a/tests/test_framework/util.py b/tests/test_framework/util.py index c8b8aac316..e2ac3329d9 100644 --- a/tests/test_framework/util.py +++ b/tests/test_framework/util.py @@ -10,15 +10,18 @@ import re from subprocess import CalledProcessError, check_output import time -from typing import Optional, Callable, List, TYPE_CHECKING, cast +from typing import Optional, Callable, List, TYPE_CHECKING, cast, Tuple, Union import socket import threading import jsonrpcclient.exceptions import solcx import web3 +from cfx_account import Account as CfxAccount +from cfx_account.signers.local import LocalAccount as CfxLocalAccount from sys import platform import yaml import shutil +import math from test_framework.simple_rpc_proxy import SimpleRpcProxy from . import coverage @@ -806,3 +809,103 @@ def test_rpc_call_with_block_object(client: "RpcClient", txs: List, rpc_call: Ca assert(expected_result_lambda(result1)) assert_equal(result2, result1) + +# acct should have cfx +# create a chain of blocks with specified transfer tx with specified num and gas +# return the last block's hash and acct nonce +def generate_blocks_for_base_fee_manipulation(rpc: "RpcClient", acct: Union[CfxLocalAccount, str], block_count=10, tx_per_block=4, gas_per_tx=13500000,initial_parent_hash:str = None) -> Tuple[str, int]: + if isinstance(acct, str): + acct = CfxAccount.from_key(acct) + starting_nonce: int = rpc.get_nonce(acct.hex_address) + + if initial_parent_hash is None: + initial_parent_hash = cast(str, rpc.block_by_epoch("latest_mined")["hash"]) + + block_pointer = initial_parent_hash + for block_count in range(block_count): + block_pointer, starting_nonce = generate_single_block_for_base_fee_manipulation(rpc, acct, tx_per_block=tx_per_block, gas_per_tx=gas_per_tx,parent_hash=block_pointer, starting_nonce=starting_nonce) + + return block_pointer, starting_nonce + block_count * tx_per_block + +def generate_single_block_for_base_fee_manipulation(rpc: "RpcClient", acct: CfxLocalAccount, referee:list[str] =[], tx_per_block=4, gas_per_tx=13500000,parent_hash:str = None, starting_nonce: int = None) -> Tuple[str, int]: + if starting_nonce is None: + starting_nonce = cast(int, rpc.get_nonce(acct.hex_address)) + + if parent_hash is None: + parent_hash = cast(str, rpc.block_by_epoch("latest_mined")["hash"]) + + new_block = rpc.generate_custom_block( + txs=[ + rpc.new_tx( + priv_key=acct.key, + receiver=acct.address, + gas=gas_per_tx, + nonce=starting_nonce + i , + gas_price=rpc.base_fee_per_gas()*2 # give enough gas price to make the tx valid + ) + for i in range(tx_per_block) + ], + parent_hash=parent_hash, + referee=referee, + ) + return new_block, starting_nonce + tx_per_block + +# for transactions in either pivot/non-pivot block +# checks priority fee is calculated as expeted +def assert_correct_fee_computation_for_core_tx(rpc: "RpcClient", tx_hash: str, burnt_ratio=0.5): + def get_gas_charged(rpc: "RpcClient", tx_hash: str) -> int: + gas_limit = int(rpc.get_tx(tx_hash)["gas"], 16) + gas_used = int(rpc.get_transaction_receipt(tx_hash)["gasUsed"], 16) + return max(int(3/4*gas_limit), gas_used) + + receipt = rpc.get_transaction_receipt(tx_hash) + # The transaction is not executed + if receipt is None: + return + + tx_data = rpc.get_tx(tx_hash) + tx_type = int(tx_data["type"], 16) + if tx_type == 2: + # original tx fields + max_fee_per_gas = int(tx_data["maxFeePerGas"], 16) + max_priority_fee_per_gas = int(tx_data["maxPriorityFeePerGas"], 16) + else: + max_fee_per_gas = int(tx_data["gasPrice"], 16) + max_priority_fee_per_gas = int(tx_data["gasPrice"], 16) + + effective_gas_price = int(receipt["effectiveGasPrice"], 16) + transaction_epoch = int(receipt["epochNumber"],16) + is_in_pivot_block = rpc.block_by_epoch(transaction_epoch)["hash"] == receipt["blockHash"] + base_fee_per_gas = rpc.base_fee_per_gas(transaction_epoch) + burnt_fee_per_gas = math.ceil(base_fee_per_gas * burnt_ratio) + gas_fee = int(receipt["gasFee"], 16) + burnt_gas_fee = int(receipt["burntGasFee"], 16) + gas_charged = get_gas_charged(rpc, tx_hash) + + # check gas fee computation + # print("effective gas price: ", effective_gas_price) + # print("gas charged: ", get_gas_charged(rpc, tx_hash)) + # print("gas fee", gas_fee) + + # check gas fee and burnt gas fee computation + if receipt["outcomeStatus"] == "0x1": # tx fails becuase of not enough cash + assert "NotEnoughCash" in receipt["txExecErrorMsg"] + # all gas is charged + assert_equal(rpc.get_balance(tx_data["from"], receipt["epochNumber"]), 0) + # gas fee less than effective gas price + assert gas_fee < effective_gas_price*gas_charged + else: + assert_equal(gas_fee, effective_gas_price*gas_charged) + # check burnt fee computation + assert_equal(burnt_gas_fee, burnt_fee_per_gas*gas_charged) + + # if max_fee_per_gas >= base_fee_per_gas, it shall follow the computation, regardless of transaction in pivot block or not + if max_fee_per_gas >= base_fee_per_gas: + priority_fee_per_gas = effective_gas_price - base_fee_per_gas + # check priority fee computation + assert_equal(priority_fee_per_gas, min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas)) + else: + # max fee per gas should be greater than burnt fee per gas + assert is_in_pivot_block == False, "Transaction should be in non-pivot block" + assert max_fee_per_gas >= burnt_fee_per_gas + diff --git a/tests/tx_consistency_test.py b/tests/tx_consistency_test.py index f3eead320b..a5db4ce784 100755 --- a/tests/tx_consistency_test.py +++ b/tests/tx_consistency_test.py @@ -148,7 +148,7 @@ def sample_node_indices(self): # randomly select N nodes to send tx. def send_tx(self, sender: Account, receiver: Account): client = RpcClient(self.nodes[0]) - tx = client.new_tx(sender.address, receiver.address, sender.nonce, value=9000, priv_key=sender.priv_key) + tx = client.new_tx(sender=sender.address, receiver=receiver.address, nonce=sender.nonce, value=9000, priv_key=sender.priv_key) def ensure_send_tx(node, tx): tx_hash = RpcClient(node).send_tx(tx)