diff --git a/Cargo.lock b/Cargo.lock index 9d8115c771b5..58770114503d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anyhow" version = "1.0.65" @@ -34,7 +40,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a941c39708478e8eea39243b5983f1c42d2717b3620ee91f4a52115fd02ac43f" dependencies = [ - "itertools", + "itertools 0.9.0", "proc-macro-error", "proc-macro2", "quote", @@ -232,6 +238,12 @@ dependencies = [ "serde", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.0.73" @@ -263,6 +275,33 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ciborium" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" + +[[package]] +name = "ciborium-ll" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.4.0" @@ -274,6 +313,18 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "3.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +dependencies = [ + "bitflags", + "clap_lex 0.2.4", + "indexmap", + "textwrap", +] + [[package]] name = "clap" version = "4.0.14" @@ -283,7 +334,7 @@ dependencies = [ "atty", "bitflags", "clap_derive", - "clap_lex", + "clap_lex 0.3.0", "once_cell", "strsim", "termcolor", @@ -302,6 +353,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.3.0" @@ -357,6 +417,85 @@ dependencies = [ "build_const", ] +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap 3.2.22", + "criterion-plot", + "itertools 0.10.5", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +dependencies = [ + "cfg-if", +] + [[package]] name = "crunchy" version = "0.2.2" @@ -491,7 +630,7 @@ version = "17.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" dependencies = [ - "ethereum-types", + "ethereum-types 0.13.1", "hex", "once_cell", "regex", @@ -509,7 +648,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" dependencies = [ "crunchy", - "fixed-hash", + "fixed-hash 0.7.0", "impl-rlp", "impl-serde", "tiny-keccak", @@ -524,7 +663,7 @@ dependencies = [ "crc", "fastrlp", "maplit", - "primitive-types", + "primitive-types 0.11.1", "thiserror", ] @@ -535,10 +674,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" dependencies = [ "ethbloom", - "fixed-hash", + "fixed-hash 0.7.0", "impl-rlp", "impl-serde", - "primitive-types", + "primitive-types 0.11.1", + "uint", +] + +[[package]] +name = "ethereum-types" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81224dc661606574f5a0f28c9947d0ee1d93ff11c5f1c4e7272f52e8c0b5483c" +dependencies = [ + "fixed-hash 0.8.0", + "primitive-types 0.12.0", "uint", ] @@ -568,6 +718,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "ethnum" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eac3c0b9fa6eb75255ebb42c0ba3e2210d102a66d2795afef6fed668f373311" + [[package]] name = "event-listener" version = "2.5.3" @@ -602,7 +758,7 @@ dependencies = [ "arrayvec", "auto_impl", "bytes", - "ethereum-types", + "ethereum-types 0.13.1", "fastrlp-derive", ] @@ -640,6 +796,17 @@ 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", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fnv" version = "1.0.7" @@ -866,6 +1033,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" + [[package]] name = "hashbrown" version = "0.12.3" @@ -1057,6 +1230,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.3" @@ -1368,6 +1550,15 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1509,6 +1700,12 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -1640,6 +1837,34 @@ dependencies = [ "spki", ] +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -1652,13 +1877,23 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" dependencies = [ - "fixed-hash", + "fixed-hash 0.7.0", "impl-codec", "impl-rlp", "impl-serde", "uint", ] +[[package]] +name = "primitive-types" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cfd65aea0c5fa0bfcc7c9e7ca828c921ef778f43d325325ec84bda371bfa75a" +dependencies = [ + "fixed-hash 0.8.0", + "uint", +] + [[package]] name = "proc-macro-crate" version = "1.2.1" @@ -1748,6 +1983,30 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1787,7 +2046,7 @@ dependencies = [ name = "reth" version = "0.1.0" dependencies = [ - "clap", + "clap 4.0.14", "eyre", "reth-primitives", "serde", @@ -1878,6 +2137,31 @@ dependencies = [ "serde_json", ] +[[package]] +name = "reth-rlp" +version = "0.1.2" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "criterion", + "ethereum-types 0.14.0", + "ethnum", + "hex-literal", + "reth-rlp", + "reth-rlp-derive", +] + +[[package]] +name = "reth-rlp-derive" +version = "0.1.1" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "reth-rpc" version = "0.1.0" @@ -1954,7 +2238,7 @@ dependencies = [ "bytes", "hashbrown", "num_enum", - "primitive-types", + "primitive-types 0.11.1", "revm_precompiles", "rlp", "sha3", @@ -1970,7 +2254,7 @@ dependencies = [ "hashbrown", "num", "once_cell", - "primitive-types", + "primitive-types 0.11.1", "ripemd", "secp256k1", "sha2", @@ -2444,6 +2728,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" + [[package]] name = "thiserror" version = "1.0.37" @@ -2473,6 +2763,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tokio" version = "1.21.2" diff --git a/Cargo.toml b/Cargo.toml index 88e6241b27bf..84ba53c2a14c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,8 @@ members = [ "bin/reth", "crate-template", + "crates/common/rlp", + "crates/common/rlp-derive", "crates/db", "crates/executor", "crates/interfaces", diff --git a/crates/common/rlp-derive/Cargo.toml b/crates/common/rlp-derive/Cargo.toml new file mode 100644 index 000000000000..9840fd111546 --- /dev/null +++ b/crates/common/rlp-derive/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "reth-rlp-derive" +version = "0.1.1" +license = "Apache-2.0" +edition = "2021" +description = "Procedural macros for fastrlp" +repository = "https://github.com/foundry-rs/reth" + +[lib] +proc-macro = true + +[dependencies] +bytes = { version = "1", default-features = false } +syn = "1" +quote = "1" +proc-macro2 = "1" \ No newline at end of file diff --git a/crates/common/rlp-derive/LICENCE b/crates/common/rlp-derive/LICENCE new file mode 100644 index 000000000000..9c8f3ea0871e --- /dev/null +++ b/crates/common/rlp-derive/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/crates/common/rlp-derive/README.md b/crates/common/rlp-derive/README.md new file mode 100644 index 000000000000..d69210bfc169 --- /dev/null +++ b/crates/common/rlp-derive/README.md @@ -0,0 +1,3 @@ +# RLP derive crate + +forked from erlier Apache licenced fastrlp-derive crate, before it changed licence to GPL. \ No newline at end of file diff --git a/crates/common/rlp-derive/src/de.rs b/crates/common/rlp-derive/src/de.rs new file mode 100644 index 000000000000..6eb1a08dd8bb --- /dev/null +++ b/crates/common/rlp-derive/src/de.rs @@ -0,0 +1,95 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub fn impl_decodable(ast: &syn::DeriveInput) -> TokenStream { + let body = if let syn::Data::Struct(s) = &ast.data { + s + } else { + panic!("#[derive(RlpDecodable)] is only defined for structs."); + }; + + let stmts: Vec<_> = + body.fields.iter().enumerate().map(|(i, field)| decodable_field(i, field)).collect(); + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics reth_rlp::Decodable for #name #ty_generics #where_clause { + fn decode(mut buf: &mut &[u8]) -> Result { + let b = &mut &**buf; + let rlp_head = reth_rlp::Header::decode(b)?; + + if !rlp_head.list { + return Err(reth_rlp::DecodeError::UnexpectedString); + } + + let started_len = b.len(); + let this = Self { + #(#stmts)* + }; + + let consumed = started_len - b.len(); + if consumed != rlp_head.payload_length { + return Err(reth_rlp::DecodeError::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }); + } + + *buf = *b; + + Ok(this) + } + } + }; + + quote! { + const _: () = { + extern crate reth_rlp; + #impl_block + }; + } +} + +pub fn impl_decodable_wrapper(ast: &syn::DeriveInput) -> TokenStream { + let body = if let syn::Data::Struct(s) = &ast.data { + s + } else { + panic!("#[derive(RlpEncodableWrapper)] is only defined for structs."); + }; + + assert_eq!( + body.fields.iter().count(), + 1, + "#[derive(RlpEncodableWrapper)] is only defined for structs with one field." + ); + + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics reth_rlp::Decodable for #name #ty_generics #where_clause { + fn decode(buf: &mut &[u8]) -> Result { + Ok(Self(reth_rlp::Decodable::decode(buf)?)) + } + } + }; + + quote! { + const _: () = { + extern crate reth_rlp; + #impl_block + }; + } +} + +fn decodable_field(index: usize, field: &syn::Field) -> TokenStream { + let id = if let Some(ident) = &field.ident { + quote! { #ident } + } else { + let index = syn::Index::from(index); + quote! { #index } + }; + + quote! { #id: reth_rlp::Decodable::decode(b)?, } +} diff --git a/crates/common/rlp-derive/src/en.rs b/crates/common/rlp-derive/src/en.rs new file mode 100644 index 000000000000..ccfadf20f716 --- /dev/null +++ b/crates/common/rlp-derive/src/en.rs @@ -0,0 +1,152 @@ +use proc_macro2::TokenStream; +use quote::quote; + +pub fn impl_encodable(ast: &syn::DeriveInput) -> TokenStream { + let body = if let syn::Data::Struct(s) = &ast.data { + s + } else { + panic!("#[derive(RlpEncodable)] is only defined for structs."); + }; + + let length_stmts: Vec<_> = + body.fields.iter().enumerate().map(|(i, field)| encodable_length(i, field)).collect(); + + let stmts: Vec<_> = + body.fields.iter().enumerate().map(|(i, field)| encodable_field(i, field)).collect(); + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let impl_block = quote! { + trait E { + fn rlp_header(&self) -> reth_rlp::Header; + } + + impl #impl_generics E for #name #ty_generics #where_clause { + fn rlp_header(&self) -> reth_rlp::Header { + let mut rlp_head = reth_rlp::Header { list: true, payload_length: 0 }; + #(#length_stmts)* + rlp_head + } + } + + impl #impl_generics reth_rlp::Encodable for #name #ty_generics #where_clause { + fn length(&self) -> usize { + let rlp_head = E::rlp_header(self); + return reth_rlp::length_of_length(rlp_head.payload_length) + rlp_head.payload_length; + } + fn encode(&self, out: &mut dyn reth_rlp::BufMut) { + E::rlp_header(self).encode(out); + #(#stmts)* + } + } + }; + + quote! { + const _: () = { + extern crate reth_rlp; + #impl_block + }; + } +} + +pub fn impl_encodable_wrapper(ast: &syn::DeriveInput) -> TokenStream { + let body = if let syn::Data::Struct(s) = &ast.data { + s + } else { + panic!("#[derive(RlpEncodableWrapper)] is only defined for structs."); + }; + + let ident = { + let fields: Vec<_> = body.fields.iter().collect(); + if fields.len() == 1 { + let field = fields.first().expect("fields.len() == 1; qed"); + field_ident(0, field) + } else { + panic!("#[derive(RlpEncodableWrapper)] is only defined for structs with one field.") + } + }; + + let name = &ast.ident; + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); + + let impl_block = quote! { + impl #impl_generics reth_rlp::Encodable for #name #ty_generics #where_clause { + fn length(&self) -> usize { + self.#ident.length() + } + fn encode(&self, out: &mut dyn reth_rlp::BufMut) { + self.#ident.encode(out) + } + } + }; + + quote! { + const _: () = { + extern crate reth_rlp; + #impl_block + }; + } +} + +pub fn impl_max_encoded_len(ast: &syn::DeriveInput) -> TokenStream { + let body = if let syn::Data::Struct(s) = &ast.data { + s + } else { + panic!("#[derive(RlpEncodable)] is only defined for structs."); + }; + + let stmts: Vec<_> = body + .fields + .iter() + .enumerate() + .map(|(index, field)| encodable_max_length(index, field)) + .collect(); + let name = &ast.ident; + + let impl_block = quote! { + unsafe impl reth_rlp::MaxEncodedLen<{ reth_rlp::const_add(reth_rlp::length_of_length(#(#stmts)*), #(#stmts)*) }> for #name {} + unsafe impl reth_rlp::MaxEncodedLenAssoc for #name { + const LEN: usize = { reth_rlp::const_add(reth_rlp::length_of_length(#(#stmts)*), { #(#stmts)* }) }; + } + }; + + quote! { + const _: () = { + extern crate reth_rlp; + #impl_block + }; + } +} + +fn field_ident(index: usize, field: &syn::Field) -> TokenStream { + if let Some(ident) = &field.ident { + quote! { #ident } + } else { + let index = syn::Index::from(index); + quote! { #index } + } +} + +fn encodable_length(index: usize, field: &syn::Field) -> TokenStream { + let ident = field_ident(index, field); + + quote! { rlp_head.payload_length += reth_rlp::Encodable::length(&self.#ident); } +} + +fn encodable_max_length(index: usize, field: &syn::Field) -> TokenStream { + let fieldtype = &field.ty; + + if index == 0 { + quote! { <#fieldtype as reth_rlp::MaxEncodedLenAssoc>::LEN } + } else { + quote! { + <#fieldtype as reth_rlp::MaxEncodedLenAssoc>::LEN } + } +} + +fn encodable_field(index: usize, field: &syn::Field) -> TokenStream { + let ident = field_ident(index, field); + + let id = quote! { self.#ident }; + + quote! { reth_rlp::Encodable::encode(&#id, out); } +} diff --git a/crates/common/rlp-derive/src/lib.rs b/crates/common/rlp-derive/src/lib.rs new file mode 100644 index 000000000000..3f4dc5d655a6 --- /dev/null +++ b/crates/common/rlp-derive/src/lib.rs @@ -0,0 +1,53 @@ +//! Derive macro for `#[derive(RlpEncodable, RlpDecodable)]`. +//! +//! For example of usage see `./tests/rlp.rs`. +//! +//! This library also supports up to 1 `#[rlp(default)]` in a struct, +//! which is similar to [`#[serde(default)]`](https://serde.rs/field-attrs.html#default) +//! with the caveat that we use the `Default` value if +//! the field deserialization fails, as we don't serialize field +//! names and there is no way to tell if it is present or not. + +extern crate proc_macro; + +mod de; +mod en; + +use de::*; +use en::*; +use proc_macro::TokenStream; + +#[proc_macro_derive(RlpEncodable, attributes(rlp))] +pub fn encodable(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = impl_encodable(&ast); + gen.into() +} + +#[proc_macro_derive(RlpEncodableWrapper, attributes(rlp))] +pub fn encodable_wrapper(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = impl_encodable_wrapper(&ast); + gen.into() +} + +#[proc_macro_derive(RlpMaxEncodedLen, attributes(rlp))] +pub fn max_encoded_len(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = impl_max_encoded_len(&ast); + gen.into() +} + +#[proc_macro_derive(RlpDecodable, attributes(rlp))] +pub fn decodable(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = impl_decodable(&ast); + gen.into() +} + +#[proc_macro_derive(RlpDecodableWrapper, attributes(rlp))] +pub fn decodable_wrapper(input: TokenStream) -> TokenStream { + let ast = syn::parse(input).unwrap(); + let gen = impl_decodable_wrapper(&ast); + gen.into() +} diff --git a/crates/common/rlp/Cargo.toml b/crates/common/rlp/Cargo.toml new file mode 100644 index 000000000000..daf13049d8e8 --- /dev/null +++ b/crates/common/rlp/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "reth-rlp" +version = "0.1.2" +edition = "2021" +license = "Apache-2.0" +description = "Fast RLP serialization library" +repository = "https://github.com/foundry-rs/reth" + +[dependencies] +arrayvec = { version = "0.7", default-features = false } +auto_impl = "1" +bytes = { version = "1", default-features = false } +ethnum = { version = "1", default-features = false, optional = true } +ethereum-types = { version = "0.14", default-features = false, optional = true } +reth-rlp-derive = { version = "0.1", path = "../rlp-derive", optional = true } + +[dev-dependencies] +reth-rlp-test = { path = ".", package = "reth-rlp", features = [ + "derive", + "std", + "ethnum", + "ethereum-types", +] } +criterion = "0.4.0" +hex-literal = "0.3" + +[features] +alloc = [] +derive = ["reth-rlp-derive"] +std = ["alloc"] + +[[bench]] +name = "bench" +harness = false \ No newline at end of file diff --git a/crates/common/rlp/LICENCE b/crates/common/rlp/LICENCE new file mode 100644 index 000000000000..9c8f3ea0871e --- /dev/null +++ b/crates/common/rlp/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/crates/common/rlp/README.md b/crates/common/rlp/README.md new file mode 100644 index 000000000000..350921bead12 --- /dev/null +++ b/crates/common/rlp/README.md @@ -0,0 +1,3 @@ +# RLP encoder decoder crate + +forked from erlier Apache licenced fastrlp crate, before it changed licence to GPL \ No newline at end of file diff --git a/crates/common/rlp/benches/bench.rs b/crates/common/rlp/benches/bench.rs new file mode 100644 index 000000000000..d29d7dfd7843 --- /dev/null +++ b/crates/common/rlp/benches/bench.rs @@ -0,0 +1,72 @@ +// Copyright 2020 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! benchmarking for rlp + +use bytes::BytesMut; +use criterion::{criterion_group, criterion_main, Criterion}; +use ethnum::*; +use hex_literal::hex; +use reth_rlp::*; + +fn bench_encode(c: &mut Criterion) { + c.bench_function("encode_u64", |b| { + b.iter(|| { + let mut out = BytesMut::new(); + let _ = 0x1023_4567_89ab_cdefu64.encode(&mut out); + }) + }); + c.bench_function("encode_u256", |b| { + b.iter(|| { + let mut out = BytesMut::new(); + let uint = U256::from_be_bytes(hex!( + "8090a0b0c0d0e0f00910203040506077000000000000000100000000000012f0" + )); + uint.encode(&mut out); + }) + }); + c.bench_function("encode_1000_u64", |b| { + b.iter(|| { + let mut out = BytesMut::new(); + reth_rlp::encode_list( + (0..1000u64).into_iter().collect::>().as_slice(), + &mut out, + ); + }) + }); +} + +fn bench_decode(c: &mut Criterion) { + c.bench_function("decode_u64", |b| { + b.iter(|| { + let data = [0x88, 0x10, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]; + let _ = u64::decode(&mut &data[..]).unwrap(); + }) + }); + c.bench_function("decode_u256", |b| { + b.iter(|| { + let data = vec![ + 0xa0, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0x09, 0x10, 0x20, 0x30, 0x40, + 0x50, 0x60, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x12, 0xf0, + ]; + let _ = U256::decode(&mut &data[..]).unwrap(); + }) + }); + c.bench_function("decode_1000_u64", |b| { + let input = (0..1000u64).into_iter().collect::>(); + let mut data = BytesMut::new(); + reth_rlp::encode_list(input.as_slice(), &mut data); + b.iter(|| { + let _ = Vec::::decode(&mut &data[..]).unwrap(); + }); + }); +} + +criterion_group!(benches, bench_encode, bench_decode); +criterion_main!(benches); diff --git a/crates/common/rlp/src/decode.rs b/crates/common/rlp/src/decode.rs new file mode 100644 index 000000000000..e9d4d3eefdee --- /dev/null +++ b/crates/common/rlp/src/decode.rs @@ -0,0 +1,560 @@ +use crate::types::Header; +use bytes::{Buf, Bytes, BytesMut}; + +pub trait Decodable: Sized { + fn decode(buf: &mut &[u8]) -> Result; +} + +#[cfg(feature = "alloc")] +mod alloc_impl { + use super::*; + + impl Decodable for ::alloc::boxed::Box + where + T: Decodable + Sized, + { + fn decode(buf: &mut &[u8]) -> Result { + T::decode(buf).map(::alloc::boxed::Box::new) + } + } + + impl Decodable for ::alloc::sync::Arc + where + T: Decodable + Sized, + { + fn decode(buf: &mut &[u8]) -> Result { + T::decode(buf).map(::alloc::sync::Arc::new) + } + } + + impl Decodable for ::alloc::string::String { + fn decode(from: &mut &[u8]) -> Result { + let h = Header::decode(from)?; + if h.list { + return Err(DecodeError::UnexpectedList) + } + let mut to = ::alloc::vec::Vec::with_capacity(h.payload_length); + to.extend_from_slice(&from[..h.payload_length]); + from.advance(h.payload_length); + + Self::from_utf8(to).map_err(|_| DecodeError::Custom("invalid string")) + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DecodeError { + Overflow, + LeadingZero, + InputTooShort, + NonCanonicalSingleByte, + NonCanonicalSize, + UnexpectedLength, + UnexpectedString, + UnexpectedList, + ListLengthMismatch { expected: usize, got: usize }, + Custom(&'static str), +} + +#[cfg(feature = "std")] +impl std::error::Error for DecodeError {} + +impl core::fmt::Display for DecodeError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + DecodeError::Overflow => write!(f, "overflow"), + DecodeError::LeadingZero => write!(f, "leading zero"), + DecodeError::InputTooShort => write!(f, "input too short"), + DecodeError::NonCanonicalSingleByte => write!(f, "non-canonical single byte"), + DecodeError::NonCanonicalSize => write!(f, "non-canonical size"), + DecodeError::UnexpectedLength => write!(f, "unexpected length"), + DecodeError::UnexpectedString => write!(f, "unexpected string"), + DecodeError::UnexpectedList => write!(f, "unexpected list"), + DecodeError::ListLengthMismatch { expected, got } => { + write!(f, "list length mismatch: expected {expected}, got {got}") + } + DecodeError::Custom(err) => write!(f, "{err}"), + } + } +} + +impl Header { + pub fn decode(buf: &mut &[u8]) -> Result { + if !buf.has_remaining() { + return Err(DecodeError::InputTooShort) + } + + let b = buf[0]; + let h: Self = { + if b < 0x80 { + Self { list: false, payload_length: 1 } + } else if b < 0xB8 { + buf.advance(1); + let h = Self { list: false, payload_length: b as usize - 0x80 }; + + if h.payload_length == 1 { + if !buf.has_remaining() { + return Err(DecodeError::InputTooShort) + } + if buf[0] < 0x80 { + return Err(DecodeError::NonCanonicalSingleByte) + } + } + + h + } else if b < 0xC0 { + buf.advance(1); + let len_of_len = b as usize - 0xB7; + if buf.len() < len_of_len { + return Err(DecodeError::InputTooShort) + } + let payload_length = usize::try_from(u64::from_be_bytes( + static_left_pad(&buf[..len_of_len]).ok_or(DecodeError::LeadingZero)?, + )) + .map_err(|_| DecodeError::Custom("Input too big"))?; + buf.advance(len_of_len); + if payload_length < 56 { + return Err(DecodeError::NonCanonicalSize) + } + + Self { list: false, payload_length } + } else if b < 0xF8 { + buf.advance(1); + Self { list: true, payload_length: b as usize - 0xC0 } + } else { + buf.advance(1); + let list = true; + let len_of_len = b as usize - 0xF7; + if buf.len() < len_of_len { + return Err(DecodeError::InputTooShort) + } + let payload_length = usize::try_from(u64::from_be_bytes( + static_left_pad(&buf[..len_of_len]).ok_or(DecodeError::LeadingZero)?, + )) + .map_err(|_| DecodeError::Custom("Input too big"))?; + buf.advance(len_of_len); + if payload_length < 56 { + return Err(DecodeError::NonCanonicalSize) + } + + Self { list, payload_length } + } + }; + + if buf.remaining() < h.payload_length { + return Err(DecodeError::InputTooShort) + } + + Ok(h) + } +} + +fn static_left_pad(data: &[u8]) -> Option<[u8; LEN]> { + if data.len() > LEN { + return None + } + + let mut v = [0; LEN]; + + if data.is_empty() { + return Some(v) + } + + if data[0] == 0 { + return None + } + + v[LEN - data.len()..].copy_from_slice(data); + Some(v) +} + +macro_rules! decode_integer { + ($t:ty) => { + impl Decodable for $t { + fn decode(buf: &mut &[u8]) -> Result { + let h = Header::decode(buf)?; + if h.list { + return Err(DecodeError::UnexpectedList) + } + if h.payload_length > (<$t>::BITS as usize / 8) { + return Err(DecodeError::Overflow) + } + if buf.remaining() < h.payload_length { + return Err(DecodeError::InputTooShort) + } + let v = <$t>::from_be_bytes( + static_left_pad(&buf[..h.payload_length]).ok_or(DecodeError::LeadingZero)?, + ); + buf.advance(h.payload_length); + Ok(v) + } + } + }; +} + +decode_integer!(usize); +decode_integer!(u8); +decode_integer!(u16); +decode_integer!(u32); +decode_integer!(u64); +decode_integer!(u128); + +impl Decodable for bool { + fn decode(buf: &mut &[u8]) -> Result { + Ok(match u8::decode(buf)? { + 0 => false, + 1 => true, + _ => return Err(DecodeError::Custom("invalid bool value, must be 0 or 1")), + }) + } +} + +#[cfg(feature = "ethnum")] +decode_integer!(ethnum::U256); + +#[cfg(feature = "ethereum-types")] +mod ethereum_types_support { + use super::*; + use ethereum_types::*; + + macro_rules! fixed_hash_impl { + ($t:ty) => { + impl Decodable for $t { + fn decode(buf: &mut &[u8]) -> Result { + Decodable::decode(buf).map(Self) + } + } + }; + } + + fixed_hash_impl!(H64); + fixed_hash_impl!(H128); + fixed_hash_impl!(H160); + fixed_hash_impl!(H256); + fixed_hash_impl!(H512); + fixed_hash_impl!(H520); + //TODO fixed_hash_impl!(Bloom); + + macro_rules! fixed_uint_impl { + ($t:ty, $n_bytes:tt) => { + impl Decodable for $t { + fn decode(buf: &mut &[u8]) -> Result { + let h = Header::decode(buf)?; + if h.list { + return Err(DecodeError::UnexpectedList) + } + if h.payload_length > $n_bytes { + return Err(DecodeError::Overflow) + } + if buf.remaining() < h.payload_length { + return Err(DecodeError::InputTooShort) + } + let n = <$t>::from_big_endian( + &static_left_pad::<$n_bytes>(&buf[..h.payload_length]) + .ok_or(DecodeError::LeadingZero)?, + ); + buf.advance(h.payload_length); + Ok(n) + } + } + }; + } + + fixed_uint_impl!(U64, 8); + fixed_uint_impl!(U128, 16); + fixed_uint_impl!(U256, 32); + fixed_uint_impl!(U512, 64); +} + +impl Decodable for [u8; N] { + fn decode(from: &mut &[u8]) -> Result { + let h = Header::decode(from)?; + if h.list { + return Err(DecodeError::UnexpectedList) + } + if h.payload_length != N { + return Err(DecodeError::UnexpectedLength) + } + + let mut to = [0_u8; N]; + to.copy_from_slice(&from[..N]); + from.advance(N); + + Ok(to) + } +} + +impl Decodable for BytesMut { + fn decode(from: &mut &[u8]) -> Result { + let h = Header::decode(from)?; + if h.list { + return Err(DecodeError::UnexpectedList) + } + let mut to = BytesMut::with_capacity(h.payload_length); + to.extend_from_slice(&from[..h.payload_length]); + from.advance(h.payload_length); + + Ok(to) + } +} + +impl Decodable for Bytes { + fn decode(buf: &mut &[u8]) -> Result { + BytesMut::decode(buf).map(BytesMut::freeze) + } +} + +pub struct Rlp<'a> { + payload_view: &'a [u8], +} + +impl<'a> Rlp<'a> { + pub fn new(mut payload: &'a [u8]) -> Result { + let h = Header::decode(&mut payload)?; + if !h.list { + return Err(DecodeError::UnexpectedString) + } + + let payload_view = &payload[..h.payload_length]; + Ok(Self { payload_view }) + } + + pub fn get_next(&mut self) -> Result, DecodeError> { + if self.payload_view.is_empty() { + return Ok(None) + } + + Ok(Some(T::decode(&mut self.payload_view)?)) + } +} + +#[cfg(feature = "alloc")] +impl Decodable for alloc::vec::Vec +where + E: Decodable, +{ + fn decode(buf: &mut &[u8]) -> Result { + let h = Header::decode(buf)?; + if !h.list { + return Err(DecodeError::UnexpectedString) + } + + let payload_view = &mut &buf[..h.payload_length]; + + let mut to = alloc::vec::Vec::new(); + while !payload_view.is_empty() { + to.push(E::decode(payload_view)?); + } + + buf.advance(h.payload_length); + + Ok(to) + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + use alloc::vec; + use core::fmt::Debug; + use ethereum_types::{U128, U256, U512, U64}; + use ethnum::AsU256; + use hex_literal::hex; + + fn check_decode(fixtures: IT) + where + T: Decodable + PartialEq + Debug, + IT: IntoIterator, &'static [u8])>, + { + for (expected, mut input) in fixtures { + assert_eq!(T::decode(&mut input), expected); + if expected.is_ok() { + assert_eq!(input, &[]); + } + } + } + + fn check_decode_list(fixtures: IT) + where + T: Decodable + PartialEq + Debug, + IT: IntoIterator, DecodeError>, &'static [u8])>, + { + for (expected, mut input) in fixtures { + assert_eq!(vec::Vec::::decode(&mut input), expected); + if expected.is_ok() { + assert_eq!(input, &[]); + } + } + } + + #[test] + fn rlp_strings() { + check_decode::(vec![ + (Ok((&hex!("00")[..]).to_vec().into()), &hex!("00")[..]), + ( + Ok((&hex!("6f62636465666768696a6b6c6d")[..]).to_vec().into()), + &hex!("8D6F62636465666768696A6B6C6D")[..], + ), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + ]) + } + + #[test] + fn rlp_fixed_length() { + check_decode(vec![ + (Ok(hex!("6f62636465666768696a6b6c6d")), &hex!("8D6F62636465666768696A6B6C6D")[..]), + (Err(DecodeError::UnexpectedLength), &hex!("8C6F62636465666768696A6B6C")[..]), + (Err(DecodeError::UnexpectedLength), &hex!("8E6F62636465666768696A6B6C6D6E")[..]), + ]) + } + + #[test] + fn rlp_u64() { + check_decode(vec![ + (Ok(9_u64), &hex!("09")[..]), + (Ok(0_u64), &hex!("80")[..]), + (Ok(0x0505_u64), &hex!("820505")[..]), + (Ok(0xCE05050505_u64), &hex!("85CE05050505")[..]), + (Err(DecodeError::Overflow), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::InputTooShort), &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Err(DecodeError::Overflow), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[test] + fn rlp_u256() { + check_decode(vec![ + (Ok(9_u8.as_u256()), &hex!("09")[..]), + (Ok(0_u8.as_u256()), &hex!("80")[..]), + (Ok(0x0505_u16.as_u256()), &hex!("820505")[..]), + (Ok(0xCE05050505_u64.as_u256()), &hex!("85CE05050505")[..]), + (Ok(0xFFFFFFFFFFFFFFFFFF7C_u128.as_u256()), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::InputTooShort), &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Err(DecodeError::Overflow), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[cfg(feature = "ethereum-types")] + #[test] + fn rlp_ethereum_types_u64() { + check_decode(vec![ + (Ok(U64::from(9_u8)), &hex!("09")[..]), + (Ok(U64::from(0_u8)), &hex!("80")[..]), + (Ok(U64::from(0x0505_u16)), &hex!("820505")[..]), + (Ok(U64::from(0xCE05050505_u64)), &hex!("85CE05050505")[..]), + (Err(DecodeError::Overflow), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::InputTooShort), &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Err(DecodeError::Overflow), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[cfg(feature = "ethereum-types")] + #[test] + fn rlp_ethereum_types_u128() { + check_decode(vec![ + (Ok(U128::from(9_u8)), &hex!("09")[..]), + (Ok(U128::from(0_u8)), &hex!("80")[..]), + (Ok(U128::from(0x0505_u16)), &hex!("820505")[..]), + (Ok(U128::from(0xCE05050505_u64)), &hex!("85CE05050505")[..]), + (Ok(U128::from(0xFFFFFFFFFFFFFFFFFF7C_u128)), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::InputTooShort), &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Err(DecodeError::Overflow), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[cfg(feature = "ethereum-types")] + #[test] + fn rlp_ethereum_types_u256() { + check_decode(vec![ + (Ok(U256::from(9_u8)), &hex!("09")[..]), + (Ok(U256::from(0_u8)), &hex!("80")[..]), + (Ok(U256::from(0x0505_u16)), &hex!("820505")[..]), + (Ok(U256::from(0xCE05050505_u64)), &hex!("85CE05050505")[..]), + (Ok(U256::from(0xFFFFFFFFFFFFFFFFFF7C_u128)), &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::InputTooShort), &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..]), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Err(DecodeError::Overflow), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[cfg(feature = "ethereum-types")] + #[test] + fn rlp_ethereum_types_u512() { + check_decode(vec![ + (Ok(U512::from(9_u8)), &hex!("09")[..]), + (Ok(U512::from(0_u8)), &hex!("80")[..]), + (Ok(U512::from(0x0505_u16)), &hex!("820505")[..]), + (Ok(U512::from(0xCE05050505_u64)), &hex!("85CE05050505")[..]), + ( + Ok(U512::from(0xFFFFFFFFFFFFFFFFFF7C_u128)), + &hex!("8AFFFFFFFFFFFFFFFFFF7C")[..], + ), + ( + Err(DecodeError::InputTooShort), + &hex!("8BFFFFFFFFFFFFFFFFFF7C")[..], + ), + (Err(DecodeError::UnexpectedList), &hex!("C0")[..]), + (Err(DecodeError::LeadingZero), &hex!("00")[..]), + (Err(DecodeError::NonCanonicalSingleByte), &hex!("8105")[..]), + (Err(DecodeError::LeadingZero), &hex!("8200F4")[..]), + (Err(DecodeError::NonCanonicalSize), &hex!("B8020004")[..]), + ( + Ok(U512::from_dec_str("115792089237316195423570985008687907853269984676653278628940326933415738736640").unwrap()), + &hex!("A101000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ( + Err(DecodeError::Overflow), + &hex!("B84101000000000000000000000000000000000000008B000000000000000000000000000000000000000000000000000000000000008B000000000000000000000000")[..], + ), + ]) + } + + #[test] + fn rlp_vectors() { + check_decode_list(vec![ + (Ok(vec![]), &hex!("C0")[..]), + (Ok(vec![0xBBCCB5_u64, 0xFFC0B5_u64]), &hex!("C883BBCCB583FFC0B5")[..]), + ]) + } +} diff --git a/crates/common/rlp/src/encode.rs b/crates/common/rlp/src/encode.rs new file mode 100644 index 000000000000..8b3017d83fcc --- /dev/null +++ b/crates/common/rlp/src/encode.rs @@ -0,0 +1,501 @@ +use crate::types::*; +use arrayvec::ArrayVec; +use auto_impl::auto_impl; +use bytes::{BufMut, Bytes, BytesMut}; +use core::borrow::Borrow; + +fn zeroless_view(v: &impl AsRef<[u8]>) -> &[u8] { + let v = v.as_ref(); + &v[v.iter().take_while(|&&b| b == 0).count()..] +} + +impl Header { + pub fn encode(&self, out: &mut dyn BufMut) { + if self.payload_length < 56 { + let code = if self.list { EMPTY_LIST_CODE } else { EMPTY_STRING_CODE }; + out.put_u8(code + self.payload_length as u8); + } else { + let len_be = self.payload_length.to_be_bytes(); + let len_be = zeroless_view(&len_be); + let code = if self.list { 0xF7 } else { 0xB7 }; + out.put_u8(code + len_be.len() as u8); + out.put_slice(len_be); + } + } +} + +pub const fn length_of_length(payload_length: usize) -> usize { + if payload_length < 56 { + 1 + } else { + 1 + 8 - payload_length.leading_zeros() as usize / 8 + } +} + +#[doc(hidden)] +pub const fn const_add(a: usize, b: usize) -> usize { + a + b +} + +#[doc(hidden)] +pub unsafe trait MaxEncodedLen: Encodable {} + +#[doc(hidden)] +pub unsafe trait MaxEncodedLenAssoc: Encodable { + const LEN: usize; +} + +/// Use this to define length of an encoded entity +/// +/// # Safety +/// Invalid value can cause the encoder to crash. +#[macro_export] +macro_rules! impl_max_encoded_len { + ($t:ty, $len:block) => { + unsafe impl MaxEncodedLen<{ $len }> for $t {} + unsafe impl MaxEncodedLenAssoc for $t { + const LEN: usize = $len; + } + }; +} + +#[auto_impl(&)] +#[cfg_attr(feature = "alloc", auto_impl(Box, Arc))] +pub trait Encodable { + fn encode(&self, out: &mut dyn BufMut); + fn length(&self) -> usize { + let mut out = BytesMut::new(); + self.encode(&mut out); + out.len() + } +} + +impl<'a> Encodable for &'a [u8] { + fn length(&self) -> usize { + let mut len = self.len(); + if self.len() != 1 || self[0] >= EMPTY_STRING_CODE { + len += length_of_length(self.len()); + } + len + } + + fn encode(&self, out: &mut dyn BufMut) { + if self.len() != 1 || self[0] >= EMPTY_STRING_CODE { + Header { list: false, payload_length: self.len() }.encode(out); + } + out.put_slice(self); + } +} + +impl Encodable for [u8; LEN] { + fn length(&self) -> usize { + (self as &[u8]).length() + } + + fn encode(&self, out: &mut dyn BufMut) { + (self as &[u8]).encode(out) + } +} + +unsafe impl MaxEncodedLenAssoc for [u8; LEN] { + const LEN: usize = LEN + length_of_length(LEN); +} + +macro_rules! encodable_uint { + ($t:ty) => { + #[allow(clippy::cmp_owned)] + impl Encodable for $t { + fn length(&self) -> usize { + if *self < <$t>::from(EMPTY_STRING_CODE) { + 1 + } else { + 1 + (<$t>::BITS as usize / 8) - (self.leading_zeros() as usize / 8) + } + } + + fn encode(&self, out: &mut dyn BufMut) { + if *self == 0 { + out.put_u8(EMPTY_STRING_CODE); + } else if *self < <$t>::from(EMPTY_STRING_CODE) { + out.put_u8(u8::try_from(*self).unwrap()); + } else { + let be = self.to_be_bytes(); + let be = zeroless_view(&be); + out.put_u8(EMPTY_STRING_CODE + be.len() as u8); + out.put_slice(be); + } + } + } + }; +} + +macro_rules! max_encoded_len_uint { + ($t:ty) => { + impl_max_encoded_len!($t, { + length_of_length(<$t>::MAX.to_be_bytes().len()) + <$t>::MAX.to_be_bytes().len() + }); + }; +} + +encodable_uint!(usize); +max_encoded_len_uint!(usize); + +encodable_uint!(u8); +max_encoded_len_uint!(u8); + +encodable_uint!(u16); +max_encoded_len_uint!(u16); + +encodable_uint!(u32); +max_encoded_len_uint!(u32); + +encodable_uint!(u64); +max_encoded_len_uint!(u64); + +encodable_uint!(u128); +max_encoded_len_uint!(u128); + +impl Encodable for bool { + fn length(&self) -> usize { + (*self as u8).length() + } + + fn encode(&self, out: &mut dyn BufMut) { + (*self as u8).encode(out) + } +} + +impl_max_encoded_len!(bool, { ::LEN }); + +#[cfg(feature = "ethnum")] +mod ethnum_support { + use super::*; + + encodable_uint!(ethnum::U256); + impl_max_encoded_len!(ethnum::U256, { length_of_length(32) + 32 }); +} + +#[cfg(feature = "ethereum-types")] +mod ethereum_types_support { + use super::*; + use ethereum_types::*; + + macro_rules! fixed_hash_impl { + ($t:ty) => { + impl Encodable for $t { + fn length(&self) -> usize { + self.0.length() + } + + fn encode(&self, out: &mut dyn bytes::BufMut) { + self.0.encode(out) + } + } + impl_max_encoded_len!($t, { length_of_length(<$t>::len_bytes()) + <$t>::len_bytes() }); + }; + } + + fixed_hash_impl!(H64); + fixed_hash_impl!(H128); + fixed_hash_impl!(H160); + fixed_hash_impl!(H256); + fixed_hash_impl!(H512); + fixed_hash_impl!(H520); + //TODO fixed_hash_impl!(Bloom); + + macro_rules! fixed_uint_impl { + ($t:ty, $n_bytes:tt) => { + impl Encodable for $t { + fn length(&self) -> usize { + if *self < <$t>::from(EMPTY_STRING_CODE) { + 1 + } else { + 1 + $n_bytes - (self.leading_zeros() as usize / 8) + } + } + + fn encode(&self, out: &mut dyn bytes::BufMut) { + let mut temp_arr = [0u8; $n_bytes]; + self.to_big_endian(&mut temp_arr[..]); + // cut the leading zeros after converting to big endian + let sliced = &temp_arr[(self.leading_zeros() / 8) as usize..]; + sliced.encode(out); + } + } + }; + } + + fixed_uint_impl!(U64, 8); + fixed_uint_impl!(U128, 16); + fixed_uint_impl!(U256, 32); + fixed_uint_impl!(U512, 64); +} + +macro_rules! slice_impl { + ($t:ty) => { + impl $crate::Encodable for $t { + fn length(&self) -> usize { + (&self[..]).length() + } + + fn encode(&self, out: &mut dyn bytes::BufMut) { + (&self[..]).encode(out) + } + } + }; +} + +#[cfg(feature = "alloc")] +mod alloc_support { + use super::*; + + extern crate alloc; + + impl Encodable for ::alloc::vec::Vec + where + T: Encodable, + { + fn length(&self) -> usize { + list_length(self) + } + + fn encode(&self, out: &mut dyn BufMut) { + encode_list(self, out) + } + } + + impl Encodable for ::alloc::string::String { + fn encode(&self, out: &mut dyn BufMut) { + self.as_bytes().encode(out); + } + fn length(&self) -> usize { + self.as_bytes().length() + } + } +} +slice_impl!(Bytes); +slice_impl!(BytesMut); + +fn rlp_list_header(v: &[K]) -> Header +where + E: Encodable + ?Sized, + K: Borrow, +{ + let mut h = Header { list: true, payload_length: 0 }; + for x in v { + h.payload_length += x.borrow().length(); + } + h +} + +pub fn list_length(v: &[K]) -> usize +where + E: Encodable, + K: Borrow, +{ + let payload_length = rlp_list_header(v).payload_length; + length_of_length(payload_length) + payload_length +} + +pub fn encode_list(v: &[K], out: &mut dyn BufMut) +where + E: Encodable + ?Sized, + K: Borrow, +{ + let h = rlp_list_header(v); + h.encode(out); + for x in v { + x.borrow().encode(out); + } +} + +pub fn encode_fixed_size, const LEN: usize>(v: &E) -> ArrayVec { + let mut out = ArrayVec::from([0_u8; LEN]); + + let mut s = out.as_mut_slice(); + + v.encode(&mut s); + + let final_len = LEN - s.len(); + out.truncate(final_len); + + out +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use super::*; + use alloc::vec; + use bytes::BytesMut; + use hex_literal::hex; + + fn encoded(t: T) -> BytesMut { + let mut out = BytesMut::new(); + t.encode(&mut out); + out + } + + fn encoded_list(t: &[T]) -> BytesMut { + let mut out1 = BytesMut::new(); + encode_list(t, &mut out1); + + let v = t.to_vec(); + assert_eq!(out1.len(), v.length()); + + let mut out2 = BytesMut::new(); + v.encode(&mut out2); + assert_eq!(out1, out2); + + out1 + } + + #[test] + fn rlp_strings() { + assert_eq!(encoded(hex!(""))[..], hex!("80")[..]); + assert_eq!(encoded(hex!("7B"))[..], hex!("7b")[..]); + assert_eq!(encoded(hex!("80"))[..], hex!("8180")[..]); + assert_eq!(encoded(hex!("ABBA"))[..], hex!("82abba")[..]); + } + + fn u8_fixtures() -> impl IntoIterator { + vec![ + (0, &hex!("80")[..]), + (1, &hex!("01")[..]), + (0x7F, &hex!("7F")[..]), + (0x80, &hex!("8180")[..]), + ] + } + + fn c>( + it: impl IntoIterator, + ) -> impl Iterator { + it.into_iter().map(|(k, v)| (k.into(), v)) + } + + fn u16_fixtures() -> impl IntoIterator { + c(u8_fixtures()).chain(vec![(0x400, &hex!("820400")[..])]) + } + + fn u32_fixtures() -> impl IntoIterator { + c(u16_fixtures()) + .chain(vec![(0xFFCCB5, &hex!("83ffccb5")[..]), (0xFFCCB5DD, &hex!("84ffccb5dd")[..])]) + } + + fn u64_fixtures() -> impl IntoIterator { + c(u32_fixtures()).chain(vec![ + (0xFFCCB5DDFF, &hex!("85ffccb5ddff")[..]), + (0xFFCCB5DDFFEE, &hex!("86ffccb5ddffee")[..]), + (0xFFCCB5DDFFEE14, &hex!("87ffccb5ddffee14")[..]), + (0xFFCCB5DDFFEE1483, &hex!("88ffccb5ddffee1483")[..]), + ]) + } + + fn u128_fixtures() -> impl IntoIterator { + c(u64_fixtures()).chain(vec![( + 0x10203E405060708090A0B0C0D0E0F2, + &hex!("8f10203e405060708090a0b0c0d0e0f2")[..], + )]) + } + + #[cfg(feature = "ethnum")] + fn u256_fixtures() -> impl IntoIterator { + c(u128_fixtures()).chain(vec![( + ethnum::U256::from_str_radix( + "0100020003000400050006000700080009000A0B4B000C000D000E01", + 16, + ) + .unwrap(), + &hex!("9c0100020003000400050006000700080009000a0b4b000c000d000e01")[..], + )]) + } + + #[cfg(feature = "ethereum-types")] + fn eth_u64_fixtures() -> impl IntoIterator { + c(u64_fixtures()).chain(vec![ + ( + ethereum_types::U64::from_str_radix("FFCCB5DDFF", 16).unwrap(), + &hex!("85ffccb5ddff")[..], + ), + ( + ethereum_types::U64::from_str_radix("FFCCB5DDFFEE", 16).unwrap(), + &hex!("86ffccb5ddffee")[..], + ), + ( + ethereum_types::U64::from_str_radix("FFCCB5DDFFEE14", 16).unwrap(), + &hex!("87ffccb5ddffee14")[..], + ), + ( + ethereum_types::U64::from_str_radix("FFCCB5DDFFEE1483", 16).unwrap(), + &hex!("88ffccb5ddffee1483")[..], + ), + ]) + } + + #[cfg(feature = "ethereum-types")] + fn eth_u128_fixtures() -> impl IntoIterator { + c(u128_fixtures()).chain(vec![( + ethereum_types::U128::from_str_radix("10203E405060708090A0B0C0D0E0F2", 16).unwrap(), + &hex!("8f10203e405060708090a0b0c0d0e0f2")[..], + )]) + } + + #[cfg(feature = "ethereum-types")] + fn eth_u256_fixtures() -> impl IntoIterator { + c(u128_fixtures()).chain(vec![( + ethereum_types::U256::from_str_radix( + "0100020003000400050006000700080009000A0B4B000C000D000E01", + 16, + ) + .unwrap(), + &hex!("9c0100020003000400050006000700080009000a0b4b000c000d000e01")[..], + )]) + } + + #[cfg(feature = "ethereum-types")] + fn eth_u512_fixtures() -> impl IntoIterator { + c(eth_u256_fixtures()).chain(vec![( + ethereum_types::U512::from_str_radix( + "0100020003000400050006000700080009000A0B4B000C000D000E010100020003000400050006000700080009000A0B4B000C000D000E01", + 16, + ) + .unwrap(), + &hex!("b8380100020003000400050006000700080009000A0B4B000C000D000E010100020003000400050006000700080009000A0B4B000C000D000E01")[..], + )]) + } + + macro_rules! uint_rlp_test { + ($fixtures:expr) => { + for (input, output) in $fixtures { + assert_eq!(encoded(input), output); + } + }; + } + + #[test] + fn rlp_uints() { + uint_rlp_test!(u8_fixtures()); + uint_rlp_test!(u16_fixtures()); + uint_rlp_test!(u32_fixtures()); + uint_rlp_test!(u64_fixtures()); + uint_rlp_test!(u128_fixtures()); + #[cfg(feature = "ethnum")] + uint_rlp_test!(u256_fixtures()); + } + + #[cfg(feature = "ethereum-types")] + #[test] + fn rlp_eth_uints() { + uint_rlp_test!(eth_u64_fixtures()); + uint_rlp_test!(eth_u128_fixtures()); + uint_rlp_test!(eth_u256_fixtures()); + uint_rlp_test!(eth_u512_fixtures()); + } + + #[test] + fn rlp_list() { + assert_eq!(encoded_list::(&[]), &hex!("c0")[..]); + assert_eq!(encoded_list(&[0xFFCCB5_u64, 0xFFC0B5_u64]), &hex!("c883ffccb583ffc0b5")[..]); + } +} diff --git a/crates/common/rlp/src/lib.rs b/crates/common/rlp/src/lib.rs new file mode 100644 index 000000000000..3882d6dff8cc --- /dev/null +++ b/crates/common/rlp/src/lib.rs @@ -0,0 +1,22 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "alloc")] +extern crate alloc; + +mod decode; +mod encode; +mod types; + +pub use bytes::BufMut; + +pub use decode::{Decodable, DecodeError, Rlp}; +pub use encode::{ + const_add, encode_fixed_size, encode_list, length_of_length, list_length, Encodable, + MaxEncodedLen, MaxEncodedLenAssoc, +}; +pub use types::*; + +#[cfg(feature = "derive")] +pub use reth_rlp_derive::{ + RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper, RlpMaxEncodedLen, +}; diff --git a/crates/common/rlp/src/types.rs b/crates/common/rlp/src/types.rs new file mode 100644 index 000000000000..333e358e6908 --- /dev/null +++ b/crates/common/rlp/src/types.rs @@ -0,0 +1,8 @@ +#[derive(Clone, Default, PartialEq)] +pub struct Header { + pub list: bool, + pub payload_length: usize, +} + +pub const EMPTY_STRING_CODE: u8 = 0x80; +pub const EMPTY_LIST_CODE: u8 = 0xC0; diff --git a/crates/common/rlp/tests/rlp.rs b/crates/common/rlp/tests/rlp.rs new file mode 100644 index 000000000000..aa029c5a05d6 --- /dev/null +++ b/crates/common/rlp/tests/rlp.rs @@ -0,0 +1,92 @@ +use bytes::{Bytes, BytesMut}; +use ethnum::U256; +use hex_literal::hex; +use reth_rlp::{DecodeError, *}; + +#[derive(Debug, PartialEq, RlpEncodable, RlpDecodable)] +struct Item { + a: Bytes, +} + +#[derive(Debug, PartialEq, RlpEncodable, RlpDecodable, RlpMaxEncodedLen)] +struct Test4Numbers { + a: u8, + b: u64, + c: U256, + d: U256, +} + +#[derive(Debug, PartialEq, RlpEncodableWrapper, RlpDecodableWrapper)] +pub struct W(Test4Numbers); + +#[derive(Debug, PartialEq, RlpEncodable)] +struct Test4NumbersGenerics<'a, D: Encodable> { + a: u8, + b: u64, + c: &'a U256, + d: &'a D, +} + +fn encoded(t: &T) -> BytesMut { + let mut out = BytesMut::new(); + t.encode(&mut out); + out +} + +#[test] +fn test_encode_item() { + let item = Item { a: b"dog".to_vec().into() }; + + let expected = vec![0xc4, 0x83, b'd', b'o', b'g']; + let out = encoded(&item); + assert_eq!(&*out, expected); + + let decoded = Decodable::decode(&mut &*expected).expect("decode failure"); + assert_eq!(item, decoded); + + let item = Test4Numbers { + a: 0x05, + b: 0xdeadbeefbaadcafe, + c: U256::from_be_bytes(hex!( + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + )), + d: U256::from_be_bytes(hex!( + "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + )), + }; + + let expected = hex!("f84c0588deadbeefbaadcafea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(); + let out = encoded(&item); + assert_eq!(&*out, expected); + + let out = reth_rlp::encode_fixed_size(&item); + assert_eq!(&*out, expected); + + let decoded = Decodable::decode(&mut &*expected).unwrap(); + assert_eq!(item, decoded); + + let mut rlp_view = Rlp::new(&*expected).unwrap(); + assert_eq!(rlp_view.get_next().unwrap(), Some(item.a)); + assert_eq!(rlp_view.get_next().unwrap(), Some(item.b)); + assert_eq!(rlp_view.get_next().unwrap(), Some(item.c)); + assert_eq!(rlp_view.get_next().unwrap(), Some(item.d)); + assert_eq!(rlp_view.get_next::().unwrap(), None); + + assert_eq!( + encoded(&Test4NumbersGenerics { a: item.a, b: item.b, c: &item.c, d: &item.d }), + expected + ); + + assert_eq!(encoded(&W(item)), expected); + assert_eq!(W::decode(&mut &*expected).unwrap().0, decoded); +} + +#[test] +fn invalid_decode_sideeffect() { + let fixture = hex!("f84d0588deadbeefbaadcafea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); + let mut sl: &[u8] = &fixture; + + assert_eq!(Test4Numbers::decode(&mut sl), Err(DecodeError::InputTooShort)); + + assert_eq!(sl.len(), fixture.len()); +}