diff --git a/Cargo.lock b/Cargo.lock index fc46475fa..632e89037 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" @@ -101,11 +101,11 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.38" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156bfc5dcd52ef9a5f33381701fa03310317e14c65093a9430d3e3557b08dcd3" +checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "num_enum", "strum", ] @@ -117,7 +117,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae09ffd7c29062431dd86061deefe4e3c6f07fa0d674930095f8dcedb0baf02c" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "alloy-serde", "auto_impl", @@ -136,37 +136,37 @@ dependencies = [ "alloy-json-abi", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-provider", "alloy-rpc-types-eth", "alloy-sol-types", "alloy-transport", "futures", "futures-util", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] name = "alloy-core" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8316d83e590f4163b221b8180008f302bda5cf5451202855cdd323e588849c" +checksum = "c3d14d531c99995de71558e8e2206c27d709559ee8e5a0452b965ea82405a013" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "alloy-sol-types", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2364c782a245cf8725ea6dbfca5f530162702b5d685992ea03ce64529136cc" +checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-sol-type-parser", "alloy-sol-types", "const-hex", @@ -182,18 +182,18 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "serde", ] [[package]] name = "alloy-eip7702" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6cee6a35793f3db8a5ffe60e86c695f321d081a567211245f503e8c498fce8" +checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "derive_more 1.0.0", "serde", @@ -207,7 +207,7 @@ checksum = "5b6aa3961694b30ba53d41006131a2fca3bdab22e4c344e46db2c639e7c2dfdd" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "alloy-serde", "c-kzg", @@ -223,18 +223,18 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53f7877ded3921d18a0a9556d55bedf84535567198c9edab2aa23106da91855" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-serde", "serde", ] [[package]] name = "alloy-json-abi" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84c506bf264110fa7e90d9924f742f40ef53c6572ea56a0b0bd714a567ed389" +checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-sol-type-parser", "serde", "serde_json", @@ -246,11 +246,11 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3694b7e480728c0b3e228384f223937f14c10caef5a4c766021190fc8f283d35" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-sol-types", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", ] @@ -264,7 +264,7 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -274,7 +274,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -285,7 +285,7 @@ checksum = "df9f3e281005943944d15ee8491534a1c7b3cbf7a7de26f8c433b842b93eb5f9" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-serde", "serde", ] @@ -297,12 +297,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9805d126f24be459b958973c0569c73e1aadd27d4535eee82b2b6764aa03616" dependencies = [ "alloy-genesis", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "k256", "rand", "serde_json", "tempfile", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", "url", ] @@ -326,9 +326,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fce5dbd6a4f118eecc4719eaa9c7ffc31c315e6c5ccde3642db927802312425" +checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" dependencies = [ "alloy-rlp", "bytes", @@ -337,9 +337,9 @@ dependencies = [ "derive_more 1.0.0", "foldhash", "getrandom 0.2.15", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.0", "itoa", "k256", "keccak-asm", @@ -367,7 +367,7 @@ dependencies = [ "alloy-network", "alloy-network-primitives", "alloy-node-bindings", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rpc-client", "alloy-rpc-types-anvil", "alloy-rpc-types-eth", @@ -384,11 +384,11 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.12.8", + "reqwest 0.12.9", "schnellru", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tracing", "url", @@ -414,7 +414,7 @@ checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -424,12 +424,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "374dbe0dc3abdc2c964f36b3d3edf9cdb3db29d16bda34aa123f03d810bec1dd" dependencies = [ "alloy-json-rpc", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-transport", "alloy-transport-http", "futures", "pin-project", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde", "serde_json", "tokio", @@ -446,7 +446,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c74832aa474b670309c20fffc2a869fa141edab7c79ff7963fad0a08de60bae1" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -458,7 +458,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca97963132f78ddfc60e43a017348e6d52eea983925c23652f5b330e8e02291" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -473,7 +473,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-rlp", "alloy-serde", "alloy-sol-types", @@ -489,7 +489,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dfa4a7ccf15b2492bb68088692481fd6b2604ccbee1d0d6c44c21427ae4df83" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "serde", "serde_json", ] @@ -500,12 +500,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e10aec39d60dc27edcac447302c7803d2371946fb737245320a05b78eb2fafd" dependencies = [ - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "async-trait", "auto_impl", "elliptic-curve", "k256", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -516,52 +516,52 @@ checksum = "d8396f6dff60700bc1d215ee03d86ff56de268af96e2bf833a14d0bafcab9882" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-signer", "async-trait", "k256", "rand", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] name = "alloy-sol-macro" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343289b4a7461ed8bab8618504c995c049c082b70c7332efd7b32125633dc05" +checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4222d70bec485ceccc5d8fd4f2909edd65b5d5e43d4aca0b5dcee65d519ae98f" +checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.6.0", + "indexmap 2.7.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e17f2677369571b976e51ea1430eb41c3690d344fef567b840bfc0b01b6f83a" +checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" dependencies = [ "alloy-json-abi", "const-hex", @@ -570,15 +570,15 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.90", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64d80ae58ffaafdff9d5d84f58d03775f66c84433916dc9a64ed16af5755da" +checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" dependencies = [ "serde", "winnow", @@ -586,12 +586,12 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6520d427d4a8eb7aa803d852d7a52ceb0c519e784c292f64bb339e636918cf27" +checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.12", + "alloy-primitives 0.8.14", "alloy-sol-macro", "const-hex", "serde", @@ -609,7 +609,7 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tower", "tracing", @@ -625,7 +625,7 @@ checksum = "5dc013132e34eeadaa0add7e74164c1503988bfba8bae885b32e0918ba85a8a6" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest 0.12.8", + "reqwest 0.12.9", "serde_json", "tower", "tracing", @@ -668,9 +668,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -683,43 +683,43 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "ark-ff" @@ -891,7 +891,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -902,7 +902,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -935,7 +935,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -991,9 +991,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bb8" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10cf871f3ff2ce56432fddc2615ac7acc3aa22ca321f8fea800846fbb32f188" +checksum = "d89aabfae550a5c44b43ab941844ffcd2e993cb6900b342debf59e9ea74acdb8" dependencies = [ "async-trait", "futures-util", @@ -1124,9 +1124,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" dependencies = [ "serde", ] @@ -1184,9 +1184,9 @@ dependencies = [ [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ "serde", ] @@ -1202,14 +1202,14 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.1.30" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "jobserver", "libc", @@ -1247,9 +1247,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -1257,9 +1257,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -1276,14 +1276,14 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" [[package]] name = "coins-bip32" @@ -1298,7 +1298,7 @@ dependencies = [ "k256", "serde", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -1314,7 +1314,7 @@ dependencies = [ "pbkdf2 0.12.2", "rand", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -1334,14 +1334,14 @@ dependencies = [ "serde_derive", "sha2", "sha3", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" @@ -1368,9 +1368,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" +checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c" dependencies = [ "cfg-if", "cpufeatures", @@ -1435,9 +1435,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1506,9 +1506,9 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", @@ -1571,7 +1571,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1595,7 +1595,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1606,7 +1606,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1650,7 +1650,7 @@ checksum = "bc2323e10c92e1cf4d86e11538512e6dc03ceb586842970b6332af3d4046a046" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1694,7 +1694,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1714,7 +1714,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "unicode-xid", ] @@ -1728,7 +1728,7 @@ dependencies = [ "fuzzy-matcher", "shell-words", "tempfile", - "thiserror 1.0.64", + "thiserror 1.0.69", "zeroize", ] @@ -1795,6 +1795,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -1888,9 +1899,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -1921,7 +1932,7 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -1990,7 +2001,7 @@ checksum = "d4291f0c7220b67ad15e9d5300ba2f215cee504f0924d60e77c9d1c77e7a69b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2001,12 +2012,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2027,7 +2038,7 @@ dependencies = [ "serde_json", "sha2", "sha3", - "thiserror 1.0.64", + "thiserror 1.0.69", "uuid 0.8.2", ] @@ -2057,7 +2068,7 @@ dependencies = [ "serde", "serde_json", "sha3", - "thiserror 1.0.64", + "thiserror 1.0.69", "uint", ] @@ -2161,7 +2172,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -2180,7 +2191,7 @@ dependencies = [ "pin-project", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] @@ -2201,7 +2212,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.90", "toml", "walkdir", ] @@ -2225,7 +2236,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.90", "toml", "walkdir", ] @@ -2242,7 +2253,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2258,7 +2269,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2283,9 +2294,9 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.89", + "syn 2.0.90", "tempfile", - "thiserror 1.0.64", + "thiserror 1.0.69", "tiny-keccak", "unicode-xid", ] @@ -2313,9 +2324,9 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.89", + "syn 2.0.90", "tempfile", - "thiserror 1.0.64", + "thiserror 1.0.69", "tiny-keccak", "unicode-xid", ] @@ -2331,7 +2342,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", ] @@ -2347,7 +2358,7 @@ dependencies = [ "semver 1.0.23", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", ] @@ -2369,7 +2380,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -2396,7 +2407,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tracing", "tracing-futures", @@ -2427,7 +2438,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tracing", @@ -2464,7 +2475,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "thiserror 1.0.64", + "thiserror 1.0.69", "tokio", "tokio-tungstenite", "tracing", @@ -2490,7 +2501,7 @@ dependencies = [ "ethers-core 2.0.13", "rand", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", ] @@ -2509,7 +2520,7 @@ dependencies = [ "ethers-core 2.0.14", "rand", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", "tracing", ] @@ -2536,7 +2547,7 @@ dependencies = [ "serde_json", "solang-parser", "svm-rs", - "thiserror 1.0.64", + "thiserror 1.0.69", "tiny-keccak", "tokio", "tracing", @@ -2568,7 +2579,7 @@ dependencies = [ "serde_json", "solang-parser", "svm-rs", - "thiserror 1.0.64", + "thiserror 1.0.69", "tiny-keccak", "tokio", "tracing", @@ -2594,9 +2605,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fastrlp" @@ -2673,9 +2684,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.34" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" +checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", @@ -2809,7 +2820,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -2973,7 +2984,7 @@ dependencies = [ "revm", "serde", "serde_json", - "serial_test 3.1.1", + "serial_test 3.2.0", "sha2", "verifiable-db", ] @@ -3001,7 +3012,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.0", "slab", "tokio", "tokio-util", @@ -3034,9 +3045,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", @@ -3195,9 +3206,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", @@ -3219,9 +3230,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -3244,7 +3255,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "rustls", "tokio", "tokio-rustls", @@ -3257,7 +3268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.30", + "hyper 0.14.31", "native-tls", "tokio", "tokio-native-tls", @@ -3271,7 +3282,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-util", "native-tls", "tokio", @@ -3281,16 +3292,16 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.1", "pin-project-lite", "socket2", "tokio", @@ -3321,6 +3332,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -3329,12 +3458,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] @@ -3366,13 +3506,13 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -3394,12 +3534,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "serde", ] @@ -3490,9 +3630,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jammdb" @@ -3521,10 +3661,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3622,7 +3763,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.8", + "regex-automata 0.4.9", ] [[package]] @@ -3636,15 +3777,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.167" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" @@ -3662,6 +3803,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -3684,7 +3831,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.0", + "hashbrown 0.15.2", ] [[package]] @@ -3744,11 +3891,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", @@ -3765,7 +3911,7 @@ dependencies = [ "eth_trie", "ethereum-types", "ethers 2.0.13", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "hex", "itertools 0.13.0", "log", @@ -3800,6 +3946,7 @@ dependencies = [ "recursion_framework", "ryhope", "serde", + "tokio", ] [[package]] @@ -3817,7 +3964,7 @@ dependencies = [ "envconfig", "eth_trie", "futures", - "hashbrown 0.15.0", + "hashbrown 0.15.2", "hex", "itertools 0.13.0", "jammdb", @@ -3837,7 +3984,7 @@ dependencies = [ "ryhope", "serde", "serde_json", - "serial_test 3.1.1", + "serial_test 3.2.0", "sqlparser", "test-log", "testfile", @@ -4011,7 +4158,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4056,9 +4203,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -4077,7 +4224,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4088,9 +4235,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -4149,14 +4296,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.12" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] @@ -4267,7 +4414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", - "thiserror 1.0.64", + "thiserror 1.0.69", "ucd-trie", ] @@ -4278,7 +4425,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.0", ] [[package]] @@ -4321,7 +4468,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4344,29 +4491,29 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -4556,7 +4703,7 @@ dependencies = [ "starkyx", "tokio", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -4566,7 +4713,7 @@ source = "git+https://github.com/Lagrange-Labs/succinctx?branch=fix-build#8580a6 dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4636,12 +4783,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ "proc-macro2", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -4720,14 +4867,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "proc-macro2" -version = "1.0.91" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -4855,7 +5002,7 @@ dependencies = [ "plonky2_monolith", "rstest 0.23.0", "serde", - "serial_test 3.1.1", + "serial_test 3.2.0", ] [[package]] @@ -4875,18 +5022,18 @@ checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", - "thiserror 1.0.64", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.8", + "regex-automata 0.4.9", "regex-syntax 0.8.5", ] @@ -4901,9 +5048,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -4942,7 +5089,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls", "hyper-tls 0.5.0", "ipnet", @@ -4974,9 +5121,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -4985,7 +5132,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.1", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -5000,7 +5147,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", "tower-service", @@ -5185,7 +5332,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.90", "unicode-ident", ] @@ -5210,7 +5357,7 @@ dependencies = [ "rlp", "ruint-macro", "serde", - "thiserror 1.0.64", + "thiserror 1.0.69", "valuable", "zeroize", ] @@ -5229,9 +5376,9 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustc-hex" @@ -5259,9 +5406,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags 2.6.0", "errno", @@ -5383,42 +5530,42 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" dependencies = [ "cfg-if", - "derive_more 0.99.18", + "derive_more 1.0.0", "parity-scale-codec", "scale-info-derive", ] [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "scc" -version = "2.2.2" +version = "2.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c1f7fc6deb21665a9060dfc7d271be784669295a31babdcd4dd2c79ae8cbfb" +checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" dependencies = [ "sdd", ] [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -5497,9 +5644,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" dependencies = [ "core-foundation-sys", "libc", @@ -5525,9 +5672,9 @@ dependencies = [ [[package]] name = "semver-parser" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" dependencies = [ "pest", ] @@ -5546,29 +5693,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", @@ -5632,7 +5779,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_derive", "serde_json", @@ -5649,7 +5796,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5661,7 +5808,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5680,16 +5827,16 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" dependencies = [ "futures", "log", "once_cell", "parking_lot", "scc", - "serial_test_derive 3.1.1", + "serial_test_derive 3.2.0", ] [[package]] @@ -5705,13 +5852,13 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "3.1.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -5817,7 +5964,7 @@ checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint 0.4.6", "num-traits", - "thiserror 1.0.64", + "thiserror 1.0.69", "time", ] @@ -5855,9 +6002,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5873,7 +6020,7 @@ dependencies = [ "lalrpop", "lalrpop-util", "phf", - "thiserror 1.0.64", + "thiserror 1.0.69", "unicode-xid", ] @@ -5908,6 +6055,12 @@ dependencies = [ "log", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "starkyx" version = "0.1.0" @@ -5995,7 +6148,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6041,7 +6194,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 1.0.64", + "thiserror 1.0.69", "url", "zip", ] @@ -6059,9 +6212,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -6070,14 +6223,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76fe0a3e1476bdaa0775b9aec5b869ed9520c2b2fedfe9c6df3618f8ea6290b" +checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6088,13 +6241,24 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -6149,9 +6313,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -6199,7 +6363,7 @@ checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6213,11 +6377,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.64", + "thiserror-impl 1.0.69", ] [[package]] @@ -6231,13 +6395,13 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6248,7 +6412,7 @@ checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6310,6 +6474,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -6327,9 +6501,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.41.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" dependencies = [ "backtrace", "bytes", @@ -6351,7 +6525,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] @@ -6467,7 +6641,7 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.0", "serde", "serde_spanned", "toml_datetime", @@ -6502,9 +6676,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -6513,20 +6687,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -6555,9 +6729,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -6591,7 +6765,7 @@ dependencies = [ "rand", "rustls", "sha1", - "thiserror 1.0.64", + "thiserror 1.0.69", "url", "utf-8", ] @@ -6634,9 +6808,9 @@ checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-normalization" @@ -6689,9 +6863,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -6704,6 +6878,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -6722,9 +6908,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "serde", ] @@ -6762,7 +6948,7 @@ dependencies = [ "recursion_framework", "ryhope", "serde", - "serial_test 3.1.1", + "serial_test 3.2.0", "tokio", ] @@ -6841,9 +7027,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" dependencies = [ "cfg-if", "once_cell", @@ -6852,36 +7038,37 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.45" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6889,22 +7076,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" [[package]] name = "wasmtimer" @@ -6922,9 +7109,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.72" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" dependencies = [ "js-sys", "wasm-bindgen", @@ -7194,6 +7381,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -7207,7 +7406,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper 0.6.0", - "thiserror 1.0.64", + "thiserror 1.0.69", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -7228,6 +7427,30 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -7246,7 +7469,28 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", + "synstructure", ] [[package]] @@ -7266,7 +7510,29 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.90", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 885c85707..4dd4f5736 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ alloy = { version = "0.6", default-features = false, features = [ "transports", "postgres", ] } + anyhow = "1.0" base64 = "0.22" bb8 = "0.8.5" diff --git a/mp2-common/Cargo.toml b/mp2-common/Cargo.toml index 5f9d71079..1be109995 100644 --- a/mp2-common/Cargo.toml +++ b/mp2-common/Cargo.toml @@ -31,7 +31,6 @@ hex.workspace = true rand.workspace = true rstest.workspace = true tokio.workspace = true - mp2_test = { path = "../mp2-test" } [features] diff --git a/mp2-common/src/array.rs b/mp2-common/src/array.rs index ffcee5aa9..d88f694fb 100644 --- a/mp2-common/src/array.rs +++ b/mp2-common/src/array.rs @@ -1,6 +1,9 @@ use crate::{ serialization::{deserialize_long_array, serialize_long_array}, - utils::{less_than_or_equal_to_unsafe, range_check_optimized, Endianness, PackerTarget}, + utils::{ + less_than_or_equal_to_unsafe, less_than_unsafe, range_check_optimized, Endianness, + PackerTarget, + }, }; use anyhow::{anyhow, Result}; use plonky2::{ @@ -600,6 +603,110 @@ where pub fn last(&self) -> T { self.arr[SIZE - 1] } + + /// This function allows you to search a larger [`Array`] by representing it as a number of + /// smaller [`Array`]s with size [`RANDOM_ACCESS_SIZE`], padding the final smaller array where required. + /// For example if we have an array of length `512` and we wish to find the value at index `324` the following + /// occurs: + /// 1) Split the original [`Array`] into `512 / 64 = 8` chunks `[A_0, ... , A_7]` + /// 2) Express `324` in base 64 (Little Endian) `[4, 5]` + /// 3) For each `i \in [0, 7]` use a [`RandomAccesGate`] to lookup the `4`th element, `v_i,3` of `A_i` + /// and create a new list of length `8` that consists of `[v_0,3, v_1,3, ... v_7,3]` + /// 4) Now use another [`RandomAccessGate`] to select the `5`th elemnt of this new list (`v_4,3` as we have zero-indexed both times) + /// + /// For comparison using [`Self::value_at`] on an [`Array`] with length `512` results in 129 rows, using this method + /// on the same [`Array`] results in 15 rows. + /// + /// As an aside, if the [`Array`] length is not divisible by `64` then we pad with zero values, since the size of the + /// [`Array`] is a compile time constant this will not affect circuit preprocessing. + pub fn random_access_large_array, const D: usize>( + &self, + b: &mut CircuitBuilder, + at: Target, + ) -> T { + // We will split the array into smaller arrays of size 64, padding the last array with zeroes if required + let padded_size = (SIZE - 1) / RANDOM_ACCESS_SIZE + 1; + + // Create an array of `Array`s + let arrays: Vec> = (0..padded_size) + .map(|i| Array { + arr: create_array(|j| { + let index = RANDOM_ACCESS_SIZE * i + j; + if index < self.arr.len() { + self.arr[index] + } else { + T::from_target(b.zero()) + } + }), + }) + .collect(); + + // We need to express `at` in base 64, we are also assuming that the initial array was smaller than 64^2 = 4096 which we enforce with a range check. + // We also check that `at` is smaller that the size of the array. + let array_size = b.constant(F::from_noncanonical_u64(SIZE as u64)); + let less_than_check = less_than_unsafe(b, at, array_size, 12); + let true_target = b._true(); + b.connect(less_than_check.target, true_target.target); + + let (low_bits, high_bits) = b.split_low_high(at, 6, 12); + + // Search each of the smaller arrays for the target at `low_bits` + let mut first_search = arrays + .into_iter() + .map(|array| { + b.random_access( + low_bits, + array + .arr + .iter() + .map(Targetable::to_target) + .collect::>(), + ) + }) + .collect::>(); + + // Now we push a number of zero targets into the array to make it a power of 2 + let next_power_of_two = first_search.len().next_power_of_two(); + let zero_target = b.zero(); + first_search.resize(next_power_of_two, zero_target); + // Serach the result for the Target at `high_bits` + T::from_target(b.random_access(high_bits, first_search)) + } + + /// Returns [`Self[at..at+SUB_SIZE]`]. + /// This is more expensive than [`Self::extract_array`] for [`Array`]s that are shorter than 64 elements long due to using [`Self::random_access_large_array`] + /// instead of [`Self::value_at`]. This function enforces that the values extracted are within the array. + /// + /// For comparison usin [`Self::extract_array`] on an [`Array`] of size `512` results in 5179 rows, using this method instead + /// results in 508 rows. + pub fn extract_array_large< + F: RichField + Extendable, + const D: usize, + const SUB_SIZE: usize, + >( + &self, + b: &mut CircuitBuilder, + at: Target, + ) -> Array { + let m = b.constant(F::from_canonical_usize(SUB_SIZE)); + let array_len = b.constant(F::from_canonical_usize(SIZE)); + let upper_bound = b.add(at, m); + let num_bits_size = SIZE.ilog2() + 1; + + let lt = less_than_or_equal_to_unsafe(b, upper_bound, array_len, num_bits_size as usize); + + let t = b._true(); + b.connect(t.target, lt.target); + + Array:: { + arr: core::array::from_fn(|i| { + let i_target = b.constant(F::from_canonical_usize(i)); + let i_plus_n_target = b.add(at, i_target); + + self.random_access_large_array(b, i_plus_n_target) + }), + } + } } /// Returns the size of the array in 32-bit units, rounded up. #[allow(non_snake_case)] @@ -815,6 +922,53 @@ mod test { run_circuit::(ValueAtCircuit { arr, idx, exp }); } + #[test] + fn test_random_access_large_array() { + const SIZE: usize = 512; + #[derive(Clone, Debug)] + struct ValueAtCircuit { + arr: [u8; SIZE], + idx: usize, + exp: u8, + } + impl UserCircuit for ValueAtCircuit + where + F: RichField + Extendable, + { + type Wires = (Array, Target, Target); + fn build(c: &mut CircuitBuilder) -> Self::Wires { + let array = Array::::new(c); + let exp_value = c.add_virtual_target(); + let index = c.add_virtual_target(); + let extracted = array.random_access_large_array(c, index); + c.connect(exp_value, extracted); + + (array, index, exp_value) + } + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + wires + .0 + .assign(pw, &create_array(|i| F::from_canonical_u8(self.arr[i]))); + pw.set_target(wires.1, F::from_canonical_usize(self.idx)); + pw.set_target(wires.2, F::from_canonical_u8(self.exp)); + } + } + + let mut rng = thread_rng(); + let mut arr = [0u8; SIZE]; + rng.fill(&mut arr[..]); + let idx: usize = rng.gen_range(0..SIZE); + let exp = arr[idx]; + run_circuit::(ValueAtCircuit { arr, idx, exp }); + + // Now we check that it fails when the index is too large + let idx = SIZE; + let result = std::panic::catch_unwind(|| { + run_circuit::(ValueAtCircuit { arr, idx, exp }) + }); + assert!(result.is_err()); + } + #[test] fn test_extract_array() { const SIZE: usize = 80; @@ -858,6 +1012,57 @@ mod test { run_circuit::(ExtractArrayCircuit { arr, idx, exp }); } + #[test] + fn test_extract_array_large() { + const SIZE: usize = 512; + const SUBSIZE: usize = 40; + #[derive(Clone, Debug)] + struct ExtractArrayCircuit { + arr: [u8; SIZE], + idx: usize, + exp: [u8; SUBSIZE], + } + impl UserCircuit for ExtractArrayCircuit + where + F: RichField + Extendable, + { + type Wires = (Array, Target, Array); + fn build(c: &mut CircuitBuilder) -> Self::Wires { + let array = Array::::new(c); + let index = c.add_virtual_target(); + let expected = Array::::new(c); + let extracted = array.extract_array_large::<_, _, SUBSIZE>(c, index); + let are_equal = expected.equals(c, &extracted); + let tru = c._true(); + c.connect(are_equal.target, tru.target); + (array, index, expected) + } + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + wires + .0 + .assign(pw, &create_array(|i| F::from_canonical_u8(self.arr[i]))); + pw.set_target(wires.1, F::from_canonical_usize(self.idx)); + wires + .2 + .assign(pw, &create_array(|i| F::from_canonical_u8(self.exp[i]))); + } + } + + let mut rng = thread_rng(); + let mut arr = [0u8; SIZE]; + rng.fill(&mut arr[..]); + let idx: usize = rng.gen_range(0..(SIZE - SUBSIZE)); + let exp = create_array(|i| arr[idx + i]); + run_circuit::(ExtractArrayCircuit { arr, idx, exp }); + + // It should panic if we try to extract an array where some of the indices fall outside of (0..SIZE) + let idx = SIZE; + let result = std::panic::catch_unwind(|| { + run_circuit::(ExtractArrayCircuit { arr, idx, exp }) + }); + assert!(result.is_err()); + } + #[test] fn test_contains_subarray() { #[derive(Clone, Debug)] @@ -1088,7 +1293,10 @@ mod test { }; run_circuit::(circuit); - arr2[0] += 1; // ensure arr2 is different from arr + arr2[0] = match arr2[0].checked_add(1) { + Some(num) => num, + None => arr2[0] - 1, + }; let res = panic::catch_unwind(|| { let circuit = TestSliceEqual { arr, diff --git a/mp2-common/src/eth.rs b/mp2-common/src/eth.rs index ba863d475..a0494d993 100644 --- a/mp2-common/src/eth.rs +++ b/mp2-common/src/eth.rs @@ -1,27 +1,48 @@ //! Module containing several structure definitions for Ethereum related operations //! such as fetching blocks, transactions, creating MPTs, getting proofs, etc. use alloy::{ + consensus::{ReceiptEnvelope as CRE, ReceiptWithBloom}, eips::BlockNumberOrTag, + network::{eip2718::Encodable2718, BlockResponse}, primitives::{Address, B256}, providers::{Provider, RootProvider}, - rlp::Encodable as AlloyEncodable, - rpc::types::{Block, EIP1186AccountProofResponse}, + rlp::{Decodable, Encodable as AlloyEncodable}, + rpc::types::{ + Block, BlockTransactions, EIP1186AccountProofResponse, Filter, ReceiptEnvelope, Transaction, + }, transports::Transport, }; -use anyhow::{bail, Result}; +use anyhow::{anyhow, bail, Context, Result}; use eth_trie::{EthTrie, MemoryDB, Trie}; use ethereum_types::H256; use log::warn; -use rlp::Rlp; + +use rlp::{Encodable, Rlp}; use serde::{Deserialize, Serialize}; -use std::{array::from_fn as create_array, sync::Arc}; +use std::{ + array::from_fn as create_array, + collections::{BTreeSet, HashMap}, + sync::Arc, +}; -use crate::{mpt_sequential::utils::bytes_to_nibbles, rlp::MAX_KEY_NIBBLE_LEN, utils::keccak256}; +use crate::{ + keccak::HASH_LEN, + mpt_sequential::utils::bytes_to_nibbles, + rlp::MAX_KEY_NIBBLE_LEN, + serialization::{deserialize_long_array, serialize_long_array}, + utils::keccak256, +}; /// Retry number for the RPC request const RETRY_NUM: usize = 3; -pub trait BlockUtil { +/// The maximum size an additional piece of data can be in bytes. +const MAX_DATA_SIZE: usize = 32; + +/// The size of an event topic rlp encoded. +const ENCODED_TOPIC_SIZE: usize = 33; + +pub trait Rlpable { fn block_hash(&self) -> Vec { keccak256(&self.rlp()) } @@ -111,6 +132,104 @@ pub struct ProofQuery { pub(crate) slot: StorageSlot, } +/// Struct used for storing relevant data to query blocks as they come in. +/// The constant `NO_TOPICS` is the number of indexed items in the event (excluding the event signature) and +/// `MAX_DATA` is the number of 32 byte words of data we expect in addition to the topics. +#[derive(Debug, Clone)] +pub struct ReceiptQuery { + /// The contract that emits the event we care about + pub contract: Address, + /// The signature of the event we wish to monitor for + pub event: EventLogInfo, +} + +/// Struct used to store all the information needed for proving a leaf is in the Receipt Trie. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReceiptProofInfo { + /// The MPT proof that this Receipt is in the tree + pub mpt_proof: Vec>, + /// The root of the Receipt Trie this receipt belongs to + pub mpt_root: H256, + /// The index of this transaction in the block + pub tx_index: u64, +} + +/// Contains all the information for an [`Event`] in rlp form +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct EventLogInfo { + /// Size in bytes of the whole log rlp encoded + pub size: usize, + /// Packed contract address to check + pub address: Address, + /// Byte offset for the address from the beginning of a Log + pub add_rel_offset: usize, + /// Packed event signature, + pub event_signature: [u8; HASH_LEN], + /// Byte offset from the start of the log to event signature + pub sig_rel_offset: usize, + /// The the offsets to the other topics for this Log + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + pub topics: [usize; NO_TOPICS], + /// The offsets to the start of the extra data stored by this Log + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + pub data: [usize; MAX_DATA], +} + +impl EventLogInfo { + /// Create a new instance from a contract [`Address`] and a [`str`] that is the event signature + pub fn new(contract: Address, event_signature: &str) -> Self { + // To calculate the total size of the log rlp encoded we use the fact that the address takes 21 bytes to encode, topics + // take 33 bytes each to incode and form a list that has length between 33 bytes and 132 bytes and data is a string that has 32 * MAX_DATA length + + // If we have more than one topic that is not the event signature the rlp encoding is a list that is over 55 bytes whose total length can be encoded in one byte, so the header length is 2 + // Otherwise its still a list but the header is a single byte. + let topics_header_len = alloy::rlp::length_of_length((1 + NO_TOPICS) * ENCODED_TOPIC_SIZE); + + // If the we have more than one piece of data it is rlp encoded as a string with length greater than 55 bytes + let data_header_len = alloy::rlp::length_of_length(MAX_DATA * MAX_DATA_SIZE); + + let address_size = 21; + let topics_size = (1 + NO_TOPICS) * ENCODED_TOPIC_SIZE + topics_header_len; + let data_size = MAX_DATA * MAX_DATA_SIZE + data_header_len; + + let payload_size = address_size + topics_size + data_size; + let header_size = alloy::rlp::length_of_length(payload_size); + + let size = header_size + payload_size; + + // The address itself starts after the header plus one byte for the address header. + let add_rel_offset = header_size + 1; + + // The event signature offset is after the header, the address and the topics list header. + let sig_rel_offset = header_size + address_size + topics_header_len + 1; + + let topics: [usize; NO_TOPICS] = + create_array(|i| sig_rel_offset + (i + 1) * ENCODED_TOPIC_SIZE); + + let data: [usize; MAX_DATA] = create_array(|i| { + header_size + address_size + topics_size + data_header_len + (i * MAX_DATA_SIZE) + }); + + let event_sig = alloy::primitives::keccak256(event_signature.as_bytes()); + + Self { + size, + address: contract, + add_rel_offset, + event_signature: event_sig.0, + sig_rel_offset, + topics, + data, + } + } +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub enum StorageSlot { /// simple storage slot like a uin256 etc that fits in 32bytes @@ -252,7 +371,71 @@ impl ProofQuery { } } -impl BlockUtil for alloy::rpc::types::Block { +impl ReceiptProofInfo { + pub fn to_receipt(&self) -> Result { + let memdb = Arc::new(MemoryDB::new(true)); + let tx_trie = EthTrie::new(Arc::clone(&memdb)); + + let mpt_key = self.tx_index.rlp_bytes(); + + let valid = tx_trie + .verify_proof(self.mpt_root, &mpt_key, self.mpt_proof.clone())? + .ok_or(anyhow!("No proof found when verifying"))?; + + let rlp_receipt = rlp::Rlp::new(&valid[1..]); + ReceiptWithBloom::decode(&mut rlp_receipt.as_raw()) + .map_err(|e| anyhow!("Could not decode receipt got: {}", e)) + } +} + +impl ReceiptQuery { + /// Construct a new [`ReceiptQuery`] from the contract [`Address`] and the event's name as a [`str`]. + pub fn new(contract: Address, event_name: &str) -> Self { + Self { + contract, + event: EventLogInfo::::new(contract, event_name), + } + } + + /// Function that returns the MPT Trie inclusion proofs for all receipts in a block whose logs contain + /// the specified event for the contract. + pub async fn query_receipt_proofs( + &self, + provider: &RootProvider, + block: BlockNumberOrTag, + ) -> Result> { + let filter = Filter::new() + .select(block) + .address(self.contract) + .event_signature(B256::from(self.event.event_signature)); + let logs = provider.get_logs(&filter).await?; + + // For each of the logs return the transacion its included in, then sort and remove duplicates. + let tx_indices = BTreeSet::from_iter(logs.iter().map_while(|log| log.transaction_index)); + + // Construct the Receipt Trie for this block so we can retrieve MPT proofs. + let mut block_util = BlockUtil::fetch(provider, block).await?; + let mpt_root = block_util.receipts_trie.root_hash()?; + let proofs = tx_indices + .into_iter() + .map(|tx_index| { + let key = tx_index.rlp_bytes(); + + let proof = block_util.receipts_trie.get_proof(&key[..])?; + + Ok(ReceiptProofInfo { + mpt_proof: proof, + mpt_root, + tx_index, + }) + }) + .collect::, eth_trie::TrieError>>()?; + + Ok(proofs) + } +} + +impl Rlpable for alloy::rpc::types::Block { fn rlp(&self) -> Vec { let mut out = Vec::new(); self.header.encode(&mut out); @@ -260,7 +443,13 @@ impl BlockUtil for alloy::rpc::types::Block { } } -impl BlockUtil for alloy::rpc::types::Header { +impl Rlpable for alloy::rpc::types::Header { + fn rlp(&self) -> Vec { + self.inner.rlp() + } +} + +impl Rlpable for alloy::consensus::Header { fn rlp(&self) -> Vec { let mut out = Vec::new(); self.encode(&mut out); @@ -268,13 +457,137 @@ impl BlockUtil for alloy::rpc::types::Header { } } +pub struct BlockUtil { + pub block: Block, + pub txs: Vec, + pub receipts_trie: EthTrie, + pub transactions_trie: EthTrie, +} + +pub struct TxWithReceipt(Transaction, ReceiptEnvelope); +impl TxWithReceipt { + pub fn receipt(&self) -> &ReceiptEnvelope { + &self.1 + } + pub fn transaction(&self) -> &Transaction { + &self.0 + } +} + +impl BlockUtil { + pub async fn fetch( + t: &RootProvider, + id: BlockNumberOrTag, + ) -> Result { + let block = t + .get_block(id.into(), alloy::rpc::types::BlockTransactionsKind::Full) + .await? + .context("can't get block")?; + let receipts = t + .get_block_receipts(id.into()) + .await? + .context("can't get receipts")?; + let BlockTransactions::Full(all_tx) = block.transactions() else { + bail!("can't see full transactions"); + }; + // check receipt root + let all_tx_map = HashMap::::from_iter( + all_tx + .iter() + .map_while(|tx| tx.transaction_index.map(|tx_index| (tx_index, tx))), + ); + let memdb = Arc::new(MemoryDB::new(true)); + let mut receipts_trie = EthTrie::new(memdb.clone()); + let mut transactions_trie = EthTrie::new(memdb.clone()); + let consensus_receipts = receipts + .into_iter() + .map(|receipt| { + let tx_index_u64 = receipt.transaction_index.unwrap(); + // If the HashMap doesn't have an entry for this tx_index then the recceipts and transactions aren't from the same block. + let transaction = all_tx_map.get(&tx_index_u64).cloned().unwrap(); + let tx_index = tx_index_u64.rlp_bytes(); + + let receipt_primitive = match receipt.inner { + CRE::Legacy(ref r) => CRE::Legacy(from_rpc_logs_to_consensus(r)), + CRE::Eip2930(ref r) => CRE::Eip2930(from_rpc_logs_to_consensus(r)), + CRE::Eip1559(ref r) => CRE::Eip1559(from_rpc_logs_to_consensus(r)), + CRE::Eip4844(ref r) => CRE::Eip4844(from_rpc_logs_to_consensus(r)), + CRE::Eip7702(ref r) => CRE::Eip7702(from_rpc_logs_to_consensus(r)), + _ => panic!("aie"), + }; + + let body_rlp = receipt_primitive.encoded_2718(); + + let tx_body_rlp = transaction.inner.encoded_2718(); + + receipts_trie + .insert(&tx_index, &body_rlp) + .expect("can't insert receipt"); + transactions_trie + .insert(&tx_index, &tx_body_rlp) + .expect("can't insert transaction"); + TxWithReceipt(transaction.clone(), receipt_primitive) + }) + .collect::>(); + receipts_trie.root_hash()?; + transactions_trie.root_hash()?; + Ok(BlockUtil { + block, + txs: consensus_receipts, + receipts_trie, + transactions_trie, + }) + } + + // recompute the receipts trie by first converting all receipts form RPC type to consensus type + // since in Alloy these are two different types and RLP functions are only implemented for + // consensus ones. + pub fn check(&mut self) -> Result<()> { + let computed = self.receipts_trie.root_hash()?; + let tx_computed = self.transactions_trie.root_hash()?; + let expected = self.block.header.receipts_root; + let tx_expected = self.block.header.transactions_root; + assert_eq!(expected.0, computed.0); + assert_eq!(tx_expected.0, tx_computed.0); + Ok(()) + } +} + +fn from_rpc_logs_to_consensus( + r: &ReceiptWithBloom, +) -> ReceiptWithBloom { + ReceiptWithBloom { + logs_bloom: r.logs_bloom, + receipt: alloy::consensus::Receipt { + status: r.receipt.status, + cumulative_gas_used: r.receipt.cumulative_gas_used, + logs: r + .receipt + .logs + .iter() + .map(|l| alloy::primitives::Log { + address: l.inner.address, + data: l.inner.data.clone(), + }) + .collect(), + }, + } +} + #[cfg(test)] mod test { #[cfg(feature = "ci")] use std::env; use std::str::FromStr; - use alloy::{primitives::Bytes, providers::ProviderBuilder}; + use alloy::{ + network::TransactionResponse, + primitives::{Bytes, Log}, + providers::{Provider, ProviderBuilder}, + rlp::Decodable, + }; + + use eth_trie::Nibbles; use ethereum_types::U64; use ethers::{ providers::{Http, Middleware}, @@ -283,38 +596,235 @@ mod test { use hashbrown::HashMap; use crate::{ - types::MAX_BLOCK_LEN, + mpt_sequential::utils::nibbles_to_bytes, utils::{Endianness, Packer}, }; - use mp2_test::eth::{get_mainnet_url, get_sepolia_url}; + use mp2_test::{ + eth::{get_mainnet_url, get_sepolia_url}, + mpt_sequential::generate_receipt_test_info, + }; + + use super::*; #[tokio::test] - #[ignore] - async fn test_rlp_andrus() -> Result<()> { + async fn test_block_receipt_trie() -> Result<()> { let url = get_sepolia_url(); - let block_number1 = 5674446; - let block_number2 = block_number1 + 1; + // get some tx and receipt let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); - let block = provider - .get_block(BlockNumberOrTag::Number(block_number1).into(), false.into()) - .await? - .unwrap(); - let comp_hash = keccak256(&block.rlp()); - let block_next = provider - .get_block(BlockNumberOrTag::from(block_number2).into(), false.into()) - .await? - .unwrap(); - let exp_hash = block_next.header.parent_hash; - assert!(comp_hash == exp_hash.as_slice()); - assert!( - block.rlp().len() <= MAX_BLOCK_LEN, - " rlp len = {}", - block.rlp().len() + let bn = 6893107; + let bna = BlockNumberOrTag::Number(bn); + let mut block = BlockUtil::fetch(&provider, bna).await?; + // check if we compute the RLP correctly now + block.check()?; + let mut be = tryethers::BlockData::fetch(bn, url).await?; + be.check()?; + let er = be.receipts_trie.root_hash()?; + let ar = block.receipts_trie.root_hash()?; + assert_eq!(er, ar); + // dissect one receipt entry in the trie + let tx_receipt = block.txs.first().unwrap(); + // https://sepolia.etherscan.io/tx/0x9bef12fafd3962b0e0d66b738445d6ea2c1f3daabe10c889bd1916acc75d698b#eventlog + println!( + "Looking at tx hash on sepolia: {}", + hex::encode(tx_receipt.0.tx_hash()) + ); + // in the MPT trie it's + // RLP ( RLP(Index), RLP ( DATA )) + // the second component is done like that: + // DATA = RLP [ Rlp(status), Rlp(gas_used), Rlp(logs_bloom), Rlp(logs) ] + // it contains multiple logs so + // logs = RLP_LIST(RLP(logs[0]), RLP(logs[1])...) + // Each RLP(logs[0]) = RLP([ RLP(Address), RLP(topics), RLP(data)]) + // RLP(topics) is a list with up to 4 topics + let rlp_encoding = tx_receipt.receipt().encoded_2718(); + println!( + "Size of RLP encoded receipt in bytes: {}", + rlp_encoding.len() + ); + let state = rlp::Rlp::new(&rlp_encoding); + assert!(state.is_list()); + // index 0 -> status, + // index 1 -> gas used + // index 2 -> logs_bloom + // index 3 -> logs + let gas_used: Vec = state.val_at(1).context("can't access gas used")?; + println!("gas used byte length: {}", gas_used.len()); + let bloom: Vec = state.val_at(2).context("can't access bloom")?; + println!("bloom byte length: {}", bloom.len()); + //let logs: Vec> = state.list_at(3).context("can't access logs")?; + //println!("logs byte length: {}", logs.len()); + + let logs_state = state.at(3).context("can't access logs field3")?; + assert!(logs_state.is_list()); + println!("logs in hex: {}", hex::encode(logs_state.data()?)); + let log_state = logs_state.at(0).context("can't access single log state")?; + assert!(log_state.is_list()); + // log: + // 0: address where it has been emitted + // 1: Topics (4 topics max, with 1 mandatory, the event sig) + // 2: Bytes32 array + let log_address: Vec = log_state.val_at(0).context("can't decode address")?; + let hex_address = hex::encode(&log_address); + assert_eq!( + hex_address, + "BBd3EDd4D3b519c0d14965d9311185CFaC8c3220".to_lowercase(), ); + // the topics are in a list + let topics: Vec> = log_state.list_at(1).context("can't decode topics")?; + // Approval (index_topic_1 address owner, index_topic_2 address approved, index_topic_3 uint256 tokenId)View Source + // first topic is signature of the event keccak(fn_name,args...) + let expected_sig = "8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"; + let found_sig = hex::encode(&topics[0]); + assert_eq!(expected_sig, found_sig); + // second topic is owner + let expected_owner = hex::encode(left_pad32(&hex::decode( + "66d2F437a12d8f9f340C226b1EDC605124e763A6", + )?)); + let found_owner = hex::encode(&topics[1]); + assert_eq!(expected_owner, found_owner); + // third topic is approved + let expected_approved = hex::encode(left_pad32(&hex::decode( + "094f1570A8B5fc99d6756aD54DF0Fd6906795cd3", + )?)); + let found_approved = hex::encode(left_pad32(&topics[2])); + assert_eq!(expected_approved, found_approved); + // final is tokenid - not in topic + let expected_data = "000000000000000000000000000000000000000000115eec47f6cf7e35000000"; + let log_data: Vec = log_state.val_at(2).context("can't decode log data")?; + let found_data = hex::encode(left_pad32( + &log_data.into_iter().take(32).collect::>(), + )); + assert_eq!(expected_data, found_data); + + let mpt_key = tx_receipt.0.transaction_index.unwrap(); + let proof = block + .receipts_trie + .get_proof(&mpt_key.rlp_bytes()) + .expect("can't retrieve mpt proof"); + let mpt_node = proof.last().unwrap(); + println!("MPT LEAF NODE: {:?}", mpt_node); + // First decode the top level header + let top_header = rlp::Rlp::new(mpt_node); + assert!(top_header.is_list()); + // then extract the buffer containing all elements (key and value) + let top_info = top_header.payload_info()?; + println!("TOP level header: {:?}", top_info); + let list_buff = &mpt_node[top_info.header_len..top_info.header_len + top_info.value_len]; + // then check the key and make sure it's equal to the RLP encoding of the index + let key_header = rlp::Rlp::new(list_buff); + assert!(!key_header.is_list()); + // key is RLP( compact ( RLP(index))) + let key_info = key_header.payload_info()?; + let compact_key = &list_buff[key_info.header_len..key_info.header_len + key_info.value_len]; + let decoded_key = rlp::encode(&nibbles_to_bytes( + Nibbles::from_compact(compact_key).nibbles(), + )); + assert_eq!(decoded_key, &mpt_key.rlp_bytes().to_vec(),); + + // then check if the value portion fits what we tested above + // value is RLP ( RLP(status, etc...)) + let outer_value_min = top_info.header_len + key_info.header_len + key_info.value_len; + let outer_value_buff = &mpt_node[outer_value_min..]; + let outer_value_state = rlp::Rlp::new(outer_value_buff); + assert!(!outer_value_state.is_list()); + let outer_payload = outer_value_state.payload_info()?; + let inner_value_min = outer_value_min + outer_payload.header_len; + let inner_value_buff = &mpt_node[inner_value_min..]; + assert_eq!(rlp_encoding, inner_value_buff); + Ok(()) + } + + #[test] + fn test_receipt_query() -> Result<()> { + test_receipt_query_helper::<1, 0>()?; + test_receipt_query_helper::<2, 0>()?; + test_receipt_query_helper::<3, 0>()?; + test_receipt_query_helper::<3, 1>()?; + test_receipt_query_helper::<3, 2>() + } + + fn test_receipt_query_helper() -> Result<()> { + // Now for each transaction we fetch the block, then get the MPT Trie proof that the receipt is included and verify it + let test_info = generate_receipt_test_info::(); + let proofs = test_info.proofs(); + let query = test_info.query(); + for proof in proofs.iter() { + let memdb = Arc::new(MemoryDB::new(true)); + let tx_trie = EthTrie::new(Arc::clone(&memdb)); + + let mpt_key = proof.tx_index.rlp_bytes(); + + let _ = tx_trie + .verify_proof(proof.mpt_root, &mpt_key, proof.mpt_proof.clone())? + .ok_or(anyhow!("No proof found when verifying"))?; + + let last_node = proof + .mpt_proof + .last() + .ok_or(anyhow!("Couldn't get first node in proof"))?; + let expected_sig: [u8; 32] = query.event.event_signature; + + // Convert to Rlp form so we can use provided methods. + let node_rlp = rlp::Rlp::new(last_node); + + // The actual receipt data is item 1 in the list + let (receipt_rlp, receipt_off) = node_rlp.at_with_offset(1)?; + // The rlp encoded Receipt is not a list but a string that is formed of the `tx_type` followed by the remaining receipt + // data rlp encoded as a list. We retrieve the payload info so that we can work out relevant offsets later. + let receipt_str_payload = receipt_rlp.payload_info()?; + + // We make a new `Rlp` struct that should be the encoding of the inner list representing the `ReceiptEnvelope` + let receipt_list = rlp::Rlp::new(&receipt_rlp.data()?[1..]); + + // The logs themselves start are the item at index 3 in this list + let (logs_rlp, logs_off) = receipt_list.at_with_offset(3)?; + + // We calculate the offset the that the logs are at from the start of the node + let logs_offset = receipt_off + receipt_str_payload.header_len + 1 + logs_off; + + // Now we produce an iterator over the logs with each logs offset. + let relevant_logs_offset = std::iter::successors(Some(0usize), |i| Some(i + 1)) + .map_while(|i| logs_rlp.at_with_offset(i).ok()) + .filter_map(|(log_rlp, log_off)| { + let mut bytes = log_rlp.data().ok()?; + let log = Log::decode(&mut bytes).ok()?; + if log.address == query.contract + && log + .data + .topics() + .contains(&B256::from(query.event.event_signature)) + { + Some(logs_offset + log_off) + } else { + Some(0usize) + } + }) + .collect::>(); + + for log_offset in relevant_logs_offset.iter() { + let mut buf = &last_node[*log_offset..*log_offset + query.event.size]; + let decoded_log = Log::decode(&mut buf)?; + let raw_bytes: [u8; 20] = last_node[*log_offset + query.event.add_rel_offset + ..*log_offset + query.event.add_rel_offset + 20] + .to_vec() + .try_into() + .unwrap(); + assert_eq!(decoded_log.address, query.contract); + assert_eq!(raw_bytes, query.contract); + let topics = decoded_log.topics(); + assert_eq!(topics[0].0, expected_sig); + let raw_bytes: [u8; 32] = last_node[*log_offset + query.event.sig_rel_offset + ..*log_offset + query.event.sig_rel_offset + 32] + .to_vec() + .try_into() + .unwrap(); + assert_eq!(topics[0].0, raw_bytes); + } + } + Ok(()) } - use super::*; #[tokio::test] async fn test_sepolia_slot() -> Result<()> { #[cfg(feature = "ci")] @@ -429,10 +939,10 @@ mod test { #[tokio::test] async fn test_pidgy_pinguin_mapping_slot() -> Result<()> { // first pinguin holder https://dune.com/queries/2450476/4027653 - // holder: 0x188b264aa1456b869c3a92eeed32117ebb835f47 + // holder: 0x29469395eaf6f95920e59f858042f0e28d98a20b // NFT id https://opensea.io/assets/ethereum/0xbd3531da5cf5857e7cfaa92426877b022e612cf8/1116 let mapping_value = - Address::from_str("0x188B264AA1456B869C3a92eeeD32117EbB835f47").unwrap(); + Address::from_str("0x29469395eaf6f95920e59f858042f0e28d98a20b").unwrap(); let nft_id: u32 = 1116; let mapping_key = left_pad32(&nft_id.to_be_bytes()); let url = get_mainnet_url(); @@ -504,7 +1014,7 @@ mod test { let previous_block = provider .get_block_by_number( BlockNumberOrTag::Number(block.header.number - 1), - true.into(), + alloy::rpc::types::BlockTransactionsKind::Full, ) .await? .unwrap(); @@ -567,7 +1077,7 @@ mod test { } /// TEST to compare alloy with ethers pub struct RLPBlock<'a, X>(pub &'a ethers::types::Block); - impl BlockUtil for ethers::types::Block { + impl Rlpable for ethers::types::Block { fn rlp(&self) -> Vec { let rlp = RLPBlock(self); rlp::encode(&rlp).to_vec() @@ -605,4 +1115,164 @@ mod test { rlp.append(inner); } } + // for compatibility check with alloy + mod tryethers { + + use std::sync::Arc; + + use anyhow::Result; + use eth_trie::{EthTrie, MemoryDB, Trie}; + use ethers::{ + providers::{Http, Middleware, Provider}, + types::{BlockId, Bytes, Transaction, TransactionReceipt, U64}, + }; + use rlp::{Encodable, RlpStream}; + + /// A wrapper around a transaction and its receipt. The receipt is used to filter + /// bad transactions, so we only compute over valid transactions. + pub struct TxAndReceipt(Transaction, TransactionReceipt); + + impl TxAndReceipt { + pub fn tx(&self) -> &Transaction { + &self.0 + } + pub fn receipt(&self) -> &TransactionReceipt { + &self.1 + } + pub fn tx_rlp(&self) -> Bytes { + self.0.rlp() + } + // TODO: this should be upstreamed to ethers-rs + pub fn receipt_rlp(&self) -> Bytes { + let tx_type = self.tx().transaction_type; + let mut rlp = RlpStream::new(); + rlp.begin_unbounded_list(); + match &self.1.status { + Some(s) if s.as_u32() == 1 => rlp.append(s), + _ => rlp.append_empty_data(), + }; + rlp.append(&self.1.cumulative_gas_used) + .append(&self.1.logs_bloom) + .append_list(&self.1.logs); + + rlp.finalize_unbounded_list(); + let rlp_bytes: Bytes = rlp.out().freeze().into(); + let mut encoded = vec![]; + match tx_type { + // EIP-2930 (0x01) + Some(x) if x == U64::from(0x1) => { + encoded.extend_from_slice(&[0x1]); + encoded.extend_from_slice(rlp_bytes.as_ref()); + encoded.into() + } + // EIP-1559 (0x02) + Some(x) if x == U64::from(0x2) => { + encoded.extend_from_slice(&[0x2]); + encoded.extend_from_slice(rlp_bytes.as_ref()); + encoded.into() + } + _ => rlp_bytes, + } + } + } + /// Structure containing the block header and its transactions / receipts. Amongst other things, + /// it is used to create a proof of inclusion for any transaction inside this block. + pub struct BlockData { + pub block: ethers::types::Block, + // TODO: add generics later - this may be re-used amongst different workers + pub tx_trie: EthTrie, + pub receipts_trie: EthTrie, + } + + impl BlockData { + pub async fn fetch + Send + Sync>( + blockid: T, + url: String, + ) -> Result { + let provider = + Provider::::try_from(url).expect("could not instantiate HTTP Provider"); + Self::fetch_from(&provider, blockid).await + } + pub async fn fetch_from + Send + Sync>( + provider: &Provider, + blockid: T, + ) -> Result { + let block = provider + .get_block_with_txs(blockid) + .await? + .expect("should have been a block"); + let receipts = provider.get_block_receipts(block.number.unwrap()).await?; + + let tx_with_receipt = block + .transactions + .clone() + .into_iter() + .map(|tx| { + let tx_hash = tx.hash(); + let r = receipts + .iter() + .find(|r| r.transaction_hash == tx_hash) + .expect("RPC sending invalid data"); + // TODO remove cloning + TxAndReceipt(tx, r.clone()) + }) + .collect::>(); + + // check transaction root + let memdb = Arc::new(MemoryDB::new(true)); + let mut tx_trie = EthTrie::new(Arc::clone(&memdb)); + for tr in tx_with_receipt.iter() { + tx_trie + .insert(&tr.receipt().transaction_index.rlp_bytes(), &tr.tx_rlp()) + .expect("can't insert tx"); + } + + // check receipt root + let memdb = Arc::new(MemoryDB::new(true)); + let mut receipts_trie = EthTrie::new(Arc::clone(&memdb)); + for tr in tx_with_receipt.iter() { + if tr.tx().transaction_index.unwrap() == U64::from(0) { + println!( + "Ethers: Index {} -> {:?}", + tr.tx().transaction_index.unwrap(), + tr.receipt_rlp().to_vec() + ); + } + receipts_trie + .insert( + &tr.receipt().transaction_index.rlp_bytes(), + // TODO: make getter value for rlp encoding + &tr.receipt_rlp(), + ) + .expect("can't insert tx"); + } + let computed = tx_trie.root_hash().expect("root hash problem"); + let expected = block.transactions_root; + assert_eq!(expected, computed); + + let computed = receipts_trie.root_hash().expect("root hash problem"); + let expected = block.receipts_root; + assert_eq!(expected, computed); + + Ok(BlockData { + block, + tx_trie, + receipts_trie, + }) + } + + // recompute the receipts trie by first converting all receipts form RPC type to consensus type + // since in Alloy these are two different types and RLP functions are only implemented for + // consensus ones. + pub fn check(&mut self) -> Result<()> { + let computed = self.receipts_trie.root_hash()?; + let tx_computed = self.tx_trie.root_hash()?; + let expected = self.block.receipts_root; + let tx_expected = self.block.transactions_root; + assert_eq!(expected.0, computed.0); + assert_eq!(tx_expected.0, tx_computed.0); + Ok(()) + } + } + } } diff --git a/mp2-common/src/mpt_sequential/key.rs b/mp2-common/src/mpt_sequential/key.rs index d7129fd84..2a14780d7 100644 --- a/mp2-common/src/mpt_sequential/key.rs +++ b/mp2-common/src/mpt_sequential/key.rs @@ -15,25 +15,37 @@ use plonky2::{ use plonky2_crypto::u32::arithmetic_u32::U32Target; use serde::{Deserialize, Serialize}; +pub type MPTKeyWire = MPTKeyWireGeneric; + +pub type ReceiptKeyWire = MPTKeyWireGeneric; + +pub const MAX_TX_KEY_NIBBLE_LEN: usize = 4; + /// Calculate the pointer from the MPT key. pub fn mpt_key_ptr(mpt_key: &[u8]) -> usize { let nibbles = Nibbles::from_compact(mpt_key); MAX_KEY_NIBBLE_LEN - 1 - nibbles.nibbles().len() } +/// Calculate the pointer from the MPT key. +pub fn receipt_key_ptr(mpt_key: &[u8]) -> usize { + let nibbles = Nibbles::from_compact(mpt_key); + MAX_TX_KEY_NIBBLE_LEN - 1 - nibbles.nibbles().len() +} + /// A structure that keeps a running pointer to the portion of the key the circuit /// already has proven. #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct MPTKeyWire { +pub struct MPTKeyWireGeneric { /// Represents the full key of the value(s) we're looking at in the MPT trie. - pub key: Array, + pub key: Array, /// Represents which portion of the key we already processed. The pointer /// goes _backwards_ since circuit starts proving from the leaf up to the root. /// i.e. pointer must be equal to F::NEG_ONE when we reach the root. pub pointer: Target, } -impl MPTKeyWire { +impl MPTKeyWireGeneric { pub fn current_nibble, const D: usize>( &self, b: &mut CircuitBuilder, @@ -72,7 +84,7 @@ impl MPTKeyWire { /// Create a new fresh key wire pub fn new, const D: usize>(b: &mut CircuitBuilder) -> Self { Self { - key: Array::::new(b), + key: Array::::new(b), pointer: b.add_virtual_target(), } } @@ -80,7 +92,7 @@ impl MPTKeyWire { pub fn assign( &self, p: &mut PartialWitness, - key_nibbles: &[u8; MAX_KEY_NIBBLE_LEN], + key_nibbles: &[u8; KEY_LENGTH], ptr: usize, ) { let f_nibbles = create_array(|i| F::from_canonical_u8(key_nibbles[i])); @@ -141,7 +153,7 @@ impl MPTKeyWire { // now we need to pack each pair of 2 bit limbs into a nibble, but for each byte we want nibbles to // be ordered in big-endian limbs - .chunks(4) + .chunks_exact(4) .flat_map(|chunk| { vec![ b.mul_const_add(F::from_canonical_u8(4), chunk[3], chunk[2]), @@ -154,7 +166,7 @@ impl MPTKeyWire { .try_into() .unwrap(), }, - pointer: b.constant(F::from_canonical_usize(MAX_KEY_NIBBLE_LEN - 1)), + pointer: b.constant(F::from_canonical_usize(KEY_LENGTH - 1)), } } } diff --git a/mp2-common/src/mpt_sequential/leaf_or_extension.rs b/mp2-common/src/mpt_sequential/leaf_or_extension.rs index 96b3b6355..e5c0cf482 100644 --- a/mp2-common/src/mpt_sequential/leaf_or_extension.rs +++ b/mp2-common/src/mpt_sequential/leaf_or_extension.rs @@ -1,10 +1,12 @@ //! MPT leaf or extension node gadget -use super::{Circuit as MPTCircuit, MPTKeyWire, PAD_LEN}; +use super::{ + advance_key_leaf_or_extension, advance_key_receipt_leaf, key::MPTKeyWireGeneric, PAD_LEN, +}; use crate::{ array::{Array, Vector, VectorWire}, keccak::{InputData, KeccakCircuit, KeccakWires}, - rlp::decode_fixed_list, + rlp::{decode_fixed_list, MAX_KEY_NIBBLE_LEN}, types::GFp, }; use plonky2::{ @@ -15,10 +17,16 @@ use plonky2::{ }; use serde::{Deserialize, Serialize}; +pub type MPTLeafOrExtensionWires = + MPTLeafOrExtensionWiresGeneric; + /// Wrapped wires for a MPT leaf or extension node #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct MPTLeafOrExtensionWires -where +pub struct MPTLeafOrExtensionWiresGeneric< + const NODE_LEN: usize, + const VALUE_LEN: usize, + const KEY_LEN: usize, +> where [(); PAD_LEN(NODE_LEN)]:, { /// MPT node @@ -26,12 +34,13 @@ where /// MPT root pub root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, /// New MPT key after advancing the current key - pub key: MPTKeyWire, + pub key: MPTKeyWireGeneric, /// New MPT value pub value: Array, } -impl MPTLeafOrExtensionWires +impl + MPTLeafOrExtensionWiresGeneric where [(); PAD_LEN(NODE_LEN)]:, { @@ -41,10 +50,12 @@ where } } +pub type MPTLeafOrExtensionNode = MPTLeafOrExtensionNodeGeneric; + /// MPT leaf or extension node gadget -pub struct MPTLeafOrExtensionNode; +pub struct MPTLeafOrExtensionNodeGeneric; -impl MPTLeafOrExtensionNode { +impl MPTLeafOrExtensionNodeGeneric { /// Build the MPT node and advance the current key. pub fn build_and_advance_key< F: RichField + Extendable, @@ -53,8 +64,8 @@ impl MPTLeafOrExtensionNode { const VALUE_LEN: usize, >( b: &mut CircuitBuilder, - current_key: &MPTKeyWire, - ) -> MPTLeafOrExtensionWires + current_key: &MPTKeyWireGeneric, + ) -> MPTLeafOrExtensionWiresGeneric where [(); PAD_LEN(NODE_LEN)]:, { @@ -70,15 +81,16 @@ impl MPTLeafOrExtensionNode { // Advance the key and extract the value (only decode two headers in the case of leaf). let rlp_headers = decode_fixed_list::<_, D, 2>(b, &node.arr.arr, zero); - let (key, value, valid) = MPTCircuit::<1, NODE_LEN>::advance_key_leaf_or_extension::< - F, - D, - 2, - VALUE_LEN, - >(b, &node.arr, current_key, &rlp_headers); + let (key, value, valid) = + advance_key_leaf_or_extension::( + b, + &node.arr, + current_key, + &rlp_headers, + ); b.connect(tru.target, valid.target); - MPTLeafOrExtensionWires { + MPTLeafOrExtensionWiresGeneric { node, root, key, @@ -86,3 +98,61 @@ impl MPTLeafOrExtensionNode { } } } + +/// Wrapped wires for a MPT receipt leaf +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct MPTReceiptLeafWiresGeneric +where + [(); PAD_LEN(NODE_LEN)]:, +{ + /// MPT node + pub node: VectorWire, + /// MPT root + pub root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, + /// New MPT key after advancing the current key + pub key: MPTKeyWireGeneric, +} + +/// Receipt leaf node as we have to do things differently for efficiency reasons. +pub struct MPTReceiptLeafNode; + +impl MPTReceiptLeafNode { + /// Build the MPT node and advance the current key. + pub fn build_and_advance_key< + F: RichField + Extendable, + const D: usize, + const NODE_LEN: usize, + >( + b: &mut CircuitBuilder, + current_key: &MPTKeyWireGeneric, + ) -> MPTReceiptLeafWiresGeneric + where + [(); PAD_LEN(NODE_LEN)]:, + { + let zero = b.zero(); + let tru = b._true(); + + // Build the node and ensure it only includes bytes. + let node = VectorWire::::new(b); + + node.assert_bytes(b); + + // Expose the keccak root of this subtree starting at this node. + let root = KeccakCircuit::<{ PAD_LEN(NODE_LEN) }>::hash_vector(b, &node); + + // We know that the rlp encoding of the compact encoding of the key is going to be in roughly the first 10 bytes of + // the node since the node is list byte, 2 bytes for list length (maybe 3), key length byte (1), key compact encoding (4 max) + // so we take 10 bytes to be safe since this won't effect the number of random access gates we use. + let rlp_headers = decode_fixed_list::<_, D, 1>(b, &node.arr.arr[..10], zero); + + let (key, valid) = advance_key_receipt_leaf::( + b, + &node, + current_key, + &rlp_headers, + ); + b.connect(tru.target, valid.target); + + MPTReceiptLeafWiresGeneric { node, root, key } + } +} diff --git a/mp2-common/src/mpt_sequential/mod.rs b/mp2-common/src/mpt_sequential/mod.rs index 92418d0f7..81e0d6286 100644 --- a/mp2-common/src/mpt_sequential/mod.rs +++ b/mp2-common/src/mpt_sequential/mod.rs @@ -1,3 +1,4 @@ +use crate::rlp::MAX_KEY_NIBBLE_LEN; use crate::serialization::{ deserialize_array, deserialize_long_array, serialize_array, serialize_long_array, }; @@ -8,14 +9,12 @@ use crate::{ compute_size_with_padding, InputData, KeccakCircuit, KeccakWires, OutputHash, HASH_LEN, PACKED_HASH_LEN, }, - rlp::{ - decode_compact_encoding, decode_fixed_list, RlpHeader, RlpList, MAX_ITEMS_IN_LIST, - MAX_KEY_NIBBLE_LEN, - }, + rlp::{decode_compact_encoding, decode_fixed_list, RlpHeader, RlpList, MAX_ITEMS_IN_LIST}, utils::{find_index_subvector, keccak256}, }; use anyhow::{anyhow, Result}; use core::array::from_fn as create_array; + use plonky2::{ field::extension::Extendable, hash::hash_types::RichField, @@ -33,8 +32,14 @@ mod key; mod leaf_or_extension; pub mod utils; -pub use key::{mpt_key_ptr, MPTKeyWire}; -pub use leaf_or_extension::{MPTLeafOrExtensionNode, MPTLeafOrExtensionWires}; +pub use key::{ + mpt_key_ptr, receipt_key_ptr, MPTKeyWire, MPTKeyWireGeneric, ReceiptKeyWire, + MAX_TX_KEY_NIBBLE_LEN, +}; +pub use leaf_or_extension::{ + MPTLeafOrExtensionNode, MPTLeafOrExtensionNodeGeneric, MPTLeafOrExtensionWires, + MPTLeafOrExtensionWiresGeneric, MPTReceiptLeafNode, MPTReceiptLeafWiresGeneric, +}; /// Number of items in the RLP encoded list in a leaf node. const NB_ITEMS_LEAF: usize = 2; @@ -44,6 +49,11 @@ const NB_ITEMS_LEAF: usize = 2; /// Given we target MPT storage proof, the value is 32 bytes + 1 byte for RLP encoding. pub const MAX_LEAF_VALUE_LEN: usize = 33; +/// This is the maximum size we allow for the value of Receipt Trie leaf +/// currently set to be the same as we allow for a branch node in the Storage Trie +/// minus the length of the key header and key +pub const MAX_RECEIPT_LEAF_VALUE_LEN: usize = 503; + /// RLP item size for the extension node pub const MPT_EXTENSION_RLP_SIZE: usize = 2; @@ -56,6 +66,17 @@ pub const MPT_BRANCH_RLP_SIZE: usize = 17; pub const fn PAD_LEN(d: usize) -> usize { compute_size_with_padding(d) } + +/// const function to allow arrays of half a generics size without additional generics +#[allow(non_snake_case)] +pub const fn NIBBLES_TO_BYTES(d: usize) -> usize { + d >> 1 +} + +/// We export a type here to keep it consistent with the already established codebase. +pub type MPTCircuit = + Circuit; + /// Circuit that simoply proves the inclusion of a value inside a MPT tree. /// /// . DEPTH is the maximal depth of the tree. If the tree is smaller, the circuit @@ -65,23 +86,29 @@ pub const fn PAD_LEN(d: usize) -> usize { /// branch node can be up to 32 * 17 = 544 bytes. /// - Note since it uses keccak, the array being hashed is larger because /// keccak requires padding. +/// KEY_LEN is the maximum length of the MPT key (differs between storage tries and transaction/receipt tries) #[derive(Clone, Debug)] -pub struct Circuit { +pub struct Circuit< + const DEPTH: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, + const KEY_LEN_BYTES: usize = { NIBBLES_TO_BYTES(KEY_LEN) }, +> { /// for ease of usage, we take vector here and the circuit is doing the padding nodes: Vec>, /// the full key that we are trying to prove in this trie /// NOTE: the key is in bytes. This code will transform it into nibbles /// before passing it to circuit, i.e. the circuit takes the key in nibbles /// whose length == MAX_KEY_NIBBLE_LEN - key: [u8; MAX_KEY_NIBBLE_LEN / 2], + key: [u8; KEY_LEN_BYTES], } #[derive(Serialize, Deserialize, Clone, Debug)] -pub struct InputWires +pub struct InputWires where [(); PAD_LEN(NODE_LEN)]:, [(); DEPTH - 1]:, { - pub(crate) key: MPTKeyWire, + pub(crate) key: MPTKeyWireGeneric, /// a vector of buffers whose size is the padded size of the maximum node length /// the padding may occur anywhere in the array but it can fit the maximum node size /// NOTE: this makes the code a bit harder grasp at first, but it's a straight @@ -122,27 +149,28 @@ where pub root: OutputHash, } -impl Circuit +impl + Circuit where [(); PAD_LEN(NODE_LEN)]:, [(); DEPTH - 1]:, { - pub fn new(key: [u8; MAX_KEY_NIBBLE_LEN / 2], proof: Vec>) -> Self { + pub fn new(key: [u8; NIBBLES_TO_BYTES(KEY_LEN)], proof: Vec>) -> Self { Self { nodes: proof, key } } pub fn create_input_wires( b: &mut CircuitBuilder, - key: Option, // Could set the full key from outside - ) -> InputWires + key: Option>, // Could set the full key from outside + ) -> InputWires where F: RichField + Extendable, { // full key is expected to be given by verifier (done in UserCircuit impl) // initial key has the pointer that is set at the maximum length - 1 (it's an index, so 0-based) - let key = key.unwrap_or_else(|| MPTKeyWire { - key: Array::::new(b), - pointer: b.constant(F::from_canonical_usize(MAX_KEY_NIBBLE_LEN) - F::ONE), + let key = key.unwrap_or_else(|| MPTKeyWireGeneric:: { + key: Array::::new(b), + pointer: b.constant(F::from_canonical_usize(KEY_LEN) - F::ONE), }); let should_process: [BoolTarget; DEPTH - 1] = create_array(|_| b.add_virtual_bool_target_safe()); @@ -162,7 +190,7 @@ where /// to be done by the caller. pub fn verify_mpt_proof( b: &mut CircuitBuilder, - inputs: &InputWires, + inputs: &InputWires, ) -> OutputWires where F: RichField + Extendable, @@ -177,12 +205,8 @@ where // small optimization here as we only need to decode two items for a leaf, since we know it's a leaf let leaf_headers = decode_fixed_list::<_, _, NB_ITEMS_LEAF>(b, &inputs.nodes[0].arr.arr, zero); - let (mut iterative_key, leaf_value, is_leaf) = Self::advance_key_leaf_or_extension( - b, - &inputs.nodes[0].arr, - &inputs.key, - &leaf_headers, - ); + let (mut iterative_key, leaf_value, is_leaf) = + advance_key_leaf_or_extension(b, &inputs.nodes[0].arr, &inputs.key, &leaf_headers); b.connect(t.target, is_leaf.target); let mut last_hash_output = leaf_hash.output_array.clone(); let mut keccak_wires = vec![leaf_hash]; @@ -239,7 +263,7 @@ where pub fn assign_wires, const D: usize>( &self, p: &mut PartialWitness, - inputs: &InputWires, + inputs: &InputWires, outputs: &OutputWires, ) -> Result<()> { let pad_len = DEPTH.checked_sub(self.nodes.len()).ok_or(anyhow!( @@ -302,8 +326,12 @@ where pub fn advance_key, const D: usize>( b: &mut CircuitBuilder, node: &Array, - key: &MPTKeyWire, - ) -> (MPTKeyWire, Array, BoolTarget) { + key: &MPTKeyWireGeneric, + ) -> ( + MPTKeyWireGeneric, + Array, + BoolTarget, + ) { let zero = b.zero(); // It will try to decode a RLP list of the maximum number of items there can be // in a list, which is 16 for a branch node (Excluding value). @@ -313,9 +341,9 @@ where // if it's more ==> node's a branch node // RLP ( RLP(hash1), RLP(hash2), ... RLP(hash16), RLP(value)) let rlp_headers = decode_fixed_list::(b, &node.arr, zero); - let leaf_info = Self::advance_key_leaf_or_extension(b, node, key, &rlp_headers); + let leaf_info = advance_key_leaf_or_extension(b, node, key, &rlp_headers); let tuple_condition = leaf_info.2; - let branch_info = Self::advance_key_branch(b, node, key, &rlp_headers); + let branch_info = advance_key_branch(b, node, key, &rlp_headers); // ensures it's either a branch or leaf/extension let tuple_or_branch = b.or(leaf_info.2, branch_info.2); @@ -327,78 +355,132 @@ where (new_key, child_hash, tuple_or_branch) } +} - /// This function advances the pointer of the MPT key. The parameters are: - /// * The key where to lookup the next nibble and thus the hash stored at - /// nibble position in the branch node. - /// * RLP headers of the current node. - /// And it returns: - /// * New key with the pointer moved. - /// * The child hash / value of the node. - /// * A boolean that must be true if the given node is a leaf or an extension. - /// * The nibble position before this advance. - pub fn advance_key_branch, const D: usize>( - b: &mut CircuitBuilder, - node: &Array, - key: &MPTKeyWire, - rlp_headers: &RlpList, - ) -> (MPTKeyWire, Array, BoolTarget, Target) { - let one = b.one(); - // assume it's a node and return the boolean condition that must be true if - // it is a node - decided in advance_key function - let seventeen = b.constant(F::from_canonical_usize(MAX_ITEMS_IN_LIST)); - let branch_condition = b.is_equal(seventeen, rlp_headers.num_fields); - - // Given we are reading the nibble from the key itself, we don't need to do - // any more checks on it. The key and pointer will be given by the verifier so - // attacker can't indicate a different nibble - let nibble = key.current_nibble(b); - - // we advance the pointer for the next iteration - let new_key = key.advance_by(b, one); - let nibble_header = rlp_headers.select(b, nibble); - let branch_child_hash = node.extract_array::(b, nibble_header.offset); - (new_key, branch_child_hash, branch_condition, nibble) - } +/// This function advances the pointer of the MPT key. The parameters are: +/// * The key where to lookup the next nibble and thus the hash stored at +/// nibble position in the branch node. +/// * RLP headers of the current node. +/// And it returns: +/// * New key with the pointer moved. +/// * The child hash / value of the node. +/// * A boolean that must be true if the given node is a leaf or an extension. +/// * The nibble position before this advance. +pub fn advance_key_branch< + F: RichField + Extendable, + const D: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, +>( + b: &mut CircuitBuilder, + node: &Array, + key: &MPTKeyWireGeneric, + rlp_headers: &RlpList, +) -> ( + MPTKeyWireGeneric, + Array, + BoolTarget, + Target, +) { + let one = b.one(); + // assume it's a node and return the boolean condition that must be true if + // it is a node - decided in advance_key function + let seventeen = b.constant(F::from_canonical_usize(MAX_ITEMS_IN_LIST)); + let branch_condition = b.is_equal(seventeen, rlp_headers.num_fields); - /// Returns the key with the pointer moved, returns the child hash / value of the node, - /// and returns booleans that must be true IF the given node is a leaf or an extension. - pub fn advance_key_leaf_or_extension< - F: RichField + Extendable, - const D: usize, - const LIST_LEN: usize, - // in case of a leaf, the value can be up to 33 bytes because of additional RLP encoding - // in case of extension, the value is 32 bytes - const VALUE_LEN: usize, - >( - b: &mut CircuitBuilder, - node: &Array, - key: &MPTKeyWire, - rlp_headers: &RlpList, - ) -> (MPTKeyWire, Array, BoolTarget) { - let two = b.two(); - let condition = b.is_equal(rlp_headers.num_fields, two); - let key_header = RlpHeader { - data_type: rlp_headers.data_type[0], - offset: rlp_headers.offset[0], - len: rlp_headers.len[0], - }; - let (extracted_key, should_true) = decode_compact_encoding(b, node, &key_header); - // it's either the _value_ of the leaf, OR the _hash_ of the child node if node = ext. - let leaf_child_hash = node.extract_array::(b, rlp_headers.offset[1]); - // note we are going _backwards_ on the key, so we need to substract the expected key length - // we want to check against - let new_key = key.advance_by(b, extracted_key.real_len); - // NOTE: there is no need to check if the extracted_key is indeed a subvector of the full key - // in this case. Indeed, in leaf/ext. there is only one key possible. Since we decoded it - // from the beginning of the node, and that the hash of the node also starts at the beginning, - // either the attacker give the right node or it gives an invalid node and hashes will not - // match. - let condition = b.and(condition, should_true); - (new_key, leaf_child_hash, condition) - } + // Given we are reading the nibble from the key itself, we don't need to do + // any more checks on it. The key and pointer will be given by the verifier so + // attacker can't indicate a different nibble + let nibble = key.current_nibble(b); + + // we advance the pointer for the next iteration + let new_key = key.advance_by(b, one); + let nibble_header = rlp_headers.select(b, nibble); + let branch_child_hash = node.extract_array::(b, nibble_header.offset); + (new_key, branch_child_hash, branch_condition, nibble) +} + +/// Returns the key with the pointer moved, returns the child hash / value of the node, +/// and returns booleans that must be true IF the given node is a leaf or an extension. +pub fn advance_key_leaf_or_extension< + F: RichField + Extendable, + const D: usize, + const LIST_LEN: usize, + // in case of a leaf, the value can be up to 33 bytes because of additional RLP encoding + // in case of extension, the value is 32 bytes + const VALUE_LEN: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, +>( + b: &mut CircuitBuilder, + node: &Array, + key: &MPTKeyWireGeneric, + rlp_headers: &RlpList, +) -> ( + MPTKeyWireGeneric, + Array, + BoolTarget, +) { + let two = b.two(); + let condition = b.is_equal(rlp_headers.num_fields, two); + let key_header = RlpHeader { + data_type: rlp_headers.data_type[0], + offset: rlp_headers.offset[0], + len: rlp_headers.len[0], + }; + let (extracted_key, should_true) = + decode_compact_encoding::<_, _, _, KEY_LEN>(b, node, &key_header); + // it's either the _value_ of the leaf, OR the _hash_ of the child node if node = ext. + let leaf_child_hash = node.extract_array::(b, rlp_headers.offset[1]); + // note we are going _backwards_ on the key, so we need to substract the expected key length + // we want to check against + let new_key = key.advance_by(b, extracted_key.real_len); + // NOTE: there is no need to check if the extracted_key is indeed a subvector of the full key + // in this case. Indeed, in leaf/ext. there is only one key possible. Since we decoded it + // from the beginning of the node, and that the hash of the node also starts at the beginning, + // either the attacker give the right node or it gives an invalid node and hashes will not + // match. + let condition = b.and(condition, should_true); + (new_key, leaf_child_hash, condition) } +/// Returns the key with the pointer moved in the case of a Receipt Trie leaf. +pub fn advance_key_receipt_leaf< + F: RichField + Extendable, + const D: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, +>( + b: &mut CircuitBuilder, + node: &VectorWire, + key: &MPTKeyWireGeneric, + rlp_headers: &RlpList<1>, +) -> (MPTKeyWireGeneric, BoolTarget) { + let key_header = RlpHeader { + data_type: rlp_headers.data_type[0], + offset: rlp_headers.offset[0], + len: rlp_headers.len[0], + }; + + // To save on operations we know the key is goin to be in the first 10 items so we + // only feed these into `decode_compact_encoding` + let sub_array: Array = Array { + arr: create_array(|i| node.arr.arr[i]), + }; + let (extracted_key, should_true) = + decode_compact_encoding::<_, _, _, KEY_LEN>(b, &sub_array, &key_header); + + // note we are going _backwards_ on the key, so we need to substract the expected key length + // we want to check against + let new_key = key.advance_by(b, extracted_key.real_len); + // NOTE: there is no need to check if the extracted_key is indeed a subvector of the full key + // in this case. Indeed, in leaf/ext. there is only one key possible. Since we decoded it + // from the beginning of the node, and that the hash of the node also starts at the beginning, + // either the attacker give the right node or it gives an invalid node and hashes will not + // match. + + (new_key, should_true) +} #[cfg(test)] mod test { use std::array::from_fn as create_array; @@ -428,31 +510,43 @@ mod test { use plonky2_crypto::u32::arithmetic_u32::U32Target; use rand::{thread_rng, RngCore}; - use crate::keccak::{HASH_LEN, PACKED_HASH_LEN}; - use crate::rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST, MAX_KEY_NIBBLE_LEN}; use crate::utils::{Endianness, PackerTarget}; use crate::{ array::Array, utils::{find_index_subvector, keccak256}, }; use crate::{eth::ProofQuery, C, D, F}; + use crate::{ + keccak::{HASH_LEN, PACKED_HASH_LEN}, + mpt_sequential::advance_key_leaf_or_extension, + }; + use crate::{ + mpt_sequential::advance_key_branch, + rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST, MAX_KEY_NIBBLE_LEN}, + }; use super::{ utils::{bytes_to_nibbles, nibbles_to_bytes, visit_node, visit_proof}, - Circuit, InputWires, MPTKeyWire, OutputWires, MAX_LEAF_VALUE_LEN, NB_ITEMS_LEAF, PAD_LEN, + Circuit, InputWires, MPTKeyWire, OutputWires, MAX_LEAF_VALUE_LEN, NB_ITEMS_LEAF, + NIBBLES_TO_BYTES, PAD_LEN, }; #[derive(Clone, Debug)] - struct TestCircuit { - c: Circuit, + struct TestCircuit< + const DEPTH: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, + const KEY_LEN_BYTES: usize = { NIBBLES_TO_BYTES(KEY_LEN) }, + > { + c: Circuit, exp_root: [u8; 32], exp_value: [u8; MAX_LEAF_VALUE_LEN], // The flag identifies if need to check the expected leaf value, it's // set to true for storage proof, and false for state proof (unconcern). checking_value: bool, } - impl UserCircuit - for TestCircuit + impl + UserCircuit for TestCircuit where F: RichField + Extendable, [(); PAD_LEN(NODE_LEN)]:, @@ -461,7 +555,7 @@ mod test { [(); HASH_LEN / 4]:, { type Wires = ( - InputWires, + InputWires, OutputWires, Array, // root Array, // value @@ -531,12 +625,16 @@ mod test { // Written as constant from ^ const DEPTH: usize = 2; const NODE_LEN: usize = 150; - verify_storage_proof_from_query::(&query, &res)?; + verify_storage_proof_from_query::(&query, &res)?; verify_state_proof_from_query(&query, &res) } /// Verify the storage proof from query result. - pub(crate) fn verify_storage_proof_from_query( + pub(crate) fn verify_storage_proof_from_query< + const DEPTH: usize, + const NODE_LEN: usize, + const KEY_LEN: usize, + >( query: &ProofQuery, res: &EIP1186AccountProofResponse, ) -> Result<()> @@ -544,6 +642,7 @@ mod test { [(); PAD_LEN(NODE_LEN)]:, [(); DEPTH - 1]:, [(); PAD_LEN(NODE_LEN) / 4]:, + [(); NIBBLES_TO_BYTES(KEY_LEN)]:, { ProofQuery::verify_storage_proof(res)?; @@ -568,8 +667,8 @@ mod test { let u8idx = find_index_subvector(&mpt_proof[i], &child_hash); assert!(u8idx.is_some()); } - let circuit = TestCircuit:: { - c: Circuit::::new(mpt_key.try_into().unwrap(), mpt_proof), + let circuit = TestCircuit:: { + c: Circuit::::new(mpt_key.try_into().unwrap(), mpt_proof), exp_root: root.try_into().unwrap(), exp_value: encoded_value.try_into().unwrap(), checking_value: false, @@ -608,8 +707,11 @@ mod test { let u8idx = find_index_subvector(&mpt_proof[i], &child_hash); assert!(u8idx.is_some()); } - let circuit = TestCircuit:: { - c: Circuit::::new(mpt_key.try_into().unwrap(), mpt_proof), + let circuit = TestCircuit:: { + c: Circuit::::new( + mpt_key.try_into().unwrap(), + mpt_proof, + ), exp_root: root.try_into().unwrap(), exp_value: [0; MAX_LEAF_VALUE_LEN], // the reason we don't check the value is the circuit is made for storage proof and it extracts a 32bytes @@ -665,8 +767,8 @@ mod test { let u8idx = find_index_subvector(&proof[i], &child_hash); assert!(u8idx.is_some()); } - let circuit = TestCircuit:: { - c: Circuit::::new(key.try_into().unwrap(), proof), + let circuit = TestCircuit:: { + c: Circuit::::new(key.try_into().unwrap(), proof), exp_root: root, // simply pad it to max size exp_value: create_array(|i| if i < VALUE_LEN { value[i] } else { 0 }), @@ -753,7 +855,9 @@ mod test { let node = Array::::new(&mut b); let key_wire = MPTKeyWire::new(&mut b); let (advanced_key, value, valid_node) = - Circuit::::advance_key(&mut b, &node, &key_wire); + Circuit::::advance_key( + &mut b, &node, &key_wire, + ); b.connect(tr.target, valid_node.target); let exp_key_ptr = b.add_virtual_target(); b.connect(advanced_key.pointer, exp_key_ptr); @@ -864,12 +968,13 @@ mod test { let key_wire = MPTKeyWire::new(&mut builder); let rlp_headers = decode_fixed_list::(&mut builder, &node.arr, zero); - let (advanced_key, value, should_true, _) = Circuit::::advance_key_branch( - &mut builder, - &node, - &key_wire, - &rlp_headers, - ); + let (advanced_key, value, should_true, _) = + advance_key_branch::<_, _, NODE_LEN, MAX_KEY_NIBBLE_LEN>( + &mut builder, + &node, + &key_wire, + &rlp_headers, + ); builder.connect(tt.target, should_true.target); let exp_key_ptr = builder.add_virtual_target(); builder.connect(advanced_key.pointer, exp_key_ptr); @@ -935,7 +1040,7 @@ mod test { let key_wire = MPTKeyWire::new(&mut builder); let rlp_headers = decode_fixed_list::(&mut builder, &node.arr, zero); let (advanced_key, value, should_true) = - Circuit::::advance_key_leaf_or_extension( + advance_key_leaf_or_extension::<_, _, _, _, NODE_LEN, MAX_KEY_NIBBLE_LEN>( &mut builder, &node, &key_wire, diff --git a/mp2-common/src/rlp.rs b/mp2-common/src/rlp.rs index 741f9e38e..01d6824ab 100644 --- a/mp2-common/src/rlp.rs +++ b/mp2-common/src/rlp.rs @@ -16,7 +16,7 @@ const MAX_LEN_BYTES: usize = 2; /// Maximum size a key can have inside a MPT node. /// 33 bytes because key is compacted encoded, so it can add up to 1 byte more. -const MAX_ENC_KEY_LEN: usize = 33; +pub const MAX_ENC_KEY_LEN: usize = 33; /// Simply the maximum number of nibbles a key can have. pub const MAX_KEY_NIBBLE_LEN: usize = 64; @@ -58,11 +58,16 @@ impl RlpList { } } } -pub fn decode_compact_encoding, const D: usize, const N: usize>( +pub fn decode_compact_encoding< + F: RichField + Extendable, + const D: usize, + const N: usize, + const KEY_LEN: usize, +>( b: &mut CircuitBuilder, input: &Array, key_header: &RlpHeader, -) -> (VectorWire, BoolTarget) { +) -> (VectorWire, BoolTarget) { let zero = b.zero(); let two = b.two(); let first_byte = input.value_at(b, key_header.offset); @@ -71,7 +76,7 @@ pub fn decode_compact_encoding, const D: usize, con let mut prev_nibbles = (least_bits, most_bits); let mut cur_nibbles: (Target, Target); - let mut nibbles: [Target; MAX_KEY_NIBBLE_LEN] = [b.zero(); MAX_KEY_NIBBLE_LEN]; + let mut nibbles: [Target; KEY_LEN] = [b.zero(); KEY_LEN]; let first_nibble = prev_nibbles.0; let first_nibble_as_bits = num_to_bits(b, 4, first_nibble); @@ -92,7 +97,10 @@ pub fn decode_compact_encoding, const D: usize, con // during the first iteration of this loop. let one = b.one(); let mut i_offset = key_header.offset; - for i in 0..MAX_ENC_KEY_LEN - 1 { + + // We calculate how many times to run the foor loop, this is only depends on + // KEY_LEN, since we skip one byte it is just KEY_LEN / 2. + for i in 0..KEY_LEN / 2 { i_offset = b.add(i_offset, one); // look now at the encoded path let x = input.value_at(b, i_offset); @@ -355,7 +363,7 @@ mod tests { use crate::array::Array; use crate::rlp::{ decode_compact_encoding, decode_fixed_list, decode_header, RlpHeader, MAX_ENC_KEY_LEN, - MAX_LEN_BYTES, + MAX_KEY_NIBBLE_LEN, MAX_LEN_BYTES, }; use crate::utils::{keccak256, less_than_or_equal_to, IntTargetWriter}; use crate::{C, D, F}; @@ -792,7 +800,11 @@ mod tests { len: builder.constant(F::from_canonical_usize(tc.key_len)), data_type: builder.constant(F::from_canonical_usize(0)), }; - let (nibbles, cond) = decode_compact_encoding(&mut builder, &wire1, &key_header); + let (nibbles, cond) = decode_compact_encoding::<_, _, _, MAX_KEY_NIBBLE_LEN>( + &mut builder, + &wire1, + &key_header, + ); builder.assert_bool(cond); let exp_nib_len = builder.constant(F::from_canonical_usize(tc.expected.len())); builder.connect(nibbles.real_len, exp_nib_len); diff --git a/mp2-test/Cargo.toml b/mp2-test/Cargo.toml index e4fd7ddbb..a2341668d 100644 --- a/mp2-test/Cargo.toml +++ b/mp2-test/Cargo.toml @@ -13,6 +13,7 @@ plonky2.workspace = true plonky2_ecgfp5.workspace = true rand.workspace = true serde.workspace = true +tokio.workspace = true mp2_common = { path = "../mp2-common" } recursion_framework = { path = "../recursion-framework" } diff --git a/mp2-test/src/circuit.rs b/mp2-test/src/circuit.rs index 262d4384e..bed5a98c9 100644 --- a/mp2-test/src/circuit.rs +++ b/mp2-test/src/circuit.rs @@ -85,7 +85,7 @@ pub fn setup_circuit< }; println!("[+] Circuit data built in {:?}s", now.elapsed().as_secs()); - + println!("FRI config: {:?}", circuit_data.common.fri_params); (wires, circuit_data, vcd) } @@ -105,6 +105,7 @@ pub fn prove_circuit< let now = std::time::Instant::now(); u.prove(&mut pw, &setup.0); let proof = setup.1.prove(pw).expect("invalid proof"); + println!("[+] Proof generated in {:?}ms", now.elapsed().as_millis()); setup .2 @@ -124,6 +125,7 @@ pub fn run_circuit< u: U, ) -> ProofWithPublicInputs { let setup = setup_circuit::(); + println!( "setup.verifierdata hash {:?}", setup.2.verifier_only.circuit_digest @@ -131,3 +133,100 @@ pub fn run_circuit< prove_circuit(&setup, &u) } + +/// Given a `PartitionWitness` that has only inputs set, populates the rest of the witness using the +/// given set of generators. +pub fn debug_generate_partial_witness< + 'a, + F: RichField + Extendable, + C: GenericConfig, + const D: usize, +>( + inputs: PartialWitness, + prover_data: &'a plonky2::plonk::circuit_data::ProverOnlyCircuitData, + common_data: &'a plonky2::plonk::circuit_data::CommonCircuitData, +) -> plonky2::iop::witness::PartitionWitness<'a, F> { + use plonky2::iop::witness::WitnessWrite; + + let config = &common_data.config; + let generators = &prover_data.generators; + let generator_indices_by_watches = &prover_data.generator_indices_by_watches; + + let mut witness = plonky2::iop::witness::PartitionWitness::new( + config.num_wires, + common_data.degree(), + &prover_data.representative_map, + ); + + for (t, v) in inputs.target_values.into_iter() { + witness.set_target(t, v); + } + + // Build a list of "pending" generators which are queued to be run. Initially, all generators + // are queued. + let mut pending_generator_indices: Vec<_> = (0..generators.len()).collect(); + + // We also track a list of "expired" generators which have already returned false. + let mut generator_is_expired = vec![false; generators.len()]; + let mut remaining_generators = generators.len(); + + let mut buffer = plonky2::iop::generator::GeneratedValues::empty(); + + // Keep running generators until we fail to make progress. + while !pending_generator_indices.is_empty() { + let mut next_pending_generator_indices = Vec::new(); + + for &generator_idx in &pending_generator_indices { + if generator_is_expired[generator_idx] { + continue; + } + + let finished = generators[generator_idx].0.run(&witness, &mut buffer); + if finished { + generator_is_expired[generator_idx] = true; + remaining_generators -= 1; + } + + // Merge any generated values into our witness, and get a list of newly-populated + // targets' representatives. + let new_target_reps = buffer + .target_values + .drain(..) + .flat_map(|(t, v)| witness.set_target_returning_rep(t, v)); + + // Enqueue unfinished generators that were watching one of the newly populated targets. + for watch in new_target_reps { + let opt_watchers = generator_indices_by_watches.get(&watch); + if let Some(watchers) = opt_watchers { + for &watching_generator_idx in watchers { + if !generator_is_expired[watching_generator_idx] { + next_pending_generator_indices.push(watching_generator_idx); + } + } + } + } + } + + pending_generator_indices = next_pending_generator_indices; + } + if remaining_generators != 0 { + println!("{} generators weren't run", remaining_generators); + + let filtered = generator_is_expired + .iter() + .enumerate() + .filter_map(|(index, flag)| if !flag { Some(index) } else { None }) + .min(); + + if let Some(min_val) = filtered { + println!("generator at index: {} is the first to not run", min_val); + println!("This has ID: {}", generators[min_val].0.id()); + + for watch in generators[min_val].0.watch_list().iter() { + println!("watching: {:?}", watch); + } + } + } + + witness +} diff --git a/mp2-test/src/mpt_sequential.rs b/mp2-test/src/mpt_sequential.rs index 97a64dfb2..42e550623 100644 --- a/mp2-test/src/mpt_sequential.rs +++ b/mp2-test/src/mpt_sequential.rs @@ -1,5 +1,15 @@ +use alloy::{ + eips::BlockNumberOrTag, + network::TransactionBuilder, + node_bindings::Anvil, + primitives::{Address, U256}, + providers::{ext::AnvilApi, Provider, ProviderBuilder}, + sol, +}; use eth_trie::{EthTrie, MemoryDB, Trie}; -use rand::{thread_rng, Rng}; + +use mp2_common::eth::{ReceiptProofInfo, ReceiptQuery}; +use rand::{distributions::uniform::SampleRange, thread_rng, Rng}; use std::sync::Arc; /// Simply the maximum number of nibbles a key can have. @@ -39,3 +49,246 @@ pub fn generate_random_storage_mpt( } (trie, keys[right_key_idx].to_vec()) } + +#[derive(Debug, Clone)] +pub struct ReceiptTestInfo { + /// The query which we have returned proofs for + pub query: ReceiptQuery, + /// The proofs for receipts relating to `self.query` + pub proofs: Vec, +} + +impl ReceiptTestInfo { + /// Getter for the proofs + pub fn proofs(&self) -> Vec { + self.proofs.clone() + } + /// Getter for the query + pub fn query(&self) -> &ReceiptQuery { + &self.query + } +} +/// This function is used so that we can generate a Receipt Trie for a blog with varying transactions +/// (i.e. some we are interested in and some we are not). +pub fn generate_receipt_test_info( +) -> ReceiptTestInfo { + // Make a contract that emits events so we can pick up on them + sol! { + #[allow(missing_docs)] + // solc v0.8.26; solc Counter.sol --via-ir --optimize --bin + #[sol(rpc, abi, bytecode="6080604052348015600e575f80fd5b506104ed8061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610085575f3560e01c80638381f58a116100595780638381f58a146100b1578063d09de08a146100cf578063d857c891146100d9578063db732279146100f557610085565b80623c7e56146100895780632dc347641461009357806331c1c63b1461009d578063338b538a146100a7575b5f80fd5b6100916100ff565b005b61009b61016b565b005b6100a56101e6565b005b6100af61023a565b005b6100b9610280565b6040516100c69190610377565b60405180910390f35b6100d7610285565b005b6100f360048036038101906100ee91906103be565b61029d565b005b6100fd610327565b005b60025f5461010d9190610416565b60015f5461011b9190610416565b5f547ff57f433eb9493cf4d9cb5763c12221d9b095804644d4ee006a78c72076cff94760035f5461014c9190610416565b6040516101599190610377565b60405180910390a4610169610285565b565b60025f546101799190610416565b60015f546101879190610416565b5f547ff03d29753fbd5ac209bab88a99b396bcc25c3e72530d02c81aea4d324ab3d74260035f546101b89190610416565b60045f546101c69190610416565b6040516101d4929190610449565b60405180910390a46101e4610285565b565b60025f546101f49190610416565b60015f546102029190610416565b5f547f1d18de2cd8798a1c29b9255930c807eb6c84ae0acb2219acbb11e0f65cf813e960405160405180910390a4610238610285565b565b60015f546102489190610416565b5f547fa6baf14d8f11d7a4497089bb3fca0adfc34837cfb1f4aa370634d36ef0305b4660405160405180910390a361027e610285565b565b5f5481565b5f8081548092919061029690610470565b9190505550565b5f81036102b9576102ac610327565b6102b4610327565b610324565b600181036102d6576102c961023a565b6102d161023a565b610323565b600281036102f3576102e66101e6565b6102ee6101e6565b610322565b60038103610310576103036100ff565b61030b6100ff565b610321565b61031861016b565b61032061016b565b5b5b5b5b50565b5f547fdcd9c7fa0342f01013bd0bf2bec103a81936162dcebd1f0c38b1d4164c17e0fc60405160405180910390a261035d610285565b565b5f819050919050565b6103718161035f565b82525050565b5f60208201905061038a5f830184610368565b92915050565b5f80fd5b61039d8161035f565b81146103a7575f80fd5b50565b5f813590506103b881610394565b92915050565b5f602082840312156103d3576103d2610390565b5b5f6103e0848285016103aa565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6104208261035f565b915061042b8361035f565b9250828201905080821115610443576104426103e9565b5b92915050565b5f60408201905061045c5f830185610368565b6104696020830184610368565b9392505050565b5f61047a8261035f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036104ac576104ab6103e9565b5b60018201905091905056fea2646970667358221220f5d14aba97b2168309da4d73f65e2c98d90f3c697213c6e51c2520cee4816aea64736f6c634300081a0033")] + contract EventEmitter { + uint256 public number; + event testEvent(uint256 indexed num); + event twoIndexed(uint256 indexed num, uint256 indexed numTwo); + event threeIndexed(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree); + event oneData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour); + event twoData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour, uint256 numFive); + + + function testEmit() public { + emit testEvent(number); + increment(); + } + + function testTwoIndexed() public { + emit twoIndexed(number, number + 1); + increment(); + } + + function testThreeIndexed() public { + emit threeIndexed(number, number + 1, number + 2); + increment(); + } + + function testOneData() public { + emit oneData(number, number + 1, number + 2, number + 3); + increment(); + } + + function testTwoData() public { + emit twoData(number, number + 1, number + 2, number + 3, number + 4); + increment(); + } + + function twoEmits(uint256 flag) public { + if (flag == 0) { + testEmit(); + testEmit(); + } else if (flag == 1) { + testTwoIndexed(); + testTwoIndexed(); + } else if (flag == 2) { + testThreeIndexed(); + testThreeIndexed(); + } else if (flag == 3) { + testOneData(); + testOneData(); + } else { + testTwoData(); + testTwoData(); + } + } + + function increment() public { + number++; + } + } + + #[sol(rpc, abi, bytecode="6080604052348015600e575f80fd5b506102288061001c5f395ff3fe608060405234801561000f575f80fd5b506004361061004a575f3560e01c8063488814e01461004e5780637229db15146100585780638381f58a14610062578063d09de08a14610080575b5f80fd5b61005661008a565b005b6100606100f8565b005b61006a610130565b6040516100779190610165565b60405180910390f35b610088610135565b005b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a26100c0610135565b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a26100f6610135565b565b5f547fbe3cbcfa5d4a62a595b4a15f51de63c11797bbef2ff687873efb0bb2852ee20f60405160405180910390a261012e610135565b565b5f5481565b5f80815480929190610146906101ab565b9190505550565b5f819050919050565b61015f8161014d565b82525050565b5f6020820190506101785f830184610156565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6101b58261014d565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036101e7576101e661017e565b5b60018201905091905056fea2646970667358221220aacdd709f2f5e659587a60249419a4459e23d06c85d31d2c0b55c3fafbf3a2cb64736f6c634300081a0033")] + + contract OtherEmitter { + uint256 public number; + event otherEvent(uint256 indexed num); + + function otherEmit() public { + emit otherEvent(number); + increment(); + } + + function twoEmits() public { + emit otherEvent(number); + increment(); + emit otherEvent(number); + increment(); + } + + function increment() public { + number++; + } + } + } + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + // Spin up a local node. + + let rpc = ProviderBuilder::new() + .with_recommended_fillers() + .on_anvil_with_config(|anvil| Anvil::arg(anvil, "--no-mining")); + + // Turn on auto mining to deploy the contracts + rpc.anvil_set_auto_mine(true).await.unwrap(); + + // Deploy the contract using anvil + let event_contract = EventEmitter::deploy(rpc.root()).await.unwrap(); + + // Deploy the contract using anvil + let other_contract = OtherEmitter::deploy(rpc.root()).await.unwrap(); + + // Disable auto mining so we can ensure that all the transaction appear in the same block + rpc.anvil_set_auto_mine(false).await.unwrap(); + rpc.anvil_auto_impersonate_account(true).await.unwrap(); + // Send a bunch of transactions, some of which are related to the event we are testing for. + let mut pending_tx_builders = vec![]; + let mut rng = rand::thread_rng(); + for i in 0..25 { + let random = match (0..5).sample_single(&mut rng) { + 0 => event_contract.testEmit().into_transaction_request(), + 1 => event_contract.testTwoIndexed().into_transaction_request(), + 2 => event_contract.testThreeIndexed().into_transaction_request(), + 3 => event_contract.testOneData().into_transaction_request(), + 4 => event_contract.testTwoData().into_transaction_request(), + _ => unreachable!(), + }; + let random_two = match (0..5).sample_single(&mut rng) { + 0 => event_contract.testEmit().into_transaction_request(), + 1 => event_contract.testTwoIndexed().into_transaction_request(), + 2 => event_contract.testThreeIndexed().into_transaction_request(), + 3 => event_contract.testOneData().into_transaction_request(), + 4 => event_contract.testTwoData().into_transaction_request(), + _ => unreachable!(), + }; + let tx_req = match i % 4 { + 0 => random, + 1 => random_two, + 2 => other_contract.otherEmit().into_transaction_request(), + 3 => other_contract.twoEmits().into_transaction_request(), + _ => unreachable!(), + }; + + let sender_address = Address::random(); + + let funding = U256::from(1e18 as u64); + rpc.anvil_set_balance(sender_address, funding) + .await + .unwrap(); + + let new_req = tx_req.with_from(sender_address); + let tx_req_final = rpc + .fill(new_req) + .await + .unwrap() + .as_builder() + .unwrap() + .clone(); + pending_tx_builders.push(rpc.send_transaction(tx_req_final).await.unwrap()); + } + + // Finally we guarantee at least three of the event we are going to query for + for _ in 0..3 { + let queried_event_req = match (NO_TOPICS, MAX_DATA) { + (1, 0) => event_contract.testEmit().into_transaction_request(), + (2, 0) => event_contract.testTwoIndexed().into_transaction_request(), + (3, 0) => event_contract.testThreeIndexed().into_transaction_request(), + (3, 1) => event_contract.testOneData().into_transaction_request(), + (3, 2) => event_contract.testTwoData().into_transaction_request(), + _ => unreachable!(), + }; + + let sender_address = Address::random(); + let funding = U256::from(1e18 as u64); + rpc.anvil_set_balance(sender_address, funding) + .await + .unwrap(); + rpc.anvil_auto_impersonate_account(true).await.unwrap(); + let new_req = queried_event_req.with_from(sender_address); + let tx_req_final = rpc + .fill(new_req) + .await + .unwrap() + .as_builder() + .unwrap() + .clone(); + pending_tx_builders.push(rpc.send_transaction(tx_req_final).await.unwrap()); + } + + // Mine a block, it should include all the transactions created above. + rpc.anvil_mine(Some(U256::from(1u8)), None).await.unwrap(); + + let mut transactions = Vec::new(); + for pending in pending_tx_builders.into_iter() { + let hash = pending.watch().await.unwrap(); + transactions.push(rpc.get_transaction_by_hash(hash).await.unwrap().unwrap()); + } + + let block_number = transactions.first().unwrap().block_number.unwrap(); + + // We want to get the event signature so we can make a ReceiptQuery + let all_events = EventEmitter::abi::events(); + + let events = match (NO_TOPICS, MAX_DATA) { + (1, 0) => all_events.get("testEvent").unwrap(), + (2, 0) => all_events.get("twoIndexed").unwrap(), + (3, 0) => all_events.get("threeIndexed").unwrap(), + (3, 1) => all_events.get("oneData").unwrap(), + (3, 2) => all_events.get("twoData").unwrap(), + _ => panic!(), + }; + + let receipt_query = ReceiptQuery::::new( + *event_contract.address(), + &events[0].signature(), + ); + + let proofs = receipt_query + .query_receipt_proofs(rpc.root(), BlockNumberOrTag::Number(block_number)) + .await + .unwrap(); + + ReceiptTestInfo { + query: receipt_query, + proofs, + } + }) +} diff --git a/mp2-v1/src/api.rs b/mp2-v1/src/api.rs index 22c31accd..129521229 100644 --- a/mp2-v1/src/api.rs +++ b/mp2-v1/src/api.rs @@ -141,6 +141,9 @@ pub fn generate_proof(params: &PublicParameters, input: CircuitInput) -> Result< length_circuit_set, ) } + final_extraction::CircuitInput::Receipt(input) => params + .final_extraction + .generate_receipt_proof(input, value_circuit_set), } } CircuitInput::CellsTree(input) => verifiable_db::api::generate_proof( diff --git a/mp2-v1/src/block_extraction/circuit.rs b/mp2-v1/src/block_extraction/circuit.rs index 0600285a8..4c69fe25d 100644 --- a/mp2-v1/src/block_extraction/circuit.rs +++ b/mp2-v1/src/block_extraction/circuit.rs @@ -22,6 +22,12 @@ const HEADER_PARENT_HASH_OFFSET: usize = 4; /// State root offset in RLP encoded header. const HEADER_STATE_ROOT_OFFSET: usize = 91; +/// Transaction root offset in RLP encoded header. +const HEADER_TRANSACTION_ROOT_OFFSET: usize = 124; + +/// Receipt root offset in RLP encoded header. +const HEADER_RECEIPT_ROOT_OFFSET: usize = 157; + /// Block number offset in RLP encoded header. const HEADER_BLOCK_NUMBER_OFFSET: usize = 449; /// We define u64 as the maximum block mnumber ever to be reached @@ -50,6 +56,25 @@ pub struct BlockCircuit { pub rlp_headers: Vec, } +/// Enum that represents the extraction type, storage, receipt or transaction +#[derive(Debug, Clone, Serialize, Deserialize, Copy)] +pub enum ExtractionType { + Storage, + Receipt, + Transaction, +} + +impl ExtractionType { + /// This function returns the offset of the relevant root for that type of extraction + pub fn offset(&self) -> usize { + match self { + ExtractionType::Storage => HEADER_STATE_ROOT_OFFSET, + ExtractionType::Receipt => HEADER_RECEIPT_ROOT_OFFSET, + ExtractionType::Transaction => HEADER_TRANSACTION_ROOT_OFFSET, + } + } +} + impl BlockCircuit { /// Creates a new instance of the circuit. pub fn new(rlp_headers: Vec) -> Result { @@ -69,17 +94,32 @@ impl BlockCircuit { rlp_headers.assert_bytes(cb); // extract the previous block hash from the RLP header - let prev_bh = Array::::from_array(create_array(|i| { + let prev_bh: Array = Array::::from_array(create_array(|i| { rlp_headers.arr.arr[HEADER_PARENT_HASH_OFFSET + i] })); let packed_prev_bh = prev_bh.pack(cb, Endianness::Little).downcast_to_targets(); // extract the state root of the block - let state_root = Array::::from_array(create_array(|i| { - rlp_headers.arr.arr[HEADER_STATE_ROOT_OFFSET + i] - })); + let state_root: Array = + Array::::from_array(create_array(|i| { + rlp_headers.arr.arr[HEADER_STATE_ROOT_OFFSET + i] + })); let state_root_packed = state_root.pack(cb, Endianness::Little); + // extract the transaction root of the block + let transaction_root: Array = + Array::::from_array(create_array(|i| { + rlp_headers.arr.arr[HEADER_TRANSACTION_ROOT_OFFSET + i] + })); + let transaction_root_packed = transaction_root.pack(cb, Endianness::Little); + + // extract the receipt root of the block + let receipt_root: Array = + Array::::from_array(create_array(|i| { + rlp_headers.arr.arr[HEADER_RECEIPT_ROOT_OFFSET + i] + })); + let receipt_root_packed = receipt_root.pack(cb, Endianness::Little); + // compute the block hash let bh_wires = KeccakCircuit::hash_vector(cb, &rlp_headers); @@ -99,6 +139,8 @@ impl BlockCircuit { &packed_prev_bh.downcast_to_targets().arr, &bn_u256.to_targets(), &state_root_packed.downcast_to_targets().arr, + &transaction_root_packed.downcast_to_targets().arr, + &receipt_root_packed.downcast_to_targets().arr, ) .register(cb); @@ -131,7 +173,7 @@ mod test { use mp2_common::{eth::left_pad_generic, u256, utils::ToFields, C, F}; use mp2_common::{ - eth::BlockUtil, + eth::Rlpable, types::CBuilder, utils::{Endianness, Packer}, D, @@ -146,10 +188,36 @@ mod test { use super::{public_inputs::PublicInputs, BlockCircuit, BlockWires}; use anyhow::Result; - pub type SepoliaBlockCircuit = BlockCircuit; - #[tokio::test] - async fn prove_and_verify_block_extraction_circuit() -> Result<()> { + pub async fn prove_and_verify_block_extraction_circuit() -> Result<()> { + #[derive(Clone, Debug)] + pub struct TestCircuit { + inner: BlockCircuit, + } + + impl TestCircuit { + pub fn new(rlp_headers: Vec) -> Result { + crate::block_extraction::circuit::ensure!( + rlp_headers.len() <= crate::block_extraction::circuit::MAX_BLOCK_LEN, + "block rlp headers too long" + ); + Ok(Self { + inner: BlockCircuit { rlp_headers }, + }) + } + } + + impl UserCircuit for TestCircuit { + type Wires = BlockWires; + + fn build(cb: &mut CBuilder) -> Self::Wires { + BlockCircuit::build(cb) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.inner.assign(pw, wires); + } + } let url = get_sepolia_url(); let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); let block_number = BlockNumberOrTag::Latest; @@ -168,20 +236,27 @@ mod test { .pack(Endianness::Little) .to_fields(); let block_hash = block.block_hash().pack(Endianness::Little).to_fields(); - let state_root = block + + let state_root = block.header.state_root.pack(Endianness::Little).to_fields(); + let transaction_root = block .header - .state_root - .0 + .transactions_root + .pack(Endianness::Little) + .to_fields(); + let receipt_root = block + .header + .receipts_root .pack(Endianness::Little) .to_fields(); + let block_number_buff = block.header.number.to_be_bytes(); const NUM_LIMBS: usize = u256::NUM_LIMBS; let block_number = left_pad_generic::(&block_number_buff.pack(Endianness::Big)) .to_fields(); - let setup = setup_circuit::<_, D, C, SepoliaBlockCircuit>(); - let circuit = SepoliaBlockCircuit::new(rlp_headers).unwrap(); + let setup = setup_circuit::<_, D, C, TestCircuit>(); + let circuit = TestCircuit::new(rlp_headers).unwrap(); let proof = prove_circuit(&setup, &circuit); let pi = PublicInputs::::from_slice(&proof.public_inputs); @@ -191,20 +266,11 @@ mod test { pi.block_hash_raw(), block.header.hash.0.pack(Endianness::Little).to_fields() ); + assert_eq!(pi.state_root_raw(), &state_root); + assert_eq!(pi.transaction_root_raw(), &transaction_root); + assert_eq!(pi.receipt_root_raw(), &receipt_root); assert_eq!(pi.block_number_raw(), &block_number); Ok(()) } - - impl UserCircuit for BlockCircuit { - type Wires = BlockWires; - - fn build(cb: &mut CBuilder) -> Self::Wires { - Self::build(cb) - } - - fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - self.assign(pw, wires); - } - } } diff --git a/mp2-v1/src/block_extraction/mod.rs b/mp2-v1/src/block_extraction/mod.rs index de6648f41..76347b1fd 100644 --- a/mp2-v1/src/block_extraction/mod.rs +++ b/mp2-v1/src/block_extraction/mod.rs @@ -15,6 +15,7 @@ use mp2_common::{ }; use serde::{Deserialize, Serialize}; +pub use circuit::ExtractionType; pub use public_inputs::PublicInputs; pub struct CircuitInput(Vec); impl CircuitInput { @@ -69,7 +70,7 @@ mod test { }; use anyhow::Result; use mp2_common::{ - eth::BlockUtil, + eth::Rlpable, proof::deserialize_proof, utils::{Endianness, FromFields, Packer, ToFields}, C, D, F, @@ -77,8 +78,9 @@ mod test { use mp2_test::eth::get_sepolia_url; use crate::block_extraction::{public_inputs::PublicInputs, PublicParameters}; + #[tokio::test] - async fn test_api() -> Result<()> { + async fn test_api_storage() -> Result<()> { let params = PublicParameters::build(); let url = get_sepolia_url(); let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); @@ -121,7 +123,7 @@ mod test { ); assert_eq!( U256::from_fields(pi.block_number_raw()), - U256::from(block.header.number), + U256::from(block.header.number) ); assert_eq!( pi.state_root_raw(), diff --git a/mp2-v1/src/block_extraction/public_inputs.rs b/mp2-v1/src/block_extraction/public_inputs.rs index 143eeac93..e376baf9f 100644 --- a/mp2-v1/src/block_extraction/public_inputs.rs +++ b/mp2-v1/src/block_extraction/public_inputs.rs @@ -12,10 +12,14 @@ use plonky2::iop::target::Target; // - `PREV_BH : [8]F` packed Keccak hash of the block // - `BN : F` Proven block number // - `SH : [8]F` Packed state root hash +// - `TH : [8]F` Packed transaction root hash +// - `RH : [8]F` Packed receipt root hash const BH_RANGE: PublicInputRange = 0..PACKED_HASH_LEN; const PREV_BH_RANGE: PublicInputRange = BH_RANGE.end..BH_RANGE.end + PACKED_HASH_LEN; const BN_RANGE: PublicInputRange = PREV_BH_RANGE.end..PREV_BH_RANGE.end + u256::NUM_LIMBS; const SH_RANGE: PublicInputRange = BN_RANGE.end..BN_RANGE.end + PACKED_HASH_LEN; +const TH_RANGE: PublicInputRange = SH_RANGE.end..SH_RANGE.end + PACKED_HASH_LEN; +const RH_RANGE: PublicInputRange = TH_RANGE.end..TH_RANGE.end + PACKED_HASH_LEN; /// Public inputs for the dynamic-length variable extraction. #[derive(Clone, Debug)] @@ -28,16 +32,29 @@ pub struct PublicInputs<'a, T> { pub(crate) bn: &'a [T], /// Packed state root pub(crate) sh: &'a [T], + /// Packed transaction root + pub(crate) th: &'a [T], + /// Packed receipt root + pub(crate) rh: &'a [T], } impl PublicInputCommon for PublicInputs<'_, Target> { - const RANGES: &'static [PublicInputRange] = &[BH_RANGE, PREV_BH_RANGE, BN_RANGE, SH_RANGE]; + const RANGES: &'static [PublicInputRange] = &[ + BH_RANGE, + PREV_BH_RANGE, + BN_RANGE, + SH_RANGE, + TH_RANGE, + RH_RANGE, + ]; fn register_args(&self, cb: &mut CBuilder) { cb.register_public_inputs(self.bh); cb.register_public_inputs(self.prev_bh); cb.register_public_inputs(self.bn); cb.register_public_inputs(self.sh); + cb.register_public_inputs(self.th); + cb.register_public_inputs(self.rh); } } @@ -48,16 +65,22 @@ impl<'a> PublicInputs<'a, Target> { prev_bh: &'a [Target], bn: &'a [Target], sh: &'a [Target], + th: &'a [Target], + rh: &'a [Target], ) -> Self { assert!(bh.len() == PACKED_HASH_LEN); assert!(prev_bh.len() == PACKED_HASH_LEN); assert!(sh.len() == PACKED_HASH_LEN); + assert!(th.len() == PACKED_HASH_LEN); + assert!(rh.len() == PACKED_HASH_LEN); assert!(bn.len() == u256::NUM_LIMBS); Self { bh, prev_bh, bn, sh, + th, + rh, } } @@ -72,6 +95,14 @@ impl<'a> PublicInputs<'a, Target> { pub fn state_root(&self) -> OutputHash { OutputHash::from_targets(self.sh) } + + pub fn transaction_root(&self) -> OutputHash { + OutputHash::from_targets(self.th) + } + + pub fn receipt_root(&self) -> OutputHash { + OutputHash::from_targets(self.rh) + } } impl PublicInputs<'_, T> { @@ -82,6 +113,8 @@ impl PublicInputs<'_, T> { .chain(self.prev_bh.iter()) .chain(self.bn.iter()) .chain(self.sh.iter()) + .chain(self.th.iter()) + .chain(self.rh.iter()) .cloned() .collect() } @@ -89,19 +122,30 @@ impl PublicInputs<'_, T> { impl<'a, T> PublicInputs<'a, T> { /// Total length of the public inputs. - pub const TOTAL_LEN: usize = SH_RANGE.end; + pub const TOTAL_LEN: usize = RH_RANGE.end; /// Creates a new instance from its internal parts. - pub fn from_parts(bh: &'a [T], prev_bh: &'a [T], bn: &'a [T], sh: &'a [T]) -> Self { + pub fn from_parts( + bh: &'a [T], + prev_bh: &'a [T], + bn: &'a [T], + sh: &'a [T], + th: &'a [T], + rh: &'a [T], + ) -> Self { assert_eq!(bh.len(), BH_RANGE.len()); assert_eq!(prev_bh.len(), PREV_BH_RANGE.len()); assert_eq!(sh.len(), SH_RANGE.len()); + assert_eq!(th.len(), TH_RANGE.len()); + assert_eq!(rh.len(), RH_RANGE.len()); Self { bh, prev_bh, bn, sh, + th, + rh, } } @@ -112,6 +156,8 @@ impl<'a, T> PublicInputs<'a, T> { prev_bh: &pi[PREV_BH_RANGE], bn: &pi[BN_RANGE], sh: &pi[SH_RANGE], + th: &pi[TH_RANGE], + rh: &pi[RH_RANGE], } } @@ -134,4 +180,14 @@ impl<'a, T> PublicInputs<'a, T> { pub const fn state_root_raw(&self) -> &[T] { self.sh } + + /// Returns the packed transaction root hash. + pub const fn transaction_root_raw(&self) -> &[T] { + self.th + } + + /// Returns the packed receipt root hash. + pub const fn receipt_root_raw(&self) -> &[T] { + self.rh + } } diff --git a/mp2-v1/src/contract_extraction/branch.rs b/mp2-v1/src/contract_extraction/branch.rs index ff27d2147..b78e7edfa 100644 --- a/mp2-v1/src/contract_extraction/branch.rs +++ b/mp2-v1/src/contract_extraction/branch.rs @@ -5,7 +5,7 @@ use anyhow::Result; use mp2_common::{ array::{Array, Vector, VectorWire}, keccak::{InputData, KeccakCircuit, KeccakWires, PACKED_HASH_LEN}, - mpt_sequential::{Circuit as MPTCircuit, PAD_LEN}, + mpt_sequential::{advance_key_branch, PAD_LEN}, public_inputs::PublicInputCommon, rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST}, types::{CBuilder, GFp}, @@ -54,12 +54,14 @@ where // validity of the hash exposed by the proofs. let headers = decode_fixed_list::<_, D, MAX_ITEMS_IN_LIST>(b, &node.arr.arr, zero); - let (new_mpt_key, hash, is_valid, _) = MPTCircuit::<1, NODE_LEN>::advance_key_branch( - b, - &node.arr, - &child_proof.mpt_key(), - &headers, - ); + let (new_mpt_key, hash, is_valid, _) = + // MPTCircuit::<1, NODE_LEN, MAX_KEY_NIBBLE_LEN> + advance_key_branch( + b, + &node.arr, + &child_proof.mpt_key(), + &headers, + ); // We always enforce it's a branch node, i.e. that it has 17 entries. b.connect(is_valid.target, ttrue.target); @@ -111,7 +113,7 @@ where _builder_parameters: Self::CircuitBuilderParams, ) -> Self { let inputs = PublicInputs::from_slice(&verified_proofs[0].public_inputs); - BranchCircuit::build(builder, inputs) + BranchCircuit::<_>::build(builder, inputs) } fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { diff --git a/mp2-v1/src/final_extraction/api.rs b/mp2-v1/src/final_extraction/api.rs index ef152d684..5e7f96498 100644 --- a/mp2-v1/src/final_extraction/api.rs +++ b/mp2-v1/src/final_extraction/api.rs @@ -11,6 +11,7 @@ use super::{ base_circuit::BaseCircuitInput, lengthed_circuit::LengthedRecursiveWires, merge_circuit::{MergeTable, MergeTableRecursiveWires}, + receipt_circuit::{ReceiptCircuitInput, ReceiptCircuitProofInputs, ReceiptRecursiveWires}, simple_circuit::SimpleCircuitRecursiveWires, BaseCircuitProofInputs, LengthedCircuit, MergeCircuit, PublicInputs, SimpleCircuit, }; @@ -20,6 +21,7 @@ pub enum CircuitInput { Simple(SimpleCircuitInput), Lengthed(LengthedCircuitInput), MergeTable(MergeCircuitInput), + Receipt(ReceiptCircuitInput), } #[derive(Clone, Debug)] pub struct FinalExtractionBuilderParams { @@ -51,6 +53,7 @@ pub struct PublicParameters { simple: CircuitWithUniversalVerifier, lengthed: CircuitWithUniversalVerifier, merge: CircuitWithUniversalVerifier, + receipt: CircuitWithUniversalVerifier, circuit_set: RecursiveCircuits, } @@ -76,12 +79,14 @@ impl PublicParameters { ); let simple = builder.build_circuit(builder_params.clone()); let lengthed = builder.build_circuit(builder_params.clone()); - let merge = builder.build_circuit(builder_params); + let merge = builder.build_circuit(builder_params.clone()); + let receipt = builder.build_circuit(builder_params); let circuits = vec![ prepare_recursive_circuit_for_circuit_set(&simple), prepare_recursive_circuit_for_circuit_set(&lengthed), prepare_recursive_circuit_for_circuit_set(&merge), + prepare_recursive_circuit_for_circuit_set(&receipt), ]; let circuit_set = RecursiveCircuits::new(circuits); @@ -90,6 +95,7 @@ impl PublicParameters { simple, lengthed, merge, + receipt, circuit_set, } } @@ -160,6 +166,19 @@ impl PublicParameters { ProofWithVK::serialize(&(proof, self.lengthed.circuit_data().verifier_only.clone()).into()) } + pub(crate) fn generate_receipt_proof( + &self, + input: ReceiptCircuitInput, + value_circuit_set: &RecursiveCircuits, + ) -> Result> { + let receipt_input = + ReceiptCircuitProofInputs::new_from_proofs(input, value_circuit_set.clone()); + let proof = self + .circuit_set + .generate_proof(&self.receipt, [], [], receipt_input)?; + ProofWithVK::serialize(&(proof, self.receipt.circuit_data().verifier_only.clone()).into()) + } + pub(crate) fn get_circuit_set(&self) -> &RecursiveCircuits { &self.circuit_set } @@ -230,6 +249,13 @@ impl CircuitInput { let length_proof = ProofWithVK::deserialize(&length_proof)?; Ok(Self::Lengthed(LengthedCircuitInput { base, length_proof })) } + + pub fn new_receipt_input(block_proof: Vec, value_proof: Vec) -> Result { + Ok(Self::Receipt(ReceiptCircuitInput::new( + block_proof, + value_proof, + )?)) + } } #[cfg(test)] @@ -247,6 +273,7 @@ mod tests { final_extraction::{ base_circuit::{test::ProofsPi, CONTRACT_SET_NUM_IO, VALUE_SET_NUM_IO}, lengthed_circuit::LENGTH_SET_NUM_IO, + receipt_circuit::test::ReceiptsProofsPi, }, length_extraction, }; @@ -270,6 +297,7 @@ mod tests { ); let proof_pis = ProofsPi::random(); + let receipt_proof_pis = ReceiptsProofsPi::generate_from_proof_pi_value(&proof_pis); let length_pis = proof_pis.length_inputs(); let len_dm = length_extraction::PublicInputs::::from_slice(&length_pis).metadata_point(); let block_proof = block_circuit @@ -284,6 +312,13 @@ mod tests { let length_proof = &length_params .generate_input_proofs::<1>([length_pis.try_into().unwrap()]) .unwrap()[0]; + let receipt_proof = &values_params + .generate_input_proofs::<1>([receipt_proof_pis + .value_inputs() + .proof_inputs + .try_into() + .unwrap()]) + .unwrap()[0]; let contract_proof: ProofWithVK = ( contract_proof.clone(), @@ -348,5 +383,31 @@ mod tests { ) .unwrap(); proof_pis.check_proof_public_inputs(proof.proof(), TableDimension::Compound, Some(len_dm)); + + let receipt_proof: ProofWithVK = ( + receipt_proof.clone(), + values_params.verifier_data_for_input_proofs::<1>()[0].clone(), + ) + .into(); + + let circuit_input = CircuitInput::new_receipt_input( + serialize_proof(&block_proof).unwrap(), + receipt_proof.serialize().unwrap(), + ) + .unwrap(); + let proof = ProofWithVK::deserialize( + ¶ms + .generate_receipt_proof( + match circuit_input { + CircuitInput::Receipt(input) => input, + _ => unreachable!(), + }, + values_params.get_recursive_circuit_set(), + ) + .unwrap(), + ) + .unwrap(); + + receipt_proof_pis.check_proof_public_inputs(proof.proof()); } } diff --git a/mp2-v1/src/final_extraction/base_circuit.rs b/mp2-v1/src/final_extraction/base_circuit.rs index a2b164a86..480a68080 100644 --- a/mp2-v1/src/final_extraction/base_circuit.rs +++ b/mp2-v1/src/final_extraction/base_circuit.rs @@ -441,6 +441,8 @@ pub(crate) mod test { ); let h = &random_vector::(PACKED_HASH_LEN).to_fields(); + let th = &random_vector::(PACKED_HASH_LEN).to_fields(); + let rh = &random_vector::(PACKED_HASH_LEN).to_fields(); let contract_dm = Point::rand(); let key = &random_vector::(MAX_KEY_NIBBLE_LEN).to_fields(); let ptr = &F::NEG_ONE; // simulating end of MPT recursion @@ -467,6 +469,8 @@ pub(crate) mod test { prev_bh: &parent_block_hash, bn: &block_number, sh: h, + th, + rh, } .to_vec(); ProofsPi { diff --git a/mp2-v1/src/final_extraction/mod.rs b/mp2-v1/src/final_extraction/mod.rs index cb6e1c6a4..3d78f3af6 100644 --- a/mp2-v1/src/final_extraction/mod.rs +++ b/mp2-v1/src/final_extraction/mod.rs @@ -3,6 +3,7 @@ mod base_circuit; mod lengthed_circuit; mod merge_circuit; mod public_inputs; +mod receipt_circuit; mod simple_circuit; pub use api::{CircuitInput, PublicParameters}; diff --git a/mp2-v1/src/final_extraction/receipt_circuit.rs b/mp2-v1/src/final_extraction/receipt_circuit.rs new file mode 100644 index 000000000..56a540370 --- /dev/null +++ b/mp2-v1/src/final_extraction/receipt_circuit.rs @@ -0,0 +1,405 @@ +use mp2_common::{ + default_config, + keccak::{OutputHash, PACKED_HASH_LEN}, + proof::{deserialize_proof, verify_proof_fixed_circuit, ProofWithVK}, + public_inputs::PublicInputCommon, + serialization::{deserialize, serialize}, + u256::UInt256Target, + utils::{FromTargets, ToTargets}, + C, D, F, +}; +use plonky2::{ + field::{goldilocks_field::GoldilocksField, types::Field}, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{ + circuit_builder::CircuitBuilder, + proof::{ProofWithPublicInputs, ProofWithPublicInputsTarget}, + }, +}; +use plonky2_ecgfp5::gadgets::curve::CurveTarget; +use recursion_framework::{ + circuit_builder::CircuitLogicWires, + framework::{ + RecursiveCircuits, RecursiveCircuitsVerifierGagdet, RecursiveCircuitsVerifierTarget, + }, +}; +use serde::{Deserialize, Serialize}; + +use crate::{block_extraction, values_extraction}; + +use super::{ + api::{FinalExtractionBuilderParams, NUM_IO}, + PublicInputs, +}; + +use anyhow::Result; + +/// This circuit is more like a gadget. This contains the logic of the common part +/// between all the final extraction circuits. It should not be used on its own. +#[derive(Debug, Clone, Copy)] +pub struct ReceiptExtractionCircuit; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReceiptExtractionWires { + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + pub(crate) dm: CurveTarget, + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + pub(crate) dv: CurveTarget, + pub(crate) bh: [Target; PACKED_HASH_LEN], + pub(crate) prev_bh: [Target; PACKED_HASH_LEN], + pub(crate) bn: UInt256Target, +} + +impl ReceiptExtractionCircuit { + pub(crate) fn build( + b: &mut CircuitBuilder, + block_pi: &[Target], + value_pi: &[Target], + ) { + // TODO: homogeinize the public inputs structs + let block_pi = + block_extraction::public_inputs::PublicInputs::::from_slice(block_pi); + let value_pi = values_extraction::PublicInputs::::new(value_pi); + + let minus_one = b.constant(GoldilocksField::NEG_ONE); + + // enforce the MPT key extraction reached the root + b.connect(value_pi.mpt_key().pointer, minus_one); + + // enforce block_pi.state_root == contract_pi.state_root + block_pi + .receipt_root() + .enforce_equal(b, &OutputHash::from_targets(value_pi.root_hash_info())); + + PublicInputs::new( + block_pi.bh, + block_pi.prev_bh, + // here the value digest is the same since for length proof, it is assumed the table + // digest is in Compound format (i.e. multiple rows inside digest already). + &value_pi.values_digest_target().to_targets(), + &value_pi.metadata_digest_target().to_targets(), + &block_pi.bn.to_targets(), + &[b._false().target], + ) + .register_args(b); + } +} + +/// The wires that are needed for the recursive framework, that concerns verifying the input +/// proofs +#[derive(Serialize, Deserialize, Clone, Debug)] +pub(crate) struct ReceiptRecursiveWires { + /// Wires containing the block and value proof + verification: ReceiptCircuitProofWires, +} + +impl CircuitLogicWires for ReceiptRecursiveWires { + type CircuitBuilderParams = FinalExtractionBuilderParams; + + type Inputs = ReceiptCircuitProofInputs; + + const NUM_PUBLIC_INPUTS: usize = NUM_IO; + + fn circuit_logic( + builder: &mut CircuitBuilder, + _verified_proofs: [&plonky2::plonk::proof::ProofWithPublicInputsTarget; 0], + builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + // value proof for table a and value proof for table b = 2 + let verification = ReceiptCircuitProofInputs::build(builder, &builder_parameters); + ReceiptExtractionCircuit::build( + builder, + verification.get_block_public_inputs(), + verification.get_value_public_inputs(), + ); + Self { verification } + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> anyhow::Result<()> { + inputs.assign_proof_targets(pw, &self.verification)?; + Ok(()) + } +} + +/// This parameter struct is not intended to be built on its own +/// but rather as a sub-component of the two final extraction parameters set. +/// This parameter contains the common logic of verifying a block and +/// value proof automatically from the right verification keys / circuit set. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct ReceiptCircuitProofWires { + /// single circuit proof extracting block hash, block number, previous hash + /// and receipt root + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + block_proof: ProofWithPublicInputsTarget, + /// circuit set extracting the values from receipt trie of the block + value_proof: RecursiveCircuitsVerifierTarget, +} + +pub(crate) const VALUE_SET_NUM_IO: usize = values_extraction::PublicInputs::::TOTAL_LEN; + +#[derive(Clone, Debug)] +pub struct ReceiptCircuitInput { + block_proof: ProofWithPublicInputs, + value_proof: ProofWithVK, +} + +impl ReceiptCircuitInput { + pub(super) fn new(block_proof: Vec, value_proof: Vec) -> Result { + Ok(Self { + block_proof: deserialize_proof(&block_proof)?, + value_proof: ProofWithVK::deserialize(&value_proof)?, + }) + } +} +#[derive(Clone, Debug)] +pub(crate) struct ReceiptCircuitProofInputs { + proofs: ReceiptCircuitInput, + value_circuit_set: RecursiveCircuits, +} + +impl ReceiptCircuitProofInputs { + pub(crate) fn new_from_proofs( + proofs: ReceiptCircuitInput, + value_circuit_set: RecursiveCircuits, + ) -> Self { + Self { + proofs, + value_circuit_set, + } + } + + pub(crate) fn build( + cb: &mut CircuitBuilder, + params: &FinalExtractionBuilderParams, + ) -> ReceiptCircuitProofWires { + let config = default_config(); + let value_proof_wires = RecursiveCircuitsVerifierGagdet::::new( + config.clone(), + ¶ms.value_circuit_set, + ) + .verify_proof_in_circuit_set(cb); + + let block_proof_wires = verify_proof_fixed_circuit(cb, ¶ms.block_vk); + ReceiptCircuitProofWires { + block_proof: block_proof_wires, + value_proof: value_proof_wires, + } + } + + pub(crate) fn assign_proof_targets( + &self, + pw: &mut PartialWitness, + wires: &ReceiptCircuitProofWires, + ) -> anyhow::Result<()> { + pw.set_proof_with_pis_target(&wires.block_proof, &self.proofs.block_proof); + + let (proof, vd) = (&self.proofs.value_proof).into(); + wires + .value_proof + .set_target(pw, &self.value_circuit_set, proof, vd)?; + + Ok(()) + } +} + +impl ReceiptCircuitProofWires { + pub(crate) fn get_block_public_inputs(&self) -> &[Target] { + self.block_proof.public_inputs.as_slice() + } + + pub(crate) fn get_value_public_inputs(&self) -> &[Target] { + self.value_proof + .get_public_input_targets::() + } +} + +#[cfg(test)] +pub(crate) mod test { + + use crate::final_extraction::{base_circuit::test::ProofsPi, PublicInputs}; + + use super::*; + use alloy::primitives::U256; + use anyhow::Result; + + use mp2_common::{ + keccak::PACKED_HASH_LEN, + utils::{Endianness, Packer, ToFields}, + }; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::random_vector, + }; + use plonky2::{ + field::types::{PrimeField64, Sample}, + hash::hash_types::HashOut, + iop::witness::WitnessWrite, + plonk::config::GenericHashOut, + }; + use plonky2_ecgfp5::curve::curve::Point; + use values_extraction::public_inputs::tests::new_extraction_public_inputs; + + #[derive(Clone, Debug)] + struct TestReceiptCircuit { + pis: ReceiptsProofsPi, + } + + struct TestReceiptWires { + pis: ReceiptsProofsPiTarget, + } + + impl UserCircuit for TestReceiptCircuit { + type Wires = TestReceiptWires; + fn build(c: &mut CircuitBuilder) -> Self::Wires { + let proofs_pi = ReceiptsProofsPiTarget::new(c); + ReceiptExtractionCircuit::build(c, &proofs_pi.blocks_pi, &proofs_pi.values_pi); + TestReceiptWires { pis: proofs_pi } + } + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + wires.pis.assign(pw, &self.pis); + } + } + + #[derive(Clone, Debug)] + pub(crate) struct ReceiptsProofsPiTarget { + pub(crate) blocks_pi: Vec, + pub(crate) values_pi: Vec, + } + + impl ReceiptsProofsPiTarget { + pub(crate) fn new(b: &mut CircuitBuilder) -> Self { + Self { + blocks_pi: b.add_virtual_targets( + block_extraction::public_inputs::PublicInputs::::TOTAL_LEN, + ), + values_pi: b + .add_virtual_targets(values_extraction::PublicInputs::::TOTAL_LEN), + } + } + pub(crate) fn assign(&self, pw: &mut PartialWitness, pis: &ReceiptsProofsPi) { + pw.set_target_arr(&self.values_pi, pis.values_pi.as_ref()); + pw.set_target_arr(&self.blocks_pi, pis.blocks_pi.as_ref()); + } + } + + /// TODO: refactor this struct to mimick exactly the base circuit wires in that it can contain + /// multiple values + #[derive(Clone, Debug)] + pub(crate) struct ReceiptsProofsPi { + pub(crate) blocks_pi: Vec, + pub(crate) values_pi: Vec, + } + + impl ReceiptsProofsPi { + /// Function takes in a [`ProofsPi`] instance and generates a set of values public inputs + /// that agree with the provided receipts root from the `blocks_pi`. + pub(crate) fn generate_from_proof_pi_value(base_info: &ProofsPi) -> ReceiptsProofsPi { + let original = base_info.value_inputs(); + let block_pi = base_info.block_inputs(); + let (k, t) = original.mpt_key_info(); + let new_value_digest = Point::rand(); + let new_metadata_digest = Point::rand(); + let new_values_pi = new_extraction_public_inputs( + &block_pi + .receipt_root_raw() + .iter() + .map(|byte| byte.to_canonical_u64() as u32) + .collect::>(), + &k.iter() + .map(|byte| byte.to_canonical_u64() as u8) + .collect::>(), + t.to_canonical_u64() as usize, + &new_value_digest.to_weierstrass(), + &new_metadata_digest.to_weierstrass(), + original.n().to_canonical_u64() as usize, + ); + + Self { + blocks_pi: base_info.blocks_pi.clone(), + values_pi: new_values_pi, + } + } + + pub(crate) fn block_inputs(&self) -> block_extraction::PublicInputs { + block_extraction::PublicInputs::from_slice(&self.blocks_pi) + } + + pub(crate) fn value_inputs(&self) -> values_extraction::PublicInputs { + values_extraction::PublicInputs::new(&self.values_pi) + } + + pub(crate) fn check_proof_public_inputs(&self, proof: &ProofWithPublicInputs) { + let proof_pis = PublicInputs::from_slice(&proof.public_inputs); + let block_pi = self.block_inputs(); + + assert_eq!(proof_pis.bn, block_pi.bn); + assert_eq!(proof_pis.h, block_pi.bh); + assert_eq!(proof_pis.ph, block_pi.prev_bh); + + // check digests + let value_pi = self.value_inputs(); + + assert_eq!(proof_pis.value_point(), value_pi.values_digest()); + + assert_eq!(proof_pis.metadata_point(), value_pi.metadata_digest()); + } + + pub(crate) fn random() -> Self { + let value_h = HashOut::::rand().to_bytes().pack(Endianness::Little); + let key = random_vector(64); + let ptr = usize::MAX; + let value_dv = Point::rand(); + let value_dm = Point::rand(); + let n = 10; + let values_pi = new_extraction_public_inputs( + &value_h, + &key, + ptr, + &value_dv.to_weierstrass(), + &value_dm.to_weierstrass(), + n, + ); + + let th = &random_vector::(PACKED_HASH_LEN).to_fields(); + let sh = &random_vector::(PACKED_HASH_LEN).to_fields(); + + // The receipts root and value root need to agree + let rh = &value_h.to_fields(); + + let block_number = U256::from(F::rand().to_canonical_u64()).to_fields(); + let block_hash = HashOut::::rand() + .to_bytes() + .pack(Endianness::Little) + .to_fields(); + let parent_block_hash = HashOut::::rand() + .to_bytes() + .pack(Endianness::Little) + .to_fields(); + let blocks_pi = block_extraction::public_inputs::PublicInputs { + bh: &block_hash, + prev_bh: &parent_block_hash, + bn: &block_number, + sh, + th, + rh, + } + .to_vec(); + ReceiptsProofsPi { + blocks_pi, + values_pi, + } + } + } + + #[test] + fn final_simple_value() -> Result<()> { + let pis = ReceiptsProofsPi::random(); + let test_circuit = TestReceiptCircuit { pis: pis.clone() }; + let proof = run_circuit::(test_circuit); + pis.check_proof_public_inputs(&proof); + Ok(()) + } +} diff --git a/mp2-v1/src/length_extraction/branch.rs b/mp2-v1/src/length_extraction/branch.rs index 157f0b590..680ecdcba 100644 --- a/mp2-v1/src/length_extraction/branch.rs +++ b/mp2-v1/src/length_extraction/branch.rs @@ -5,9 +5,9 @@ use core::array; use mp2_common::{ array::{Vector, VectorWire}, keccak::{InputData, KeccakCircuit, KeccakWires, PACKED_HASH_LEN}, - mpt_sequential::Circuit as MPTCircuit, + mpt_sequential::advance_key_branch, public_inputs::PublicInputCommon, - rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST}, + rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST, MAX_KEY_NIBBLE_LEN}, types::{CBuilder, GFp}, utils::{Endianness, PackerTarget}, D, @@ -79,7 +79,9 @@ impl BranchLengthCircuit { let key = child_proof.mpt_key_wire(); let (key, hash, is_branch, _) = - MPTCircuit::<1, MAX_BRANCH_NODE_LEN>::advance_key_branch(cb, &node.arr, &key, &headers); + advance_key_branch::<_, D, MAX_BRANCH_NODE_LEN, MAX_KEY_NIBBLE_LEN>( + cb, &node.arr, &key, &headers, + ); // asserts this is a branch node cb.assert_one(is_branch.target); diff --git a/mp2-v1/src/lib.rs b/mp2-v1/src/lib.rs index 3e9cb8414..1b1397c28 100644 --- a/mp2-v1/src/lib.rs +++ b/mp2-v1/src/lib.rs @@ -17,6 +17,7 @@ pub const MAX_BRANCH_NODE_LEN_PADDED: usize = PAD_LEN(532); pub const MAX_EXTENSION_NODE_LEN: usize = 69; pub const MAX_EXTENSION_NODE_LEN_PADDED: usize = PAD_LEN(69); pub const MAX_LEAF_NODE_LEN: usize = MAX_EXTENSION_NODE_LEN; +pub const MAX_RECEIPT_LEAF_NODE_LEN: usize = 512; pub mod api; pub mod block_extraction; diff --git a/mp2-v1/src/values_extraction/api.rs b/mp2-v1/src/values_extraction/api.rs index a1bcaa6a8..e79662dd6 100644 --- a/mp2-v1/src/values_extraction/api.rs +++ b/mp2-v1/src/values_extraction/api.rs @@ -4,14 +4,16 @@ use super::{ branch::{BranchCircuit, BranchWires}, extension::{ExtensionNodeCircuit, ExtensionNodeWires}, leaf_mapping::{LeafMappingCircuit, LeafMappingWires}, + leaf_receipt::{ReceiptLeafCircuit, ReceiptLeafWires}, leaf_single::{LeafSingleCircuit, LeafSingleWires}, public_inputs::PublicInputs, }; -use crate::{api::InputNode, MAX_BRANCH_NODE_LEN, MAX_LEAF_NODE_LEN}; +use crate::{api::InputNode, MAX_BRANCH_NODE_LEN, MAX_LEAF_NODE_LEN, MAX_RECEIPT_LEAF_NODE_LEN}; use anyhow::{bail, ensure, Result}; use log::debug; use mp2_common::{ default_config, + eth::{ReceiptProofInfo, ReceiptQuery}, mpt_sequential::PAD_LEN, proof::{ProofInputSerialized, ProofWithVK}, storage_key::{MappingSlot, SimpleSlot}, @@ -32,6 +34,7 @@ use std::array; type LeafSingleWire = LeafSingleWires; type LeafMappingWire = LeafMappingWires; +type LeafReceiptWire = ReceiptLeafWires; type ExtensionInput = ProofInputSerialized; type BranchInput = ProofInputSerialized; const NUM_IO: usize = PublicInputs::::TOTAL_LEN; @@ -42,6 +45,7 @@ const NUM_IO: usize = PublicInputs::::TOTAL_LEN; pub enum CircuitInput { LeafSingle(LeafSingleCircuit), LeafMapping(LeafMappingCircuit), + LeafReceipt(ReceiptLeafCircuit), Extension(ExtensionInput), BranchSingle(BranchInput), BranchMapping(BranchInput), @@ -73,6 +77,16 @@ impl CircuitInput { }) } + /// Create a circuit input for proving a leaf MPT node of a transaction receipt. + pub fn new_receipt_leaf( + info: &ReceiptProofInfo, + query: &ReceiptQuery, + ) -> Self { + CircuitInput::LeafReceipt( + ReceiptLeafCircuit::new(info, query).expect("Could not construct Receipt Leaf Circuit"), + ) + } + /// Create a circuit input for proving an extension MPT node. pub fn new_extension(node: Vec, child_proof: Vec) -> Self { CircuitInput::Extension(ExtensionInput { @@ -106,6 +120,7 @@ impl CircuitInput { pub struct PublicParameters { leaf_single: CircuitWithUniversalVerifier, leaf_mapping: CircuitWithUniversalVerifier, + leaf_receipt: CircuitWithUniversalVerifier, extension: CircuitWithUniversalVerifier, #[cfg(not(test))] branches: BranchCircuits, @@ -285,8 +300,8 @@ impl_branch_circuits!(BranchCircuits, 2, 9, 16); impl_branch_circuits!(TestBranchCircuits, 1, 4, 9); /// Number of circuits in the set -/// 3 branch circuits + 1 extension + 1 leaf single + 1 leaf mapping -const MAPPING_CIRCUIT_SET_SIZE: usize = 6; +/// 3 branch circuits + 1 extension + 1 leaf single + 1 leaf mapping + 1 leaf receipt +const MAPPING_CIRCUIT_SET_SIZE: usize = 7; impl PublicParameters { /// Generates the circuit parameters for the MPT circuits. @@ -311,6 +326,10 @@ impl PublicParameters { let leaf_mapping = circuit_builder.build_circuit::>(()); + debug!("Building leaf receipt circuit"); + let leaf_receipt = + circuit_builder.build_circuit::>(()); + debug!("Building extension circuit"); let extension = circuit_builder.build_circuit::(()); @@ -323,6 +342,7 @@ impl PublicParameters { let mut circuits_set = vec![ leaf_single.get_verifier_data().circuit_digest, leaf_mapping.get_verifier_data().circuit_digest, + leaf_receipt.get_verifier_data().circuit_digest, extension.get_verifier_data().circuit_digest, ]; circuits_set.extend(branches.circuit_set()); @@ -331,6 +351,7 @@ impl PublicParameters { PublicParameters { leaf_single, leaf_mapping, + leaf_receipt, extension, branches, #[cfg(not(test))] @@ -349,6 +370,9 @@ impl PublicParameters { CircuitInput::LeafMapping(leaf) => set .generate_proof(&self.leaf_mapping, [], [], leaf) .map(|p| (p, self.leaf_mapping.get_verifier_data().clone()).into()), + CircuitInput::LeafReceipt(leaf) => set + .generate_proof(&self.leaf_receipt, [], [], leaf) + .map(|p| (p, self.leaf_receipt.get_verifier_data().clone()).into()), CircuitInput::Extension(ext) => { let mut child_proofs = ext.get_child_proofs()?; @@ -410,7 +434,10 @@ mod tests { mpt_sequential::utils::bytes_to_nibbles, types::{GFp, ADDRESS_LEN}, }; - use mp2_test::{mpt_sequential::generate_random_storage_mpt, utils::random_vector}; + use mp2_test::{ + mpt_sequential::{generate_random_storage_mpt, generate_receipt_test_info}, + utils::random_vector, + }; use plonky2::field::types::Field; use plonky2_ecgfp5::curve::curve::Point; use serial_test::serial; @@ -684,6 +711,69 @@ mod tests { ); } + #[test] + fn test_receipt_api() { + let receipt_proof_infos = generate_receipt_test_info::<1, 0>(); + let receipt_proofs = receipt_proof_infos.proofs(); + let query = receipt_proof_infos.query(); + // We check that we have enough receipts and then take the second and third info + // (the MPT proof for the first node is different). + // Then check that the node above both is a branch. + assert!(receipt_proofs.len() > 3); + let second_info = &receipt_proofs[1]; + let third_info = &receipt_proofs[2]; + + let proof_length_1 = second_info.mpt_proof.len(); + let proof_length_2 = third_info.mpt_proof.len(); + + let list_one = rlp::decode_list::>(&second_info.mpt_proof[proof_length_1 - 2]); + let list_two = rlp::decode_list::>(&third_info.mpt_proof[proof_length_2 - 2]); + + assert!(list_one == list_two); + assert!(list_one.len() == 17); + + println!("Generating params..."); + let params = build_circuits_params(); + + println!("Proving leaf 1..."); + let leaf_input_1 = CircuitInput::new_receipt_leaf(second_info, query); + let now = std::time::Instant::now(); + let leaf_proof1 = generate_proof(¶ms, leaf_input_1).unwrap(); + { + let lp = ProofWithVK::deserialize(&leaf_proof1).unwrap(); + let pub1 = PublicInputs::new(&lp.proof.public_inputs); + let (_, ptr) = pub1.mpt_key_info(); + println!("pointer: {}", ptr); + } + println!( + "Proof for leaf 1 generated in {} ms", + now.elapsed().as_millis() + ); + + println!("Proving leaf 2..."); + let leaf_input_2 = CircuitInput::new_receipt_leaf(third_info, query); + let now = std::time::Instant::now(); + let leaf_proof2 = generate_proof(¶ms, leaf_input_2).unwrap(); + println!( + "Proof for leaf 2 generated in {} ms", + now.elapsed().as_millis() + ); + + // The branch case for receipts is identical to that of a mapping so we use the same api. + println!("Proving branch..."); + let branch_input = CircuitInput::new_mapping_variable_branch( + second_info.mpt_proof[proof_length_1 - 2].clone(), + vec![leaf_proof1, leaf_proof2], + ); + + let now = std::time::Instant::now(); + generate_proof(¶ms, branch_input).unwrap(); + println!( + "Proof for branch node generated in {} ms", + now.elapsed().as_millis() + ); + } + fn test_circuits(is_simple_aggregation: bool, num_children: usize) { let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); let chain_id = 10; diff --git a/mp2-v1/src/values_extraction/branch.rs b/mp2-v1/src/values_extraction/branch.rs index afc352859..cdbf117bc 100644 --- a/mp2-v1/src/values_extraction/branch.rs +++ b/mp2-v1/src/values_extraction/branch.rs @@ -5,9 +5,9 @@ use anyhow::Result; use mp2_common::{ array::{Array, Vector, VectorWire}, keccak::{InputData, KeccakCircuit, KeccakWires, HASH_LEN, PACKED_HASH_LEN}, - mpt_sequential::{Circuit as MPTCircuit, MPTKeyWire, PAD_LEN}, + mpt_sequential::{advance_key_branch, MPTKeyWire, NIBBLES_TO_BYTES, PAD_LEN}, public_inputs::PublicInputCommon, - rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST}, + rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST, MAX_KEY_NIBBLE_LEN}, serialization::{deserialize, serialize}, types::{CBuilder, GFp}, utils::{less_than, Endianness, PackerTarget}, @@ -60,7 +60,10 @@ where pub fn build( b: &mut CBuilder, inputs: &[PublicInputs; N_CHILDREN], - ) -> BranchWires { + ) -> BranchWires + where + [(); NIBBLES_TO_BYTES(MAX_KEY_NIBBLE_LEN)]:, + { let zero = b.zero(); let one = b.one(); let ttrue = b._true(); @@ -129,7 +132,7 @@ where let child_key = proof_inputs.mpt_key(); let (_, hash, is_valid, nibble) = - MPTCircuit::<1, NODE_LEN>::advance_key_branch(b, &node.arr, &child_key, &headers); + advance_key_branch(b, &node.arr, &child_key, &headers); // We always enforce it's a branch node, i.e. that it has 17 entries. b.connect(is_valid.target, ttrue.target); diff --git a/mp2-v1/src/values_extraction/leaf_receipt.rs b/mp2-v1/src/values_extraction/leaf_receipt.rs new file mode 100644 index 000000000..cef724c25 --- /dev/null +++ b/mp2-v1/src/values_extraction/leaf_receipt.rs @@ -0,0 +1,790 @@ +//! Module handling the leaf node inside a Receipt Trie + +use crate::MAX_RECEIPT_LEAF_NODE_LEN; + +use super::{ + public_inputs::{PublicInputs, PublicInputsArgs}, + DATA_PREFIX, GAS_USED_PREFIX, LOG_NUMBER_PREFIX, TOPIC_PREFIX, TX_INDEX_PREFIX, +}; + +use alloy::{ + primitives::{Address, Log, B256}, + rlp::Decodable, +}; +use anyhow::{anyhow, Result}; +use mp2_common::{ + array::{Array, Vector, VectorWire}, + eth::{EventLogInfo, ReceiptProofInfo, ReceiptQuery}, + group_hashing::CircuitBuilderGroupHashing, + keccak::{InputData, KeccakCircuit, KeccakWires, HASH_LEN}, + mpt_sequential::{MPTKeyWire, MPTReceiptLeafNode, PAD_LEN}, + poseidon::H, + public_inputs::PublicInputCommon, + rlp::MAX_KEY_NIBBLE_LEN, + types::{CBuilder, GFp}, + utils::{less_than, less_than_or_equal_to_unsafe, Endianness, PackerTarget, ToTargets}, + D, F, +}; +use plonky2::{ + field::types::Field, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{circuit_builder::CircuitBuilder, config::Hasher}, +}; + +use plonky2_ecgfp5::gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}; + +use recursion_framework::circuit_builder::CircuitLogicWires; +use rlp::Encodable; +use serde::{Deserialize, Serialize}; +use std::{array::from_fn, iter}; +/// Maximum number of logs per transaction we can process +const MAX_LOGS_PER_TX: usize = 1; + +/// The number of bytes that `gas_used` could take up in the receipt. +/// We set a max of 3 here because this would be over half the gas in the block for Ethereum. +const MAX_GAS_SIZE: u64 = 3; + +/// The size of a topic in bytes in the rlp encoded receipt +const TOPICS_SIZE: usize = 32; + +/// The maximum number of topics that aren't the event signature. +const MAX_TOPICS: usize = 3; + +/// The maximum number of additional pieces of data we allow in an event (each being 32 bytes long). +const MAX_ADDITIONAL_DATA: usize = 2; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct ReceiptLeafWires +where + [(); PAD_LEN(NODE_LEN)]:, +{ + /// The event we are monitoring for + pub event: EventWires, + /// The node bytes + pub node: VectorWire, + /// the hash of the node bytes + pub root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, + /// The index of this receipt in the block + pub index: Target, + /// The offsets of the relevant logs inside the node + pub relevant_logs_offset: VectorWire, + /// The key in the MPT Trie + pub mpt_key: MPTKeyWire, + /// The column ID for the transaction index + pub tx_index_column_id: Target, + /// The column ID for the log number in the receipt + pub log_number_column_id: Target, + /// The gas used column ID + pub gas_used_column_id: Target, +} + +/// Contains all the information for an [`Event`] in rlp form +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct EventWires { + /// Size in bytes of the whole event + size: Target, + /// Packed contract address to check + address: Array, + /// Byte offset for the address from the beginning of a Log + add_rel_offset: Target, + /// Packed event signature, + event_signature: Array, + /// Byte offset from the start of the log to event signature + sig_rel_offset: Target, + /// The topics for this Log + topics: [LogColumn; MAX_TOPICS], + /// The extra data stored by this Log + data: [LogColumn; MAX_ADDITIONAL_DATA], +} + +/// Contains all the information for a [`Log`] in rlp form +#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq, Eq)] +pub struct LogColumn { + column_id: Target, + /// The byte offset from the beggining of the log to this target + rel_byte_offset: Target, + /// The length of this topic/data + len: Target, +} + +impl LogColumn { + /// Assigns a log colum from a [`LogDataInfo`] + pub fn assign(&self, pw: &mut PartialWitness, info: &LogDataInfo) { + pw.set_target(self.column_id, info.column_id); + pw.set_target( + self.rel_byte_offset, + F::from_canonical_usize(info.rel_byte_offset), + ); + pw.set_target(self.len, F::from_canonical_usize(info.len)); + } +} + +impl EventWires { + /// Convert to an array for metadata digest + pub fn to_vec(&self) -> Vec { + let mut out = Vec::new(); + out.push(self.size); + out.extend_from_slice(&self.address.arr); + out.push(self.add_rel_offset); + out.extend_from_slice(&self.event_signature.arr); + out.push(self.sig_rel_offset); + + out + } + + #[allow(clippy::too_many_arguments)] + pub fn verify_logs_and_extract_values( + &self, + b: &mut CBuilder, + value: &VectorWire, + relevant_logs_offsets: &VectorWire, + tx_index: Target, + tx_index_column_id: Target, + log_number_column_id: Target, + gas_used_column_id: Target, + ) -> (Target, CurveTarget) { + let t = b._true(); + let one = b.one(); + let two = b.two(); + let zero = b.zero(); + let curve_zero = b.curve_zero(); + let mut row_points = Vec::new(); + + // Extract the gas used in the transaction, since the position of this can vary because it is after the key + // we have to prove we extracted from the correct location. + let header_len_len = b.add_const( + value.arr[0], + F::from_canonical_u64(1) - F::from_canonical_u64(247), + ); + let key_header = value.arr.random_access_large_array(b, header_len_len); + let less_than_val = b.constant(F::from_canonical_u8(128)); + let single_value = less_than(b, key_header, less_than_val, 8); + let key_len_maybe = b.add_const(key_header, F::ONE - F::from_canonical_u64(128)); + let key_len = b.select(single_value, one, key_len_maybe); + + // This is the start of the string that is the rlp encoded receipt (a string since the first element is transaction type). + // From here we subtract 183 to get the length of the length, then the encoded gas used is at length of length + 1 (for tx type) + (1 + list length) + // + 1 (for status) + 1 to get the header for the gas used string. + let string_offset = b.add(key_len, header_len_len); + let string_header = value.arr.random_access_large_array(b, string_offset); + let string_len_len = b.add_const(string_header, -F::from_canonical_u64(183)); + + let list_offset = b.add_many([string_offset, string_len_len, two]); + let list_header = value.arr.random_access_large_array(b, list_offset); + + let gas_used_offset_lo = b.add_const( + list_header, + F::from_canonical_u64(2) - F::from_canonical_u64(247), + ); + let gas_used_offset = b.add(gas_used_offset_lo, list_offset); + + let gas_used_header = value.arr.random_access_large_array(b, gas_used_offset); + let gas_used_len = b.add_const(gas_used_header, -F::from_canonical_u64(128)); + + let initial_gas_index = b.add(gas_used_offset, one); + let final_gas_index = b.add(gas_used_offset, gas_used_len); + + let combiner = b.constant(F::from_canonical_u64(1 << 8)); + + let gas_used = (0..MAX_GAS_SIZE).fold(zero, |acc, i| { + let access_index = b.add_const(initial_gas_index, F::from_canonical_u64(i)); + let array_value = value.arr.random_access_large_array(b, access_index); + + // If we have extracted a value from an index in the desired range (so lte final_gas_index) we want to add it. + // If access_index was strictly less than final_gas_index we need to multiply by 1 << 8 after (since the encoding is big endian) + let valid = less_than_or_equal_to_unsafe(b, access_index, final_gas_index, 12); + let need_scalar = less_than(b, access_index, final_gas_index, 12); + + let to_add = b.select(valid, array_value, zero); + + let scalar = b.select(need_scalar, combiner, one); + let tmp = b.add(acc, to_add); + b.mul(tmp, scalar) + }); + + // Map the gas used to a curve point for the value digest, gas used is the first column so use one as its column id. + let gas_digest = b.map_to_curve_point(&[gas_used_column_id, gas_used]); + let tx_index_digest = b.map_to_curve_point(&[tx_index_column_id, tx_index]); + + let initial_row_digest = b.add_curve_point(&[gas_digest, tx_index_digest]); + // We also keep track of the number of real logs we process as each log forms a row in our table + let mut n = zero; + for (index, log_offset) in relevant_logs_offsets.arr.arr.into_iter().enumerate() { + let mut points = Vec::new(); + // Extract the address bytes + let address_start = b.add(log_offset, self.add_rel_offset); + + let address_bytes = value.arr.extract_array_large::<_, _, 20>(b, address_start); + + let address_check = address_bytes.equals(b, &self.address); + // Extract the signature bytes + let sig_start = b.add(log_offset, self.sig_rel_offset); + + let sig_bytes = value.arr.extract_array_large::<_, _, 32>(b, sig_start); + + let sig_check = sig_bytes.equals(b, &self.event_signature); + + // We check to see if the relevant log offset is zero (this indicates a dummy value) + let dummy = b.is_equal(log_offset, zero); + + let address_to_enforce = b.select(dummy, t.target, address_check.target); + let sig_to_enforce = b.select(dummy, t.target, sig_check.target); + + b.connect(t.target, address_to_enforce); + b.connect(t.target, sig_to_enforce); + + for &log_column in self.topics.iter().chain(self.data.iter()) { + let data_start = b.add(log_offset, log_column.rel_byte_offset); + // The data is always 32 bytes long + let data_bytes = value.arr.extract_array_large::<_, _, 32>(b, data_start); + + // Pack the data and get the digest + let packed_data = data_bytes.arr.pack(b, Endianness::Big); + + let data_digest = b.map_to_curve_point( + &std::iter::once(log_column.column_id) + .chain(packed_data) + .collect::>(), + ); + + // For each column we use the `column_id` field to tell if its a dummy or not, zero indicates a dummy. + let dummy_column = b.is_equal(log_column.column_id, zero); + + let selected_point = b.select_curve_point(dummy_column, curve_zero, data_digest); + + points.push(selected_point); + } + // If this is a real row we record the gas used in the transaction + points.push(initial_row_digest); + + // We also keep track of which log this is in the receipt to avoid having identical rows in the table in the case + // that the event we are tracking can be emitted multiple times in the same transaction but has no topics or data. + let log_number = b.constant(F::from_canonical_usize(index + 1)); + let log_no_digest = b.map_to_curve_point(&[log_number_column_id, log_number]); + points.push(log_no_digest); + + let increment = b.select(dummy, zero, one); + n = b.add(n, increment); + let row_point_sum = b.add_curve_point(&points); + let sum_digest = b.map_to_curve_point(&row_point_sum.to_targets()); + let point_to_add = b.select_curve_point(dummy, curve_zero, sum_digest); + row_points.push(point_to_add); + } + + (n, b.add_curve_point(&row_points)) + } +} + +/// Circuit to prove a transaction receipt contains logs relating to a specific event. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ReceiptLeafCircuit { + /// This is the RLP encoded leaf node in the Receipt Trie. + pub node: Vec, + /// The transaction index, telling us where the receipt is in the block. The RLP encoding of the index + /// is also the key used in the Receipt Trie. + pub tx_index: u64, + /// The size of the node in bytes + pub size: usize, + /// The address of the contract that emits the log + pub address: Address, + /// The offset of the address in the rlp encoded log + pub rel_add_offset: usize, + /// The event signature hash + pub event_signature: [u8; HASH_LEN], + /// The offset of the event signatur ein the rlp encoded log + pub sig_rel_offset: usize, + /// The other topics information + pub topics: [LogDataInfo; MAX_TOPICS], + /// Any additional data that we will extract from the log + pub data: [LogDataInfo; MAX_ADDITIONAL_DATA], + /// This is the offsets in the node to the start of the logs that relate to `event_info` + pub relevant_logs_offset: Vec, +} + +/// Contains all the information for data contained in an [`Event`] +#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] +pub struct LogDataInfo { + /// The column id of this piece of info + pub column_id: GFp, + /// The byte offset from the beggining of the log to this target + pub rel_byte_offset: usize, + /// The length of this piece of data + pub len: usize, +} + +impl ReceiptLeafCircuit +where + [(); PAD_LEN(NODE_LEN)]:, +{ + /// Create a new [`ReceiptLeafCircuit`] from a [`ReceiptProofInfo`] and a [`ReceiptQuery`] + pub fn new( + proof_info: &ReceiptProofInfo, + query: &ReceiptQuery, + ) -> Result { + // Since the compact encoding of the key is stored first plus an additional list header and + // then the first element in the receipt body is the transaction type we calculate the offset to that point + + let last_node = proof_info + .mpt_proof + .last() + .ok_or(anyhow!("Could not get last node in receipt trie proof"))?; + + // Convert to Rlp form so we can use provided methods. + let node_rlp = rlp::Rlp::new(last_node); + + // The actual receipt data is item 1 in the list + let (receipt_rlp, receipt_off) = node_rlp.at_with_offset(1)?; + // The rlp encoded Receipt is not a list but a string that is formed of the `tx_type` followed by the remaining receipt + // data rlp encoded as a list. We retrieve the payload info so that we can work out relevant offsets later. + let receipt_str_payload = receipt_rlp.payload_info()?; + + // We make a new `Rlp` struct that should be the encoding of the inner list representing the `ReceiptEnvelope` + let receipt_list = rlp::Rlp::new(&receipt_rlp.data()?[1..]); + + // The logs themselves start are the item at index 3 in this list + let (logs_rlp, logs_off) = receipt_list.at_with_offset(3)?; + + // We calculate the offset the that the logs are at from the start of the node + let logs_offset = receipt_off + receipt_str_payload.header_len + 1 + logs_off; + + // Now we produce an iterator over the logs with each logs offset. + let relevant_logs_offset = iter::successors(Some(0usize), |i| Some(i + 1)) + .map_while(|i| logs_rlp.at_with_offset(i).ok()) + .filter_map(|(log_rlp, log_off)| { + let mut bytes = log_rlp.as_raw(); + let log = Log::decode(&mut bytes).ok()?; + + if log.address == query.contract + && log + .data + .topics() + .contains(&B256::from(query.event.event_signature)) + { + Some(logs_offset + log_off) + } else { + Some(0usize) + } + }) + .take(MAX_LOGS_PER_TX) + .collect::>(); + + let EventLogInfo:: { + size, + address, + add_rel_offset, + event_signature, + sig_rel_offset, + topics, + data, + } = query.event; + + // We need a fixed number of topics for the circuit so we use dummies to pad to the correct length. + let mut final_topics = [LogDataInfo::default(); MAX_TOPICS]; + + final_topics.iter_mut().enumerate().for_each(|(j, topic)| { + if j < NO_TOPICS { + let input = [ + address.as_slice(), + event_signature.as_slice(), + TOPIC_PREFIX, + &[j as u8 + 1], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let column_id = H::hash_no_pad(&input).elements[0]; + *topic = LogDataInfo { + column_id, + rel_byte_offset: topics[j], + len: TOPICS_SIZE, + }; + } + }); + + // We need a fixed number of pieces of data for the circuit so we use dummies to pad to the correct length. + let mut final_data = [LogDataInfo::default(); MAX_ADDITIONAL_DATA]; + final_data.iter_mut().enumerate().for_each(|(j, d)| { + if j < MAX_DATA { + let input = [ + address.as_slice(), + event_signature.as_slice(), + DATA_PREFIX, + &[j as u8 + 1], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let column_id = H::hash_no_pad(&input).elements[0]; + *d = LogDataInfo { + column_id, + rel_byte_offset: data[j], + len: TOPICS_SIZE, + }; + }; + }); + + Ok(Self { + node: last_node.clone(), + tx_index: proof_info.tx_index, + size, + address, + rel_add_offset: add_rel_offset, + event_signature, + sig_rel_offset, + topics: final_topics, + data: final_data, + relevant_logs_offset, + }) + } + + pub fn build(b: &mut CBuilder) -> ReceiptLeafWires { + // Build the event wires + let event_wires = Self::build_event_wires(b); + + let zero = b.zero(); + let curve_zero = b.curve_zero(); + // Add targets for the data specific to this receipt + let index = b.add_virtual_target(); + + let relevant_logs_offset = VectorWire::::new(b); + + let mpt_key = MPTKeyWire::new(b); + + // Build the node wires. + let wires = MPTReceiptLeafNode::build_and_advance_key::<_, D, NODE_LEN>(b, &mpt_key); + + let node = wires.node; + let root = wires.root; + // Add targets for the column ids for tx index, log number and gas used + let tx_index_column_id = b.add_virtual_target(); + let log_number_column_id = b.add_virtual_target(); + let gas_used_column_id = b.add_virtual_target(); + + // For each relevant log in the transaction we have to verify it lines up with the event we are monitoring for + let (n, dv) = event_wires.verify_logs_and_extract_values::( + b, + &node, + &relevant_logs_offset, + index, + tx_index_column_id, + log_number_column_id, + gas_used_column_id, + ); + + let mut core_metadata = event_wires.to_vec(); + core_metadata.push(tx_index_column_id); + core_metadata.push(log_number_column_id); + core_metadata.push(gas_used_column_id); + + let initial_dm = b.map_to_curve_point(&core_metadata); + + let mut meta_data_points = vec![initial_dm]; + + for topic in event_wires.topics.iter() { + let is_id_zero = b.is_equal(topic.column_id, zero); + let column_id_digest = b.map_one_to_curve_point(topic.column_id); + let selected = b.select_curve_point(is_id_zero, curve_zero, column_id_digest); + meta_data_points.push(selected); + } + + for data in event_wires.data.iter() { + let is_id_zero = b.is_equal(data.column_id, zero); + let column_id_digest = b.map_one_to_curve_point(data.column_id); + let selected = b.select_curve_point(is_id_zero, curve_zero, column_id_digest); + meta_data_points.push(selected); + } + + let dm = b.add_curve_point(&meta_data_points); + + // Register the public inputs + PublicInputsArgs { + h: &root.output_array, + k: &wires.key, + dv, + dm, + n, + } + .register_args(b); + + ReceiptLeafWires { + event: event_wires, + node, + root, + index, + relevant_logs_offset, + mpt_key, + tx_index_column_id, + log_number_column_id, + gas_used_column_id, + } + } + + fn build_event_wires(b: &mut CBuilder) -> EventWires { + let size = b.add_virtual_target(); + + // Packed address + let address = Array::::new(b); + + // relative offset of the address + let add_rel_offset = b.add_virtual_target(); + + // Event signature + let event_signature = Array::::new(b); + + // Signature relative offset + let sig_rel_offset = b.add_virtual_target(); + + // topics + let topics: [LogColumn; 3] = from_fn(|_| Self::build_log_column(b)); + + // data + let data: [LogColumn; 2] = from_fn(|_| Self::build_log_column(b)); + + EventWires { + size, + address, + add_rel_offset, + event_signature, + sig_rel_offset, + topics, + data, + } + } + + fn build_log_column(b: &mut CBuilder) -> LogColumn { + let column_id = b.add_virtual_target(); + let rel_byte_offset = b.add_virtual_target(); + let len = b.add_virtual_target(); + + LogColumn { + column_id, + rel_byte_offset, + len, + } + } + + pub fn assign(&self, pw: &mut PartialWitness, wires: &ReceiptLeafWires) { + self.assign_event_wires(pw, &wires.event); + + let pad_node = + Vector::::from_vec(&self.node).expect("invalid node given"); + wires.node.assign(pw, &pad_node); + KeccakCircuit::<{ PAD_LEN(NODE_LEN) }>::assign( + pw, + &wires.root, + &InputData::Assigned(&pad_node), + ); + pw.set_target(wires.index, GFp::from_canonical_u64(self.tx_index)); + + let relevant_logs_vector = + Vector::::from_vec(&self.relevant_logs_offset) + .expect("Could not assign relevant logs offsets"); + wires.relevant_logs_offset.assign(pw, &relevant_logs_vector); + + let key_encoded = self.tx_index.rlp_bytes(); + let key_nibbles: [u8; MAX_KEY_NIBBLE_LEN] = key_encoded + .iter() + .flat_map(|byte| [byte / 16, byte % 16]) + .chain(iter::repeat(0u8)) + .take(MAX_KEY_NIBBLE_LEN) + .collect::>() + .try_into() + .expect("Couldn't create mpt key with correct length"); + + wires.mpt_key.assign(pw, &key_nibbles, key_encoded.len()); + + // Work out the column ids for tx_index, log_number and gas_used + let tx_index_input = [ + self.address.as_slice(), + self.event_signature.as_slice(), + TX_INDEX_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let tx_index_column_id = H::hash_no_pad(&tx_index_input).elements[0]; + + let log_number_input = [ + self.address.as_slice(), + self.event_signature.as_slice(), + LOG_NUMBER_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let log_number_column_id = H::hash_no_pad(&log_number_input).elements[0]; + + let gas_used_input = [ + self.address.as_slice(), + self.event_signature.as_slice(), + GAS_USED_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let gas_used_column_id = H::hash_no_pad(&gas_used_input).elements[0]; + + pw.set_target(wires.tx_index_column_id, tx_index_column_id); + pw.set_target(wires.log_number_column_id, log_number_column_id); + pw.set_target(wires.gas_used_column_id, gas_used_column_id); + } + + pub fn assign_event_wires(&self, pw: &mut PartialWitness, wires: &EventWires) { + pw.set_target(wires.size, F::from_canonical_usize(self.size)); + + wires + .address + .assign(pw, &self.address.0.map(GFp::from_canonical_u8)); + + pw.set_target( + wires.add_rel_offset, + F::from_canonical_usize(self.rel_add_offset), + ); + + wires + .event_signature + .assign(pw, &self.event_signature.map(GFp::from_canonical_u8)); + + pw.set_target( + wires.sig_rel_offset, + F::from_canonical_usize(self.sig_rel_offset), + ); + + wires + .topics + .iter() + .zip(self.topics.iter()) + .for_each(|(topic_wire, topic_info)| topic_wire.assign(pw, topic_info)); + wires + .data + .iter() + .zip(self.data.iter()) + .for_each(|(data_wire, data_info)| data_wire.assign(pw, data_info)); + } +} + +/// Num of children = 0 +impl CircuitLogicWires for ReceiptLeafWires { + type CircuitBuilderParams = (); + + type Inputs = ReceiptLeafCircuit; + + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + + fn circuit_logic( + builder: &mut CircuitBuilder, + _verified_proofs: [&plonky2::plonk::proof::ProofWithPublicInputsTarget; 0], + _builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + ReceiptLeafCircuit::build(builder) + } + + fn assign_input( + &self, + inputs: Self::Inputs, + pw: &mut PartialWitness, + ) -> anyhow::Result<()> { + inputs.assign(pw, self); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::values_extraction::{ + compute_receipt_leaf_metadata_digest, compute_receipt_leaf_value_digest, + }; + + use super::{ + //super::{compute_receipt_leaf_metadata_digest, compute_receipt_leaf_value_digest}, + *, + }; + + use mp2_common::{ + utils::{keccak256, Packer}, + C, + }; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + mpt_sequential::generate_receipt_test_info, + }; + + #[derive(Clone, Debug)] + struct TestReceiptLeafCircuit { + c: ReceiptLeafCircuit, + } + + impl UserCircuit for TestReceiptLeafCircuit + where + [(); PAD_LEN(NODE_LEN)]:, + { + // Leaf wires + expected extracted value + type Wires = ReceiptLeafWires; + + fn build(b: &mut CircuitBuilder) -> Self::Wires { + ReceiptLeafCircuit::::build(b) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.c.assign(pw, wires); + } + } + #[test] + fn test_leaf_circuit() { + const NODE_LEN: usize = 512; + test_leaf_circuit_helper::<1, 0, NODE_LEN>(); + test_leaf_circuit_helper::<2, 0, NODE_LEN>(); + test_leaf_circuit_helper::<3, 0, NODE_LEN>(); + test_leaf_circuit_helper::<3, 1, NODE_LEN>(); + test_leaf_circuit_helper::<3, 2, NODE_LEN>(); + } + + fn test_leaf_circuit_helper< + const NO_TOPICS: usize, + const MAX_DATA: usize, + const NODE_LEN: usize, + >() + where + [(); PAD_LEN(NODE_LEN)]:, + { + let receipt_proof_infos = generate_receipt_test_info::(); + let proofs = receipt_proof_infos.proofs(); + let info = proofs.first().unwrap(); + let query = receipt_proof_infos.query(); + + let c = ReceiptLeafCircuit::::new::(info, query).unwrap(); + let test_circuit = TestReceiptLeafCircuit { c }; + + let node = info.mpt_proof.last().unwrap().clone(); + + assert!(node.len() <= NODE_LEN); + let proof = run_circuit::(test_circuit); + let pi = PublicInputs::new(&proof.public_inputs); + + // Check the output hash + { + let exp_hash = keccak256(&node).pack(Endianness::Little); + assert_eq!(pi.root_hash(), exp_hash); + } + + // Check value digest + { + let exp_digest = compute_receipt_leaf_value_digest(&proofs[0], &query.event); + assert_eq!(pi.values_digest(), exp_digest.to_weierstrass()); + } + + // Check metadata digest + { + let exp_digest = compute_receipt_leaf_metadata_digest(&query.event); + assert_eq!(pi.metadata_digest(), exp_digest.to_weierstrass()); + } + } +} diff --git a/mp2-v1/src/values_extraction/mod.rs b/mp2-v1/src/values_extraction/mod.rs index e40681f5c..7e14d4658 100644 --- a/mp2-v1/src/values_extraction/mod.rs +++ b/mp2-v1/src/values_extraction/mod.rs @@ -1,6 +1,9 @@ -use alloy::primitives::Address; +use alloy::{ + consensus::TxReceipt, + primitives::{Address, IntoLogData}, +}; use mp2_common::{ - eth::left_pad32, + eth::{left_pad32, EventLogInfo, ReceiptProofInfo}, group_hashing::map_to_curve_point, poseidon::H, types::{GFp, MAPPING_KEY_LEN, MAPPING_LEAF_VALUE_LEN}, @@ -18,6 +21,7 @@ pub mod api; mod branch; mod extension; mod leaf_mapping; +mod leaf_receipt; mod leaf_single; pub mod public_inputs; @@ -31,6 +35,21 @@ pub(crate) const VALUE_ID_PREFIX: &[u8] = b"VAL"; pub(crate) const BLOCK_ID_DST: &[u8] = b"BLOCK_NUMBER"; +/// Prefix used for making a topic column id. +const TOPIC_PREFIX: &[u8] = b"topic"; + +/// Prefix used for making a data column id. +const DATA_PREFIX: &[u8] = b"data"; + +/// Prefix for transaction index +const TX_INDEX_PREFIX: &[u8] = b"tx index"; + +/// Prefix for log number +const LOG_NUMBER_PREFIX: &[u8] = b"log number"; + +/// Prefix for gas used +const GAS_USED_PREFIX: &[u8] = b" gas used"; + pub fn identifier_block_column() -> u64 { let inputs: Vec = BLOCK_ID_DST.to_fields(); H::hash_no_pad(&inputs).elements[0].to_canonical_u64() @@ -158,3 +177,227 @@ pub fn compute_leaf_mapping_metadata_digest(key_id: u64, value_id: u64, slot: u8 GFp::from_canonical_u8(slot), ]) } +/// Calculate `metadata_digest = D(address || signature || topics)` for receipt leaf. +/// Topics is an array of 5 values (some are dummies), each being `column_id`, `rel_byte_offset` (from the start of the log) +/// and `len`. +pub fn compute_receipt_leaf_metadata_digest( + event: &EventLogInfo, +) -> Digest { + let mut out = Vec::new(); + out.push(event.size); + out.extend_from_slice(&event.address.0.map(|byte| byte as usize)); + out.push(event.add_rel_offset); + out.extend_from_slice(&event.event_signature.map(|byte| byte as usize)); + out.push(event.sig_rel_offset); + + let mut field_out = out + .into_iter() + .map(GFp::from_canonical_usize) + .collect::>(); + // Work out the column ids for tx_index, log_number and gas_used + let tx_index_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + TX_INDEX_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let tx_index_column_id = H::hash_no_pad(&tx_index_input).elements[0]; + + let log_number_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + LOG_NUMBER_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let log_number_column_id = H::hash_no_pad(&log_number_input).elements[0]; + + let gas_used_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + GAS_USED_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let gas_used_column_id = H::hash_no_pad(&gas_used_input).elements[0]; + field_out.push(tx_index_column_id); + field_out.push(log_number_column_id); + field_out.push(gas_used_column_id); + + let core_metadata = map_to_curve_point(&field_out); + + let topic_digests = event + .topics + .iter() + .enumerate() + .map(|(j, _)| { + let input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + TOPIC_PREFIX, + &[j as u8 + 1], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let column_id = H::hash_no_pad(&input).elements[0]; + map_to_curve_point(&[column_id]) + }) + .collect::>(); + + let data_digests = event + .data + .iter() + .enumerate() + .map(|(j, _)| { + let input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + DATA_PREFIX, + &[j as u8 + 1], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let column_id = H::hash_no_pad(&input).elements[0]; + map_to_curve_point(&[column_id]) + }) + .collect::>(); + + iter::once(core_metadata) + .chain(topic_digests) + .chain(data_digests) + .fold(Digest::NEUTRAL, |acc, p| acc + p) +} + +/// Calculate `value_digest` for receipt leaf. +pub fn compute_receipt_leaf_value_digest( + receipt_proof_info: &ReceiptProofInfo, + event: &EventLogInfo, +) -> Digest { + let receipt = receipt_proof_info.to_receipt().unwrap(); + let gas_used = receipt.cumulative_gas_used(); + + // Only use events that we are indexing + let address = event.address; + let sig = event.event_signature; + + // Work out the column ids for tx_index, log_number and gas_used + let tx_index_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + TX_INDEX_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let tx_index_column_id = H::hash_no_pad(&tx_index_input).elements[0]; + + let log_number_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + LOG_NUMBER_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let log_number_column_id = H::hash_no_pad(&log_number_input).elements[0]; + + let gas_used_input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + GAS_USED_PREFIX, + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let gas_used_column_id = H::hash_no_pad(&gas_used_input).elements[0]; + + let index_digest = map_to_curve_point(&[ + tx_index_column_id, + GFp::from_canonical_u64(receipt_proof_info.tx_index), + ]); + + let gas_digest = + map_to_curve_point(&[gas_used_column_id, GFp::from_noncanonical_u128(gas_used)]); + let mut n = 0; + receipt + .logs() + .iter() + .cloned() + .filter_map(|log| { + let log_address = log.address; + let log_data = log.to_log_data(); + let (topics, data) = log_data.split(); + + if log_address == address && topics[0].0 == sig { + n += 1; + let topics_value_digest = topics + .iter() + .enumerate() + .skip(1) + .map(|(j, fixed)| { + let packed = fixed.0.pack(mp2_common::utils::Endianness::Big).to_fields(); + let input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + TOPIC_PREFIX, + &[j as u8], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let mut values = vec![H::hash_no_pad(&input).elements[0]]; + values.extend_from_slice(&packed); + map_to_curve_point(&values) + }) + .collect::>(); + let data_value_digest = data + .chunks(32) + .enumerate() + .map(|(j, fixed)| { + let packed = fixed.pack(mp2_common::utils::Endianness::Big).to_fields(); + let input = [ + event.address.as_slice(), + event.event_signature.as_slice(), + DATA_PREFIX, + &[j as u8 + 1], + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + let mut values = vec![H::hash_no_pad(&input).elements[0]]; + values.extend_from_slice(&packed); + map_to_curve_point(&values) + }) + .collect::>(); + let log_no_digest = + map_to_curve_point(&[log_number_column_id, GFp::from_canonical_usize(n)]); + let initial_digest = index_digest + gas_digest + log_no_digest; + + let row_value = iter::once(initial_digest) + .chain(topics_value_digest) + .chain(data_value_digest) + .fold(Digest::NEUTRAL, |acc, p| acc + p); + + Some(map_to_curve_point(&row_value.to_fields())) + } else { + None + } + }) + .fold(Digest::NEUTRAL, |acc, p| acc + p) +} diff --git a/mp2-v1/src/values_extraction/public_inputs.rs b/mp2-v1/src/values_extraction/public_inputs.rs index ee7e39118..806d897b7 100644 --- a/mp2-v1/src/values_extraction/public_inputs.rs +++ b/mp2-v1/src/values_extraction/public_inputs.rs @@ -17,7 +17,7 @@ use plonky2_ecgfp5::{ curve::curve::WeierstrassPoint, gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, }; -use std::array; +use std::{array, fmt::Debug}; // Leaf/Extension/Branch node Public Inputs: // - `H : [8]F` packed Keccak hash of the extension node diff --git a/mp2-v1/tests/common/block_extraction.rs b/mp2-v1/tests/common/block_extraction.rs index 1bda85eba..51b50c5c1 100644 --- a/mp2-v1/tests/common/block_extraction.rs +++ b/mp2-v1/tests/common/block_extraction.rs @@ -1,7 +1,7 @@ use alloy::primitives::U256; use anyhow::Result; use mp2_common::{ - eth::BlockUtil, + eth::Rlpable, proof::deserialize_proof, utils::{Endianness, Packer, ToFields}, C, D, F, diff --git a/mp2-v1/tests/integrated_tests.rs b/mp2-v1/tests/integrated_tests.rs index 8ce01bcb4..54694a385 100644 --- a/mp2-v1/tests/integrated_tests.rs +++ b/mp2-v1/tests/integrated_tests.rs @@ -33,6 +33,7 @@ use common::{ }; use envconfig::Envconfig; use log::info; + use parsil::{ assembler::DynamicCircuitPis, parse_and_validate,