diff --git a/Cargo.lock b/Cargo.lock index ca8929f5a2..f7393c2c71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,99 +18,921 @@ dependencies = [ "generic-array", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy-chains" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db5bcdd086f0b1b9610140a12c59b757397be90bd130d8d836fc8da0815a34" +dependencies = [ + "alloy-primitives", + "num_enum", + "strum 0.27.2", +] + +[[package]] +name = "alloy-consensus" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e30ab0d3e3c32976f67fc1a96179989e45a69594af42003a6663332f9b0bb9d" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "k256", + "once_cell", + "rand 0.8.5", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20736b1f9d927d875d8777ef0c2250d4c57ea828529a9dbfa2c628db57b911e" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-eips" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85157b7be31fc4adf6acfefcb0d4308cba5dbd7a8d8e62bcc02ff37d6131a" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "borsh", + "c-kzg", + "derive_more", + "either", + "ethereum_ssz", + "ethereum_ssz_derive", + "serde", + "serde_with", + "sha2 0.10.9", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-evm" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e7b4fb2418490bca9978e74208215ed5fcb21a10aba7eea487abaa60fd588db" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-rpc-types-eth", + "alloy-sol-types", + "auto_impl", + "derive_more", + "op-alloy", + "op-revm", + "revm", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + +[[package]] +name = "alloy-json-abi" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e3cf01219c966f95a460c95f1d4c30e12f6c18150c21a30b768af2a2a29142" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f045b69b5e80b8944b25afe74ae6b974f3044d84b4a7a113da04745b2524cc" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http 1.3.1", + "serde", + "serde_json", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b314ed5bdc7f449c53853125af2db5ac4d3954a9f4b205e7d694f02fc1932d1" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9762ac5cca67b0f6ab614f7f8314942eead1c8eeef61511ea43a6ff048dbe0" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6a0fb18dd5fb43ec5f0f6a20be1ce0287c79825827de5744afaa6c957737c33" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.12.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.2", + "rapidhash", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", +] + +[[package]] +name = "alloy-provider" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea8f7ca47514e7f552aa9f3f141ab17351332c6637e3bf00462d8e7c5f10f51f" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", + "alloy-network", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-client", + "alloy-rpc-types-eth", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "async-stream", + "async-trait", + "auto_impl", + "dashmap", + "either", + "futures", + "futures-utils-wasm", + "lru 0.16.3", + "parking_lot", + "pin-project", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tracing", + "wasmtimer", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f70d83b765fdc080dbcd4f4db70d8d23fe4761f2f02ebfa9146b833900634b4" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "alloy-rpc-client" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26dd083153d2cb73cce1516f5a3f9c3af74764a2761d901581a355777468bd8f" +dependencies = [ + "alloy-json-rpc", + "alloy-primitives", + "alloy-transport", + "futures", + "pin-project", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tower", + "tracing", + "wasmtimer", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b4a6f49d161ef83354d5ba3c8bc83c8ee464cb90182b215551d5c4b846579be" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933aaaace9faa6d7efda89472add89a8bfd15270318c47a2be8bb76192c951e2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "rand 0.8.5", + "serde", + "strum 0.27.2", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11920b16ab7c86052f990dcb4d25312fb2889faf506c4ee13dc946b450536989" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-serde" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a0d2d5c64881f3723232eaaf6c2d9f4f88b061c63e87194b2db785ff3aa31f" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea4ac9765e5a7582877ca53688e041fe184880fe75f16edf0945b24a319c710" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve 0.13.8", + "k256", + "thiserror 2.0.17", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09eb18ce0df92b4277291bbaa0ed70545d78b02948df756bbd3d6214bf39a218" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95d9fa2daf21f59aa546d549943f10b5cce1ae59986774019fbedae834ffe01b" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.12.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.110", + "syn-solidity", + "tiny-keccak", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9396007fe69c26ee118a19f4dee1f5d1d6be186ea75b3881adf16d87f8444686" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.110", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af67a0b0dcebe14244fc92002cd8d96ecbf65db4639d479f5fcd5805755a4c27" +dependencies = [ + "serde", + "winnow", +] + +[[package]] +name = "alloy-sol-types" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09aeea64f09a7483bdcd4193634c7e5cf9fd7775ee767585270cd8ce2d69dc95" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-transport" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e72f5c4ba505ebead6a71144d72f21a70beadfb2d84e0a560a985491ecb71de" +dependencies = [ + "alloy-json-rpc", + "auto_impl", + "base64 0.22.1", + "derive_more", + "futures", + "futures-utils-wasm", + "parking_lot", + "serde", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tower", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "alloy-trie" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428aa0f0e0658ff091f8f667c406e034b431cb10abd39de4f507520968acc499" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arrayvec", + "derive_more", + "nybbles", + "serde", + "smallvec", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2183706e24173309b0ab0e34d3e53cf3163b71a419803b2b3b0c1fb7ff7a941" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-r1cs-std", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[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", + "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", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "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", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" dependencies = [ - "memchr", + "num-bigint", + "num-traits", + "quote", + "syn 1.0.109", ] [[package]] -name = "allocator-api2" -version = "0.2.21" +name = "ark-ff-macros" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "ark-ff-macros" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "libc", + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] -name = "anes" -version = "0.1.6" +name = "ark-poly" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] [[package]] -name = "anstream" -version = "0.6.21" +name = "ark-r1cs-std" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", ] [[package]] -name = "anstyle" -version = "1.0.13" +name = "ark-relations" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", + "tracing-subscriber 0.2.25", +] [[package]] -name = "anstyle-parse" -version = "0.2.7" +name = "ark-serialize" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" dependencies = [ - "utf8parse", + "ark-std 0.3.0", + "digest 0.9.0", ] [[package]] -name = "anstyle-query" -version = "1.1.5" +name = "ark-serialize" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ - "windows-sys 0.61.2", + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint", ] [[package]] -name = "anstyle-wincon" -version = "3.0.11" +name = "ark-serialize" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", ] [[package]] -name = "anyhow" -version = "1.0.100" +name = "ark-serialize-derive" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] [[package]] -name = "arbitrary" -version = "1.4.2" +name = "ark-std" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" dependencies = [ - "derive_arbitrary", + "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 = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.5", ] [[package]] @@ -125,6 +947,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ + "serde", "zeroize", ] @@ -139,6 +962,28 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -147,7 +992,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -156,6 +1001,27 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -301,7 +1167,7 @@ dependencies = [ "http 0.2.12", "http 1.3.1", "http-body 0.4.6", - "lru", + "lru 0.12.5", "percent-encoding", "regex-lite", "sha2 0.10.9", @@ -612,7 +1478,7 @@ dependencies = [ "aws-smithy-async", "aws-smithy-runtime-api", "aws-smithy-types", - "rustc_version", + "rustc_version 0.4.1", "tracing", ] @@ -668,6 +1534,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "base16ct" version = "0.1.1" @@ -723,12 +1595,40 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", +] + [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.8.2" @@ -774,12 +1674,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "borsh" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da5ab77c1437701eeff7c88d968729e7766172279eab0676857b3d63af7a6f" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686c856aa6aac0c4498f936d7d6a02df690f614c03e4d906d1018062b5c5e2c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "bumpalo" version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "byteorder" version = "1.5.0" @@ -791,6 +1720,9 @@ name = "bytes" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] [[package]] name = "bytes-utils" @@ -802,6 +1734,21 @@ dependencies = [ "either", ] +[[package]] +name = "c-kzg" +version = "2.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + [[package]] name = "cassowary" version = "0.3.0" @@ -880,6 +1827,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link 0.2.1", ] @@ -953,7 +1901,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1014,7 +1962,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1032,7 +1980,7 @@ dependencies = [ "prometheus-client", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1068,7 +2016,7 @@ dependencies = [ "ratatui", "serde_json", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1151,7 +2099,7 @@ dependencies = [ "rand 0.8.5", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1189,7 +2137,7 @@ version = "0.0.65" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -1222,7 +2170,7 @@ dependencies = [ "rstest", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1320,7 +2268,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", "uuid", ] @@ -1344,7 +2292,7 @@ dependencies = [ "serde", "serde_json", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1365,7 +2313,7 @@ dependencies = [ "serde", "serde_yaml", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", "uuid", ] @@ -1391,7 +2339,7 @@ dependencies = [ "ratatui", "serde_json", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1403,10 +2351,10 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "toml", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1451,7 +2399,7 @@ dependencies = [ "rand_distr", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1507,7 +2455,7 @@ dependencies = [ "serde_json", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1530,7 +2478,33 @@ dependencies = [ "rand 0.8.5", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", +] + +[[package]] +name = "commonware-revm" +version = "0.0.65" +dependencies = [ + "alloy-evm", + "anyhow", + "bytes", + "clap", + "commonware-broadcast", + "commonware-codec", + "commonware-consensus", + "commonware-cryptography", + "commonware-p2p", + "commonware-parallel", + "commonware-runtime", + "commonware-storage", + "commonware-utils", + "futures", + "governor", + "libc", + "rand 0.8.5", + "revm", + "thiserror 2.0.17", + "tracing", ] [[package]] @@ -1569,7 +2543,7 @@ dependencies = [ "tokio", "tracing", "tracing-opentelemetry", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1609,7 +2583,7 @@ dependencies = [ "rstest", "thiserror 2.0.17", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", "zstd", ] @@ -1685,7 +2659,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.3.20", ] [[package]] @@ -1790,7 +2764,19 @@ dependencies = [ "tonic", "tracing", "tracing-core", - "tracing-subscriber", + "tracing-subscriber 0.3.20", +] + +[[package]] +name = "const-hex" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", ] [[package]] @@ -1799,6 +2785,26 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -2054,7 +3060,7 @@ dependencies = [ "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "rustc_version", + "rustc_version 0.4.1", "subtle", "zeroize", ] @@ -2067,7 +3073,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2089,22 +3095,47 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] name = "darling_core" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", + "serde", "strsim", - "syn", + "syn 2.0.110", ] [[package]] @@ -2113,9 +3144,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2160,6 +3202,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -2170,7 +3235,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2191,7 +3256,8 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.110", + "unicode-xid", ] [[package]] @@ -2223,7 +3289,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2247,6 +3313,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ecdsa" version = "0.14.8" @@ -2269,6 +3341,7 @@ dependencies = [ "digest 0.10.7", "elliptic-curve 0.13.8", "rfc6979 0.4.0", + "serdect", "signature 2.2.0", "spki 0.7.3", ] @@ -2315,11 +3388,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] [[package]] name = "elliptic-curve" @@ -2356,6 +3444,7 @@ dependencies = [ "pkcs8 0.10.2", "rand_core 0.6.4", "sec1 0.7.3", + "serdect", "subtle", "zeroize", ] @@ -2369,6 +3458,26 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -2385,6 +3494,46 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ethereum_serde_utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dc1355dbb41fbbd34ec28d4fb2a57d9a70c67ac3c19f6a5ca4d4a176b9e997a" +dependencies = [ + "alloy-primitives", + "hex", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "ethereum_ssz" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" +dependencies = [ + "alloy-primitives", + "ethereum_serde_utils", + "itertools 0.13.0", + "serde", + "serde_derive", + "smallvec", + "typenum", +] + +[[package]] +name = "ethereum_ssz_derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "event-listener" version = "5.4.1" @@ -2412,6 +3561,28 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.12.1" @@ -2444,6 +3615,18 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[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.5.7" @@ -2508,6 +3691,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -2564,7 +3753,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -2603,6 +3792,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "generic-array" version = "0.14.7" @@ -2647,6 +3842,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "gmp-mpfr-sys" +version = "1.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + [[package]] name = "governor" version = "0.10.2" @@ -2704,7 +3909,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -2723,7 +3928,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -2741,6 +3946,12 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hashbrown" version = "0.14.5" @@ -2767,6 +3978,8 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash 0.2.0", + "serde", + "serde_core", ] [[package]] @@ -2800,6 +4013,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + [[package]] name = "hmac" version = "0.12.1" @@ -3151,6 +4373,37 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.12.0" @@ -3159,6 +4412,8 @@ checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -3185,11 +4440,11 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ - "darling", + "darling 0.20.11", "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3225,6 +4480,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.13.0" @@ -3269,6 +4533,39 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "once_cell", + "serdect", + "sha2 0.10.9", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -3345,12 +4642,32 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "lru" +version = "0.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" +dependencies = [ + "hashbrown 0.16.1", +] + [[package]] name = "lru-slab" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "matchers" version = "0.2.0" @@ -3467,6 +4784,20 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -3478,6 +4809,15 @@ 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-conv" version = "0.1.0" @@ -3493,6 +4833,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.2" @@ -3505,23 +4856,59 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ - "autocfg", - "libm", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] -name = "num_cpus" -version = "1.17.0" +name = "nybbles" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +checksum = "7b5676b5c379cf5b03da1df2b3061c4a4e2aa691086a56ac923e08c143f53f59" dependencies = [ - "hermit-abi", - "libc", + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", ] [[package]] @@ -3561,6 +4948,120 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "op-alloy" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848" +dependencies = [ + "op-alloy-consensus", + "op-alloy-network", + "op-alloy-provider", + "op-alloy-rpc-types", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-consensus" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "serde", + "thiserror 2.0.17", +] + +[[package]] +name = "op-alloy-network" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034183dca6bff6632e7c24c92e75ff5f0eabb58144edb4d8241814851334d47" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-eth", + "alloy-signer", + "op-alloy-consensus", + "op-alloy-rpc-types", +] + +[[package]] +name = "op-alloy-provider" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types-engine", + "alloy-transport", + "async-trait", + "op-alloy-rpc-types-engine", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd87c6b9e5b6eee8d6b76f41b04368dca0e9f38d83338e5b00e730c282098a4" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "derive_more", + "op-alloy-consensus", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "op-alloy-rpc-types-engine" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727699310a18cdeed32da3928c709e2704043b6584ed416397d5da65694efc" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-engine", + "alloy-serde", + "derive_more", + "ethereum_ssz", + "ethereum_ssz_derive", + "op-alloy-consensus", + "serde", + "sha2 0.10.9", + "snap", + "thiserror 2.0.17", +] + +[[package]] +name = "op-revm" +version = "14.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1475a779c73999fc803778524042319691b31f3d6699d2b560c4ed8be1db802a" +dependencies = [ + "auto_impl", + "revm", + "serde", +] + [[package]] name = "opaque-debug" version = "0.3.1" @@ -3590,7 +5091,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3714,6 +5215,34 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "parking" version = "2.2.1" @@ -3764,6 +5293,59 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -3781,7 +5363,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3900,6 +5482,17 @@ dependencies = [ "elliptic-curve 0.13.8", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + [[package]] name = "proc-macro-crate" version = "3.4.0" @@ -3909,6 +5502,28 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "proc-macro2" version = "1.0.103" @@ -3938,7 +5553,7 @@ checksum = "9adf1691c04c0a5ff46ff8f262b58beb07b0dbb61f96f9f54f6cbd82106ed87f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -3980,7 +5595,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -4083,6 +5698,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -4092,6 +5713,7 @@ dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.4", + "serde", ] [[package]] @@ -4102,6 +5724,7 @@ checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", + "serde", ] [[package]] @@ -4140,6 +5763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom 0.3.4", + "serde", ] [[package]] @@ -4161,6 +5785,15 @@ dependencies = [ "rand_core 0.9.3", ] +[[package]] +name = "rapidhash" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8b5b858a440a0bc02625b62dd95131b9201aa9f69f411195dd4a7cfb1de3d7" +dependencies = [ + "rustversion", +] + [[package]] name = "ratatui" version = "0.29.0" @@ -4174,9 +5807,9 @@ dependencies = [ "indoc", "instability", "itertools 0.13.0", - "lru", + "lru 0.12.5", "paste", - "strum", + "strum 0.26.3", "unicode-segmentation", "unicode-truncate", "unicode-width 0.2.0", @@ -4238,6 +5871,26 @@ dependencies = [ "readme-rustdocifier", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "regex" version = "1.12.2" @@ -4309,20 +5962,210 @@ dependencies = [ "rustls 0.23.35", "rustls-pki-types", "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-native-tls", - "tokio-rustls 0.26.4", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.4", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "revm" +version = "33.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c85ed0028f043f87b3c88d4a4cb6f0a76440085523b6a8afe5ff003cf418054" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c6b5e6e8dd1e28a4a60e5f46615d4ef0809111c9e63208e55b5c7058200fb0" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "12.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f038f0c9c723393ac897a5df9140b21cfa98f5753a2cb7d0f28fa430c4118abf" +dependencies = [ + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "13.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "431c9a14e4ef1be41ae503708fd02d974f80ef1f2b6b23b5e402e8d854d1b225" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "9.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980d8d6bba78c5dd35b83abbb6585b0b902eb25ea4448ed7bfba6283b0337191" +dependencies = [ + "alloy-eips", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "8.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cce03e3780287b07abe58faf4a7f5d8be7e81321f93ccf3343c8f7755602bae" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", + "tokio", +] + +[[package]] +name = "revm-handler" +version = "14.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d44f8f6dbeec3fecf9fe55f78ef0a758bdd92ea46cd4f1ca6e2a946b32c367f3" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "14.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5617e49216ce1ca6c8826bcead0386bc84f49359ef67cde6d189961735659f93" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", + "serde_json", +] + +[[package]] +name = "revm-interpreter" +version = "31.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec36405f7477b9dccdc6caa3be19adf5662a7a0dffa6270cdb13a090c077e5" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a62958af953cc4043e93b5be9b8497df84cc3bd612b865c49a7a7dfa26a84e2" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "c-kzg", + "cfg-if", + "k256", + "p256 0.13.2", + "revm-primitives", + "ripemd", + "rug", + "secp256k1 0.31.1", + "sha2 0.10.9", +] + +[[package]] +name = "revm-primitives" +version = "21.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29e161db429d465c09ba9cbff0df49e31049fe6b549e28eb0b7bd642fcbd4412" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8be953b7e374dbdea0773cf360debed8df394ea8d82a8b240a6b5da37592fc" +dependencies = [ + "bitflags", + "revm-bytecode", + "revm-primitives", + "serde", ] [[package]] @@ -4360,6 +6203,25 @@ dependencies = [ "windows-sys 0.52.0", ] +[[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 = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + [[package]] name = "rstest" version = "0.26.1" @@ -4384,24 +6246,85 @@ dependencies = [ "quote", "regex", "relative-path", - "rustc_version", - "syn", + "rustc_version 0.4.1", + "syn 2.0.110", "unicode-ident", ] +[[package]] +name = "rug" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58ad2e973fe3c3214251a840a621812a4f40468da814b1a3d6947d433c2af11f" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + +[[package]] +name = "ruint" +version = "1.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c141e807189ad38a07276942c6623032d3753c8859c146104ac2e4d68865945a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.5", + "rand 0.9.2", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver", + "semver 1.0.27", ] [[package]] @@ -4564,6 +6487,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4604,10 +6551,52 @@ dependencies = [ "der 0.7.10", "generic-array", "pkcs8 0.10.2", + "serdect", "subtle", "zeroize", ] +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "secp256k1-sys 0.10.1", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3c81b43dc2d8877c216a3fccf76677ee1ebccd429566d3e67447290d0c42b2" +dependencies = [ + "bitcoin_hashes", + "rand 0.9.2", + "secp256k1-sys 0.11.0", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb913707158fadaf0d8702c2db0e857de66eb003ccfdda5924b5f5ac98efb38" +dependencies = [ + "cc", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -4644,12 +6633,30 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + [[package]] name = "serde" version = "1.0.228" @@ -4677,7 +6684,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -4686,6 +6693,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ + "indexmap 2.12.0", "itoa", "memchr", "ryu", @@ -4725,19 +6733,60 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.0", + "schemars 0.9.0", + "schemars 1.2.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.12.0", "itoa", "ryu", "serde", "unsafe-libyaml", ] +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct 0.2.0", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -4773,6 +6822,26 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" +dependencies = [ + "cc", + "cfg-if", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4844,6 +6913,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.11" @@ -4855,6 +6930,15 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" @@ -4935,7 +7019,16 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", ] [[package]] @@ -4948,7 +7041,19 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.110", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.110", ] [[package]] @@ -4963,6 +7068,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.110" @@ -4974,6 +7090,18 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn-solidity" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f92d01b5de07eaf324f7fca61cc6bd3d82bbc1de5b6c963e6fe79e86f36580d" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.110", +] + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4991,7 +7119,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5029,6 +7157,12 @@ dependencies = [ "libc", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tempfile" version = "3.23.0" @@ -5068,7 +7202,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5079,7 +7213,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5107,6 +7241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", @@ -5130,6 +7265,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -5191,7 +7335,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5233,6 +7377,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -5254,7 +7399,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap", + "indexmap 2.12.0", "serde_core", "serde_spanned", "toml_datetime", @@ -5278,7 +7423,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap", + "indexmap 2.12.0", "toml_datetime", "toml_parser", "winnow", @@ -5347,7 +7492,7 @@ checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 2.12.0", "pin-project-lite", "slab", "sync_wrapper", @@ -5408,7 +7553,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5447,7 +7592,7 @@ dependencies = [ "tracing", "tracing-core", "tracing-log", - "tracing-subscriber", + "tracing-subscriber 0.3.20", "web-time", ] @@ -5461,6 +7606,15 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.20" @@ -5495,6 +7649,24 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "unarray" version = "0.1.4" @@ -5536,6 +7708,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -5717,7 +7895,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.110", "wasm-bindgen-shared", ] @@ -5730,6 +7908,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasmtimer" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" +dependencies = [ + "futures", + "js-sys", + "parking_lot", + "pin-utils", + "slab", + "wasm-bindgen", +] + [[package]] name = "web-sys" version = "0.3.82" @@ -5857,7 +8049,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -5868,7 +8060,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6135,6 +8327,15 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x25519-dalek" version = "2.0.1" @@ -6172,7 +8373,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "synstructure", ] @@ -6193,7 +8394,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6213,7 +8414,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", "synstructure", ] @@ -6234,7 +8435,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] @@ -6267,7 +8468,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.110", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a7c3b27cf4..8ba342e6f4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ members = [ "examples/log", "examples/sync", "examples/reshare", + "examples/revm", # Fuzz builds "broadcast/fuzz", diff --git a/examples/revm/Cargo.toml b/examples/revm/Cargo.toml new file mode 100644 index 0000000000..643901fa1f --- /dev/null +++ b/examples/revm/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "commonware-revm" +edition.workspace = true +publish = true +version.workspace = true +license.workspace = true +description = "REVM-based example chain running on threshold-simplex (simplex + threshold BLS signatures)." +readme = "README.md" +homepage.workspace = true +repository = "https://github.com/commonwarexyz/monorepo/tree/main/examples/revm" + +[lints] +workspace = true + +[dependencies] + +# EVM execution +alloy-evm = { version = "=0.25.1", default-features = false, features = ["std"] } +anyhow.workspace = true +bytes.workspace = true +clap.workspace = true +commonware-broadcast.workspace = true +commonware-codec.workspace = true +commonware-consensus.workspace = true +commonware-cryptography.workspace = true +commonware-p2p.workspace = true +commonware-parallel.workspace = true +commonware-runtime.workspace = true +commonware-storage.workspace = true +commonware-utils.workspace = true +futures.workspace = true +governor.workspace = true +libc.workspace = true +rand.workspace = true +revm = { version = "33.1.0", default-features = false, features = ["std", "asyncdb"] } +thiserror.workspace = true +tracing.workspace = true + +[[bin]] +name = "commonware-revm" +bench = false diff --git a/examples/revm/README.md b/examples/revm/README.md new file mode 100644 index 0000000000..78ffdc9a97 --- /dev/null +++ b/examples/revm/README.md @@ -0,0 +1,85 @@ +# commonware-revm + +[![Crates.io](https://img.shields.io/crates/v/commonware-revm.svg)](https://crates.io/crates/commonware-revm) + +REVM-based example chain driven by threshold-simplex (`commonware_consensus::simplex`) and executed with `alloy-evm`. + +## What This Demonstrates + +- Threshold-simplex orders opaque 32-byte digests; full blocks are disseminated and backfilled by `commonware_consensus::marshal` over `commonware_p2p::simulated`. +- Blocks carry a batch of EVM transactions plus an advertised 32-byte `state_root` commitment. +- Validators re-execute proposed blocks with `alloy-evm` / `revm` and reject proposals whose `state_root` mismatches. +- State is persisted in QMDB and exposed to REVM via `WrapDatabaseAsync` + `CacheDB` (QMDB is the base store; CacheDB is the speculative overlay). +- The `state_root` is derived from authenticated QMDB partition roots (accounts, storage, code). +- Seed plumbing: threshold-simplex certificate seed signatures are hashed to 32 bytes and injected as `block.prevrandao` (EIP-4399). +- Bonus: a stateful precompile at `0x00000000000000000000000000000000000000ff` returns the current block's `prevrandao` (32 bytes). + +## Components + +- `src/types.rs`: canonical block/tx types and digest mapping (`ConsensusDigest = sha256(BlockId)`). +- `src/application/`: proposal/verification logic (marshaled), shared state (mempool + DB snapshots), reporters, and query handle. +- `src/execution.rs`: EVM execution (`EthEvmBuilder`) and the seed precompile. +- `src/commitment.rs`: canonical `StateChanges` encoding. +- `src/sim/`: tokio, single-process simulation harness (N nodes, simulated P2P). + +## How It Works + +This example is intentionally "digest-first": + +- Simplex agrees on a `ConsensusDigest` for each height (32 bytes). +- The application maps `ConsensusDigest <-> Block` and ensures a digest is only accepted if the + corresponding block re-executes to the advertised `state_root`. + +### Block Lifecycle (One Height) + +1. Genesis: the application creates the genesis block and prefunds accounts in the EVM DB. +2. Propose: when Simplex asks a leader to propose, the application builds a child block, executes + its txs, stores the block + resulting DB snapshot locally, and returns the full block to the + `commonware_consensus::application::marshaled::Marshaled` wrapper (consensus still orders only + the block commitment digest). +3. Disseminate: marshal broadcasts the full block and serves backfill requests for missing ancestors. +4. Verify: validators re-execute the block on the parent snapshot and accept only if the computed + `state_root` matches the advertised `state_root` (the wrapper notifies marshal on success). +5. Finalize: marshal delivers finalized blocks to the node, the simulation records the digest, and stops after the configured + number of finalizations per node. + +The main glue points are `src/sim/node.rs` (wiring) and `src/application/` (application logic). + +### Seed Lifecycle + +- On notarization/finalization, threshold-simplex emits a seed signature. +- This example hashes that seed signature to 32 bytes and stores it alongside the finalized digest. +- The next block uses the parent digest's stored seed hash as `prevrandao` (EIP-4399). +- The seed precompile returns the current block's `prevrandao` so contracts can read it. + +## Run (Tokio Simulation) + +```sh +cargo run -p commonware-revm --release -- --nodes 4 --blocks 5 --seed 1 +``` + +Flags: + +- `--nodes`: number of validators (default: 4) +- `--blocks`: number of finalized blocks to wait for per node (default: 3) +- `--seed`: seeded DKG + demo inputs (default: 1) + +Expected output is consistent for a given `--seed` and includes: + +- Finalized head digest (agreed by consensus) +- Final `state_root` commitment +- Final balances for the example accounts +- Latest tracked threshold seed (32 bytes) and the current block's `prevrandao` + +## Test + +```sh +cargo test -p commonware-revm +``` + +## Notes and Next Steps + +- This is intentionally minimal and does not implement an Ethereum trie; `state_root` comes from authenticated QMDB partition roots. +- Transactions are built directly as EVM call environments (no signature/fee market modeling); gas price is set to 0. +- The demo block stream is minimal (a single transfer is injected early); extend `src/application/` to add more tx generation. +- The example now uses a QMDB-backed persistence layer with per-finalized-block batch commits. diff --git a/examples/revm/docs/ARCHITECTURE.md b/examples/revm/docs/ARCHITECTURE.md new file mode 100644 index 0000000000..c86b5c4147 --- /dev/null +++ b/examples/revm/docs/ARCHITECTURE.md @@ -0,0 +1,111 @@ +# REVM Example Architecture + +This document walks through the REVM simulation example with a top-down view that emphasizes the domain model (blocks, transactions, QMDB-rooted state) and the runtime components that operate on it. The diagram in `examples/revm/docs/revm_architecture.png` is the recommended starting point; its source lives in `examples/revm/docs/revm_architecture.dot`. + +## 1. High-Level Workflow + +1. **CLI entry point** (`examples/revm/src/main.rs`): Parses `nodes`, `blocks`, and `seed` flags, then calls `simulate(cfg)`. +2. **Simulation harness** (`examples/revm/src/simulation/mod.rs`): Runs on a deterministic Tokio executor, derives threshold schemes, builds the `BootstrapConfig`, sets up the network, then starts `N` nodes and waits for a finalized head. This is the orchestration layer that keeps the demo deterministic and observable. +3. **Nodes**: Each node wires together: + - **Consensus (Threshold Simplex)**: Orders block commitments and emits notarization/finalization events. + - **Marshal**: Delivers blocks over the simulated network and requests ancestors when needed. + - **Application** (`examples/revm/src/application/app.rs`): Handles proposing/verifying full REVM blocks and maintains local state snapshots. +4. **Reporters**: The `SeedReporter` captures seeds from simplex activity, and the `FinalizedReporter` replays finalized blocks, verifies execution via QMDB, persists snapshots, and notifies the harness when a block is processed. + +## 2. Domain Model + +| Term | Description | File | +|------|-------------|------| +| **Block** | Parent pointer, height, prevrandao seed, state root, transactions. Blocks are encoded/decoded via `examples/revm/src/domain/types.rs` and committed via the simplex digest. | `examples/revm/src/domain/types.rs` | +| **Tx** | Minimal transaction (from, to, value, gas limit, calldata) with deterministic codec for gossip. | `examples/revm/src/domain/types.rs` | +| **BootstrapConfig** | Genesis allocation plus bootstrap transactions applied before consensus starts. | `examples/revm/src/domain/types.rs` | +| **StateChanges & QmdbChanges** | Deterministic encodings of touched accounts/storage used for execution tracing and QMDB persistence. | `examples/revm/src/domain/commitment.rs`, `examples/revm/src/qmdb/changes.rs` | +| **StateRoot** | Hash combining QMDB partition roots plus a namespace tag, ensuring authenticated state. | `examples/revm/src/qmdb/mod.rs`, `examples/revm/src/domain/types.rs` | + +## 3. Core Components + +### Ledger View (`examples/revm/src/application/ledger/mod.rs`) + +- Holds the mempool, per-digest snapshots, seed cache, and persisted digest set. +- Stores snapshots as `LedgerSnapshot` (parent digest, `RevmDb`, `StateRoot`, `QmdbChanges`) in `examples/revm/src/application/ledger/snapshot_store.rs`. +- Provides helpers to preview roots (without durably writing) and to persist snapshots via `QmdbLedger`. + +### QMDB Adapter & Persistence + +- `QmdbLedger` (`examples/revm/src/qmdb/service.rs`) orchestrates partitioned stores (`accounts`, `storage`, `code`) through `QmdbState` (`examples/revm/src/qmdb/state.rs`) and exposes: + - `database()` → a `QmdbRefDb` adapter (`examples/revm/src/qmdb/adapter.rs`) to satisfy REVM's sync API. + - `preview_root()` → computes the state commitment that would result from staged changes. + - `commit_changes()` → applies the batch, updates the in-memory stores, and returns the new root. +- `QmdbChanges`, `AccountUpdate`, and `AccountRecord` live in `examples/revm/src/qmdb/changes.rs` and `examples/revm/src/qmdb/model.rs` and translate REVM's `EvmState` into authenticated batches keyed by addresses, storage slots, and code hashes. + +### Execution Layer (`examples/revm/src/application/execution.rs`) + +- `execute_txs` uses Alloy/REVM with a custom seed precompile to run each tx in the provided `RevmDb`. +- After each transaction it: + 1. Builds deterministic `StateChanges` for tracing and tests. + 2. Applies touched accounts to a `QmdbChanges` batch. + 3. Commits the changes back to `RevmDb`. +- The `ExecutionOutcome` contains both the per-tx `StateChanges` and the aggregated `QmdbChanges`. + +### Application (`examples/revm/src/application/app.rs`) + +- `RevmApplication` implements `Application`/`VerifyingApplication` for consensus integration. +- On `propose`, it: + 1. Collects mempool transactions while avoiding duplicates via ancestor scanning. + 2. Executes the transactions using the shared `RevmDb`. + 3. Previews the resulting QMDB root and updates the ledger view with the snapshot. +- On `verify`, it replays the block to recompute the root and ensures it matches the declared `state_root`. + +### Reporters (`examples/revm/src/application/reporters/seed.rs`, `examples/revm/src/application/reporters/finalized.rs`) + +- `SeedReporter` watches simplex activity and writes hashed seeds into the ledger view so `RevmApplication` can populate future `prevrandao`. +- `FinalizedReporter` reacts to `marshal::Update::Block`: + 1. Replays the block via `execute_txs` if it's not already finalized. + 2. Validates the computed root against the block. + 3. Persists the snapshot through `QmdbLedger`. + 4. Prunes the mempool, acknowledges Marshal, and emits finalized events to the simulation harness. + +### Ledger Aggregates & Services (`examples/revm/src/application/ledger/mod.rs`) + +- **Mempool**: owns pending transactions and exposes insert/build/prune commands so proposals and finalizers work against a consistent queue (`examples/revm/src/application/ledger/mempool.rs`). +- **SnapshotStore**: maintains `LedgerSnapshot`s plus the persisted digest set, handles ancestor lookups, merges pending `QmdbChanges`, and tracks which digests have been committed (`examples/revm/src/application/ledger/snapshot_store.rs`). +- **SeedCache**: keeps per-digest seed hashes so the deterministic `prevrandao` values are pulled from a shared source (`examples/revm/src/application/ledger/seed_cache.rs`). +- **LedgerService**: domain service that wraps `LedgerView` and exposes high-level commands (`submit_tx`, `build_txs`, `parent_snapshot`, `preview_root`, `insert_snapshot`, `persist_snapshot`, `prune_mempool`, `seed_for_parent`, `set_seed`, `query_state_root`). The application and reporters talk to `LedgerService` instead of mutating the aggregates directly. + + +### Domain Events + +`LedgerService` publishes `LedgerEvent`s through a broadcast channel whenever meaningful operations occur: +1. `TransactionSubmitted(TxId)` when the mempool accepts a new transaction. +2. `SnapshotPersisted(ConsensusDigest)` when QMDB commits a finalized digest. +3. `SeedUpdated(ConsensusDigest, B256)` when the per-digest seed cache is refreshed. + +Consumers can call `LedgerService::subscribe()` to obtain a listener and react to these events (e.g., instrumentation, metrics, or simulation probes) without touching the aggregates directly. + +### Ledger Observers (`examples/revm/src/application/observers.rs`) + +Ledger observers subscribe to domain events, emit telemetry/log output for seed refreshes and transaction submissions, and keep the observation context decoupled from the aggregates. + +## 4. Flows Illustrated + +1. **Proposal Flow**: + - CLI invokes simulation → `LedgerService` ingests submitted transactions and keeps them in the `Mempool`. + - `RevmApplication` asks the service for the parent snapshot, builds a proposal batch, executes it, previews the root via `SnapshotStore`, and records a new `LedgerSnapshot`. + - The computed state root travels with the proposed block, while the snapshot remains available for replay and persistence. + +2. **Finalization Flow**: + - Marshal delivers a finalized block to `FinalizedReporter`. + - The reporter replays the block, validates the root, and commands `LedgerService` to persist the authenticated updates (marking the digest in `SnapshotStore` and committing via `QmdbLedger`). + - `LedgerService` prunes the `Mempool`, and the simulation harness observes the `finalized` domain event so other nodes can progress. + +3. **Seed Flow**: + - `SeedReporter` listens to simplex notarizations/finalizations, hashes each seed, and stores it in the `SeedCache` through `LedgerService`. + - `RevmApplication` reuses the cached seed when computing `prevrandao` for future proposals. + +## 5. Diagram + +![REVM example architecture](revm_architecture.png) + +## 6. Related Docs + +For the DDD vocabulary, aggregates, and bounded contexts, see `examples/revm/docs/DOMAIN_MODEL.md`. diff --git a/examples/revm/docs/DOMAIN_MODEL.md b/examples/revm/docs/DOMAIN_MODEL.md new file mode 100644 index 0000000000..f5cdf6f69b --- /dev/null +++ b/examples/revm/docs/DOMAIN_MODEL.md @@ -0,0 +1,80 @@ +# REVM Domain Model + +This document exposes the REVM example through a domain-driven lens so that contributors can reason about the +core entities, aggregates, services, and events before diving into the concrete implementation. + +## 1. Ubiquitous Language +- **Node** – a simulated participant that wires consensus, marshal, and application logic (`examples/revm/src/application/node/mod.rs`). +- **LedgerSnapshot** – a captured execution result (parent digest, `RevmDb`, `StateRoot`, `QmdbChanges`) tied to a digest (`examples/revm/src/application/ledger/snapshot_store.rs`). +- **LedgerView** – the aggregate that owns the mempool, snapshot store, seed cache, and persistence driver. +- **LedgerService** – the domain service that exposes high-level ledger commands and emits `LedgerEvent`s (`examples/revm/src/application/ledger/mod.rs`). +- **QmdbLedger** – the persistence service that owns the QMDB partitions, previews potential roots, and commits finalized `QmdbChanges` (`examples/revm/src/qmdb/service.rs`). +- **Block** – a value object with parent pointer, height, `prevrandao`, `state_root`, and transactions (`examples/revm/src/domain/types.rs`). +- **Transaction (Tx)** – the value object placed in the mempool and executed inside the REVM (`examples/revm/src/domain/types.rs`). +- **BootstrapConfig** – genesis allocation plus bootstrap transactions applied before consensus starts (`examples/revm/src/domain/types.rs`). +- **StateRoot / ConsensusDigest** – authenticated identifiers derived from `QmdbChanges` and the block commitment. +- **SeedReporter / FinalizedReporter** – services that react to consensus events and interact with the ledger (`examples/revm/src/application/reporters/seed.rs`, `examples/revm/src/application/reporters/finalized.rs`). + +## 2. Entities +Entities represent mutable objects with identity that survive across commands: + +- **LedgerSnapshot** (`examples/revm/src/application/ledger/snapshot_store.rs`): identified by the digest it came from, it tracks the `RevmDb` state, the digest's `parent`, and the `QmdbChanges` needed to rebuild the snapshot. +- **LedgerService** (`examples/revm/src/application/ledger/mod.rs`): while it wraps the `LedgerView` aggregate, it behaves as a rich entity that carries listeners/subscribers and orchestrates ledger commands. + +## 3. Value Objects +While the public API never mutates these, they carry the data that commands and queries operate on: + +- **Block / Tx** (`examples/revm/src/domain/types.rs`): deterministic structures consumed and produced by proposals/verifications. +- **BootstrapConfig** (`examples/revm/src/domain/types.rs`): static inputs for genesis allocation and pre-consensus transactions. +- **ConsensusDigest / StateRoot** (`examples/revm/src/domain/types.rs` / `examples/revm/src/qmdb/mod.rs`): opaque hashes that identify snapshots and prove authentication. +- **SeedHash (`B256`)**: deterministic randomness reused in `prevrandao`. + +## 4. Aggregates +Aggregates are self-consistent clusters of entities/value objects that handle consistency and invariants: + +- **LedgerView** (`examples/revm/src/application/ledger/mod.rs`): the root aggregate that owns the mutable state (`LedgerState`) protected by a mutex. Its responsibilities include: + - `Mempool` – accepts, builds, and prunes transactions while ensuring no duplicates (`examples/revm/src/application/ledger/mempool.rs`). + - `SnapshotStore` – stores recorded `LedgerSnapshot`s, tracks which digests were persisted, and can merge cached `QmdbChanges` when replaying ancestors (`examples/revm/src/application/ledger/snapshot_store.rs`). + - `SeedCache` – retains the per-digest randomness used to derive `prevrandao` (`examples/revm/src/application/ledger/seed_cache.rs`). +- `QmdbLedger` – the persistence backend that previews and commits `QmdbChanges` (`examples/revm/src/qmdb/service.rs`). + +## 5. Domain Services +Domain services express operations that span aggregates: + +- **LedgerService** (`examples/revm/src/application/ledger/mod.rs`): exposes commands such as `submit_tx`, `parent_snapshot`, `preview_root`, `persist_snapshot`, and `set_seed`. It holds an observer list for domain events, keeping proposals, verifications, and reporters synchronized. +- **RevmApplication** (`examples/revm/src/application/app.rs`): offers the `Application`/`VerifyingApplication` implementation that consensus calls during propose/verify. It relies on `LedgerService` for ledger commands and ensures blocks only advance after `QmdbChanges` are committed. +- **SeedReporter / FinalizedReporter** (`examples/revm/src/application/reporters/seed.rs`, `examples/revm/src/application/reporters/finalized.rs`): respond to marshal events, update the ledger, persist snapshots, refresh seeds, and emit `finalized` signals to the harness. + +## 6. Domain Events +`LedgerService` emits `LedgerEvent`s (`examples/revm/src/domain/events.rs`) so other services can react without tightly coupling to the aggregates: + +1. `TransactionSubmitted(TxId)` – emitted when the mempool admits a new transaction. +2. `SnapshotPersisted(ConsensusDigest)` – emitted after a snapshot is successfully persisted via `QmdbLedger`. +3. `SeedUpdated(ConsensusDigest, B256)` – emitted whenever the cached seed hash is refreshed. + +Consumers subscribe via `LedgerService::subscribe()` to instrument, log, or drive auxiliary behavior (e.g., the harness noting proposal readiness). + +## 7. Bounded Contexts +DDD thrives when contexts are explicit: + +- **Consensus & Marshal** (`commonware_consensus`, `examples/revm/src/application/node/marshal.rs`): orders blocks, delivers ancestors, and expects the application to prove payloads. This context owns the signing/verification logic. +- **Application Execution** (`RevmApplication`, `execute_txs`, `QmdbChanges`): handles REVM execution, root previewing, and snapshot caching. +- **Persistence / QMDB** (`examples/revm/src/qmdb`): exposes `QmdbLedger`, `RevmDb`, and atomic commit/preview helpers. +- **Simulation Harness** (`examples/revm/src/simulation`): orchestrates nodes, deterministically steps the runtime, and interprets domain events for logging/termination. + +### Aggregate Reference + +| Aggregate | Primary File | Responsibilities | +|-----------|--------------|------------------| +| `LedgerView` | `examples/revm/src/application/ledger/mod.rs` | Hosts mutexed mempool, snapshot store, seed cache, and `QmdbLedger`, and exposes `preview_root`, `insert_snapshot`, and `persist_snapshot`. | +| `SnapshotStore` | `examples/revm/src/application/ledger/snapshot_store.rs` | Stores cached `LedgerSnapshot`s, tracks persisted digests, and rebuilds `QmdbChanges` chains when replaying missed ancestors. | +| `LedgerService` | `examples/revm/src/application/ledger/mod.rs` | Orchestrates ledger commands, publishes `LedgerEvent`s, and keeps observer listeners synchronized. | +| `RevmApplication` | `examples/revm/src/application/app.rs` | Implements `Application`/`VerifyingApplication`, using `LedgerService` for proposal/verification logic and deterministic execution. | +| `SeedReporter` / `FinalizedReporter` | `examples/revm/src/application/reporters` | React to simplex/marshal events to refresh seeds and persist snapshots while delegating the heavy work to `LedgerService`. | +| `LedgerObservers` | `examples/revm/src/application/observers.rs` | Subscribes to `LedgerEvent`s and surfaces telemetry/logging for transaction submissions, seed updates, and persistence. | + +## 8. Notes + +This document is intentionally focused on the domain vocabulary, aggregates, and services. For end-to-end runtime flows (simulation wiring and node startup), see `examples/revm/docs/ARCHITECTURE.md`. + +Linking these flows back to the aggregates ensures the application remains understandable and the DDD vocabulary stays consistent across the example. diff --git a/examples/revm/docs/revm_architecture.dot b/examples/revm/docs/revm_architecture.dot new file mode 100644 index 0000000000..b210415d31 --- /dev/null +++ b/examples/revm/docs/revm_architecture.dot @@ -0,0 +1,101 @@ +digraph revm_architecture { + rankdir=TB; + splines=true; + nodesep=0.4; + ranksep=0.6; + fontname="Helvetica"; + fontsize=12; + + node [ + shape=box, + style="rounded,filled", + fillcolor="white", + color="#666666", + fontname="Helvetica", + fontsize=10 + ]; + edge [ + color="#555555", + fontname="Helvetica", + fontsize=9, + arrowsize=0.7 + ]; + + subgraph cluster_sim { + label="Simulation / Scenario"; + style="rounded,filled"; + color="#C9E3F8"; + fillcolor="#EDF6FF"; + + CLI[label="CLI\nsimulate(...)"]; + Demo[label="Demo scenario\n(genesis + one tx)"]; + SimHarness[label="Simulation harness"]; + SimNet[label="Simulated P2P\nnetwork"]; + + CLI -> SimHarness; + Demo -> SimHarness; + SimHarness -> SimNet; + } + + subgraph cluster_node { + label="Node (single participant)"; + style="rounded,filled"; + color="#D9D9D9"; + fillcolor="#F7F7F7"; + + subgraph cluster_consensus { + label="Consensus context"; + style="rounded,filled"; + color="#F3E6B3"; + fillcolor="#FFF8E1"; + + Simplex[label="Threshold Simplex\norders digests"]; + Marshal[label="Marshal\nbroadcast + backfill"]; + P2P[label="Transport channels"]; + + P2P -> Simplex [label="votes, certs, resolver"]; + P2P -> Marshal [label="blocks, backfill"]; + } + + subgraph cluster_app { + label="Application context"; + style="rounded,filled"; + color="#BFE6D0"; + fillcolor="#E9FFF4"; + + App[label="RevmApplication\npropose + verify"]; + Ledger[label="LedgerService\nmempool + snapshots + seeds"]; + Exec[label="execute_txs\n(REVM)"]; + SeedRep[label="SeedReporter"]; + FinalRep[label="FinalizedReporter"]; + } + + subgraph cluster_storage { + label="Storage"; + style="rounded,filled"; + color="#F3C4C4"; + fillcolor="#FFF0F0"; + + QMDB[label="QMDB\naccounts / storage / code"]; + } + } + + SimNet -> P2P [label="connect peers"]; + SimHarness -> App [label="start node"]; + + Simplex -> App [label="propose/verify"]; + App -> Simplex [label="block digest"]; + App -> Marshal [label="full blocks"]; + Marshal -> App [label="backfill ancestors"]; + + Simplex -> SeedRep [label="seed signatures"]; + Marshal -> FinalRep [label="finalized blocks"]; + SeedRep -> Ledger [label="store seed hash"]; + FinalRep -> Ledger [label="persist snapshot"]; + + App -> Ledger [label="build txs, snapshots"]; + Ledger -> Exec [label="parent snapshot"]; + Exec -> Ledger [label="state changes"]; + Ledger -> QMDB [label="commit changes / preview root"]; + QMDB -> Ledger [label="read root, balances"]; +} diff --git a/examples/revm/docs/revm_architecture.png b/examples/revm/docs/revm_architecture.png new file mode 100644 index 0000000000..ac0539fba7 Binary files /dev/null and b/examples/revm/docs/revm_architecture.png differ diff --git a/examples/revm/src/application/app.rs b/examples/revm/src/application/app.rs new file mode 100644 index 0000000000..9a05a688a6 --- /dev/null +++ b/examples/revm/src/application/app.rs @@ -0,0 +1,199 @@ +//! Consensus-facing application implementation for the REVM chain example. +//! +//! Threshold-simplex orders only block commitments (digests). Full blocks are disseminated and +//! backfilled by `commonware_consensus::marshal`. The `commonware_consensus::application::marshaled::Marshaled` +//! wrapper bridges these layers by fetching required ancestors from marshal and calling into this +//! module with an `AncestorStream` you can iterate to walk back over pending blocks. +//! +//! The node wiring that wraps this application lives in `examples/revm/src/application/node.rs`. + +use super::{ + execution::{evm_env, execute_txs}, + ledger::{LedgerService, LedgerView}, +}; +use crate::{ + domain::{Block, TxId}, + ConsensusDigest, PublicKey, +}; +use alloy_evm::revm::primitives::B256; +use commonware_consensus::{ + marshal::ingress::mailbox::AncestorStream, + simplex::{scheme::Scheme, types::Context}, + Application, VerifyingApplication, +}; +use commonware_cryptography::{certificate::Scheme as CertScheme, Committable as _}; +use commonware_runtime::{Clock, Metrics, Spawner}; +use futures::StreamExt as _; +use rand::Rng; +use std::{collections::BTreeSet, marker::PhantomData}; + +/// Helper function for propose that owns all its inputs. +async fn propose_inner( + // Ledger service commands for proposal preparation. + state: LedgerService, + max_txs: usize, + mut ancestry: AncestorStream, +) -> Option +where + S: CertScheme, +{ + let parent = ancestry.next().await?; + + // Transactions remain in the mempool until the block that includes them finalizes. Walk + // back over pending ancestors so we do not propose a block that re-includes in-flight txs. + let mut included = BTreeSet::::new(); + for tx in parent.txs.iter() { + included.insert(tx.id()); + } + while let Some(block) = ancestry.next().await { + for tx in block.txs.iter() { + included.insert(tx.id()); + } + } + + let parent_digest = parent.commitment(); + let parent_snapshot = state.parent_snapshot(parent_digest).await?; + let seed_hash = state.seed_for_parent(parent_digest).await; + let prevrandao = seed_hash.unwrap_or_else(|| B256::from(parent_digest.0)); + let height = parent.height + 1; + + let txs = state.build_txs(max_txs, &included).await; + + let env = evm_env(height, prevrandao); + let (db, outcome) = execute_txs(parent_snapshot.db, env, &txs).ok()?; + + let mut child = Block { + parent: parent.id(), + height, + prevrandao, + state_root: parent.state_root, + txs, + }; + child.state_root = state + .preview_root(parent_digest, outcome.qmdb_changes.clone()) + .await + .ok()?; + + let digest = child.commitment(); + state + .insert_snapshot( + digest, + parent_digest, + db, + child.state_root, + outcome.qmdb_changes, + ) + .await; + Some(child) +} + +/// Helper function for verify that owns all its inputs. +async fn verify_inner( + state: LedgerService, + mut ancestry: AncestorStream, +) -> bool +where + S: CertScheme, +{ + let block = match ancestry.next().await { + Some(block) => block, + None => return false, + }; + let parent = match ancestry.next().await { + Some(block) => block, + None => return false, + }; + + let parent_digest = parent.commitment(); + let Some(parent_snapshot) = state.parent_snapshot(parent_digest).await else { + return false; + }; + + let env = evm_env(block.height, block.prevrandao); + let (db, outcome) = match execute_txs(parent_snapshot.db, env, &block.txs) { + Ok(result) => result, + Err(_) => return false, + }; + let state_root = match state + .preview_root(parent_digest, outcome.qmdb_changes.clone()) + .await + { + Ok(root) => root, + Err(_) => return false, + }; + if state_root != block.state_root { + return false; + } + + let digest = block.commitment(); + state + .insert_snapshot(digest, parent_digest, db, state_root, outcome.qmdb_changes) + .await; + true +} + +#[derive(Clone)] +/// Consensus-facing REVM application that bridges marshal and REVM state. +pub(crate) struct RevmApplication { + /// Maximum number of transactions to include when proposing new blocks. + max_txs: usize, + /// Ledger service used to orchestrate ledger commands. + state: LedgerService, + /// Marker tracking the signing scheme used by this application instance. + _scheme: PhantomData, +} + +impl RevmApplication { + /// Create a REVM application with the shared state handle. + pub(crate) fn new(max_txs: usize, state: LedgerView) -> Self { + Self { + max_txs, + state: LedgerService::new(state), + _scheme: PhantomData, + } + } +} + +impl Application for RevmApplication +where + E: Rng + Spawner + Metrics + Clock, + S: Scheme + + commonware_cryptography::certificate::Scheme, +{ + type SigningScheme = S; + type Context = Context; + type Block = Block; + + fn genesis(&mut self) -> impl std::future::Future + Send { + let block = self.state.genesis_block(); + async move { block } + } + + fn propose( + &mut self, + context: (E, Self::Context), + ancestry: AncestorStream, + ) -> impl std::future::Future> + Send { + let state = self.state.clone(); + let max_txs = self.max_txs; + let _ = context; + async move { propose_inner(state, max_txs, ancestry).await } + } +} + +impl VerifyingApplication for RevmApplication +where + E: Rng + Spawner + Metrics + Clock, + S: Scheme + + commonware_cryptography::certificate::Scheme, +{ + fn verify( + &mut self, + context: (E, Self::Context), + ancestry: AncestorStream, + ) -> impl std::future::Future + Send { + let state = self.state.clone(); + let _ = context; + async move { verify_inner(state, ancestry).await } + } +} diff --git a/examples/revm/src/application/execution.rs b/examples/revm/src/application/execution.rs new file mode 100644 index 0000000000..563389b91f --- /dev/null +++ b/examples/revm/src/application/execution.rs @@ -0,0 +1,451 @@ +//! EVM execution for the example chain. +//! +//! This module uses `alloy-evm` as the integration layer above `revm` and keeps the execution +//! backend generic over the `Database + DatabaseCommit` seam. +//! +//! The example also installs a small precompile that returns `block.prevrandao` (EIP-4399), which +//! is sourced from the threshold-simplex seed. + +#[cfg(test)] +mod tests { + use super::{ + evm_env, execute_txs, precompiles_with_seed, seed_precompile_address, tx_env_from_db, + }; + use crate::domain::Tx; + use alloy_evm::{ + revm::{ + database::InMemoryDB, + primitives::{Address, Bytes, B256, U256}, + state::AccountInfo, + Database as _, DatabaseCommit as _, + }, + Evm as _, + }; + + fn addr(byte: u8) -> Address { + Address::from([byte; 20]) + } + + fn fund(db: &mut InMemoryDB, address: Address, balance: U256, nonce: u64) { + db.insert_account_info( + address, + AccountInfo { + balance, + nonce, + ..Default::default() + }, + ); + } + + fn nonce(db: &mut InMemoryDB, address: Address) -> u64 { + db.basic(address) + .unwrap() + .map(|info| info.nonce) + .unwrap_or(0) + } + + #[test] + fn test_execute_single_transfer() { + // Prepare + let sender = addr(0x11); + let recipient = addr(0x22); + let seed = B256::from([7u8; 32]); + let height = 1; + let mut db = InMemoryDB::default(); + fund( + &mut db, + sender, + U256::from(1_000_000u64), + /* nonce */ 0, + ); + let tx = Tx { + from: sender, + to: recipient, + value: U256::from(100), + gas_limit: 21_000, + data: Bytes::new(), + }; + + // Execute + let (mut db, outcome) = execute_txs(db, evm_env(height, seed), &[tx]).unwrap(); + + // Assert (outcome) + assert_eq!(outcome.tx_changes.len(), 1); + assert!(!outcome.tx_changes[0].is_empty()); + + // Assert (state) + let sender_info = db.basic(sender).unwrap().unwrap(); + let recipient_info = db.basic(recipient).unwrap().unwrap(); + assert_eq!(sender_info.balance, U256::from(1_000_000u64 - 100)); + assert_eq!(sender_info.nonce, 1); + assert_eq!(recipient_info.balance, U256::from(100u64)); + assert_eq!(recipient_info.nonce, 0); + } + + #[test] + fn test_seed_precompile_returns_block_prevrandao() { + use alloy_evm::revm::context_interface::result::ExecutionResult; + + // Prepare + let caller = addr(0x11); + let seed = B256::from([7u8; 32]); + let height = 1; + let mut db = InMemoryDB::default(); + fund( + &mut db, + caller, + U256::from(1_000_000u64), + /* nonce */ 0, + ); + + let env = evm_env(height, seed); + let spec = env.cfg_env.spec; + let precompiles = precompiles_with_seed(spec); + let mut evm = alloy_evm::eth::EthEvmBuilder::new(db, env) + .precompiles(precompiles) + .build(); + + let tx = Tx { + from: caller, + to: seed_precompile_address(), + value: U256::ZERO, + gas_limit: 100_000, + data: Bytes::new(), + }; + + // Execute + let chain_id = evm.chain_id(); + let tx_env = tx_env_from_db(evm.db_mut(), &tx, chain_id).unwrap(); + let alloy_evm::revm::context_interface::result::ResultAndState { result, state: _ } = + evm.transact_raw(tx_env).unwrap(); + + // Assert + match result { + ExecutionResult::Success { output, .. } => { + assert_eq!(output.into_data().as_ref(), seed.as_slice()); + } + other => panic!("unexpected execution result: {other:?}"), + } + } + + #[test] + fn test_contract_can_read_seed_precompile() { + use alloy_evm::revm::context_interface::result::{ExecutionResult, Output}; + + // Prepare + let caller = addr(0x11); + let seed = B256::from([9u8; 32]); + let height = 1; + let mut db = InMemoryDB::default(); + fund( + &mut db, + caller, + U256::from(1_000_000u64), + /* nonce */ 0, + ); + + let env = evm_env(height, seed); + let spec = env.cfg_env.spec; + let precompiles = precompiles_with_seed(spec); + let mut evm = alloy_evm::eth::EthEvmBuilder::new(db, env) + .precompiles(precompiles) + .build(); + + let runtime = seed_reader_runtime(); + let init = seed_reader_init(&runtime); + + // Execute (deploy contract) + let create_nonce = nonce(evm.db_mut(), caller); + + let create = alloy_evm::revm::context::TxEnv { + caller, + kind: alloy_evm::revm::primitives::TxKind::Create, + value: U256::ZERO, + gas_limit: 500_000, + data: init, + nonce: create_nonce, + chain_id: Some(evm.chain_id()), + gas_price: 0, + gas_priority_fee: None, + ..Default::default() + }; + + let alloy_evm::revm::context_interface::result::ResultAndState { + result: create_result, + state: create_state, + } = evm.transact_raw(create).unwrap(); + let deployed = match create_result { + ExecutionResult::Success { + output: Output::Create(_, Some(address)), + .. + } => address, + other => panic!("unexpected create result: {other:?}"), + }; + evm.db_mut().commit(create_state); + + // Execute (call deployed contract) + let call_nonce = nonce(evm.db_mut(), caller); + + let call = alloy_evm::revm::context::TxEnv { + caller, + kind: alloy_evm::revm::primitives::TxKind::Call(deployed), + value: U256::ZERO, + gas_limit: 200_000, + data: Bytes::new(), + nonce: call_nonce, + chain_id: Some(evm.chain_id()), + gas_price: 0, + gas_priority_fee: None, + ..Default::default() + }; + + let alloy_evm::revm::context_interface::result::ResultAndState { + result: call_result, + state: _, + } = evm.transact_raw(call).unwrap(); + + // Assert + match call_result { + ExecutionResult::Success { output, .. } => { + assert_eq!(output.into_data().as_ref(), seed.as_slice()); + } + other => panic!("unexpected call result: {other:?}"), + } + } + + fn seed_reader_runtime() -> Bytes { + // Runtime program: + // - STATICCALL seed precompile with no calldata + // - return exactly 32 bytes from memory[0..32) + let address = seed_precompile_address(); + + let mut bytecode = Vec::new(); + bytecode.extend_from_slice(&[0x60, 0x20, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x73]); + bytecode.extend_from_slice(address.as_slice()); + bytecode.extend_from_slice(&[0x61, 0xFF, 0xFF, 0xFA, 0x50, 0x60, 0x20, 0x60, 0x00, 0xF3]); + + Bytes::from(bytecode) + } + + fn seed_reader_init(runtime: &Bytes) -> Bytes { + // Init program: + // - copy runtime to memory[0..len) + // - return memory[0..len) + let runtime_len = runtime.len() as u16; + let mut bytecode = Vec::new(); + bytecode.extend_from_slice(&[0x61]); + bytecode.extend_from_slice(&runtime_len.to_be_bytes()); + bytecode.extend_from_slice(&[0x60, 0x0c, 0x60, 0x00, 0x39, 0x61]); + bytecode.extend_from_slice(&runtime_len.to_be_bytes()); + bytecode.extend_from_slice(&[0x60, 0x00, 0xf3]); + bytecode.extend_from_slice(runtime.as_ref()); + Bytes::from(bytecode) + } +} + +use crate::{ + domain::{AccountChange, StateChanges, Tx}, + qmdb::{AccountUpdate, QmdbChanges}, +}; +use alloy_evm::{ + eth::EthEvmBuilder, + precompiles::{DynPrecompile, PrecompilesMap}, + revm::{ + context::TxEnv, + context_interface::result::ResultAndState, + precompile::{PrecompileId, PrecompileOutput, PrecompileSpecId, Precompiles}, + primitives::{hardfork::SpecId, Address, Bytes, TxKind, B256, KECCAK_EMPTY, U256}, + state::{Account, EvmState}, + DatabaseCommit, + }, + Database as AlloyDatabase, Evm, EvmEnv, +}; +use anyhow::Context as _; +use std::collections::BTreeMap; + +/// Example chain id used by the simulation. +pub const CHAIN_ID: u64 = 1337; +pub const SEED_PRECOMPILE_ADDRESS_BYTES: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, +]; + +/// Address of the example "seed" precompile. +pub fn seed_precompile_address() -> Address { + Address::from(SEED_PRECOMPILE_ADDRESS_BYTES) +} + +/// Build an `EvmEnv` for a given block height and `prevrandao`. +pub fn evm_env(height: u64, prevrandao: B256) -> EvmEnv { + let mut env: EvmEnv = EvmEnv::default(); + env.cfg_env.chain_id = CHAIN_ID; + env.block_env.number = U256::from(height); + env.block_env.timestamp = U256::from(height); + env.block_env.prevrandao = Some(prevrandao); + env +} + +pub(crate) fn precompiles_with_seed(spec: SpecId) -> PrecompilesMap { + let mut precompiles = + PrecompilesMap::from_static(Precompiles::new(PrecompileSpecId::from_spec_id(spec))); + + let address = seed_precompile_address(); + // This precompile is stateful (not pure) because it depends on the current block env. + precompiles.apply_precompile(&address, |_| { + Some(DynPrecompile::new_stateful( + PrecompileId::Custom("commonware_seed".into()), + |input| { + use alloy_evm::revm::context_interface::Block as _; + let seed = input + .internals + .block_env() + .prevrandao() + .unwrap_or(B256::ZERO); + Ok(PrecompileOutput::new( + 0, + Bytes::copy_from_slice(seed.as_slice()), + )) + }, + )) + }); + + precompiles +} + +pub(crate) fn tx_env_from_db(db: &mut DB, tx: &Tx, chain_id: u64) -> anyhow::Result +where + DB: AlloyDatabase, +{ + let nonce = match db.basic(tx.from).context("read sender account")? { + Some(info) => info.nonce, + None => 0, + }; + + Ok(TxEnv { + caller: tx.from, + kind: TxKind::Call(tx.to), + value: tx.value, + gas_limit: tx.gas_limit, + data: tx.data.clone(), + nonce, + chain_id: Some(chain_id), + gas_price: 0, + gas_priority_fee: None, + ..Default::default() + }) +} + +#[derive(Debug, Clone)] +/// Result of executing a batch of transactions. +pub struct ExecutionOutcome { + /// Canonical per-transaction state deltas observed during execution. + pub tx_changes: Vec, + /// Per-account changes used to persist finalized blocks to QMDB. + pub(crate) qmdb_changes: QmdbChanges, +} + +/// Execute a batch of transactions and commit them to the provided DB. +/// +/// Notes: +/// - Uses `transact_raw` so the state diff is available for downstream processing. +/// - Commits the diff into the DB after each transaction. +pub fn execute_txs(db: DB, env: EvmEnv, txs: &[Tx]) -> anyhow::Result<(DB, ExecutionOutcome)> +where + DB: AlloyDatabase + DatabaseCommit, +{ + let spec = env.cfg_env.spec; + let precompiles = precompiles_with_seed(spec); + let mut evm = EthEvmBuilder::new(db, env).precompiles(precompiles).build(); + let chain_id = evm.chain_id(); + + let mut tx_changes = Vec::with_capacity(txs.len()); + let mut qmdb_changes = QmdbChanges::default(); + + for tx in txs { + let tx_env = tx_env_from_db(evm.db_mut(), tx, chain_id).context("build tx env")?; + + let ResultAndState { result: _, state } = evm.transact_raw(tx_env).context("execute tx")?; + + let changes = state_changes_from_evm_state(&state); + apply_evm_state_to_qmdb_changes(&mut qmdb_changes, &state); + evm.db_mut().commit(state); + tx_changes.push(changes); + } + + let (db, _) = evm.finish(); + Ok(( + db, + ExecutionOutcome { + tx_changes, + qmdb_changes, + }, + )) +} + +pub(crate) fn state_changes_from_evm_state(state: &EvmState) -> StateChanges { + let mut changes = StateChanges::default(); + for (address, account) in state.iter() { + if !account.is_touched() { + continue; + } + changes + .accounts + .insert(*address, account_change_from_evm_account(account)); + } + changes +} + +pub(crate) fn apply_evm_state_to_qmdb_changes(changes: &mut QmdbChanges, state: &EvmState) { + for (address, account) in state.iter() { + if !account.is_touched() { + continue; + } + let update = account_update_from_evm_account(account); + changes.apply_update(*address, update); + } +} + +fn account_change_from_evm_account(account: &Account) -> AccountChange { + let mut storage = BTreeMap::new(); + for (slot, slot_value) in account.changed_storage_slots() { + storage.insert(*slot, slot_value.present_value()); + } + + AccountChange { + touched: account.is_touched(), + created: account.is_created(), + selfdestructed: account.is_selfdestructed(), + nonce: account.info.nonce, + balance: account.info.balance, + code_hash: account.info.code_hash, + storage, + } +} + +fn account_update_from_evm_account(account: &Account) -> AccountUpdate { + let mut storage = BTreeMap::new(); + for (slot, slot_value) in account.changed_storage_slots() { + storage.insert(*slot, slot_value.present_value()); + } + + let code = account + .info + .code + .as_ref() + .map(|code| code.original_byte_slice().to_vec()); + let code_hash = if account.info.code_hash == B256::ZERO { + KECCAK_EMPTY + } else { + account.info.code_hash + }; + + AccountUpdate { + created: account.is_created(), + selfdestructed: account.is_selfdestructed(), + nonce: account.info.nonce, + balance: account.info.balance, + code_hash, + code, + storage, + } +} diff --git a/examples/revm/src/application/handle.rs b/examples/revm/src/application/handle.rs new file mode 100644 index 0000000000..d193a5c9b5 --- /dev/null +++ b/examples/revm/src/application/handle.rs @@ -0,0 +1,48 @@ +//! Handle for interacting with the application state. +//! +//! The simulation harness uses this handle to: +//! - submit transactions into the node-local mempool, and +//! - query state at a finalized digest for assertions. + +use super::ledger::LedgerService; +use crate::{ + domain::{LedgerEvent, StateRoot, Tx}, + ConsensusDigest, +}; +use alloy_evm::revm::primitives::{Address, B256, U256}; +use futures::channel::mpsc::UnboundedReceiver; + +#[derive(Clone)] +/// Handle that exposes application queries and submissions to the simulation harness. +pub struct NodeHandle { + /// Ledger service used by the simulation harness. + state: LedgerService, +} + +impl NodeHandle { + pub(crate) const fn new(state: LedgerService) -> Self { + Self { state } + } + + /// Subscribe to the ledger domain event stream. + #[allow(dead_code)] + pub fn subscribe_events(&self) -> UnboundedReceiver { + self.state.subscribe() + } + + pub async fn submit_tx(&self, tx: Tx) -> bool { + self.state.submit_tx(tx).await + } + + pub async fn query_balance(&self, digest: ConsensusDigest, address: Address) -> Option { + self.state.query_balance(digest, address).await + } + + pub async fn query_state_root(&self, digest: ConsensusDigest) -> Option { + self.state.query_state_root(digest).await + } + + pub async fn query_seed(&self, digest: ConsensusDigest) -> Option { + self.state.query_seed(digest).await + } +} diff --git a/examples/revm/src/application/ledger/mempool.rs b/examples/revm/src/application/ledger/mempool.rs new file mode 100644 index 0000000000..bc65663d6f --- /dev/null +++ b/examples/revm/src/application/ledger/mempool.rs @@ -0,0 +1,30 @@ +use crate::domain::{Tx, TxId}; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Default, Clone)] +pub(crate) struct Mempool(BTreeMap); + +impl Mempool { + pub(crate) const fn new() -> Self { + Self(BTreeMap::new()) + } + + pub(crate) fn insert(&mut self, tx: Tx) -> bool { + self.0.insert(tx.id(), tx).is_none() + } + + pub(crate) fn build(&self, max_txs: usize, excluded: &BTreeSet) -> Vec { + self.0 + .iter() + .filter(|(tx_id, _)| !excluded.contains(tx_id)) + .take(max_txs) + .map(|(_, tx)| tx.clone()) + .collect() + } + + pub(crate) fn prune(&mut self, txs: &[Tx]) { + for tx in txs { + self.0.remove(&tx.id()); + } + } +} diff --git a/examples/revm/src/application/ledger/mod.rs b/examples/revm/src/application/ledger/mod.rs new file mode 100644 index 0000000000..9dc5868d1c --- /dev/null +++ b/examples/revm/src/application/ledger/mod.rs @@ -0,0 +1,324 @@ +//! Node-local state for the REVM chain example. +//! +//! Threshold-simplex orders only block digests. Full blocks are verified by the application and +//! disseminated/backfilled by `commonware_consensus::marshal`. This module holds the minimal +//! shared state needed by the example: +//! - a mempool of submitted transactions, +//! - per-block execution snapshots (CacheDB overlay over QMDB) keyed by the consensus digest, and +//! - a per-digest seed hash used to populate the next block's `prevrandao`. +//! +//! The simulation harness queries this state through `crate::application::NodeHandle`. + +mod mempool; +mod seed_cache; +mod snapshot_store; + +use crate::{ + domain::{Block, LedgerEvent, LedgerEvents, StateRoot, Tx, TxId}, + qmdb::{QmdbChanges, QmdbConfig, QmdbLedger, RevmDb}, + ConsensusDigest, +}; +use alloy_evm::revm::{ + primitives::{Address, B256, U256}, + Database as _, +}; +use commonware_cryptography::Committable as _; +use commonware_runtime::{buffer::PoolRef, tokio, Metrics}; +use futures::{channel::mpsc::UnboundedReceiver, lock::Mutex}; +use mempool::Mempool; +use seed_cache::SeedCache; +use snapshot_store::{LedgerSnapshot, SnapshotStore}; +use std::{collections::BTreeSet, sync::Arc}; +#[derive(Clone)] +/// Ledger view that owns the mutexed execution state. +pub(crate) struct LedgerView { + /// Mutex-protected running state. + inner: Arc>, + /// Genesis block stored so the automaton can replay from height 0. + genesis_block: Block, +} + +/// Internal ledger state guarded by the mutex inside `LedgerView`. +pub(crate) struct LedgerState { + /// Pending transactions that are not yet included in finalized blocks. + mempool: Mempool, + /// Execution snapshots indexed by digest so we can replay ancestors. + snapshots: SnapshotStore, + /// Cached seeds for each digest used to compute prevrandao. + seeds: SeedCache, + /// Underlying QMDB ledger service for persistence. + qmdb: QmdbLedger, +} + +/// Minimal mempool helper that avoids duplicating logics across services. +impl LedgerView { + pub(crate) async fn init( + context: tokio::Context, + buffer_pool: PoolRef, + partition_prefix: String, + genesis_alloc: Vec<(Address, U256)>, + ) -> anyhow::Result { + let qmdb = QmdbLedger::init( + context.with_label("qmdb"), + QmdbConfig::new(partition_prefix, buffer_pool), + genesis_alloc, + ) + .await?; + let genesis_root = qmdb.root().await?; + + let genesis_block = Block { + parent: crate::BlockId(B256::ZERO), + height: 0, + prevrandao: B256::ZERO, + state_root: genesis_root, + txs: Vec::new(), + }; + let genesis_digest = genesis_block.commitment(); + let db = RevmDb::new(qmdb.database()?); + + Ok(Self { + inner: Arc::new(Mutex::new(LedgerState { + mempool: Mempool::new(), + snapshots: SnapshotStore::new( + genesis_digest, + LedgerSnapshot { + parent: None, + db, + state_root: genesis_block.state_root, + qmdb_changes: QmdbChanges::default(), + }, + ), + seeds: SeedCache::new(genesis_digest), + qmdb, + })), + genesis_block, + }) + } + + pub(crate) fn genesis_block(&self) -> Block { + self.genesis_block.clone() + } + + pub(crate) async fn submit_tx(&self, tx: Tx) -> bool { + let mut inner = self.inner.lock().await; + inner.mempool.insert(tx) + } + + pub(crate) async fn query_balance( + &self, + digest: ConsensusDigest, + address: Address, + ) -> Option { + let mut inner = self.inner.lock().await; + inner + .snapshots + .get_mut(&digest)? + .db + .basic(address) + .ok() + .flatten() + .map(|info| info.balance) + } + + pub(crate) async fn query_state_root(&self, digest: ConsensusDigest) -> Option { + let inner = self.inner.lock().await; + inner + .snapshots + .get(&digest) + .map(|snapshot| snapshot.state_root) + } + + pub(crate) async fn query_seed(&self, digest: ConsensusDigest) -> Option { + let inner = self.inner.lock().await; + inner.seeds.get(&digest) + } + + pub(crate) async fn seed_for_parent(&self, parent: ConsensusDigest) -> Option { + let inner = self.inner.lock().await; + inner.seeds.get(&parent) + } + + pub(crate) async fn set_seed(&self, digest: ConsensusDigest, seed_hash: B256) { + let mut inner = self.inner.lock().await; + inner.seeds.insert(digest, seed_hash); + } + + pub(crate) async fn parent_snapshot(&self, parent: ConsensusDigest) -> Option { + let inner = self.inner.lock().await; + inner.snapshots.get(&parent).cloned() + } + + pub(crate) async fn insert_snapshot( + &self, + digest: ConsensusDigest, + parent: ConsensusDigest, + db: RevmDb, + root: StateRoot, + qmdb_changes: QmdbChanges, + ) { + let mut inner = self.inner.lock().await; + inner.snapshots.insert( + digest, + LedgerSnapshot { + parent: Some(parent), + db, + state_root: root, + qmdb_changes, + }, + ); + } + + pub(crate) async fn preview_qmdb_root( + &self, + parent: ConsensusDigest, + changes: QmdbChanges, + ) -> anyhow::Result { + // Get the handle and release the lock before awaiting + let (changes, qmdb) = { + let inner = self.inner.lock().await; + let changes = inner.merged_changes_from(parent, changes)?; + (changes, inner.qmdb.clone()) + }; + qmdb.preview_root(changes).await.map_err(Into::into) + } + + pub(crate) async fn persist_snapshot(&self, digest: ConsensusDigest) -> anyhow::Result<()> { + let (changes, qmdb) = { + let inner = self.inner.lock().await; + if inner.snapshots.is_persisted(&digest) { + return Ok(()); + } + let snapshot = inner + .snapshots + .get(&digest) + .ok_or_else(|| anyhow::anyhow!("missing snapshot"))?; + (snapshot.qmdb_changes.clone(), inner.qmdb.clone()) + }; + qmdb.commit_changes(changes).await?; + let mut inner = self.inner.lock().await; + inner.snapshots.mark_persisted(digest); + Ok(()) + } + + pub(crate) async fn prune_mempool(&self, txs: &[Tx]) { + let mut inner = self.inner.lock().await; + inner.mempool.prune(txs); + } + + pub(crate) async fn build_txs(&self, max_txs: usize, excluded: &BTreeSet) -> Vec { + let inner = self.inner.lock().await; + inner.mempool.build(max_txs, excluded) + } +} + +impl LedgerState { + fn merged_changes_from( + &self, + parent: ConsensusDigest, + changes: QmdbChanges, + ) -> anyhow::Result { + self.snapshots.merged_changes_from(parent, changes) + } +} + +#[derive(Clone)] +/// Domain service that exposes high-level ledger commands. +pub(crate) struct LedgerService { + view: LedgerView, + events: LedgerEvents, +} + +impl LedgerService { + pub(crate) fn new(view: LedgerView) -> Self { + Self { + view, + events: LedgerEvents::new(), + } + } + + fn publish(&self, event: LedgerEvent) { + self.events.publish(event); + } + + #[allow(dead_code)] + pub(crate) fn subscribe(&self) -> UnboundedReceiver { + self.events.subscribe() + } + + pub(crate) fn genesis_block(&self) -> Block { + self.view.genesis_block() + } + + pub(crate) async fn submit_tx(&self, tx: Tx) -> bool { + let tx_id = tx.id(); + let inserted = self.view.submit_tx(tx).await; + if inserted { + self.publish(LedgerEvent::TransactionSubmitted(tx_id)); + } + inserted + } + + pub(crate) async fn query_balance( + &self, + digest: ConsensusDigest, + address: Address, + ) -> Option { + self.view.query_balance(digest, address).await + } + + pub(crate) async fn query_state_root(&self, digest: ConsensusDigest) -> Option { + self.view.query_state_root(digest).await + } + + pub(crate) async fn query_seed(&self, digest: ConsensusDigest) -> Option { + self.view.query_seed(digest).await + } + + pub(crate) async fn seed_for_parent(&self, parent: ConsensusDigest) -> Option { + self.view.seed_for_parent(parent).await + } + + pub(crate) async fn set_seed(&self, digest: ConsensusDigest, seed_hash: B256) { + self.view.set_seed(digest, seed_hash).await; + self.publish(LedgerEvent::SeedUpdated(digest, seed_hash)); + } + + pub(crate) async fn parent_snapshot(&self, parent: ConsensusDigest) -> Option { + self.view.parent_snapshot(parent).await + } + + pub(crate) async fn insert_snapshot( + &self, + digest: ConsensusDigest, + parent: ConsensusDigest, + db: RevmDb, + root: StateRoot, + changes: QmdbChanges, + ) { + self.view + .insert_snapshot(digest, parent, db, root, changes) + .await; + } + + pub(crate) async fn preview_root( + &self, + parent: ConsensusDigest, + changes: QmdbChanges, + ) -> anyhow::Result { + self.view.preview_qmdb_root(parent, changes).await + } + + pub(crate) async fn persist_snapshot(&self, digest: ConsensusDigest) -> anyhow::Result<()> { + let result = self.view.persist_snapshot(digest).await; + self.publish(LedgerEvent::SnapshotPersisted(digest)); + result + } + + pub(crate) async fn prune_mempool(&self, txs: &[Tx]) { + self.view.prune_mempool(txs).await; + } + + pub(crate) async fn build_txs(&self, max_txs: usize, excluded: &BTreeSet) -> Vec { + self.view.build_txs(max_txs, excluded).await + } +} diff --git a/examples/revm/src/application/ledger/seed_cache.rs b/examples/revm/src/application/ledger/seed_cache.rs new file mode 100644 index 0000000000..409ccfde13 --- /dev/null +++ b/examples/revm/src/application/ledger/seed_cache.rs @@ -0,0 +1,22 @@ +use crate::ConsensusDigest; +use alloy_evm::revm::primitives::B256; +use std::collections::BTreeMap; + +#[derive(Clone)] +pub(crate) struct SeedCache(BTreeMap); + +impl SeedCache { + pub(crate) fn new(genesis_digest: ConsensusDigest) -> Self { + let mut seeds = BTreeMap::new(); + seeds.insert(genesis_digest, B256::ZERO); + Self(seeds) + } + + pub(crate) fn get(&self, digest: &ConsensusDigest) -> Option { + self.0.get(digest).copied() + } + + pub(crate) fn insert(&mut self, digest: ConsensusDigest, seed: B256) { + self.0.insert(digest, seed); + } +} diff --git a/examples/revm/src/application/ledger/snapshot_store.rs b/examples/revm/src/application/ledger/snapshot_store.rs new file mode 100644 index 0000000000..533bd9e50f --- /dev/null +++ b/examples/revm/src/application/ledger/snapshot_store.rs @@ -0,0 +1,77 @@ +use crate::{ + qmdb::{QmdbChanges, RevmDb}, + ConsensusDigest, StateRoot, +}; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Clone)] +pub(crate) struct LedgerSnapshot { + pub(crate) parent: Option, + pub(crate) db: RevmDb, + pub(crate) state_root: StateRoot, + pub(crate) qmdb_changes: QmdbChanges, +} + +#[derive(Clone)] +pub(crate) struct SnapshotStore { + snapshots: BTreeMap, + persisted: BTreeSet, +} + +impl SnapshotStore { + pub(crate) fn new(genesis_digest: ConsensusDigest, genesis_snapshot: LedgerSnapshot) -> Self { + let mut snapshots = BTreeMap::new(); + snapshots.insert(genesis_digest, genesis_snapshot); + let persisted = BTreeSet::from([genesis_digest]); + Self { + snapshots, + persisted, + } + } + + pub(crate) fn get(&self, digest: &ConsensusDigest) -> Option<&LedgerSnapshot> { + self.snapshots.get(digest) + } + + pub(crate) fn get_mut(&mut self, digest: &ConsensusDigest) -> Option<&mut LedgerSnapshot> { + self.snapshots.get_mut(digest) + } + + pub(crate) fn insert(&mut self, digest: ConsensusDigest, snapshot: LedgerSnapshot) { + self.snapshots.insert(digest, snapshot); + } + + pub(crate) fn mark_persisted(&mut self, digest: ConsensusDigest) { + self.persisted.insert(digest); + } + + pub(crate) fn is_persisted(&self, digest: &ConsensusDigest) -> bool { + self.persisted.contains(digest) + } + + pub(crate) fn merged_changes_from( + &self, + mut parent: ConsensusDigest, + changes: QmdbChanges, + ) -> anyhow::Result { + let mut chain = Vec::new(); + while !self.persisted.contains(&parent) { + let snapshot = self + .snapshots + .get(&parent) + .ok_or_else(|| anyhow::anyhow!("missing snapshot"))?; + let Some(next) = snapshot.parent else { + return Err(anyhow::anyhow!("missing parent snapshot")); + }; + chain.push(snapshot.qmdb_changes.clone()); + parent = next; + } + + let mut merged = QmdbChanges::default(); + for delta in chain.into_iter().rev() { + merged.merge(delta); + } + merged.merge(changes); + Ok(merged) + } +} diff --git a/examples/revm/src/application/mod.rs b/examples/revm/src/application/mod.rs new file mode 100644 index 0000000000..900d9f0a42 --- /dev/null +++ b/examples/revm/src/application/mod.rs @@ -0,0 +1,18 @@ +//! Chain application logic (block production and verification). + +mod app; +pub(crate) mod execution; +mod handle; +mod ledger; +mod node; +mod observers; +mod reporters; + +pub(crate) use app::RevmApplication; +pub use handle::NodeHandle; +pub(crate) use ledger::{LedgerService, LedgerView}; +pub(crate) use node::{ + start_node, threshold_schemes, NodeEnvironment, ThresholdScheme, TransportControl, +}; +pub(crate) use observers::LedgerObservers; +pub(crate) use reporters::{FinalizedReporter, SeedReporter}; diff --git a/examples/revm/src/application/node/channels.rs b/examples/revm/src/application/node/channels.rs new file mode 100644 index 0000000000..26265edbc8 --- /dev/null +++ b/examples/revm/src/application/node/channels.rs @@ -0,0 +1,55 @@ +use super::config::{ + ChannelReceiver, ChannelSender, Peer, CHANNEL_BACKFILL, CHANNEL_BLOCKS, CHANNEL_CERTS, + CHANNEL_RESOLVER, CHANNEL_VOTES, +}; +use anyhow::Context as _; +use commonware_p2p::simulated; +use governor::Quota; + +pub(super) struct NodeChannels { + /// Channel pair used for voting traffic. + pub(super) votes: (ChannelSender, ChannelReceiver), + /// Channel pair used for certificate gossip. + pub(super) certs: (ChannelSender, ChannelReceiver), + /// Channel pair used for resolver/backfill control requests. + pub(super) resolver: (ChannelSender, ChannelReceiver), + /// Channel pair used for full block broadcast. + pub(super) blocks: (ChannelSender, ChannelReceiver), + /// Channel pair used for marshal backfill responses. + pub(super) backfill: (ChannelSender, ChannelReceiver), +} + +/// Register the simulated transport channels for a node. +pub(super) async fn register_channels( + control: &mut simulated::Control, + quota: Quota, +) -> anyhow::Result { + let votes = control + .register(CHANNEL_VOTES, quota) + .await + .context("register votes channel")?; + let certs = control + .register(CHANNEL_CERTS, quota) + .await + .context("register certs channel")?; + let resolver = control + .register(CHANNEL_RESOLVER, quota) + .await + .context("register resolver channel")?; + let blocks = control + .register(CHANNEL_BLOCKS, quota) + .await + .context("register blocks channel")?; + let backfill = control + .register(CHANNEL_BACKFILL, quota) + .await + .context("register backfill channel")?; + + Ok(NodeChannels { + votes, + certs, + resolver, + blocks, + backfill, + }) +} diff --git a/examples/revm/src/application/node/config.rs b/examples/revm/src/application/node/config.rs new file mode 100644 index 0000000000..55cf297221 --- /dev/null +++ b/examples/revm/src/application/node/config.rs @@ -0,0 +1,103 @@ +use crate::{ + domain::{BlockCfg, TxCfg}, + PublicKey, +}; +use anyhow::Context as _; +use commonware_consensus::simplex; +use commonware_cryptography::{ + bls12381::{ + dkg, + primitives::{sharing::Mode, variant::MinSig}, + }, + ed25519, Signer as _, +}; +use commonware_p2p::simulated; +use commonware_runtime::{buffer::PoolRef, tokio}; +use commonware_utils::{ordered::Set, N3f1, NZUsize, TryCollect as _, NZU16, NZU32}; +use governor::Quota; +use rand::{rngs::StdRng, SeedableRng as _}; + +pub(crate) type ThresholdScheme = simplex::scheme::bls12381_threshold::Scheme; + +/// Namespace used by simplex votes in this example. +pub(crate) const SIMPLEX_NAMESPACE: &[u8] = b"_COMMONWARE_REVM_SIMPLEX"; + +/// Mailbox depth for each simulated transport channel. +pub(crate) const MAILBOX_SIZE: usize = 1024; +/// Channel id used for voting traffic. +pub(crate) const CHANNEL_VOTES: u64 = 0; +/// Channel id used for certificate gossip delivery. +pub(crate) const CHANNEL_CERTS: u64 = 1; +/// Channel id used for resolver/backfill requests. +pub(crate) const CHANNEL_RESOLVER: u64 = 2; +/// Channel id used for full block broadcast traffic. +pub(crate) const CHANNEL_BLOCKS: u64 = 3; +// Marshal backfill requests/responses use a resolver protocol and are kept separate from the +// best-effort broadcast channel used for full blocks. +/// Channel id used for marshal backfill replies. +pub(crate) const CHANNEL_BACKFILL: u64 = 4; +/// Maximum transactions per block encoded by the REVM codec. +const BLOCK_CODEC_MAX_TXS: usize = 64; +/// Maximum calldata bytes per transaction admitted by the block codec. +const BLOCK_CODEC_MAX_CALLDATA: usize = 1024; + +pub(crate) type Peer = PublicKey; +pub(crate) type ChannelSender = simulated::Sender; +pub(crate) type ChannelReceiver = simulated::Receiver; + +// This example keeps everything in a single epoch for simplicity. The `Marshaled` wrapper also +// supports epoch boundaries, but exercising that logic is out-of-scope for this demo. +pub(crate) const EPOCH_LENGTH: u64 = u64::MAX; +/// Partition prefix used for node-local storage. +pub(crate) const PARTITION_PREFIX: &str = "revm"; + +/// Default rate limit applied to simulated transport channels. +pub(crate) const fn default_quota() -> Quota { + Quota::per_second(NZU32!(1_000)) +} + +/// Default buffer pool used by node-local storage. +pub(crate) fn default_buffer_pool() -> PoolRef { + PoolRef::new(NZU16!(16_384), NZUsize!(10_000)) +} + +/// Default block codec configuration for REVM transactions. +pub(crate) const fn block_codec_cfg() -> BlockCfg { + BlockCfg { + max_txs: BLOCK_CODEC_MAX_TXS, + tx: TxCfg { + max_calldata_bytes: BLOCK_CODEC_MAX_CALLDATA, + }, + } +} + +/// Derive deterministic participants and threshold-simplex signing schemes. +pub(crate) fn threshold_schemes( + seed: u64, + n: usize, +) -> anyhow::Result<(Vec, Vec)> { + let participants: Set = (0..n) + .map(|i| ed25519::PrivateKey::from_seed(seed.wrapping_add(i as u64)).public_key()) + .try_collect() + .expect("participant public keys are unique"); + + let mut rng = StdRng::seed_from_u64(seed); + let (output, shares) = + dkg::deal::(&mut rng, Mode::default(), participants.clone()) + .context("dkg deal failed")?; + + let mut schemes = Vec::with_capacity(n); + for pk in participants.iter() { + let share = shares.get_value(pk).expect("share exists").clone(); + let scheme = simplex::scheme::bls12381_threshold::Scheme::signer( + SIMPLEX_NAMESPACE, + participants.clone(), + output.public().clone(), + share, + ) + .context("signer should exist")?; + schemes.push(scheme); + } + + Ok((participants.into(), schemes)) +} diff --git a/examples/revm/src/application/node/env.rs b/examples/revm/src/application/node/env.rs new file mode 100644 index 0000000000..d2f756bc5f --- /dev/null +++ b/examples/revm/src/application/node/env.rs @@ -0,0 +1,19 @@ +use super::config::Peer; +use commonware_runtime::tokio; + +/// Transport-specific controls used by node wiring. +pub(crate) trait TransportControl { + type Control: Clone; + type Manager; + + fn control(&self, me: Peer) -> Self::Control; + fn manager(&self) -> Self::Manager; +} + +/// Runtime-specific dependencies that the node wiring relies on. +pub(crate) trait NodeEnvironment { + type Transport: TransportControl; + + fn context(&self) -> tokio::Context; + fn transport(&mut self) -> &mut Self::Transport; +} diff --git a/examples/revm/src/application/node/marshal.rs b/examples/revm/src/application/node/marshal.rs new file mode 100644 index 0000000000..9b52469d71 --- /dev/null +++ b/examples/revm/src/application/node/marshal.rs @@ -0,0 +1,206 @@ +use super::config::{ + ChannelReceiver, ChannelSender, Peer, ThresholdScheme, EPOCH_LENGTH, MAILBOX_SIZE, +}; +use crate::{ + application::FinalizedReporter, + domain::{Block, BlockCfg}, +}; +use anyhow::Context as _; +use commonware_broadcast::buffered; +use commonware_consensus::{ + marshal, + types::{Epoch, FixedEpocher}, +}; +use commonware_p2p::simulated; +use commonware_parallel::Sequential; +use commonware_runtime::{buffer::PoolRef, tokio, Metrics as _}; +use commonware_storage::archive::immutable; +use commonware_utils::{NZUsize, NZU64}; +use std::{sync::Arc, time::Duration}; + +#[derive(Clone)] +struct ConstantSchemeProvider(Arc); + +impl commonware_cryptography::certificate::Provider for ConstantSchemeProvider { + type Scope = Epoch; + type Scheme = ThresholdScheme; + + fn scoped(&self, _epoch: Epoch) -> Option> { + Some(self.0.clone()) + } + + fn all(&self) -> Option> { + Some(self.0.clone()) + } +} + +impl From for ConstantSchemeProvider { + fn from(scheme: ThresholdScheme) -> Self { + Self(Arc::new(scheme)) + } +} + +pub(super) struct MarshalStart { + /// Node index used for naming partitions/metrics. + pub(super) index: usize, + /// Base prefix used for marshal partitions. + pub(super) partition_prefix: String, + /// Node identity key used for network links. + pub(super) public_key: Peer, + /// Control channel used to register and rate-limit transport channels. + pub(super) control: simulated::Control, + /// P2P manager holding the transport/peering state. + pub(super) manager: M, + /// Threshold signing scheme for this node. + pub(super) scheme: ThresholdScheme, + /// Buffer pool that backs all storage archives. + pub(super) buffer_pool: PoolRef, + /// Codec settings for block serialization. + pub(super) block_codec_config: BlockCfg, + /// Channels used for block broadcasts. + pub(super) blocks: (ChannelSender, ChannelReceiver), + /// Channels used for marshal backfill requests/responses. + pub(super) backfill: (ChannelSender, ChannelReceiver), + /// Application-level reporter that observes finalized blocks. + pub(super) application: FinalizedReporter, +} + +/// Wire up the marshal actor for block dissemination/backfill and finalized reporting. +pub(super) async fn start_marshal( + context: &tokio::Context, + start: MarshalStart, +) -> anyhow::Result> +where + M: commonware_p2p::Manager, +{ + let MarshalStart { + index, + partition_prefix, + public_key, + control, + manager, + scheme, + buffer_pool, + block_codec_config, + blocks, + backfill, + application, + } = start; + + // Marshal wires together: + // - a best-effort broadcast for blocks, + // - a request/response resolver for ancestor backfill, and + // - local archives for finalized blocks and certificates. + let ctx = context.with_label(&format!("marshal_{index}")); + let partition_prefix = format!("{partition_prefix}-marshal-{index}"); + let scheme_provider = ConstantSchemeProvider::from(scheme.clone()); + + let resolver_cfg = marshal::resolver::p2p::Config { + public_key: public_key.clone(), + manager, + blocker: control.clone(), + mailbox_size: MAILBOX_SIZE, + initial: Duration::from_millis(200), + timeout: Duration::from_millis(200), + fetch_retry_timeout: Duration::from_millis(100), + priority_requests: false, + priority_responses: false, + }; + let resolver = marshal::resolver::p2p::init(&ctx, resolver_cfg, backfill); + + let broadcast_cfg = buffered::Config { + public_key: public_key.clone(), + mailbox_size: MAILBOX_SIZE, + deque_size: 10, + priority: false, + codec_config: block_codec_config, + }; + let (broadcast_engine, buffer) = + buffered::Engine::<_, Peer, Block>::new(ctx.with_label("broadcast"), broadcast_cfg); + broadcast_engine.start(blocks); + + let finalizations_by_height = immutable::Archive::init( + ctx.with_label("finalizations_by_height"), + immutable::Config { + metadata_partition: format!( + "{partition_prefix}-finalizations-by-height-metadata" + ), + freezer_table_partition: format!( + "{partition_prefix}-finalizations-by-height-freezer-table" + ), + freezer_table_initial_size: 64, + freezer_table_resize_frequency: 10, + freezer_table_resize_chunk_size: 10, + freezer_key_partition: format!( + "{partition_prefix}-finalizations-by-height-freezer-key" + ), + freezer_key_buffer_pool: buffer_pool.clone(), + freezer_value_partition: format!( + "{partition_prefix}-finalizations-by-height-freezer-value" + ), + freezer_value_target_size: 1024, + freezer_value_compression: None, + ordinal_partition: format!( + "{partition_prefix}-finalizations-by-height-ordinal" + ), + items_per_section: NZU64!(10), + freezer_key_write_buffer: NZUsize!(1024 * 1024), + freezer_value_write_buffer: NZUsize!(1024 * 1024), + ordinal_write_buffer: NZUsize!(1024 * 1024), + replay_buffer: NZUsize!(1024 * 1024), + codec_config: ::certificate_codec_config_unbounded(), + }, + ) + .await + .context("init finalizations archive")?; + + let finalized_blocks = immutable::Archive::init( + ctx.with_label("finalized_blocks"), + immutable::Config { + metadata_partition: format!("{partition_prefix}-finalized-blocks-metadata"), + freezer_table_partition: format!("{partition_prefix}-finalized-blocks-freezer-table"), + freezer_table_initial_size: 64, + freezer_table_resize_frequency: 10, + freezer_table_resize_chunk_size: 10, + freezer_key_partition: format!("{partition_prefix}-finalized-blocks-freezer-key"), + freezer_key_buffer_pool: buffer_pool.clone(), + freezer_value_partition: format!("{partition_prefix}-finalized-blocks-freezer-value"), + freezer_value_target_size: 1024, + freezer_value_compression: None, + ordinal_partition: format!("{partition_prefix}-finalized-blocks-ordinal"), + items_per_section: NZU64!(10), + freezer_key_write_buffer: NZUsize!(1024 * 1024), + freezer_value_write_buffer: NZUsize!(1024 * 1024), + ordinal_write_buffer: NZUsize!(1024 * 1024), + replay_buffer: NZUsize!(1024 * 1024), + codec_config: block_codec_config, + }, + ) + .await + .context("init blocks archive")?; + + let epocher = FixedEpocher::new(NZU64!(EPOCH_LENGTH)); + let (actor, mailbox, _last_processed_height) = marshal::Actor::init( + ctx.clone(), + finalizations_by_height, + finalized_blocks, + marshal::Config { + provider: scheme_provider, + epocher, + partition_prefix, + mailbox_size: MAILBOX_SIZE, + view_retention_timeout: commonware_consensus::types::ViewDelta::new(10), + prunable_items_per_section: NZU64!(10), + buffer_pool, + replay_buffer: NZUsize!(1024 * 1024), + key_write_buffer: NZUsize!(1024 * 1024), + value_write_buffer: NZUsize!(1024 * 1024), + block_codec_config, + max_repair: NZUsize!(16), + strategy: Sequential, + }, + ) + .await; + actor.start(application, buffer, resolver); + Ok(mailbox) +} diff --git a/examples/revm/src/application/node/mod.rs b/examples/revm/src/application/node/mod.rs new file mode 100644 index 0000000000..62d503043c --- /dev/null +++ b/examples/revm/src/application/node/mod.rs @@ -0,0 +1,15 @@ +//! Node wiring for the tokio runtime simulation. +//! +//! Each node runs: +//! - a marshal instance (block dissemination, backfill, and finalized block delivery), and +//! - a threshold-simplex engine instance that orders opaque digests. + +mod channels; +mod config; +mod env; +mod marshal; +mod start; + +pub(crate) use config::{threshold_schemes, ThresholdScheme}; +pub(crate) use env::{NodeEnvironment, TransportControl}; +pub(crate) use start::start_node; diff --git a/examples/revm/src/application/node/start.rs b/examples/revm/src/application/node/start.rs new file mode 100644 index 0000000000..1fa3839723 --- /dev/null +++ b/examples/revm/src/application/node/start.rs @@ -0,0 +1,162 @@ +use super::{ + channels::{register_channels, NodeChannels}, + config::{ + block_codec_cfg, default_buffer_pool, default_quota, Peer, ThresholdScheme, EPOCH_LENGTH, + MAILBOX_SIZE, PARTITION_PREFIX, + }, + env::{NodeEnvironment, TransportControl}, + marshal::{start_marshal, MarshalStart}, +}; +use crate::{ + application::{ + FinalizedReporter, LedgerObservers, LedgerService, LedgerView, NodeHandle, RevmApplication, + SeedReporter, + }, + domain::LedgerEvent, + BootstrapConfig, FinalizationEvent, +}; +use anyhow::Context as _; +use commonware_consensus::{ + application::marshaled::Marshaled, + simplex::{self, elector::Random}, + types::{Epoch, FixedEpocher, ViewDelta}, + Reporters, +}; +use commonware_cryptography::bls12381::primitives::variant::MinSig; +use commonware_p2p::simulated; +use commonware_parallel::Sequential; +use commonware_runtime::{tokio, Metrics as _, Spawner as _}; +use commonware_utils::{NZUsize, NZU64}; +use futures::{channel::mpsc, StreamExt as _}; +use std::time::Duration; + +/// Initialize and run a single node (QMDB/state + marshal + simplex engine). +pub(crate) async fn start_node( + env: &mut E, + index: usize, + public_key: Peer, + scheme: ThresholdScheme, + finalized_tx: mpsc::UnboundedSender, + bootstrap: &BootstrapConfig, +) -> anyhow::Result +where + E: NodeEnvironment, + E::Transport: TransportControl< + Control = simulated::Control, + Manager = simulated::Manager, + >, +{ + let context = env.context(); + let quota = default_quota(); + let buffer_pool = default_buffer_pool(); + let partition_prefix = PARTITION_PREFIX; + + let (mut control, manager) = { + let transport = env.transport(); + (transport.control(public_key.clone()), transport.manager()) + }; + let blocker = control.clone(); + + let NodeChannels { + votes, + certs, + resolver, + blocks, + backfill, + } = register_channels(&mut control, quota).await?; + + let block_cfg = block_codec_cfg(); + let state = LedgerView::init( + context.with_label(&format!("state_{index}")), + buffer_pool.clone(), + format!("{partition_prefix}-qmdb-{index}"), + bootstrap.genesis_alloc.clone(), + ) + .await + .context("init qmdb")?; + + let ledger = LedgerService::new(state.clone()); + LedgerObservers::spawn(ledger.clone(), context.clone()); + let mut domain_events = ledger.subscribe(); + let finalized_tx_clone = finalized_tx.clone(); + let node_id = index as u32; + let event_context = context.clone(); + event_context.spawn(move |_| async move { + while let Some(event) = domain_events.next().await { + if let LedgerEvent::SnapshotPersisted(digest) = event { + let _ = finalized_tx_clone.unbounded_send((node_id, digest)); + } + } + }); + let handle = NodeHandle::new(ledger.clone()); + let app = RevmApplication::::new(block_cfg.max_txs, state.clone()); + + let finalized_reporter = FinalizedReporter::new(ledger.clone()); + + let marshal_mailbox = start_marshal( + &context, + MarshalStart { + index, + partition_prefix: partition_prefix.to_string(), + public_key: public_key.clone(), + control: control.clone(), + manager, + scheme: scheme.clone(), + buffer_pool: buffer_pool.clone(), + block_codec_config: block_cfg, + blocks, + backfill, + application: finalized_reporter, + }, + ) + .await?; + + // Adapt the application to simplex by delegating full-block dissemination/backfill to marshal. + let epocher = FixedEpocher::new(NZU64!(EPOCH_LENGTH)); + let marshaled = Marshaled::new( + context.with_label(&format!("marshaled_{index}")), + app, + marshal_mailbox.clone(), + epocher, + ); + + let seed_reporter = SeedReporter::::new(ledger.clone()); + // Feed both the application-specific reporter (seed hashing) and marshal itself with simplex + // activity (notarizations/finalizations). + let reporter = Reporters::from((seed_reporter, marshal_mailbox.clone())); + + // Submit bootstrap transactions before starting consensus so the first leader can + // include them without relying on a hardcoded "height == 1" rule. + for tx in &bootstrap.bootstrap_txs { + let _ = handle.submit_tx(tx.clone()).await; + } + + let engine = simplex::Engine::new( + context.with_label(&format!("engine_{index}")), + simplex::Config { + scheme, + elector: Random, + blocker, + automaton: marshaled.clone(), + relay: marshaled, + reporter, + strategy: Sequential, + partition: format!("{partition_prefix}-{index}"), + mailbox_size: MAILBOX_SIZE, + epoch: Epoch::zero(), + replay_buffer: NZUsize!(1024 * 1024), + write_buffer: NZUsize!(1024 * 1024), + leader_timeout: Duration::from_secs(1), + notarization_timeout: Duration::from_secs(2), + nullify_retry: Duration::from_secs(5), + fetch_timeout: Duration::from_secs(1), + activity_timeout: ViewDelta::new(20), + skip_timeout: ViewDelta::new(10), + fetch_concurrent: 8, + buffer_pool, + }, + ); + engine.start(votes, certs, resolver); + + Ok(handle) +} diff --git a/examples/revm/src/application/observers.rs b/examples/revm/src/application/observers.rs new file mode 100644 index 0000000000..3bbcbce290 --- /dev/null +++ b/examples/revm/src/application/observers.rs @@ -0,0 +1,32 @@ +use super::ledger::LedgerService; +use crate::domain::LedgerEvent; +use commonware_runtime::Spawner; +use futures::StreamExt; +use tracing::{debug, trace}; + +/// Observers that react to ledger domain events without mutating aggregates. +pub(crate) struct LedgerObservers; + +impl LedgerObservers { + pub(crate) fn spawn(service: LedgerService, spawner: S) + where + S: Spawner, + { + let mut receiver = service.subscribe(); + spawner.shared(true).spawn(move |_| async move { + while let Some(event) = receiver.next().await { + match event { + LedgerEvent::TransactionSubmitted(id) => { + trace!(tx=?id, "mempool accepted transaction"); + } + LedgerEvent::SeedUpdated(digest, seed) => { + debug!(digest=?digest, seed=?seed, "seed cache refreshed"); + } + LedgerEvent::SnapshotPersisted(digest) => { + trace!(?digest, "snapshot persisted"); + } + } + } + }); + } +} diff --git a/examples/revm/src/application/reporters/finalized.rs b/examples/revm/src/application/reporters/finalized.rs new file mode 100644 index 0000000000..c5b325755a --- /dev/null +++ b/examples/revm/src/application/reporters/finalized.rs @@ -0,0 +1,72 @@ +use super::super::{ + execution::{evm_env, execute_txs}, + ledger::LedgerService, +}; +use crate::domain::Block; +use commonware_consensus::{marshal::Update, Block as _, Reporter}; +use commonware_cryptography::Committable as _; +use commonware_utils::acknowledgement::Acknowledgement as _; + +/// Helper function for `FinalizedReporter::report` that owns all its inputs. +async fn finalized_report_inner(state: LedgerService, update: Update) { + match update { + Update::Tip(_, _) => {} + Update::Block(block, ack) => { + let mut block = block; + let digest = block.commitment(); + if state.query_state_root(digest).await.is_none() { + let parent_digest = block.parent(); + let parent_snapshot = state + .parent_snapshot(parent_digest) + .await + .expect("missing parent snapshot"); + let env = evm_env(block.height, block.prevrandao); + let (next_block, db, outcome) = + execute_txs(parent_snapshot.db, env, &block.txs) + .map(|(db, outcome)| (block, db, outcome)) + .expect("execute finalized block"); + block = next_block; + let state_root = state + .preview_root(parent_digest, outcome.qmdb_changes.clone()) + .await + .expect("preview qmdb root"); + assert_eq!(state_root, block.state_root, "state root mismatch"); + state + .insert_snapshot(digest, parent_digest, db, state_root, outcome.qmdb_changes) + .await; + } + state + .persist_snapshot(digest) + .await + .expect("persist finalized block"); + state.prune_mempool(&block.txs).await; + // Marshal waits for the application to acknowledge processing before advancing the + // delivery floor. Without this, the node can stall on finalized block delivery. + ack.acknowledge(); + } + } +} + +#[derive(Clone)] +/// Persists finalized blocks. +pub(crate) struct FinalizedReporter { + /// Ledger service used to verify blocks and persist snapshots. + state: LedgerService, +} + +impl FinalizedReporter { + pub(crate) const fn new(state: LedgerService) -> Self { + Self { state } + } +} + +impl Reporter for FinalizedReporter { + type Activity = Update; + + fn report(&mut self, update: Self::Activity) -> impl std::future::Future + Send { + let state = self.state.clone(); + async move { + finalized_report_inner(state, update).await; + } + } +} diff --git a/examples/revm/src/application/reporters/mod.rs b/examples/revm/src/application/reporters/mod.rs new file mode 100644 index 0000000000..41fd825ae5 --- /dev/null +++ b/examples/revm/src/application/reporters/mod.rs @@ -0,0 +1,11 @@ +//! Reporters used by the REVM chain example. +//! +//! The example maintains two reporter contexts: +//! - `seed`: watches simplex activity and caches `prevrandao` seeds via `LedgerEvent::SeedUpdated`. +//! - `finalized`: replays finalized blocks and persists their snapshots through `LedgerService`. + +mod finalized; +mod seed; + +pub(crate) use finalized::FinalizedReporter; +pub(crate) use seed::SeedReporter; diff --git a/examples/revm/src/application/reporters/seed.rs b/examples/revm/src/application/reporters/seed.rs new file mode 100644 index 0000000000..4f598d6461 --- /dev/null +++ b/examples/revm/src/application/reporters/seed.rs @@ -0,0 +1,74 @@ +use super::super::ledger::LedgerService; +use crate::ConsensusDigest; +use alloy_evm::revm::primitives::{keccak256, B256}; +use commonware_consensus::{ + simplex::{ + scheme::{bls12381_threshold, bls12381_threshold::Seedable as _}, + types::Activity, + }, + Reporter, +}; +use commonware_cryptography::bls12381::primitives::variant::Variant; +use std::marker::PhantomData; + +/// Helper function for SeedReporter::report that owns all its inputs. +async fn seed_report_inner( + state: LedgerService, + activity: Activity, ConsensusDigest>, +) { + match activity { + Activity::Notarization(notarization) => { + state + .set_seed( + notarization.proposal.payload, + SeedReporter::::hash_seed(notarization.seed()), + ) + .await; + } + Activity::Finalization(finalization) => { + state + .set_seed( + finalization.proposal.payload, + SeedReporter::::hash_seed(finalization.seed()), + ) + .await; + } + _ => {} + } +} + +#[derive(Clone)] +/// Tracks simplex activity to store seed hashes for future proposals. +pub(crate) struct SeedReporter { + /// Ledger service that keeps per-digest seeds and snapshots. + state: LedgerService, + /// Marker indicating the variant for the threshold scheme in use. + _variant: PhantomData, +} + +impl SeedReporter { + pub(crate) const fn new(state: LedgerService) -> Self { + Self { + state, + _variant: PhantomData, + } + } + + fn hash_seed(seed: impl commonware_codec::Encode) -> B256 { + keccak256(seed.encode()) + } +} + +impl Reporter for SeedReporter +where + V: Variant, +{ + type Activity = Activity, ConsensusDigest>; + + fn report(&mut self, activity: Self::Activity) -> impl std::future::Future + Send { + let state = self.state.clone(); + async move { + seed_report_inner(state, activity).await; + } + } +} diff --git a/examples/revm/src/domain/commitment.rs b/examples/revm/src/domain/commitment.rs new file mode 100644 index 0000000000..16b9338da2 --- /dev/null +++ b/examples/revm/src/domain/commitment.rs @@ -0,0 +1,238 @@ +//! Deterministic state-change helpers for the example chain. +//! +//! `StateChanges` uses `BTreeMap` so the encoded form is canonical and deterministic. + +use alloy_evm::revm::primitives::{Address, B256, U256}; +use bytes::{Buf, BufMut}; +use commonware_codec::{EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write}; +use std::collections::BTreeMap; + +#[derive(Clone, Copy, Debug)] +/// Limits used when decoding deterministic state changes. +pub struct StateChangesCfg { + /// Maximum number of touched accounts allowed in a delta. + pub max_accounts: usize, + /// Maximum number of storage slots that can be decoded per account. + pub max_storage_slots: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Canonical representation of a touched account's post-transaction state. +pub struct AccountChange { + pub touched: bool, + pub created: bool, + pub selfdestructed: bool, + pub nonce: u64, + pub balance: U256, + pub code_hash: B256, + pub storage: BTreeMap, +} + +#[derive(Clone, Debug, PartialEq, Eq, Default)] +/// Canonical per-transaction state delta. +pub struct StateChanges { + pub accounts: BTreeMap, +} + +impl StateChanges { + pub fn is_empty(&self) -> bool { + self.accounts.is_empty() + } +} + +impl Write for AccountChange { + fn write(&self, buf: &mut impl BufMut) { + self.touched.write(buf); + self.created.write(buf); + self.selfdestructed.write(buf); + self.nonce.write(buf); + write_u256(&self.balance, buf); + write_b256(&self.code_hash, buf); + + self.storage.len().write(buf); + for (slot, value) in self.storage.iter() { + write_u256(slot, buf); + write_u256(value, buf); + } + } +} + +impl EncodeSize for AccountChange { + fn encode_size(&self) -> usize { + self.touched.encode_size() + + self.created.encode_size() + + self.selfdestructed.encode_size() + + self.nonce.encode_size() + + 32 + + 32 + + self.storage.len().encode_size() + + self.storage.len() * (32 + 32) + } +} + +impl Read for AccountChange { + type Cfg = StateChangesCfg; + + fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result { + let touched = bool::read(buf)?; + let created = bool::read(buf)?; + let selfdestructed = bool::read(buf)?; + let nonce = u64::read(buf)?; + let balance = read_u256(buf)?; + let code_hash = read_b256(buf)?; + + let slots = usize::read_cfg(buf, &RangeCfg::new(0..=cfg.max_storage_slots))?; + let mut storage = BTreeMap::new(); + for _ in 0..slots { + let slot = read_u256(buf)?; + let value = read_u256(buf)?; + storage.insert(slot, value); + } + + Ok(Self { + touched, + created, + selfdestructed, + nonce, + balance, + code_hash, + storage, + }) + } +} + +impl Write for StateChanges { + fn write(&self, buf: &mut impl BufMut) { + self.accounts.len().write(buf); + for (address, change) in self.accounts.iter() { + write_address(address, buf); + change.write(buf); + } + } +} + +impl EncodeSize for StateChanges { + fn encode_size(&self) -> usize { + self.accounts.len().encode_size() + + self + .accounts + .values() + .map(|change| 20 + change.encode_size()) + .sum::() + } +} + +impl Read for StateChanges { + type Cfg = StateChangesCfg; + + fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result { + let accounts = usize::read_cfg(buf, &RangeCfg::new(0..=cfg.max_accounts))?; + let mut map = BTreeMap::new(); + for _ in 0..accounts { + let address = read_address(buf)?; + let change = AccountChange::read_cfg(buf, cfg)?; + map.insert(address, change); + } + Ok(Self { accounts: map }) + } +} + +fn write_address(value: &Address, buf: &mut impl BufMut) { + buf.put_slice(value.as_slice()); +} + +fn read_address(buf: &mut impl Buf) -> Result { + if buf.remaining() < 20 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 20]; + buf.copy_to_slice(&mut out); + Ok(Address::from(out)) +} + +fn write_b256(value: &B256, buf: &mut impl BufMut) { + buf.put_slice(value.as_slice()); +} + +fn read_b256(buf: &mut impl Buf) -> Result { + if buf.remaining() < 32 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 32]; + buf.copy_to_slice(&mut out); + Ok(B256::from(out)) +} + +fn write_u256(value: &U256, buf: &mut impl BufMut) { + buf.put_slice(&value.to_be_bytes::<32>()); +} + +fn read_u256(buf: &mut impl Buf) -> Result { + if buf.remaining() < 32 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 32]; + buf.copy_to_slice(&mut out); + Ok(U256::from_be_bytes(out)) +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_evm::revm::primitives::{Address, B256, U256}; + use commonware_codec::Encode as _; + use commonware_codec::Decode as _; + use std::collections::BTreeMap; + + fn cfg() -> StateChangesCfg { + StateChangesCfg { + max_accounts: 16, + max_storage_slots: 64, + } + } + + fn sample_changes_order_a() -> StateChanges { + let mut changes = StateChanges::default(); + + let mut storage1 = BTreeMap::new(); + storage1.insert(U256::from(2u64), U256::from(200u64)); + storage1.insert(U256::from(1u64), U256::from(100u64)); + changes.accounts.insert( + Address::from([0x11u8; 20]), + AccountChange { + touched: true, + created: false, + selfdestructed: false, + nonce: 7, + balance: U256::from(1234u64), + code_hash: B256::from([0xAAu8; 32]), + storage: storage1, + }, + ); + + let mut storage2 = BTreeMap::new(); + storage2.insert(U256::from(5u64), U256::from(42u64)); + changes.accounts.insert( + Address::from([0x22u8; 20]), + AccountChange { + touched: true, + created: true, + selfdestructed: false, + nonce: 1, + balance: U256::from(999u64), + code_hash: B256::from([0xBBu8; 32]), + storage: storage2, + }, + ); + + changes + } + + #[test] + fn test_state_changes_roundtrip() { + let changes = sample_changes_order_a(); + let encoded = changes.encode(); + let decoded = StateChanges::decode_cfg(encoded, &cfg()).expect("decode changes"); + assert_eq!(changes, decoded); + } +} diff --git a/examples/revm/src/domain/events.rs b/examples/revm/src/domain/events.rs new file mode 100644 index 0000000000..b903bfa4b3 --- /dev/null +++ b/examples/revm/src/domain/events.rs @@ -0,0 +1,42 @@ +//! Domain events for the REVM example. + +use super::TxId; +use crate::ConsensusDigest; +use alloy_evm::revm::primitives::B256; +use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender}; +use std::sync::{Arc, Mutex as StdMutex}; + +/// Ledger-related domain events. +#[derive(Clone, Debug)] +pub(crate) enum LedgerEvent { + #[allow(dead_code)] + TransactionSubmitted(TxId), + #[allow(dead_code)] + SnapshotPersisted(ConsensusDigest), + #[allow(dead_code)] + SeedUpdated(ConsensusDigest, B256), +} + +#[derive(Clone)] +pub(crate) struct LedgerEvents { + listeners: Arc>>>, +} + +impl LedgerEvents { + pub(crate) fn new() -> Self { + Self { + listeners: Arc::new(StdMutex::new(Vec::new())), + } + } + + pub(crate) fn publish(&self, event: LedgerEvent) { + let mut guard = self.listeners.lock().unwrap(); + guard.retain(|sender| sender.unbounded_send(event.clone()).is_ok()); + } + + pub(crate) fn subscribe(&self) -> UnboundedReceiver { + let (sender, receiver) = unbounded(); + self.listeners.lock().unwrap().push(sender); + receiver + } +} diff --git a/examples/revm/src/domain/mod.rs b/examples/revm/src/domain/mod.rs new file mode 100644 index 0000000000..46ee5d6936 --- /dev/null +++ b/examples/revm/src/domain/mod.rs @@ -0,0 +1,9 @@ +//! Domain model types and deterministic state-change helpers for the REVM example. + +mod commitment; +mod events; +mod types; + +pub use commitment::{AccountChange, StateChanges, StateChangesCfg}; +pub(crate) use events::{LedgerEvent, LedgerEvents}; +pub use types::{block_id, Block, BlockCfg, BlockId, BootstrapConfig, StateRoot, Tx, TxCfg, TxId}; diff --git a/examples/revm/src/domain/types.rs b/examples/revm/src/domain/types.rs new file mode 100644 index 0000000000..125f0a461e --- /dev/null +++ b/examples/revm/src/domain/types.rs @@ -0,0 +1,56 @@ +//! Canonical types and encodings for the example chain. +//! +//! The example uses `commonware-codec` for deterministic, bounded decoding of untrusted bytes. +//! +//! - `BlockId` is `keccak256(Encode(Block))`. +//! - Consensus orders `ConsensusDigest = sha256(BlockId)` (the block's `Committable`). +//! - `StateRoot` is a 32-byte commitment over QMDB partition roots. + +use alloy_evm::revm::primitives::{Address, U256}; + +mod block; +mod ids; +mod tx; + +#[cfg(test)] +mod tests; + +pub use block::{block_id, Block, BlockCfg}; +pub use ids::{BlockId, StateRoot, TxId}; +pub use tx::{Tx, TxCfg}; + +#[derive(Clone, Debug)] +/// Genesis allocation plus bootstrap transactions applied before consensus starts. +/// +/// # Examples +/// ```no_run +/// use alloy_evm::revm::primitives::{Address, Bytes, U256}; +/// use commonware_revm::{BootstrapConfig, Tx}; +/// +/// let from = Address::from([0x11u8; 20]); +/// let to = Address::from([0x22u8; 20]); +/// let tx = Tx { +/// from, +/// to, +/// value: U256::from(100u64), +/// gas_limit: 21_000, +/// data: Bytes::new(), +/// }; +/// let bootstrap = BootstrapConfig::new(vec![(from, U256::from(1_000_000u64))], vec![tx]); +/// # let _ = bootstrap; +/// ``` +pub struct BootstrapConfig { + /// Genesis allocation applied before consensus starts. + pub genesis_alloc: Vec<(Address, U256)>, + /// Transactions to submit before consensus starts. + pub bootstrap_txs: Vec, +} + +impl BootstrapConfig { + pub const fn new(genesis_alloc: Vec<(Address, U256)>, bootstrap_txs: Vec) -> Self { + Self { + genesis_alloc, + bootstrap_txs, + } + } +} diff --git a/examples/revm/src/domain/types/block.rs b/examples/revm/src/domain/types/block.rs new file mode 100644 index 0000000000..d71135c0e8 --- /dev/null +++ b/examples/revm/src/domain/types/block.rs @@ -0,0 +1,115 @@ +use super::{ + ids::{read_b256, write_b256, BlockId, StateRoot}, + tx::{Tx, TxCfg}, +}; +use alloy_evm::revm::primitives::{keccak256, B256}; +use bytes::{Buf, BufMut}; +use commonware_codec::{Encode, EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write}; +use commonware_cryptography::{Committable, Digestible, Hasher as _, Sha256}; + +#[derive(Clone, Copy, Debug)] +/// Configuration used when decoding blocks and their transactions. +pub struct BlockCfg { + /// Maximum number of transactions that can be encoded in a block. + pub max_txs: usize, + /// Per-transaction codec configuration. + pub tx: TxCfg, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Example block type agreed on by consensus (via its digest). +pub struct Block { + /// Identifier of the parent block. + pub parent: BlockId, + /// Block height (number of committed ancestors). + pub height: u64, + /// Seed-derived randomness used for future prevrandao. + pub prevrandao: B256, + /// State commitment resulting from this block. + pub state_root: StateRoot, + /// Transactions included in the block. + pub txs: Vec, +} + +impl Block { + pub fn id(&self) -> BlockId { + block_id(self) + } +} + +pub fn block_id(block: &Block) -> BlockId { + BlockId(keccak256(block.encode())) +} + +fn digest_for_block_id(id: &BlockId) -> crate::ConsensusDigest { + let mut hasher = Sha256::default(); + hasher.update(id.0.as_slice()); + hasher.finalize() +} + +impl Digestible for Block { + type Digest = crate::ConsensusDigest; + + fn digest(&self) -> Self::Digest { + digest_for_block_id(&self.id()) + } +} + +impl Committable for Block { + type Commitment = crate::ConsensusDigest; + + fn commitment(&self) -> Self::Commitment { + digest_for_block_id(&self.id()) + } +} + +impl commonware_consensus::Heightable for Block { + fn height(&self) -> commonware_consensus::types::Height { + commonware_consensus::types::Height::new(self.height) + } +} + +impl commonware_consensus::Block for Block { + fn parent(&self) -> Self::Commitment { + digest_for_block_id(&self.parent) + } +} + +impl Write for Block { + fn write(&self, buf: &mut impl BufMut) { + self.parent.write(buf); + self.height.write(buf); + write_b256(&self.prevrandao, buf); + self.state_root.write(buf); + self.txs.write(buf); + } +} + +impl EncodeSize for Block { + fn encode_size(&self) -> usize { + self.parent.encode_size() + + self.height.encode_size() + + 32 + + self.state_root.encode_size() + + self.txs.encode_size() + } +} + +impl Read for Block { + type Cfg = BlockCfg; + + fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result { + let parent = BlockId::read(buf)?; + let height = u64::read(buf)?; + let prevrandao = read_b256(buf)?; + let state_root = StateRoot::read(buf)?; + let txs = Vec::::read_cfg(buf, &(RangeCfg::new(0..=cfg.max_txs), cfg.tx))?; + Ok(Self { + parent, + height, + prevrandao, + state_root, + txs, + }) + } +} diff --git a/examples/revm/src/domain/types/ids.rs b/examples/revm/src/domain/types/ids.rs new file mode 100644 index 0000000000..5aebcb5afd --- /dev/null +++ b/examples/revm/src/domain/types/ids.rs @@ -0,0 +1,82 @@ +use alloy_evm::revm::primitives::B256; +use bytes::{Buf, BufMut}; +use commonware_codec::{Error as CodecError, FixedSize, Read, Write}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// Block identifier (32 bytes). +pub struct BlockId(pub B256); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +/// Transaction identifier (32 bytes). +pub struct TxId(pub B256); + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +/// State commitment (32 bytes). +pub struct StateRoot(pub B256); + +impl FixedSize for BlockId { + const SIZE: usize = 32; +} + +impl FixedSize for TxId { + const SIZE: usize = 32; +} + +impl FixedSize for StateRoot { + const SIZE: usize = 32; +} + +pub(super) fn write_b256(value: &B256, buf: &mut impl BufMut) { + buf.put_slice(value.as_slice()); +} + +pub(super) fn read_b256(buf: &mut impl Buf) -> Result { + if buf.remaining() < 32 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 32]; + buf.copy_to_slice(&mut out); + Ok(B256::from(out)) +} + +impl Write for BlockId { + fn write(&self, buf: &mut impl BufMut) { + write_b256(&self.0, buf); + } +} + +impl Read for BlockId { + type Cfg = (); + + fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result { + Ok(Self(read_b256(buf)?)) + } +} + +impl Write for TxId { + fn write(&self, buf: &mut impl BufMut) { + write_b256(&self.0, buf); + } +} + +impl Read for TxId { + type Cfg = (); + + fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result { + Ok(Self(read_b256(buf)?)) + } +} + +impl Write for StateRoot { + fn write(&self, buf: &mut impl BufMut) { + write_b256(&self.0, buf); + } +} + +impl Read for StateRoot { + type Cfg = (); + + fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result { + Ok(Self(read_b256(buf)?)) + } +} diff --git a/examples/revm/src/domain/types/tests.rs b/examples/revm/src/domain/types/tests.rs new file mode 100644 index 0000000000..2d1b5a713a --- /dev/null +++ b/examples/revm/src/domain/types/tests.rs @@ -0,0 +1,66 @@ +use super::*; +use alloy_evm::revm::primitives::{keccak256, Address, Bytes, B256, U256}; +use commonware_codec::{Decode as _, Encode as _}; + +fn cfg() -> BlockCfg { + BlockCfg { + max_txs: 64, + tx: TxCfg { + max_calldata_bytes: 1024, + }, + } +} + +#[test] +fn test_tx_roundtrip_and_id_stable() { + let tx = Tx { + from: Address::from([0x11u8; 20]), + to: Address::from([0x22u8; 20]), + value: U256::from(1234u64), + gas_limit: 50_000, + data: Bytes::from(vec![1, 2, 3]), + }; + let encoded = tx.encode(); + let decoded = Tx::decode_cfg( + encoded.clone(), + &TxCfg { + max_calldata_bytes: 1024, + }, + ) + .expect("decode tx"); + assert_eq!(tx, decoded); + assert_eq!(tx.id(), decoded.id()); + assert_eq!(tx.id(), TxId(keccak256(encoded))); +} + +#[test] +fn test_block_roundtrip_and_id_stable() { + let txs = vec![ + Tx { + from: Address::from([0x11u8; 20]), + to: Address::from([0x22u8; 20]), + value: U256::from(1u64), + gas_limit: 21_000, + data: Bytes::new(), + }, + Tx { + from: Address::from([0x33u8; 20]), + to: Address::from([0x44u8; 20]), + value: U256::from(2u64), + gas_limit: 50_000, + data: Bytes::from(vec![9, 9, 9]), + }, + ]; + let block = Block { + parent: BlockId(B256::from([0xAAu8; 32])), + height: 7, + prevrandao: B256::from([0x55u8; 32]), + state_root: StateRoot(B256::from([0xBBu8; 32])), + txs, + }; + let encoded = block.encode(); + let decoded = Block::decode_cfg(encoded.clone(), &cfg()).expect("decode block"); + assert_eq!(block, decoded); + assert_eq!(block.id(), decoded.id()); + assert_eq!(block.id(), BlockId(keccak256(encoded))); +} diff --git a/examples/revm/src/domain/types/tx.rs b/examples/revm/src/domain/types/tx.rs new file mode 100644 index 0000000000..f10afb750a --- /dev/null +++ b/examples/revm/src/domain/types/tx.rs @@ -0,0 +1,76 @@ +use super::ids::TxId; +use alloy_evm::revm::primitives::{keccak256, Address, Bytes, U256}; +use bytes::{Buf, BufMut}; +use commonware_codec::{Encode, EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt, Write}; + +#[derive(Clone, Copy, Debug)] +/// Configuration used when decoding transactions from bytes. +pub struct TxCfg { + /// Maximum calldata size accepted by the codec. + pub max_calldata_bytes: usize, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// Minimal transaction type for the example. +/// +/// This is not a signed Ethereum transaction. It is just enough information to build a `TxEnv` +/// for REVM execution in the simulation. +pub struct Tx { + /// Sender address. + pub from: Address, + /// Recipient address. + pub to: Address, + /// Amount transferred (Wei). + pub value: U256, + /// Gas limit supplied for execution. + pub gas_limit: u64, + /// Calldata passed to the contract. + pub data: Bytes, +} + +impl Tx { + pub fn id(&self) -> TxId { + TxId(keccak256(self.encode())) + } +} + +impl Write for Tx { + fn write(&self, buf: &mut impl BufMut) { + buf.put_slice(self.from.as_slice()); + buf.put_slice(self.to.as_slice()); + buf.put_slice(&self.value.to_be_bytes::<32>()); + self.gas_limit.write(buf); + self.data.as_ref().write(buf); + } +} + +impl EncodeSize for Tx { + fn encode_size(&self) -> usize { + 20 + 20 + 32 + self.gas_limit.encode_size() + self.data.as_ref().encode_size() + } +} + +impl Read for Tx { + type Cfg = TxCfg; + + fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result { + if buf.remaining() < 20 + 20 + 32 { + return Err(CodecError::EndOfBuffer); + } + let mut from = [0u8; 20]; + buf.copy_to_slice(&mut from); + let mut to = [0u8; 20]; + buf.copy_to_slice(&mut to); + let mut value = [0u8; 32]; + buf.copy_to_slice(&mut value); + let gas_limit = u64::read(buf)?; + let data = Vec::::read_cfg(buf, &(RangeCfg::new(0..=cfg.max_calldata_bytes), ()))?; + Ok(Self { + from: Address::from(from), + to: Address::from(to), + value: U256::from_be_bytes(value), + gas_limit, + data: Bytes::from(data), + }) + } +} diff --git a/examples/revm/src/lib.rs b/examples/revm/src/lib.rs new file mode 100644 index 0000000000..5ef320331b --- /dev/null +++ b/examples/revm/src/lib.rs @@ -0,0 +1,23 @@ +//! REVM-based example chain driven by threshold-simplex. +//! +//! This example uses `alloy-evm` as the integration layer above `revm` and keeps the execution +//! backend generic over the database trait boundary (`Database` + `DatabaseCommit`). + +mod application; +mod domain; +mod qmdb; +mod simulation; + +pub use application::execution::{ + evm_env, execute_txs, seed_precompile_address, ExecutionOutcome, CHAIN_ID, + SEED_PRECOMPILE_ADDRESS_BYTES, +}; +pub use domain::{ + block_id, AccountChange, Block, BlockCfg, BlockId, BootstrapConfig, StateChanges, + StateChangesCfg, StateRoot, Tx, TxCfg, TxId, +}; +pub use simulation::{simulate, SimConfig, SimOutcome}; + +pub type ConsensusDigest = commonware_cryptography::sha256::Digest; +pub type PublicKey = commonware_cryptography::ed25519::PublicKey; +pub(crate) type FinalizationEvent = (u32, ConsensusDigest); diff --git a/examples/revm/src/main.rs b/examples/revm/src/main.rs new file mode 100644 index 0000000000..9eb5c24df8 --- /dev/null +++ b/examples/revm/src/main.rs @@ -0,0 +1,45 @@ +use clap::{value_parser, Arg, Command}; +use commonware_revm::{simulate, SimConfig}; + +fn main() -> anyhow::Result<()> { + let matches = Command::new("commonware-revm") + .about("threshold-simplex + EVM execution example") + .arg( + Arg::new("nodes") + .long("nodes") + .required(false) + .default_value("4") + .value_parser(value_parser!(usize)), + ) + .arg( + Arg::new("blocks") + .long("blocks") + .required(false) + .default_value("3") + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("seed") + .long("seed") + .required(false) + .default_value("1") + .value_parser(value_parser!(u64)), + ) + .get_matches(); + + let nodes = *matches.get_one::("nodes").expect("defaulted"); + let blocks = *matches.get_one::("blocks").expect("defaulted"); + let seed = *matches.get_one::("seed").expect("defaulted"); + + let outcome = simulate(SimConfig { + nodes, + blocks, + seed, + })?; + println!("finalized head: {:?}", outcome.head); + println!("state root: {:?}", outcome.state_root); + println!("seed: {:?}", outcome.seed); + println!("from balance: {:?}", outcome.from_balance); + println!("to balance: {:?}", outcome.to_balance); + Ok(()) +} diff --git a/examples/revm/src/qmdb/adapter.rs b/examples/revm/src/qmdb/adapter.rs new file mode 100644 index 0000000000..2a9295aecb --- /dev/null +++ b/examples/revm/src/qmdb/adapter.rs @@ -0,0 +1,137 @@ +//! REVM database adapter backed by QMDB. +//! +//! This module exposes an async database interface for QMDB and bridges it +//! into REVM's synchronous `DatabaseRef` using `WrapDatabaseAsync`. The async +//! adapter is intentionally thin so the example can rely on QMDB's internal +//! caching and batching. + +use super::{ + keys::{account_key, code_key, storage_key}, + Error, QmdbInner, +}; +use alloy_evm::revm::{ + database_interface::{ + async_db::{DatabaseAsyncRef, WrapDatabaseAsync}, + DatabaseRef, + }, + primitives::{Address, Bytes, B256, KECCAK_EMPTY, U256}, + state::{AccountInfo, Bytecode}, +}; +use futures::lock::Mutex; +use std::sync::Arc; + +/// Async QMDB view that implements `DatabaseAsyncRef` for REVM. +#[derive(Clone)] +pub(crate) struct QmdbAsyncDb { + inner: Arc>, + gate: Arc>, +} + +impl QmdbAsyncDb { + /// Wraps shared QMDB state for the async REVM database bridge. + pub(super) const fn new(inner: Arc>, gate: Arc>) -> Self { + Self { inner, gate } + } +} + +impl DatabaseAsyncRef for QmdbAsyncDb { + type Error = Error; + + fn basic_async_ref( + &self, + address: Address, + ) -> impl std::future::Future, Self::Error>> + Send { + let inner = self.inner.clone(); + let gate = self.gate.clone(); + async move { + let _guard = gate.lock().await; + let inner = inner.lock().await; + let stores = inner.stores()?; + let record = stores.accounts.get(&account_key(address)).await?; + Ok(record.and_then(|record| record.as_info())) + } + } + + fn code_by_hash_async_ref( + &self, + code_hash: B256, + ) -> impl std::future::Future> + Send { + let inner = self.inner.clone(); + let gate = self.gate.clone(); + async move { + if code_hash == KECCAK_EMPTY || code_hash == B256::ZERO { + return Ok(Bytecode::default()); + } + + let _guard = gate.lock().await; + let inner = inner.lock().await; + let stores = inner.stores()?; + let code = stores.code.get(&code_key(code_hash)).await?; + let code = code.ok_or(Error::MissingCode(code_hash))?; + Ok(Bytecode::new_raw(Bytes::copy_from_slice(&code))) + } + } + + fn storage_async_ref( + &self, + address: Address, + index: U256, + ) -> impl std::future::Future> + Send { + let inner = self.inner.clone(); + let gate = self.gate.clone(); + async move { + let _guard = gate.lock().await; + let inner = inner.lock().await; + let stores = inner.stores()?; + let record = stores.accounts.get(&account_key(address)).await?; + let Some(record) = record else { + return Ok(U256::ZERO); + }; + if !record.exists { + return Ok(U256::ZERO); + } + let key = storage_key(address, record.storage_generation, index); + let value = stores.storage.get(&key).await?; + Ok(value.map(|entry| entry.0).unwrap_or_default()) + } + } + + fn block_hash_async_ref( + &self, + _number: u64, + ) -> impl std::future::Future> + Send { + std::future::ready(Ok(B256::ZERO)) + } +} + +/// Sync REVM database wrapper for the async QMDB adapter. +#[derive(Clone)] +pub(crate) struct QmdbRefDb { + pub(crate) inner: Arc>, +} + +impl std::fmt::Debug for QmdbRefDb { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QmdbRefDb").finish() + } +} + +impl DatabaseRef for QmdbRefDb { + type Error = Error; + + fn basic_ref(&self, address: Address) -> Result, Self::Error> { + self.inner.basic_ref(address) + } + + fn code_by_hash_ref(&self, code_hash: B256) -> Result { + self.inner.code_by_hash_ref(code_hash) + } + + fn storage_ref(&self, address: Address, index: U256) -> Result { + self.inner.storage_ref(address, index) + } + + fn block_hash_ref(&self, number: u64) -> Result { + self.inner.block_hash_ref(number) + } +} diff --git a/examples/revm/src/qmdb/changes.rs b/examples/revm/src/qmdb/changes.rs new file mode 100644 index 0000000000..c997644c12 --- /dev/null +++ b/examples/revm/src/qmdb/changes.rs @@ -0,0 +1,100 @@ +//! QMDB change tracking and merge helpers. +//! +//! The execution layer builds `QmdbChanges` for finalized blocks and the +//! QMDB layer applies them to the underlying stores. + +use alloy_evm::revm::primitives::{Address, B256, U256}; +use std::collections::BTreeMap; + +/// Aggregated changes to be persisted into QMDB. +#[derive(Clone, Debug, Default)] +pub(crate) struct QmdbChanges { + /// Per-address updates collected from the REVM overlay. + pub(crate) accounts: BTreeMap, +} + +/// Per-account changes derived from REVM state. +#[derive(Clone, Debug)] +pub(crate) struct AccountUpdate { + /// True if the account was created during execution. + pub(crate) created: bool, + /// True if the account was selfdestructed during execution. + pub(crate) selfdestructed: bool, + /// Updated nonce value. + pub(crate) nonce: u64, + /// Updated balance value. + pub(crate) balance: U256, + /// Code hash after execution. + pub(crate) code_hash: B256, + /// Code bytes if a new code blob was deployed or changed. + pub(crate) code: Option>, + /// Updated storage slots (slot -> value). + pub(crate) storage: BTreeMap, +} + +impl QmdbChanges { + /// Merges updates from a later block into the current change set. + pub(crate) fn merge(&mut self, other: Self) { + for (address, update) in other.accounts { + match self.accounts.entry(address) { + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(update); + } + std::collections::btree_map::Entry::Occupied(mut entry) => { + entry.get_mut().merge(update); + } + } + } + } + + /// Applies a per-account update into the change set. + pub(crate) fn apply_update(&mut self, address: Address, update: AccountUpdate) { + match self.accounts.entry(address) { + std::collections::btree_map::Entry::Vacant(entry) => { + entry.insert(update); + } + std::collections::btree_map::Entry::Occupied(mut entry) => { + entry.get_mut().merge(update); + } + } + } +} + +impl AccountUpdate { + /// Merges an update from later execution into the current view. + fn merge(&mut self, update: Self) { + let Self { + created, + selfdestructed, + nonce, + balance, + code_hash, + code, + storage, + } = update; + + if created { + self.storage.clear(); + self.created = true; + } + + if selfdestructed { + self.storage.clear(); + } + + self.selfdestructed = selfdestructed; + self.nonce = nonce; + self.balance = balance; + + if self.code_hash != code_hash || code.is_some() { + self.code = code; + } + self.code_hash = code_hash; + + if !selfdestructed { + for (slot, value) in storage { + self.storage.insert(slot, value); + } + } + } +} diff --git a/examples/revm/src/qmdb/config.rs b/examples/revm/src/qmdb/config.rs new file mode 100644 index 0000000000..caae692fda --- /dev/null +++ b/examples/revm/src/qmdb/config.rs @@ -0,0 +1,91 @@ +use super::{ + types::{AccountStore, CodeStore, Context, StorageStore}, + Error, Stores, +}; +use commonware_codec::RangeCfg; +use commonware_runtime::{buffer::PoolRef, Metrics as _}; +use commonware_storage::{qmdb::any::VariableConfig, translator::EightCap}; +use commonware_utils::{NZUsize, NZU64}; + +const CODE_MAX_BYTES: usize = 24_576; + +/// QMDB configuration for the REVM example. +#[derive(Clone)] +pub(crate) struct QmdbConfig { + /// Prefix used to derive the QMDB partition names. + pub(crate) partition_prefix: String, + /// Buffer pool shared by the underlying QMDB stores. + pub(crate) buffer_pool: PoolRef, +} + +impl QmdbConfig { + /// Creates a new configuration for the example QMDB partitions. + pub(crate) const fn new(partition_prefix: String, buffer_pool: PoolRef) -> Self { + Self { + partition_prefix, + buffer_pool, + } + } +} + +/// Builds a QMDB any-store config with example-appropriate defaults. +fn store_config( + prefix: &str, + name: &str, + buffer_pool: PoolRef, + log_codec_config: C, +) -> VariableConfig { + VariableConfig { + mmr_journal_partition: format!("{prefix}-{name}-mmr"), + mmr_metadata_partition: format!("{prefix}-{name}-mmr-meta"), + mmr_items_per_blob: NZU64!(128), + mmr_write_buffer: NZUsize!(1024 * 1024), + log_partition: format!("{prefix}-{name}-log"), + log_write_buffer: NZUsize!(1024 * 1024), + log_compression: None, + log_codec_config, + log_items_per_blob: NZU64!(128), + translator: EightCap, + thread_pool: None, + buffer_pool, + } +} + +pub(super) async fn open_stores(context: Context, config: QmdbConfig) -> Result { + let accounts = AccountStore::init( + context.with_label("accounts"), + store_config( + &config.partition_prefix, + "accounts", + config.buffer_pool.clone(), + (), + ), + ) + .await?; + let storage = StorageStore::init( + context.with_label("storage"), + store_config( + &config.partition_prefix, + "storage", + config.buffer_pool.clone(), + (), + ), + ) + .await?; + let code = CodeStore::init( + context.with_label("code"), + store_config( + &config.partition_prefix, + "code", + config.buffer_pool.clone(), + (RangeCfg::new(0..=CODE_MAX_BYTES), ()), + ), + ) + .await?; + + Ok(Stores { + accounts, + storage, + code, + }) +} diff --git a/examples/revm/src/qmdb/error.rs b/examples/revm/src/qmdb/error.rs new file mode 100644 index 0000000000..409510a484 --- /dev/null +++ b/examples/revm/src/qmdb/error.rs @@ -0,0 +1,17 @@ +use alloy_evm::revm::{database_interface::DBErrorMarker, primitives::B256}; +use thiserror::Error; + +/// Errors surfaced by the QMDB-backed REVM adapter. +#[derive(Debug, Error)] +pub(crate) enum Error { + #[error("qmdb error: {0}")] + Qmdb(#[from] commonware_storage::qmdb::Error), + #[error("missing tokio runtime for WrapDatabaseAsync")] + MissingRuntime, + #[error("missing code for hash {0:?}")] + MissingCode(B256), + #[error("qmdb store unavailable: {0}")] + StoreUnavailable(&'static str), +} + +impl DBErrorMarker for Error {} diff --git a/examples/revm/src/qmdb/keys.rs b/examples/revm/src/qmdb/keys.rs new file mode 100644 index 0000000000..012464da7b --- /dev/null +++ b/examples/revm/src/qmdb/keys.rs @@ -0,0 +1,39 @@ +//! Key schema helpers for QMDB. +//! +//! Keys are fixed-length to keep translations simple and avoid collisions: +//! - Accounts: 20-byte address. +//! - Code: 32-byte hash. +//! - Storage: 20-byte address, 8-byte storage generation, 32-byte slot. + +use alloy_evm::revm::primitives::{Address, B256, U256}; +use commonware_utils::sequence::FixedBytes; + +/// QMDB key for account records. +pub(crate) type AccountKey = FixedBytes<20>; +/// QMDB key for storage records. +pub(crate) type StorageKey = FixedBytes<60>; +/// QMDB key for contract bytecode. +pub(crate) type CodeKey = FixedBytes<32>; + +/// Converts an address into an account key. +pub(crate) const fn account_key(address: Address) -> AccountKey { + AccountKey::new(address.into_array()) +} + +/// Converts a code hash into a code key. +pub(crate) const fn code_key(hash: B256) -> CodeKey { + CodeKey::new(hash.0) +} + +/// Builds the storage key for a specific account, generation, and slot. +/// +/// The storage generation is incremented when an account is recreated so +/// previous storage slots are logically discarded without needing to delete +/// every key explicitly. +pub(crate) fn storage_key(address: Address, generation: u64, slot: U256) -> StorageKey { + let mut out = [0u8; 60]; + out[..20].copy_from_slice(address.as_slice()); + out[20..28].copy_from_slice(&generation.to_be_bytes()); + out[28..60].copy_from_slice(&slot.to_be_bytes::<32>()); + StorageKey::new(out) +} diff --git a/examples/revm/src/qmdb/mod.rs b/examples/revm/src/qmdb/mod.rs new file mode 100644 index 0000000000..0f9477e225 --- /dev/null +++ b/examples/revm/src/qmdb/mod.rs @@ -0,0 +1,34 @@ +//! QMDB-backed storage adapter for the REVM example. +//! +//! This module wires a QMDB-backed key/value model into REVM's database +//! interfaces. QMDB provides persistence and authenticated structure, while +//! REVM executes against a synchronous in-memory overlay via `CacheDB`. +//! +//! Design at a glance: +//! - Accounts, storage, and code live in separate QMDB partitions. +//! - Reads go through `DatabaseAsyncRef` and are bridged into sync REVM calls +//! via `WrapDatabaseAsync` (Tokio runtime required). +//! - Writes are staged in the REVM overlay and applied to QMDB in batches when +//! the example decides a block is finalized. + +mod adapter; +mod changes; +mod config; +mod error; +mod keys; +mod model; +mod service; +mod state; +mod store; +mod types; + +pub(crate) use adapter::QmdbRefDb; +pub(crate) use changes::{AccountUpdate, QmdbChanges}; +pub(crate) use config::QmdbConfig; +pub(crate) use error::Error; +pub(crate) use service::QmdbLedger; +pub(crate) use state::{state_root_from_roots, QmdbState, RevmDb}; +pub(crate) use store::{QmdbInner, Stores}; +pub(crate) use types::{ + AccountStore, AccountStoreDirty, CodeStore, CodeStoreDirty, StorageStore, StorageStoreDirty, +}; diff --git a/examples/revm/src/qmdb/model.rs b/examples/revm/src/qmdb/model.rs new file mode 100644 index 0000000000..ad06595bd0 --- /dev/null +++ b/examples/revm/src/qmdb/model.rs @@ -0,0 +1,145 @@ +//! Value encodings for QMDB. +//! +//! Records are encoded with `commonware_codec` and use fixed-size, big-endian +//! representations for Ethereum integers and hashes. + +use alloy_evm::revm::{ + primitives::{B256, KECCAK_EMPTY, U256}, + state::AccountInfo, +}; +use bytes::{Buf, BufMut}; +use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt, Write}; + +/// Persisted account data stored in QMDB. +#[derive(Clone, Debug)] +pub(crate) struct AccountRecord { + /// Whether the account exists. + pub(crate) exists: bool, + /// Account nonce. + pub(crate) nonce: u64, + /// Account balance. + pub(crate) balance: U256, + /// Hash of the account's contract bytecode. + pub(crate) code_hash: B256, + /// Storage generation for invalidating old storage slots on re-creation. + pub(crate) storage_generation: u64, +} + +impl AccountRecord { + /// Returns an empty account marker with the given storage generation. + pub(crate) const fn empty(storage_generation: u64) -> Self { + Self { + exists: false, + nonce: 0, + balance: U256::ZERO, + code_hash: KECCAK_EMPTY, + storage_generation, + } + } + + /// Converts the record into REVM's account info, if the account exists. + pub(crate) const fn as_info(&self) -> Option { + if !self.exists { + return None; + } + Some(AccountInfo { + balance: self.balance, + nonce: self.nonce, + code_hash: self.code_hash, + code: None, + }) + } +} + +impl Write for AccountRecord { + fn write(&self, buf: &mut impl BufMut) { + self.exists.write(buf); + self.nonce.write(buf); + write_u256(self.balance, buf); + write_b256(self.code_hash, buf); + self.storage_generation.write(buf); + } +} + +impl EncodeSize for AccountRecord { + fn encode_size(&self) -> usize { + self.exists.encode_size() + + self.nonce.encode_size() + + 32 + + 32 + + self.storage_generation.encode_size() + } +} + +impl Read for AccountRecord { + type Cfg = (); + + fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result { + let exists = bool::read(buf)?; + let nonce = u64::read(buf)?; + let balance = read_u256(buf)?; + let code_hash = read_b256(buf)?; + let storage_generation = u64::read(buf)?; + Ok(Self { + exists, + nonce, + balance, + code_hash, + storage_generation, + }) + } +} + +/// Persisted storage slot value. +#[derive(Clone, Copy, Debug)] +pub(crate) struct StorageRecord(pub(crate) U256); + +impl Write for StorageRecord { + fn write(&self, buf: &mut impl BufMut) { + write_u256(self.0, buf); + } +} + +impl EncodeSize for StorageRecord { + fn encode_size(&self) -> usize { + 32 + } +} + +impl Read for StorageRecord { + type Cfg = (); + + fn read_cfg(buf: &mut impl Buf, _: &Self::Cfg) -> Result { + Ok(Self(read_u256(buf)?)) + } +} + +/// Writes a 256-bit integer in big-endian form. +pub(crate) fn write_u256(value: U256, buf: &mut impl BufMut) { + buf.put_slice(&value.to_be_bytes::<32>()); +} + +/// Reads a 256-bit integer in big-endian form. +pub(crate) fn read_u256(buf: &mut impl Buf) -> Result { + if buf.remaining() < 32 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 32]; + buf.copy_to_slice(&mut out); + Ok(U256::from_be_bytes(out)) +} + +/// Writes a 256-bit hash in big-endian form. +pub(crate) fn write_b256(value: B256, buf: &mut impl BufMut) { + buf.put_slice(value.as_slice()); +} + +/// Reads a 256-bit hash in big-endian form. +pub(crate) fn read_b256(buf: &mut impl Buf) -> Result { + if buf.remaining() < 32 { + return Err(CodecError::EndOfBuffer); + } + let mut out = [0u8; 32]; + buf.copy_to_slice(&mut out); + Ok(B256::from(out)) +} diff --git a/examples/revm/src/qmdb/service.rs b/examples/revm/src/qmdb/service.rs new file mode 100644 index 0000000000..85614d0dda --- /dev/null +++ b/examples/revm/src/qmdb/service.rs @@ -0,0 +1,45 @@ +use super::{adapter::QmdbRefDb, Error, QmdbChanges, QmdbConfig, QmdbState}; +use crate::domain::StateRoot; +use alloy_evm::revm::primitives::{Address, U256}; +use commonware_runtime::tokio::Context; +use std::sync::Arc; + +/// Domain service that owns QMDB persistence for the REVM example. +#[derive(Clone)] +pub(crate) struct QmdbLedger { + state: Arc, +} + +impl QmdbLedger { + /// Initializes the QMDB partitions and populates the genesis allocation. + pub(crate) async fn init( + context: Context, + config: QmdbConfig, + genesis_alloc: Vec<(Address, U256)>, + ) -> Result { + let state = QmdbState::init(context, config, genesis_alloc).await?; + Ok(Self { + state: Arc::new(state), + }) + } + + /// Exposes a synchronous REVM database view backed by QMDB. + pub(crate) fn database(&self) -> Result { + self.state.database() + } + + /// Computes the preview root for a change set without committing. + pub(crate) async fn preview_root(&self, changes: QmdbChanges) -> Result { + self.state.preview_root(changes).await + } + + /// Commits the provided changes to QMDB and returns the resulting root. + pub(crate) async fn commit_changes(&self, changes: QmdbChanges) -> Result { + self.state.commit_changes(changes).await + } + + /// Returns the current authenticated root stored in QMDB. + pub(crate) async fn root(&self) -> Result { + self.state.root().await + } +} diff --git a/examples/revm/src/qmdb/state.rs b/examples/revm/src/qmdb/state.rs new file mode 100644 index 0000000000..ddb37438e1 --- /dev/null +++ b/examples/revm/src/qmdb/state.rs @@ -0,0 +1,190 @@ +use super::{ + adapter::QmdbAsyncDb, + config::open_stores, + store::{QmdbInner, Stores}, + Error, QmdbChanges, QmdbConfig, QmdbRefDb, +}; +use crate::domain::StateRoot; +use alloy_evm::revm::{ + database::CacheDB, + database_interface::async_db::WrapDatabaseAsync, + primitives::{keccak256, Address, B256, U256}, +}; +use commonware_cryptography::sha256::Digest as QmdbDigest; +use futures::lock::Mutex; +use std::sync::Arc; + +const QMDB_ROOT_NAMESPACE: &[u8] = b"_COMMONWARE_REVM_QMDB_ROOT"; + +type Context = commonware_runtime::tokio::Context; + +/// Owns QMDB handles and exposes a REVM database view plus persistence hooks. +#[derive(Clone)] +pub(crate) struct QmdbState { + inner: Arc>, + gate: Arc>, + context: Context, + config: QmdbConfig, +} + +impl QmdbState { + /// Initializes QMDB partitions and populates the genesis allocation. + pub(crate) async fn init( + context: Context, + config: QmdbConfig, + genesis_alloc: Vec<(Address, U256)>, + ) -> Result { + let stores = open_stores(context.clone(), config.clone()).await?; + let state = Self { + inner: Arc::new(Mutex::new(QmdbInner::new(stores))), + gate: Arc::new(Mutex::new(())), + context, + config, + }; + state.bootstrap_genesis(genesis_alloc).await?; + Ok(state) + } + + /// Creates a sync REVM database view backed by the async QMDB adapter. + /// + /// This uses REVM's `WrapDatabaseAsync` bridge and therefore requires a + /// Tokio runtime to be available when called. + pub(crate) fn database(&self) -> Result { + let async_db = QmdbAsyncDb::new(self.inner.clone(), self.gate.clone()); + let wrapped = WrapDatabaseAsync::new(async_db).ok_or(Error::MissingRuntime)?; + Ok(QmdbRefDb { + inner: Arc::new(wrapped), + }) + } + + /// Computes the state commitment that would result from applying the changes. + /// + /// This does not make changes durable. The commitment is derived from the authenticated roots + /// of the accounts, storage, and code partitions. + pub(crate) async fn preview_root(&self, changes: QmdbChanges) -> Result { + let _guard = self.gate.lock().await; + if changes.accounts.is_empty() { + let inner = self.inner.lock().await; + return Ok(state_root_from_stores(inner.stores()?)); + } + + let stores = open_stores(self.context.clone(), self.config.clone()).await?; + stores.preview_root(changes).await + } + + /// Applies state changes to QMDB and commits them to durable storage. + /// + /// The commitment is derived from the authenticated roots of the accounts, + /// storage, and code partitions. + pub(crate) async fn commit_changes(&self, changes: QmdbChanges) -> Result { + let _guard = self.gate.lock().await; + if changes.accounts.is_empty() { + let inner = self.inner.lock().await; + return Ok(state_root_from_stores(inner.stores()?)); + } + + let stores = open_stores(self.context.clone(), self.config.clone()).await?; + let stores = stores.apply_changes(changes).await?; + let root = state_root_from_stores(&stores); + let mut inner = self.inner.lock().await; + inner.restore_stores(stores); + Ok(root) + } + + /// Returns the current authenticated state commitment. + pub(crate) async fn root(&self) -> Result { + let _guard = self.gate.lock().await; + let inner = self.inner.lock().await; + Ok(state_root_from_stores(inner.stores()?)) + } + + #[allow(dead_code)] + pub(crate) async fn get_account( + &self, + address: Address, + ) -> Result, Error> { + let _guard = self.gate.lock().await; + let inner = self.inner.lock().await; + let stores = inner.stores()?; + stores + .accounts + .get(&super::keys::account_key(address)) + .await + .map_err(Error::from) + } + + #[allow(dead_code)] + pub(crate) async fn get_code(&self, code_hash: B256) -> Result>, Error> { + let _guard = self.gate.lock().await; + let inner = self.inner.lock().await; + let stores = inner.stores()?; + stores + .code + .get(&super::keys::code_key(code_hash)) + .await + .map_err(Error::from) + } + + #[allow(dead_code)] + pub(crate) async fn get_storage(&self, address: Address, index: U256) -> Result { + let _guard = self.gate.lock().await; + let inner = self.inner.lock().await; + let stores = inner.stores()?; + let record = stores + .accounts + .get(&super::keys::account_key(address)) + .await?; + let Some(record) = record else { + return Ok(U256::ZERO); + }; + if !record.exists { + return Ok(U256::ZERO); + } + let key = super::keys::storage_key(address, record.storage_generation, index); + let value = stores.storage.get(&key).await?; + Ok(value.map(|entry| entry.0).unwrap_or_default()) + } + + /// Writes genesis balances into the accounts partition. + async fn bootstrap_genesis(&self, genesis_alloc: Vec<(Address, U256)>) -> Result<(), Error> { + let _guard = self.gate.lock().await; + if genesis_alloc.is_empty() { + return Ok(()); + } + + let mut inner = self.inner.lock().await; + let stores = inner.take_stores()?; + let result = stores.apply_genesis(genesis_alloc).await; + match result { + Ok(stores) => { + inner.restore_stores(stores); + Ok(()) + } + Err(err) => Err(err), + } + } +} + +/// Execution database type used by the REVM example. +pub(crate) type RevmDb = CacheDB; + +fn state_root_from_stores(stores: &Stores) -> StateRoot { + state_root_from_roots( + stores.accounts.root(), + stores.storage.root(), + stores.code.root(), + ) +} + +pub(crate) fn state_root_from_roots( + accounts: QmdbDigest, + storage: QmdbDigest, + code: QmdbDigest, +) -> StateRoot { + let mut buf = Vec::with_capacity(QMDB_ROOT_NAMESPACE.len() + (32 * 3)); + buf.extend_from_slice(QMDB_ROOT_NAMESPACE); + buf.extend_from_slice(accounts.as_ref()); + buf.extend_from_slice(storage.as_ref()); + buf.extend_from_slice(code.as_ref()); + StateRoot(keccak256(buf)) +} diff --git a/examples/revm/src/qmdb/store.rs b/examples/revm/src/qmdb/store.rs new file mode 100644 index 0000000000..ce58fe733f --- /dev/null +++ b/examples/revm/src/qmdb/store.rs @@ -0,0 +1,303 @@ +//! QMDB store plumbing for the REVM example. +//! +//! This module keeps the store ownership and update flow separate from the +//! public-facing API so `mod.rs` stays focused on the example entry points. + +use super::{ + keys::{account_key, code_key, storage_key, AccountKey, CodeKey, StorageKey}, + model::{AccountRecord, StorageRecord}, + state_root_from_roots, AccountStore, AccountStoreDirty, CodeStore, CodeStoreDirty, Error, + QmdbChanges, StorageStore, StorageStoreDirty, +}; +use crate::domain::StateRoot; +use alloy_evm::revm::primitives::{Address, KECCAK_EMPTY, U256}; +use commonware_storage::kv::{Batchable as _, Updatable as _}; + +/// Durable QMDB stores backing the example state. +pub(crate) struct Stores { + /// Account metadata keyed by address. + pub(crate) accounts: AccountStore, + /// Storage slots keyed by address, generation, and slot. + pub(crate) storage: StorageStore, + /// Contract bytecode keyed by code hash. + pub(crate) code: CodeStore, +} + +/// Dirty QMDB stores used while applying updates. +pub(super) struct DirtyStores { + pub(super) accounts: AccountStoreDirty, + pub(super) storage: StorageStoreDirty, + pub(super) code: CodeStoreDirty, +} + +/// Batched updates prepared for QMDB writes. +pub(super) struct StoreBatches { + pub(super) accounts: Vec<(AccountKey, Option)>, + pub(super) storage: Vec<(StorageKey, Option)>, + pub(super) code: Vec<(CodeKey, Option>)>, +} + +/// Slot that temporarily yields ownership of the QMDB stores during updates. +pub(super) struct StoresSlot(Option); + +impl StoresSlot { + /// Creates a new slot holding initialized stores. + pub(super) const fn new(stores: Stores) -> Self { + Self(Some(stores)) + } + + /// Returns a shared reference to the stores, if available. + pub(super) fn get(&self) -> Result<&Stores, Error> { + self.0 + .as_ref() + .ok_or(Error::StoreUnavailable("stores unavailable")) + } + + /// Takes ownership of the stores while an update is in progress. + pub(super) fn take(&mut self) -> Result { + self.0 + .take() + .ok_or(Error::StoreUnavailable("stores unavailable")) + } + + /// Restores the stores after a successful update. + pub(super) fn restore(&mut self, stores: Stores) { + self.0 = Some(stores); + } +} + +/// Shared QMDB store state guarded by the async mutex in `QmdbState`. +pub(crate) struct QmdbInner { + stores: StoresSlot, +} + +impl QmdbInner { + /// Wraps initialized stores for shared access. + pub(crate) const fn new(stores: Stores) -> Self { + Self { + stores: StoresSlot::new(stores), + } + } + + /// Returns a shared reference to the stores, if available. + pub(crate) fn stores(&self) -> Result<&Stores, Error> { + self.stores.get() + } + + /// Takes ownership of the stores for an update. + pub(crate) fn take_stores(&mut self) -> Result { + self.stores.take() + } + + /// Restores stores after a successful update. + pub(crate) fn restore_stores(&mut self, stores: Stores) { + self.stores.restore(stores); + } +} + +impl Stores { + /// Transitions durable stores into their non-durable update state. + pub(super) fn into_dirty(self) -> DirtyStores { + DirtyStores { + accounts: self.accounts.into_mutable(), + storage: self.storage.into_mutable(), + code: self.code.into_mutable(), + } + } + + /// Applies a finalized change set and returns updated durable stores. + pub(crate) async fn apply_changes(self, changes: QmdbChanges) -> Result { + let dirty = self.into_dirty(); + let (mut dirty, batches) = dirty.build_batches(changes).await?; + dirty.apply_batches(batches).await?; + dirty.commit().await + } + + /// Applies the genesis allocation to a fresh store set. + pub(crate) async fn apply_genesis( + self, + genesis_alloc: Vec<(Address, U256)>, + ) -> Result { + let Self { + accounts, + storage, + code, + } = self; + let mut accounts = accounts.into_mutable(); + let batch_ops = { + let mut batch = accounts.start_batch(); + for (address, balance) in genesis_alloc { + let record = AccountRecord { + exists: true, + nonce: 0, + balance, + code_hash: KECCAK_EMPTY, + storage_generation: 0, + }; + batch.update(account_key(address), record).await?; + } + batch.into_iter().collect::>() + }; + accounts.write_batch(batch_ops.into_iter()).await?; + let (accounts, _) = accounts.into_merkleized().commit(None).await?; + Ok(Self { + accounts, + storage, + code, + }) + } + + /// Computes the state commitment after applying changes without committing durability. + pub(crate) async fn preview_root(self, changes: QmdbChanges) -> Result { + let dirty = self.into_dirty(); + let (mut dirty, batches) = dirty.build_batches(changes).await?; + dirty.apply_batches(batches).await?; + + let accounts = dirty.accounts.into_merkleized(); + let storage = dirty.storage.into_merkleized(); + let code = dirty.code.into_merkleized(); + Ok(state_root_from_roots( + accounts.root(), + storage.root(), + code.root(), + )) + } +} + +impl DirtyStores { + /// Builds batched QMDB operations from a finalized change set. + /// + /// Takes ownership of `DirtyStores` and `QmdbChanges` to avoid holding references + /// across await points, which would trigger RPITIT cross-crate Send bound issues. + /// + /// The function is structured to: + /// 1. Pre-fetch all existing account data (async lookups complete before batch building) + /// 2. Build all batch operations synchronously without await in the loop + pub(super) async fn build_batches( + self, + changes: QmdbChanges, + ) -> Result<(Self, StoreBatches), Error> { + let stores = self; + // Convert changes to owned Vec to avoid holding BTreeMap iterator across await + let account_updates: Vec<_> = changes.accounts.into_iter().collect(); + + // Extract just the addresses for pre-fetching (owned, not borrowed) + let addresses: Vec<_> = account_updates.iter().map(|(addr, _)| *addr).collect(); + + // Pre-fetch all existing account records we need (complete all async ops first) + let mut existing_accounts = Vec::with_capacity(addresses.len()); + for address in addresses { + let key = account_key(address); + let existing = stores.accounts.get(&key).await?; + existing_accounts.push(existing); + } + + // Now build batches synchronously - no await points in this section + let mut accounts_batch = stores.accounts.start_batch(); + let mut storage_batch = stores.storage.start_batch(); + let mut code_batch = stores.code.start_batch(); + + // Collect all batch operations into vecs + let mut account_ops = Vec::new(); + let mut storage_ops = Vec::new(); + let mut code_ops = Vec::new(); + + for ((address, update), existing) in account_updates.into_iter().zip(existing_accounts) { + let account_key = account_key(address); + let base_generation = existing + .as_ref() + .map(|record| record.storage_generation) + .unwrap_or(0); + + let storage_generation = if update.created || update.selfdestructed { + base_generation.saturating_add(1) + } else { + base_generation + }; + + let record = if update.selfdestructed { + AccountRecord::empty(storage_generation) + } else { + AccountRecord { + exists: true, + nonce: update.nonce, + balance: update.balance, + code_hash: update.code_hash, + storage_generation, + } + }; + account_ops.push((account_key, Some(record))); + + if update.selfdestructed { + continue; + } + + if let Some(code) = update.code.as_ref() { + if !code.is_empty() && update.code_hash != KECCAK_EMPTY { + code_ops.push((code_key(update.code_hash), Some(code.clone()))); + } + } + + // Convert storage iteration to owned Vec to avoid borrow + let storage_slots: Vec<_> = update.storage.into_iter().collect(); + for (slot, value) in storage_slots { + let key = storage_key(address, storage_generation, slot); + if value.is_zero() { + storage_ops.push((key, None)); + } else { + storage_ops.push((key, Some(StorageRecord(value)))); + } + } + } + + // Apply all batch operations (these may await but don't hold iterator borrows) + for (key, value) in account_ops { + if let Some(v) = value { + accounts_batch.update(key, v).await?; + } + } + for (key, value) in storage_ops { + if let Some(v) = value { + storage_batch.update(key, v).await?; + } else { + storage_batch.delete_unchecked(key).await?; + } + } + for (key, value) in code_ops { + if let Some(v) = value { + code_batch.update(key, v).await?; + } + } + + let batches = StoreBatches { + accounts: accounts_batch.into_iter().collect(), + storage: storage_batch.into_iter().collect(), + code: code_batch.into_iter().collect(), + }; + Ok((stores, batches)) + } + + /// Writes batches to the dirty stores before commit. + pub(super) async fn apply_batches(&mut self, batches: StoreBatches) -> Result<(), Error> { + self.accounts + .write_batch(batches.accounts.into_iter()) + .await?; + self.storage + .write_batch(batches.storage.into_iter()) + .await?; + self.code.write_batch(batches.code.into_iter()).await?; + Ok(()) + } + + /// Commits all dirty stores and returns them in durable form. + pub(super) async fn commit(self) -> Result { + let (accounts, _) = self.accounts.into_merkleized().commit(None).await?; + let (storage, _) = self.storage.into_merkleized().commit(None).await?; + let (code, _) = self.code.into_merkleized().commit(None).await?; + Ok(Stores { + accounts, + storage, + code, + }) + } +} diff --git a/examples/revm/src/qmdb/types.rs b/examples/revm/src/qmdb/types.rs new file mode 100644 index 0000000000..84fe8afafd --- /dev/null +++ b/examples/revm/src/qmdb/types.rs @@ -0,0 +1,45 @@ +use super::{ + keys::{AccountKey, CodeKey, StorageKey}, + model::{AccountRecord, StorageRecord}, +}; +use commonware_cryptography::sha256::Sha256 as QmdbHasher; +use commonware_runtime::tokio; +use commonware_storage::{ + qmdb::{any, NonDurable, Unmerkleized}, + translator::EightCap, +}; + +pub(crate) type Context = tokio::Context; +pub(crate) type AccountStore = + any::unordered::variable::Db; +pub(crate) type StorageStore = + any::unordered::variable::Db; +pub(crate) type CodeStore = + any::unordered::variable::Db, QmdbHasher, EightCap>; +pub(crate) type AccountStoreDirty = any::unordered::variable::Db< + Context, + AccountKey, + AccountRecord, + QmdbHasher, + EightCap, + Unmerkleized, + NonDurable, +>; +pub(crate) type StorageStoreDirty = any::unordered::variable::Db< + Context, + StorageKey, + StorageRecord, + QmdbHasher, + EightCap, + Unmerkleized, + NonDurable, +>; +pub(crate) type CodeStoreDirty = any::unordered::variable::Db< + Context, + CodeKey, + Vec, + QmdbHasher, + EightCap, + Unmerkleized, + NonDurable, +>; diff --git a/examples/revm/src/simulation/demo.rs b/examples/revm/src/simulation/demo.rs new file mode 100644 index 0000000000..fc0318b3ed --- /dev/null +++ b/examples/revm/src/simulation/demo.rs @@ -0,0 +1,38 @@ +//! Deterministic demo scenario for the simulation. +//! +//! The example chain "prefunds" two addresses and injects a single transfer at height 1. + +use alloy_evm::revm::primitives::{Address, Bytes as EvmBytes, U256}; + +#[derive(Clone, Debug)] +pub(super) struct DemoTransfer { + pub(super) from: Address, + pub(super) to: Address, + pub(super) alloc: Vec<(Address, U256)>, + pub(super) tx: crate::Tx, + pub(super) expected_from: U256, + pub(super) expected_to: U256, +} + +impl DemoTransfer { + pub(super) fn new() -> Self { + let from = Address::from([0x11u8; 20]); + let to = Address::from([0x22u8; 20]); + let tx = crate::Tx { + from, + to, + value: U256::from(100u64), + gas_limit: 21_000, + data: EvmBytes::new(), + }; + + Self { + from, + to, + alloc: vec![(from, U256::from(1_000_000u64)), (to, U256::ZERO)], + tx, + expected_from: U256::from(1_000_000u64 - 100), + expected_to: U256::from(100u64), + } + } +} diff --git a/examples/revm/src/simulation/mod.rs b/examples/revm/src/simulation/mod.rs new file mode 100644 index 0000000000..03f24f2081 --- /dev/null +++ b/examples/revm/src/simulation/mod.rs @@ -0,0 +1,365 @@ +//! Simulation harness for the example chain. +//! +//! Spawns N nodes in a single process using the tokio runtime and the simulated P2P transport. +//! The harness waits for a fixed number of finalized blocks and asserts all nodes converge on the +//! same head, state commitment, and balances. + +use crate::{ + application::{ + start_node, threshold_schemes, NodeEnvironment, ThresholdScheme, TransportControl, + }, + BootstrapConfig, ConsensusDigest, FinalizationEvent, +}; +use alloy_evm::revm::primitives::{B256, U256}; +use anyhow::Context as _; +use commonware_cryptography::ed25519; +use commonware_p2p::{simulated, Manager as _}; +use commonware_runtime::{tokio, Metrics as _, Runner as _}; +use commonware_utils::{ordered::Set, TryCollect as _}; +use futures::{channel::mpsc, StreamExt as _}; +use std::{ + net::{IpAddr, Ipv4Addr, SocketAddr}, + time::Duration, +}; + +mod demo; + +/// Maximum size (bytes) of a single simulated network message. +pub(super) const MAX_MSG_SIZE: usize = 1024 * 1024; +/// Fixed latency (milliseconds) for simulated P2P links. +pub(super) const P2P_LINK_LATENCY_MS: u64 = 5; + +#[derive(Clone, Copy, Debug)] +/// Configuration for a simulation run. +pub struct SimConfig { + /// Number of nodes participating in the simulation. + pub nodes: usize, + /// Number of blocks to finalize before stopping. + pub blocks: u64, + /// Seed used for deterministic randomness. + pub seed: u64, +} + +#[derive(Clone, Copy, Debug)] +/// Summary of a completed simulation run. +pub struct SimOutcome { + /// Finalized head digest (the value ordered by threshold-simplex). + pub head: ConsensusDigest, + /// State commitment at the head digest. + pub state_root: crate::StateRoot, + /// Latest tracked threshold-simplex seed hash (used as `prevrandao`). + pub seed: B256, + /// Final balance of the sender account after the demo transfer. + pub from_balance: U256, + /// Final balance of the receiver account after the demo transfer. + pub to_balance: U256, +} + +type NodeHandle = crate::application::NodeHandle; +type SimTransport = simulated::Oracle; + +fn transport_control( + transport: &SimTransport, + me: ed25519::PublicKey, +) -> simulated::Control { + simulated::Oracle::control(transport, me) +} + +fn transport_manager( + transport: &SimTransport, +) -> simulated::Manager { + simulated::Oracle::manager(transport) +} + +struct SimEnvironment<'a> { + context: tokio::Context, + transport: &'a mut SimTransport, +} + +impl<'a> SimEnvironment<'a> { + const fn new(context: tokio::Context, transport: &'a mut SimTransport) -> Self { + Self { context, transport } + } +} + +impl TransportControl for SimTransport { + type Control = simulated::Control; + type Manager = simulated::Manager; + + fn control(&self, me: ed25519::PublicKey) -> Self::Control { + transport_control(self, me) + } + + fn manager(&self) -> Self::Manager { + transport_manager(self) + } +} + +impl NodeEnvironment for SimEnvironment<'_> { + type Transport = SimTransport; + + fn context(&self) -> tokio::Context { + self.context.clone() + } + + fn transport(&mut self) -> &mut SimTransport { + self.transport + } +} + +#[cfg(unix)] +fn raise_open_file_limit() { + use libc::{getrlimit, rlimit, setrlimit, RLIMIT_NOFILE}; + + // Best effort: avoid hitting low per-process fd limits during simulations. + // SAFETY: best-effort process limit adjustment with well-defined libc calls. + unsafe { + let mut limits = rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if getrlimit(RLIMIT_NOFILE, &mut limits) != 0 { + return; + } + if limits.rlim_cur < limits.rlim_max { + let updated = rlimit { + rlim_cur: limits.rlim_max, + rlim_max: limits.rlim_max, + }; + let _ = setrlimit(RLIMIT_NOFILE, &updated); + } + } +} + +#[cfg(not(unix))] +fn raise_open_file_limit() {} + +/// Run the multi-node simulation and return the final outcome. +pub fn simulate(cfg: SimConfig) -> anyhow::Result { + raise_open_file_limit(); + let executor = tokio::Runner::default(); + executor.start(|context| async move { run_sim(context, cfg).await }) +} + +async fn run_sim(context: tokio::Context, cfg: SimConfig) -> anyhow::Result { + let (participants_vec, schemes) = threshold_schemes(cfg.seed, cfg.nodes)?; + let participants_set = participants_set(&participants_vec)?; + + let mut transport = start_network(&context, participants_set).await; + connect_all_peers(&mut transport, &participants_vec).await?; + + let demo = demo::DemoTransfer::new(); + let bootstrap = BootstrapConfig::new(demo.alloc.clone(), vec![demo.tx.clone()]); + + let (nodes, mut finalized_rx) = start_all_nodes( + &context, + &mut transport, + &participants_vec, + &schemes, + &bootstrap, + ) + .await?; + + let head = wait_for_finalized_head(&mut finalized_rx, cfg.nodes, cfg.blocks).await?; + let (state_root, seed) = assert_all_nodes_converged(&nodes, head, &demo).await?; + + Ok(SimOutcome { + head, + state_root, + seed, + from_balance: demo.expected_from, + to_balance: demo.expected_to, + }) +} + +/// Spawn all nodes (application + consensus) for a simulation run. +async fn start_all_nodes( + context: &tokio::Context, + transport: &mut SimTransport, + participants: &[ed25519::PublicKey], + schemes: &[ThresholdScheme], + bootstrap: &BootstrapConfig, +) -> anyhow::Result<(Vec, mpsc::UnboundedReceiver)> { + let (finalized_tx, finalized_rx) = mpsc::unbounded::(); + let mut nodes = Vec::with_capacity(participants.len()); + let mut env = SimEnvironment::new(context.clone(), transport); + + for (i, pk) in participants.iter().cloned().enumerate() { + let handle = start_node( + &mut env, + i, + pk, + schemes[i].clone(), + finalized_tx.clone(), + bootstrap, + ) + .await?; + nodes.push(handle); + } + + Ok((nodes, finalized_rx)) +} + +/// Ensure the provided round-robin identity list is unique for the simulation. +fn participants_set( + participants: &[ed25519::PublicKey], +) -> anyhow::Result> { + participants + .iter() + .cloned() + .try_collect() + .map_err(|_| anyhow::anyhow!("participant public keys are not unique")) +} + +/// Boot the simulated p2p network and register the participant set. +async fn start_network( + context: &tokio::Context, + participants: Set, +) -> SimTransport { + let base_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0); + let (network, transport) = simulated::Network::new_with_base_addr( + context.with_label("network"), + simulated::Config { + max_size: MAX_MSG_SIZE as u32, + disconnect_on_block: true, + tracked_peer_sets: None, + }, + base_addr, + ); + network.start(); + + transport.manager().update(0, participants).await; + transport +} + +/// Connect all peers in a full mesh with fixed links. +async fn connect_all_peers( + transport: &mut SimTransport, + peers: &[ed25519::PublicKey], +) -> anyhow::Result<()> { + for a in peers.iter() { + for b in peers.iter() { + if a == b { + continue; + } + transport + .add_link( + a.clone(), + b.clone(), + simulated::Link { + latency: Duration::from_millis(P2P_LINK_LATENCY_MS), + jitter: Duration::from_millis(0), + success_rate: 1.0, + }, + ) + .await + .context("add_link")?; + } + } + Ok(()) +} + +/// Wait until each node has observed `blocks` finalizations and return the common head digest. +async fn wait_for_finalized_head( + finalized_rx: &mut mpsc::UnboundedReceiver, + nodes: usize, + blocks: u64, +) -> anyhow::Result { + if blocks == 0 { + return Err(anyhow::anyhow!("blocks must be greater than zero")); + } + + let mut counts = vec![0u64; nodes]; + let mut nth = vec![None; nodes]; + while nth.iter().any(Option::is_none) { + let Some((node, digest)) = finalized_rx.next().await else { + break; + }; + let idx = node as usize; + if nth[idx].is_some() { + continue; + } + counts[idx] += 1; + if counts[idx] == blocks { + nth[idx] = Some(digest); + } + } + + let head = nth + .first() + .and_then(|d| *d) + .ok_or_else(|| anyhow::anyhow!("missing finalization"))?; + for (i, d) in nth.iter().enumerate() { + let Some(d) = d else { + return Err(anyhow::anyhow!("node {i} missing finalization")); + }; + if *d != head { + return Err(anyhow::anyhow!("divergent finalized heads")); + } + } + Ok(head) +} + +/// Query each node's application store at `head` and assert they all agree on the outcome. +async fn assert_all_nodes_converged( + nodes: &[NodeHandle], + head: ConsensusDigest, + demo: &demo::DemoTransfer, +) -> anyhow::Result<(crate::StateRoot, B256)> { + let mut state_root = None; + let mut seed = None; + for node in nodes.iter() { + let from_balance = node + .query_balance(head, demo.from) + .await + .ok_or_else(|| anyhow::anyhow!("missing from balance"))?; + let to_balance = node + .query_balance(head, demo.to) + .await + .ok_or_else(|| anyhow::anyhow!("missing to balance"))?; + if from_balance != demo.expected_from || to_balance != demo.expected_to { + return Err(anyhow::anyhow!("unexpected balances")); + } + + let root = node + .query_state_root(head) + .await + .ok_or_else(|| anyhow::anyhow!("missing state root"))?; + state_root = match state_root { + None => Some(root), + Some(prev) if prev == root => Some(prev), + Some(_) => return Err(anyhow::anyhow!("divergent state roots")), + }; + + let node_seed = node + .query_seed(head) + .await + .ok_or_else(|| anyhow::anyhow!("missing seed"))?; + seed = match seed { + None => Some(node_seed), + Some(prev) if prev == node_seed => Some(prev), + Some(_) => return Err(anyhow::anyhow!("divergent seeds")), + }; + } + + let state_root = state_root.ok_or_else(|| anyhow::anyhow!("missing state root"))?; + let seed = seed.ok_or_else(|| anyhow::anyhow!("missing seed"))?; + Ok((state_root, seed)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sim_smoke() { + let outcome = simulate(SimConfig { + nodes: 4, + blocks: 3, + seed: 42, + }) + .unwrap(); + assert_eq!(outcome.from_balance, U256::from(1_000_000u64 - 100)); + assert_eq!(outcome.to_balance, U256::from(100u64)); + } +} diff --git a/p2p/src/simulated/network.rs b/p2p/src/simulated/network.rs index 1593f1b0b5..6e39082d84 100644 --- a/p2p/src/simulated/network.rs +++ b/p2p/src/simulated/network.rs @@ -122,6 +122,9 @@ pub struct Network // Incremented for each new peer next_addr: SocketAddr, + // If true, peers bind to ephemeral ports and the actual addresses are recorded. + ephemeral_ports: bool, + // Channel to receive messages from the oracle ingress: mpsc::UnboundedReceiver>, @@ -173,6 +176,19 @@ impl Network /// Returns a tuple containing the network instance and the oracle that can /// be used to modify the state of the network during context. pub fn new(mut context: E, cfg: Config) -> (Self, Oracle) { + let base_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::from_bits(context.next_u32())), 0); + Self::new_with_base_addr(context, cfg, base_addr) + } + + /// Create a new simulated network with an explicit base socket address. + /// + /// When the base address uses port 0, each peer binds to an OS-assigned port and the actual + /// address is recorded for later dials. + pub fn new_with_base_addr( + context: E, + cfg: Config, + base_addr: SocketAddr, + ) -> (Self, Oracle) { let (sender, receiver) = mpsc::unbounded(); let (oracle_mailbox, oracle_receiver) = UnboundedMailbox::new(); let sent_messages = Family::::default(); @@ -184,16 +200,15 @@ impl Network received_messages.clone(), ); - // Start with a pseudo-random IP address to assign sockets to for new peers - let next_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::from_bits(context.next_u32())), 0); - + let ephemeral_ports = base_addr.port() == 0; ( Self { context: ContextCell::new(context), max_size: cfg.max_size, disconnect_on_block: cfg.disconnect_on_block, tracked_peer_sets: cfg.tracked_peer_sets, - next_addr, + next_addr: base_addr, + ephemeral_ports, ingress: oracle_receiver, oracle_mailbox: oracle_mailbox.clone(), sender, @@ -219,6 +234,9 @@ impl Network /// number overflows. fn get_next_socket(&mut self) -> SocketAddr { let result = self.next_addr; + if self.ephemeral_ports { + return result; + } // Increment the port number, or the IP address if the port number overflows. // Allows the ip address to overflow (wrapping). @@ -493,6 +511,7 @@ impl Network .await; // Once ready, add to peers + let socket = peer.socket; self.peers.insert(public_key.clone(), peer); (socket, true) @@ -1156,13 +1175,16 @@ impl Peer

{ } }); + // Initialize listener and capture the bound address. + let listener = context.bind(socket).await.unwrap(); + let socket = listener.local_addr().unwrap_or(socket); + // Spawn a task that accepts new connections and spawns a task for each connection let (ready_tx, ready_rx) = oneshot::channel(); context .with_label("listener") .spawn(move |context| async move { - // Initialize listener - let mut listener = context.bind(socket).await.unwrap(); + let mut listener = listener; let _ = ready_tx.send(()); // Continually accept new connections