diff --git a/Cargo.lock b/Cargo.lock index 35d372346..ffa083363 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy" @@ -88,6 +88,7 @@ dependencies = [ "alloy-contract", "alloy-core", "alloy-eips", + "alloy-json-rpc", "alloy-network", "alloy-node-bindings", "alloy-provider", @@ -101,11 +102,11 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.1.47" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18c5c520273946ecf715c0010b4e3503d7eba9893cd9ce6b7fff5654c4a3c470" +checksum = "4ab9d1367c6ffb90c93fb4a9a4989530aa85112438c6f73a734067255d348469" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "num_enum", "strum", ] @@ -117,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae09ffd7c29062431dd86061deefe4e3c6f07fa0d674930095f8dcedb0baf02c" dependencies = [ "alloy-eips", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rlp", "alloy-serde", "auto_impl", @@ -136,7 +137,7 @@ dependencies = [ "alloy-json-abi", "alloy-network", "alloy-network-primitives", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-provider", "alloy-rpc-types-eth", "alloy-sol-types", @@ -148,32 +149,32 @@ dependencies = [ [[package]] name = "alloy-core" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d14d531c99995de71558e8e2206c27d709559ee8e5a0452b965ea82405a013" +checksum = "648275bb59110f88cc5fa9a176845e52a554ebfebac2d21220bcda8c9220f797" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rlp", "alloy-sol-types", ] [[package]] name = "alloy-dyn-abi" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80759b3f57b3b20fa7cd8fef6479930fc95461b58ff8adea6e87e618449c8a1d" +checksum = "bc9138f4f0912793642d453523c3116bd5d9e11de73b70177aa7cb3e94b98ad2" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-sol-type-parser", "alloy-sol-types", "const-hex", "itoa", "serde", "serde_json", - "winnow", + "winnow 0.6.26", ] [[package]] @@ -182,7 +183,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rlp", "serde", ] @@ -193,7 +194,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c986539255fb839d1533c128e190e557e52ff652c9ef62939e233a81dd93f7e" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rlp", "derive_more 1.0.0", "serde", @@ -207,7 +208,7 @@ checksum = "5b6aa3961694b30ba53d41006131a2fca3bdab22e4c344e46db2c639e7c2dfdd" dependencies = [ "alloy-eip2930", "alloy-eip7702", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rlp", "alloy-serde", "c-kzg", @@ -223,18 +224,18 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e53f7877ded3921d18a0a9556d55bedf84535567198c9edab2aa23106da91855" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-serde", "serde", ] [[package]] name = "alloy-json-abi" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac4b22b3e51cac09fd2adfcc73b55f447b4df669f983c13f7894ec82b607c63f" +checksum = "24acd2f5ba97c7a320e67217274bc81fe3c3174b8e6144ec875d9d54e760e278" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-sol-type-parser", "serde", "serde_json", @@ -246,7 +247,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3694b7e480728c0b3e228384f223937f14c10caef5a4c766021190fc8f283d35" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-sol-types", "serde", "serde_json", @@ -264,7 +265,7 @@ dependencies = [ "alloy-eips", "alloy-json-rpc", "alloy-network-primitives", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rpc-types-eth", "alloy-serde", "alloy-signer", @@ -285,7 +286,7 @@ checksum = "df9f3e281005943944d15ee8491534a1c7b3cbf7a7de26f8c433b842b93eb5f9" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-serde", "serde", ] @@ -297,7 +298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9805d126f24be459b958973c0569c73e1aadd27d4535eee82b2b6764aa03616" dependencies = [ "alloy-genesis", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "k256", "rand", "serde_json", @@ -326,9 +327,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db948902dfbae96a73c2fbf1f7abec62af034ab883e4c777c3fd29702bd6e2c" +checksum = "ec878088ec6283ce1e90d280316aadd3d6ce3de06ff63d68953c855e7e447e92" dependencies = [ "alloy-rlp", "bytes", @@ -338,8 +339,7 @@ dependencies = [ "foldhash", "getrandom 0.2.15", "hashbrown 0.15.2", - "hex-literal", - "indexmap 2.6.0", + "indexmap 2.7.1", "itoa", "k256", "keccak-asm", @@ -367,7 +367,7 @@ dependencies = [ "alloy-network", "alloy-network-primitives", "alloy-node-bindings", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-rpc-client", "alloy-rpc-types-anvil", "alloy-rpc-types-eth", @@ -384,7 +384,7 @@ dependencies = [ "lru", "parking_lot", "pin-project", - "reqwest 0.12.9", + "reqwest 0.12.12", "schnellru", "serde", "serde_json", @@ -397,9 +397,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0822426598f95e45dd1ea32a738dac057529a709ee645fcc516ffa4cbde08f" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" dependencies = [ "alloy-rlp-derive", "arrayvec 0.7.6", @@ -408,13 +408,13 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b09cae092c27b6f1bde952653a22708691802e57bfef4a2973b80bea21efd3f" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[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.14", + "alloy-primitives 0.8.19", "alloy-transport", "alloy-transport-http", "futures", "pin-project", - "reqwest 0.12.9", + "reqwest 0.12.12", "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.14", + "alloy-primitives 0.8.19", "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.14", + "alloy-primitives 0.8.19", "alloy-rpc-types-eth", "alloy-serde", "serde", @@ -473,7 +473,7 @@ dependencies = [ "alloy-consensus", "alloy-eips", "alloy-network-primitives", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "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.14", + "alloy-primitives 0.8.19", "serde", "serde_json", ] @@ -500,7 +500,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e10aec39d60dc27edcac447302c7803d2371946fb737245320a05b78eb2fafd" dependencies = [ - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "async-trait", "auto_impl", "elliptic-curve", @@ -516,7 +516,7 @@ checksum = "d8396f6dff60700bc1d215ee03d86ff56de268af96e2bf833a14d0bafcab9882" dependencies = [ "alloy-consensus", "alloy-network", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-signer", "async-trait", "k256", @@ -526,42 +526,42 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfd7853b65a2b4f49629ec975fee274faf6dff15ab8894c620943398ef283c0" +checksum = "8d039d267aa5cbb7732fa6ce1fd9b5e9e29368f580f80ba9d7a8450c794de4b2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "alloy-sol-macro-expander" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ec42f342d9a9261699f8078e57a7a4fda8aaa73c1a212ed3987080e6a9cd13" +checksum = "620ae5eee30ee7216a38027dec34e0585c55099f827f92f50d11e3d2d3a4a954" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck 0.5.0", - "indexmap 2.6.0", + "indexmap 2.7.1", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2c50e6a62ee2b4f7ab3c6d0366e5770a21cad426e109c2f40335a1b3aff3df" +checksum = "ad9f7d057e00f8c5994e4ff4492b76532c51ead39353aa2ed63f8c50c0f4d52e" dependencies = [ "alloy-json-abi", "const-hex", @@ -570,28 +570,28 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.96", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac17c6e89a50fb4a758012e4b409d9a0ba575228e69b539fe37d7a1bd507ca4a" +checksum = "74e60b084fe1aef8acecda2743ff2d93c18ff3eb67a2d3b12f62582a1e66ef5e" dependencies = [ "serde", - "winnow", + "winnow 0.6.26", ] [[package]] name = "alloy-sol-types" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dc0fffe397aa17628160e16b89f704098bf3c9d74d5d369ebc239575936de5" +checksum = "c1382302752cd751efd275f4d6ef65877ddf61e0e6f5ac84ef4302b79a33a31a" dependencies = [ "alloy-json-abi", - "alloy-primitives 0.8.14", + "alloy-primitives 0.8.19", "alloy-sol-macro", "const-hex", "serde", @@ -625,7 +625,7 @@ checksum = "5dc013132e34eeadaa0add7e74164c1503988bfba8bae885b32e0918ba85a8a6" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest 0.12.9", + "reqwest 0.12.12", "serde_json", "tower", "tracing", @@ -707,19 +707,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "ark-ff" @@ -891,18 +892,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -929,13 +930,13 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1051,9 +1052,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "bitvec" @@ -1100,9 +1101,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byte-slice-cast" @@ -1199,7 +1200,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -1207,9 +1208,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "jobserver", "libc", @@ -1224,9 +1225,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1247,9 +1248,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -1257,9 +1258,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -1269,21 +1270,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "coins-bip32" @@ -1345,25 +1346,25 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "unicode-width", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] @@ -1435,9 +1436,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -1453,9 +1454,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1472,15 +1473,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-bigint" @@ -1571,7 +1572,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1595,7 +1596,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1606,7 +1607,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1638,19 +1639,19 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f" [[package]] name = "delegate" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2323e10c92e1cf4d86e11538512e6dc03ceb586842970b6332af3d4046a046" +checksum = "297806318ef30ad066b15792a8372858020ae3ca2e414ee6c2133b1eb9e9e945" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1694,7 +1695,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1714,7 +1715,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "unicode-xid", ] @@ -1803,7 +1804,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -1893,9 +1894,9 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "encoding_rs" @@ -1932,14 +1933,14 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -1973,9 +1974,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -2001,7 +2002,7 @@ checksum = "d4291f0c7220b67ad15e9d5300ba2f215cee504f0924d60e77c9d1c77e7a69b1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -2212,7 +2213,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.96", "toml", "walkdir", ] @@ -2236,7 +2237,7 @@ dependencies = [ "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.89", + "syn 2.0.96", "toml", "walkdir", ] @@ -2253,7 +2254,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -2269,7 +2270,7 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -2294,7 +2295,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.89", + "syn 2.0.96", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -2324,7 +2325,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.89", + "syn 2.0.96", "tempfile", "thiserror 1.0.69", "tiny-keccak", @@ -2339,7 +2340,7 @@ dependencies = [ "chrono", "ethers-core 2.0.13", "reqwest 0.11.27", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -2355,7 +2356,7 @@ dependencies = [ "chrono", "ethers-core 2.0.14", "reqwest 0.11.27", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "thiserror 1.0.69", @@ -2542,7 +2543,7 @@ dependencies = [ "path-slash", "rayon", "regex", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "solang-parser", @@ -2574,7 +2575,7 @@ dependencies = [ "path-slash", "rayon", "regex", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "solang-parser", @@ -2605,9 +2606,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fastrlp" @@ -2620,6 +2621,17 @@ dependencies = [ "bytes", ] +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec 0.7.6", + "auto_impl", + "bytes", +] + [[package]] name = "ff" version = "0.13.0" @@ -2700,9 +2712,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "foreign-types" @@ -2820,7 +2832,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -2922,6 +2934,18 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "gimli" version = "0.31.1" @@ -2945,14 +2969,14 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gloo-timers" @@ -2991,7 +3015,7 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", - "env_logger 0.11.5", + "env_logger 0.11.6", "gnark-utils", "hex", "itertools 0.13.0", @@ -3033,7 +3057,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.6.0", + "indexmap 2.7.1", "slab", "tokio", "tokio-util", @@ -3144,11 +3168,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3164,9 +3188,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -3191,7 +3215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.2.0", ] [[package]] @@ -3202,16 +3226,16 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a" [[package]] name = "httpdate" @@ -3227,9 +3251,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -3251,14 +3275,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "httparse", "itoa", @@ -3276,7 +3300,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.31", + "hyper 0.14.32", "rustls", "tokio", "tokio-rustls", @@ -3289,7 +3313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.31", + "hyper 0.14.32", "native-tls", "tokio", "tokio-native-tls", @@ -3303,7 +3327,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-util", "native-tls", "tokio", @@ -3320,9 +3344,9 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", - "hyper 1.5.1", + "hyper 1.6.0", "pin-project-lite", "socket2", "tokio", @@ -3468,7 +3492,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -3533,7 +3557,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -3555,9 +3579,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown 0.15.2", @@ -3601,19 +3625,19 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37" dependencies = [ "hermit-abi 0.4.0", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3682,10 +3706,11 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.73" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb15147158e79fd8b8afd0252522769c4f48725460b37338544d8379d94fc8f9" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -3797,9 +3822,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" @@ -3813,15 +3838,15 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "libc", ] [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "litemap" @@ -3841,9 +3866,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "lru" @@ -3902,9 +3927,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] @@ -3957,7 +3982,7 @@ version = "0.1.0" dependencies = [ "alloy", "anyhow", - "env_logger 0.11.5", + "env_logger 0.11.6", "eth_trie", "log", "mp2_common", @@ -3967,6 +3992,7 @@ dependencies = [ "recursion_framework", "ryhope", "serde", + "tokio", ] [[package]] @@ -3980,7 +4006,7 @@ dependencies = [ "bincode", "csv", "derive_more 1.0.0", - "env_logger 0.11.5", + "env_logger 0.11.6", "envconfig", "eth_trie", "futures", @@ -3996,6 +4022,7 @@ dependencies = [ "paste", "plonky2", "plonky2_crypto", + "plonky2_ecdsa", "plonky2_ecgfp5", "rand", "rand_chacha", @@ -4016,9 +4043,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", @@ -4178,14 +4205,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -4223,11 +4250,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.68" +version = "0.10.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +checksum = "f5e534d133a060a3c19daec1eb3e98ec6f4685978834f2dbadfe2ec215bab64e" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "cfg-if", "foreign-types", "libc", @@ -4244,14 +4271,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" @@ -4297,34 +4324,33 @@ dependencies = [ "ansitok", "bytecount", "fnv", - "unicode-width", + "unicode-width 0.1.11", ] [[package]] name = "parity-scale-codec" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec 0.7.6", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", "parity-scale-codec-derive", - "rustversion", "serde", ] [[package]] name = "parity-scale-codec-derive" -version = "3.7.0" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 1.0.109", ] [[package]] @@ -4364,7 +4390,7 @@ dependencies = [ "serde_json", "sqlparser", "stderrlog", - "thiserror 2.0.3", + "thiserror 2.0.11", "verifiable-db", ] @@ -4430,12 +4456,12 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.11", "ucd-trie", ] @@ -4446,7 +4472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.6.0", + "indexmap 2.7.1", ] [[package]] @@ -4461,35 +4487,35 @@ dependencies = [ [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared 0.11.2", + "phf_shared 0.11.3", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared 0.11.3", "rand", ] [[package]] name = "phf_macros" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared 0.11.2", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -4498,43 +4524,43 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher", + "siphasher 1.0.1", ] [[package]] name = "pin-project" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" +checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" +checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -4718,13 +4744,13 @@ dependencies = [ "serde", "serde_json", "serde_plain", - "serde_with 3.11.0", + "serde_with 3.12.0", "sha2", "sha256", "starkyx", "tokio", "tracing", - "uuid 1.11.0", + "uuid 1.12.1", ] [[package]] @@ -4734,7 +4760,7 @@ source = "git+https://github.com/Lagrange-Labs/succinctx?branch=fix-build#8580a6 dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -4804,12 +4830,12 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "prettyplease" -version = "0.2.25" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" +checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -4888,14 +4914,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -4908,7 +4934,7 @@ checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.6.0", + "bitflags 2.8.0", "lazy_static", "num-traits", "rand", @@ -4928,9 +4954,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -5016,7 +5042,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode", - "env_logger 0.11.5", + "env_logger 0.11.6", "log", "mp2_common", "plonky2", @@ -5028,11 +5054,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", ] [[package]] @@ -5110,7 +5136,7 @@ dependencies = [ "h2", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.31", + "hyper 0.14.32", "hyper-rustls", "hyper-tls 0.5.0", "ipnet", @@ -5142,18 +5168,18 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.9" +version = "0.12.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" dependencies = [ "base64 0.22.1", "bytes", "futures-core", "futures-util", - "http 1.1.0", + "http 1.2.0", "http-body 1.0.1", "http-body-util", - "hyper 1.5.1", + "hyper 1.6.0", "hyper-tls 0.6.0", "hyper-util", "ipnet", @@ -5171,6 +5197,7 @@ dependencies = [ "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", + "tower", "tower-service", "url", "wasm-bindgen", @@ -5223,7 +5250,7 @@ dependencies = [ "alloy-primitives 0.4.2", "alloy-rlp", "auto_impl", - "bitflags 2.6.0", + "bitflags 2.8.0", "bitvec", "enumn", "hashbrown 0.14.5", @@ -5353,22 +5380,24 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.89", + "syn 2.0.96", "unicode-ident", ] [[package]] name = "ruint" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" +checksum = "f5ef8fb1dd8de3870cb8400d51b4c2023854bbafd5431a3ac7e7317243e22d2f" dependencies = [ "alloy-rlp", "ark-ff 0.3.0", "ark-ff 0.4.2", "bytes", - "fastrlp", + "fastrlp 0.3.1", + "fastrlp 0.4.0", "num-bigint 0.4.6", + "num-integer", "num-traits", "parity-scale-codec", "postgres-types", @@ -5378,7 +5407,7 @@ dependencies = [ "rlp", "ruint-macro", "serde", - "thiserror 1.0.69", + "thiserror 2.0.11", "valuable", "zeroize", ] @@ -5397,9 +5426,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" @@ -5422,20 +5451,20 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.25", ] [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -5470,9 +5499,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -5486,9 +5515,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "rusty-fork" @@ -5520,7 +5549,7 @@ dependencies = [ "serde_json", "sha256", "simple_logger", - "thiserror 2.0.3", + "thiserror 2.0.11", "tokio", "tokio-postgres", "tracing", @@ -5528,9 +5557,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "salsa20" @@ -5571,14 +5600,14 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "scc" -version = "2.2.5" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b202022bb57c049555430e11fc22fea12909276a80a4c3d368da36ac1d88ed" +checksum = "28e1c91382686d21b5ac7959341fcb9780fa7c03773646995a87c950fa7be640" dependencies = [ "sdd", ] @@ -5594,9 +5623,9 @@ dependencies = [ [[package]] name = "schnellru" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649" dependencies = [ "ahash", "cfg-if", @@ -5633,9 +5662,9 @@ dependencies = [ [[package]] name = "sdd" -version = "3.0.4" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" +checksum = "478f121bb72bbf63c52c93011ea1791dca40140dfe13f8336c4c5ac952c33aa9" [[package]] name = "sec1" @@ -5657,7 +5686,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.8.0", "core-foundation", "core-foundation-sys", "libc", @@ -5666,9 +5695,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.12.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -5685,9 +5714,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" dependencies = [ "serde", ] @@ -5715,29 +5744,29 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -5793,19 +5822,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.11.0", + "serde_with_macros 3.12.0", "time", ] @@ -5818,19 +5847,19 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "serde_with_macros" -version = "3.11.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -5880,7 +5909,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -5980,13 +6009,13 @@ dependencies = [ [[package]] name = "simple_asn1" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint 0.4.6", "num-traits", - "thiserror 1.0.69", + "thiserror 2.0.11", "time", ] @@ -6007,6 +6036,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -6170,7 +6205,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6212,7 +6247,7 @@ dependencies = [ "hex", "once_cell", "reqwest 0.11.27", - "semver 1.0.23", + "semver 1.0.25", "serde", "serde_json", "sha2", @@ -6234,9 +6269,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.89" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -6245,14 +6280,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0523f59468a2696391f2a772edc089342aacd53c3caa2ac3264e598edf119b" +checksum = "b84e4d83a0a6704561302b917a932484e1cae2d8c6354c64be8b7bac1c1fe057" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6278,7 +6313,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6335,12 +6370,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom 0.3.1", "once_cell", "rustix", "windows-sys 0.59.0", @@ -6368,24 +6404,24 @@ dependencies = [ [[package]] name = "test-log" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dffced63c2b5c7be278154d76b479f9f9920ed34e7574201407f0b14e2bbb93" +checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f" dependencies = [ - "env_logger 0.11.5", + "env_logger 0.11.6", "test-log-macros", "tracing-subscriber", ] [[package]] name = "test-log-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999e24eaa32083191ba4e425deb75cdf25efefabe5aaccb7446dd0d4122a3f5" +checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6408,11 +6444,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.11", ] [[package]] @@ -6423,18 +6459,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6458,9 +6494,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -6479,9 +6515,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -6508,9 +6544,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -6523,9 +6559,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.41.1" +version = "1.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" dependencies = [ "backtrace", "bytes", @@ -6541,13 +6577,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6598,9 +6634,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -6625,9 +6661,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -6659,27 +6695,28 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee" dependencies = [ - "indexmap 2.6.0", + "indexmap 2.7.1", "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.7.0", ] [[package]] name = "tower" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", + "tokio", "tower-layer", "tower-service", ] @@ -6715,7 +6752,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -6824,15 +6861,15 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-normalization" @@ -6855,6 +6892,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -6930,18 +6973,18 @@ dependencies = [ [[package]] name = "uuid" -version = "1.11.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" dependencies = [ "serde", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -6962,6 +7005,8 @@ dependencies = [ "log", "mp2_common", "mp2_test", + "mp2_v1", + "num", "plonky2", "plonky2_crypto", "plonky2_ecdsa", @@ -7041,6 +7086,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -7049,35 +7103,35 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.96" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21d3b25c3ea1126a2ad5f4f9068483c2af1e64168f847abe863a526b8dbfe00b" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.96" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52857d4c32e496dc6537646b5b117081e71fd2ff06de792e3577a150627db283" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.46" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951fe82312ed48443ac78b66fa43eded9999f738f6022e67aead7b708659e49a" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -7088,9 +7142,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.96" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "920b0ffe069571ebbfc9ddc0b36ba305ef65577c94b06262ed793716a1afd981" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7098,22 +7152,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.96" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf59002391099644be3524e23b781fa43d2be0c5aa0719a18c0731b9d195cab6" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.96" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5047c5392700766601942795a436d7d2599af60dcc3cc1248c9120bfb0827b0" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasmtimer" @@ -7131,9 +7188,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.73" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "476364ff87d0ae6bfb661053a9104ab312542658c3d8f963b7ace80b6f9b26b9" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -7386,9 +7443,18 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419" dependencies = [ "memchr", ] @@ -7403,6 +7469,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "write16" version = "1.0.0" @@ -7469,7 +7544,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "synstructure", ] @@ -7491,7 +7566,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -7511,7 +7586,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", "synstructure", ] @@ -7532,7 +7607,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] @@ -7554,7 +7629,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.89", + "syn 2.0.96", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9436c46a4..13e11b5a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,12 +25,14 @@ alloy = { version = "0.6", default-features = false, features = [ "rlp", "rpc", "rpc-types", + "json-rpc", "signer-local", "sol-types", "transport-http", "transports", "postgres", ] } + anyhow = "1.0" base64 = "0.22" bb8 = "0.8.5" @@ -75,7 +77,7 @@ rand_chacha = "0.3.1" revm = { version = "3.5", default-features = false } rlp = "0.5" rstest = "0.23" -serde = { version = "1.0", features = ["derive"] } +serde = { version = "1.0", features = ["derive", "std"] } serde_json = "1.0" serial_test = "3.0" sha2 = "0.10" diff --git a/inspect/src/index.rs b/inspect/src/index.rs index cd3cbc642..ed9c631ea 100644 --- a/inspect/src/index.rs +++ b/inspect/src/index.rs @@ -14,7 +14,7 @@ use crate::repl::PayloadFormatter; pub(crate) type IndexDb = MerkleTreeKvDb< BlockTree, IndexNode, - PgsqlStorage>, + PgsqlStorage, false>, >; struct IndexPayloadFormatterDisplay { diff --git a/inspect/src/main.rs b/inspect/src/main.rs index 411ec61bb..99c3adc14 100644 --- a/inspect/src/main.rs +++ b/inspect/src/main.rs @@ -5,7 +5,7 @@ use repl::Repl; use rows::{RowDb, RowPayloadFormatter}; use ryhope::{ storage::pgsql::{SqlServerConnection, SqlStorageSettings, ToFromBytea}, - Epoch, InitSettings, + InitSettings, UserEpoch, }; use serde::Serialize; @@ -26,7 +26,7 @@ struct Args { #[arg(short = 'E', long = "at")] /// If set, try to view the tree at this epoch - epoch: Option, + epoch: Option, #[command(subcommand)] /// The type of tree to load from the database @@ -77,6 +77,8 @@ async fn main() -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(args.db_uri.clone()), table: args.db_table, + external_mapper: None, // not necessary even if there is an external epoch mapper, + // since we are initializing the tree with `InitSettings::MustExist` }, ) .await?; @@ -91,7 +93,7 @@ async fn main() -> Result<()> { let mut repl = Repl::new(tree_db, payload_fmt).await?; if let Some(epoch) = args.epoch { - repl.set_epoch(epoch)?; + repl.set_epoch(epoch).await?; } repl.run().await } @@ -101,6 +103,7 @@ async fn main() -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(args.db_uri.clone()), table: args.db_table, + external_mapper: None, }, ) .await?; @@ -109,7 +112,7 @@ async fn main() -> Result<()> { let mut repl = Repl::new(tree_db, payload_fmt).await?; if let Some(epoch) = args.epoch { - repl.set_epoch(epoch)?; + repl.set_epoch(epoch).await?; } repl.run().await } diff --git a/inspect/src/repl.rs b/inspect/src/repl.rs index c7748f2b9..84e765658 100644 --- a/inspect/src/repl.rs +++ b/inspect/src/repl.rs @@ -1,4 +1,4 @@ -use anyhow::{anyhow, bail}; +use anyhow::{anyhow, bail, Result}; use colored::Colorize; use dialoguer::{console, theme::ColorfulTheme, FuzzySelect, Input}; use itertools::Itertools; @@ -8,7 +8,7 @@ use ryhope::{ TreeStorage, }, tree::{MutableTree, PrintableTree, TreeTopology}, - Epoch, MerkleTreeKvDb, NodePayload, + MerkleTreeKvDb, NodePayload, UserEpoch, }; use std::io::Write; use tabled::{builder::Builder, settings::Style}; @@ -57,7 +57,7 @@ pub(crate) struct Repl< F: PayloadFormatter, > { current_key: T::Key, - current_epoch: Epoch, + current_epoch: UserEpoch, db: MerkleTreeKvDb, tty: console::Term, payload_fmt: F, @@ -77,7 +77,7 @@ impl< { pub async fn new(db: MerkleTreeKvDb, payload_fmt: F) -> anyhow::Result { let current_key = db.root().await?.ok_or(anyhow!("tree is empty"))?; - let current_epoch = db.current_epoch(); + let current_epoch = db.current_epoch().await?; Ok(Self { current_key, @@ -105,19 +105,19 @@ impl< .unwrap(); } - pub fn set_epoch(&mut self, epoch: Epoch) -> anyhow::Result<()> { - if epoch < self.db.initial_epoch() { + pub async fn set_epoch(&mut self, epoch: UserEpoch) -> Result<()> { + if epoch < self.db.initial_epoch().await { bail!( "epoch `{}` is older than initial epoch `{}`", epoch, - self.db.initial_epoch() + self.db.initial_epoch().await ); } - if epoch > self.db.current_epoch() { + if epoch > self.db.current_epoch().await? { bail!( "epoch `{}` is newer than latest epoch `{}`", epoch, - self.db.current_epoch() + self.db.current_epoch().await? ); } @@ -147,9 +147,9 @@ impl< async fn travel(&mut self) -> anyhow::Result<()> { loop { - let epoch: Epoch = Input::new().with_prompt("target epoch:").interact_text()?; + let epoch: UserEpoch = Input::new().with_prompt("target epoch:").interact_text()?; - self.set_epoch(epoch)?; + self.set_epoch(epoch).await?; } } diff --git a/inspect/src/rows.rs b/inspect/src/rows.rs index af859e80d..b4a963b9a 100644 --- a/inspect/src/rows.rs +++ b/inspect/src/rows.rs @@ -17,7 +17,7 @@ use crate::repl::PayloadFormatter; pub(crate) type RowDb = MerkleTreeKvDb< RowTree, RowPayload, - PgsqlStorage>, + PgsqlStorage, true>, >; struct RowPayloadFormatterDisplay { diff --git a/mp2-common/Cargo.toml b/mp2-common/Cargo.toml index 2ca2673a0..143a36aef 100644 --- a/mp2-common/Cargo.toml +++ b/mp2-common/Cargo.toml @@ -32,9 +32,9 @@ hex.workspace = true rand.workspace = true rstest.workspace = true tokio.workspace = true - mp2_test = { path = "../mp2-test" } + [features] ci = ["mp2_test/ci"] original_poseidon = [] diff --git a/mp2-common/src/array.rs b/mp2-common/src/array.rs index ffcee5aa9..8326e3953 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::{ @@ -16,7 +19,7 @@ use plonky2_crypto::u32::arithmetic_u32::U32Target; use serde::{Deserialize, Serialize}; use std::{array::from_fn as create_array, fmt::Debug, ops::Index}; -use crate::utils::{less_than, less_than_or_equal_to}; +use crate::utils::less_than; /// Utility trait to convert any value into its field representation equivalence pub trait ToField { @@ -166,16 +169,21 @@ impl VectorWire { ) -> Array { let zero = b.zero(); let pad_t = b.constant(F::from_canonical_usize(PAD_LEN)); + // initialize is_lt to false + let mut is_lt = b._false(); Array { arr: create_array(|i| { // ((pad_len - i) < real_len) * vec[real_len - (pad_len-i)] // i.e. reading value backwards and inserting in order let it = b.constant(F::from_canonical_usize(i)); let jt = b.sub(pad_t, it); - let is_lt = - less_than_or_equal_to(b, jt, self.real_len, (PAD_LEN.ilog2() + 1) as usize); + // update is_lt incrementally: it should become true when PAD_LEN-i == self.real_len + let is_eq = b.is_equal(jt, self.real_len); + is_lt = b.or(is_lt, is_eq); let idx = b.sub(self.real_len, jt); - let val = self.arr.value_at_failover(b, idx); + let idx = b.select(is_lt, idx, zero); // workaround to always have a in-bound access in + // the array + let val = self.arr.value_at(b, idx); b.select(is_lt, val, zero) }), } @@ -480,12 +488,13 @@ where slice_len: Target, ) { let tru = b._true(); + let mut take = b._false(); for (i, (our, other)) in self.arr.iter().zip(other.arr.iter()).enumerate() { let it = b.constant(F::from_canonical_usize(i)); - // TODO: fixed to 6 becaues max nibble len = 64 - TO CHANGE - let before_end = less_than(b, it, slice_len, 6); + let reached_end = b.is_equal(slice_len, it); + take = b.or(take, reached_end); let eq = b.is_equal(our.to_target(), other.to_target()); - let res = b.select(before_end, eq.target, tru.target); + let res = b.select(take, tru.target, eq.target); b.connect(res, tru.target); } } @@ -518,25 +527,19 @@ where b: &mut CircuitBuilder, at: Target, ) -> Array { + let tru = b._true(); let m = b.constant(F::from_canonical_usize(SUB_SIZE)); + let orig_size = b.constant(F::from_canonical_usize(SIZE)); let upper_bound = b.add(at, m); let num_bits_size = SIZE.ilog2() + 1; + // By enforcing that upper_bound is less than or equal to total size we don't need to check at each step + let lt = less_than_or_equal_to_unsafe(b, upper_bound, orig_size, num_bits_size as usize); + b.connect(lt.target, tru.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); - // ((i + offset) <= n + M) - // unsafe should be ok since the function assumes that `at + SUB_SIZE <= SIZE` - let lt = less_than_or_equal_to_unsafe( - b, - i_plus_n_target, - upper_bound, - num_bits_size as usize, - ); - // ((i+n) <= n+M) * (i+n) - let j = b.mul(lt.target, i_plus_n_target); - // out_val = arr[((i+n)<=n+M) * (i+n)] - self.value_at(b, j) + self.value_at(b, i_plus_n_target) }), } } @@ -600,6 +603,54 @@ 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 { + large_slice_random_access(b, &self.arr, at) + } + + /// 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 { + 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)] @@ -632,6 +683,114 @@ impl Array { /// Plonky2 API const RANDOM_ACCESS_SIZE: usize = 64; +/// This function allows you to search a large slice of [`Targetable`] 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 slice 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 [`Array::value_at`] on an [`Array`] with length `512` results in 129 rows, using this method +/// on the same [`Array`] results in 15 rows. +pub(crate) fn large_slice_random_access( + b: &mut CircuitBuilder, + slice: &[T], + at: Target, +) -> T +where + F: RichField + Extendable, + T: Targetable + Clone + Serialize + for<'de> Deserialize<'de>, +{ + let zero = b.zero(); + // We will split the array into smaller arrays of size 64, padding the last array with zeroes if required + let padded_size = (slice.len() - 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 < slice.len() { + slice[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, if it is not the output defaults to zero. + let array_size = b.constant(F::from_noncanonical_u64(slice.len() as u64)); + let less_than_check = less_than_unsafe(b, at, array_size, 12); + + let lookup_index = b.select(less_than_check, at, zero); + let (low_bits, high_bits) = b.split_low_high(lookup_index, 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` + let second_search = b.random_access(high_bits, first_search); + T::from_target(b.select(less_than_check, second_search, zero)) +} + +/// Function to extract value from a slice using random access gates. +/// If `at` is outside the range of the slice it defaults to return zero. +pub fn extract_value( + b: &mut CircuitBuilder, + data: &[T], + at: Target, +) -> T +where + F: RichField + Extendable, + T: Targetable + Clone + Serialize + for<'de> Deserialize<'de>, +{ + // We check to see if the index `at` is a constant, if it is we can directly return the value + if let Some(val) = b.target_as_constant(at) { + let index = val.to_canonical_u64() as usize; + return data[index]; + } + let data_len = data.len(); + let zero = b.zero(); + // Only use random_access when SIZE is a power of 2 and smaller than 64 + // see https://stackoverflow.com/a/600306/1202623 for the trick + if data_len <= RANDOM_ACCESS_SIZE { + let next_power_two = data_len.next_power_of_two(); + // Escape hatch when we can use random_access from plonky2 base + T::from_target( + b.random_access( + at, + data.iter() + .map(Targetable::to_target) + .chain(std::iter::repeat(zero)) + .take(next_power_two) + .collect::>(), + ), + ) + } else { + large_slice_random_access(b, data, at) + } +} + #[cfg(test)] mod test { use core::array::from_fn as create_array; @@ -815,6 +974,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 +1064,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 +1345,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/digest.rs b/mp2-common/src/digest.rs index 9265af657..f55133a31 100644 --- a/mp2-common/src/digest.rs +++ b/mp2-common/src/digest.rs @@ -2,82 +2,17 @@ use crate::group_hashing::{ circuit_hashed_scalar_mul, cond_circuit_hashed_scalar_mul, cond_field_hashed_scalar_mul, field_hashed_scalar_mul, map_to_curve_point, }; -use crate::serialization::{deserialize, serialize}; use crate::types::CBuilder; use crate::utils::ToFields; use crate::{group_hashing::CircuitBuilderGroupHashing, utils::ToTargets}; -use crate::{D, F}; -use derive_more::{From, Into}; use plonky2::iop::target::BoolTarget; -use plonky2::iop::witness::{PartialWitness, WitnessWrite}; -use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2_ecgfp5::{ curve::curve::Point, gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, }; -use serde::{Deserialize, Serialize}; pub type DigestTarget = CurveTarget; pub type Digest = Point; -/// Whether the table's digest is composed of a single row, or multiple rows. -/// For example when extracting mapping entries in one single sweep of the MPT, the digest contains -/// multiple rows inside. -/// When extracting single variables on one sweep, there is only a single row contained in the -/// digest. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub enum TableDimension { - /// Set to Single for types that only generate a single row at a given block. For example, a - /// uint256 or a bytes32 will only generate a single row per block. - Single, - /// Set to Compound for types that - /// 1. have multiple entries (like an mapping, unlike a single uin256 for example) - /// 2. don't need or have an associated length slot to combine with - /// - /// It happens contracts don't have a length slot associated with the mapping like ERC20 and - /// thus there is no proof circuits have looked at _all_ the entries due to limitations on EVM - /// (there is no mapping.len()). - Compound, -} - -impl TableDimension { - pub fn assign_wire(&self, pw: &mut PartialWitness, wire: &TableDimensionWire) { - match self { - TableDimension::Single => pw.set_bool_target(wire.0, false), - TableDimension::Compound => pw.set_bool_target(wire.0, true), - } - } - - pub fn conditional_row_digest(&self, digest: Digest) -> Digest { - match self { - TableDimension::Single => map_to_curve_point(&digest.to_fields()), - TableDimension::Compound => digest, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug, From, Into, Eq, PartialEq)] -pub struct TableDimensionWire( - #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] pub BoolTarget, -); - -impl TableDimensionWire { - pub fn conditional_row_digest( - &self, - c: &mut CircuitBuilder, - digest: CurveTarget, - ) -> CurveTarget { - let single = c.map_to_curve_point(&digest.to_targets()); - // if the table is a compound table, i.e. multiple rows accumulated in the digest, then - // there is no need to apply digest one more time. On the other hand, if it is not - // compounded, i.e. there is only a sum of cells digest, then we need to create the "row" - // digest, thus applying the digest one more time. - // - // TableDimension::Single => false, - // TableDimension::Compound => true, - c.curve_select(self.0, digest, single) - } -} - /// Generic struct that can either hold a digest in circuit (DigestTarget) or a digest outside /// circuit, useful for testing. #[derive(Clone, Debug)] @@ -183,10 +118,7 @@ impl SplitDigestTarget { mod test { use crate::{types::CBuilder, utils::FromFields, C, D, F}; - use super::{ - Digest, DigestTarget, SplitDigestPoint, SplitDigestTarget, TableDimension, - TableDimensionWire, - }; + use super::{Digest, DigestTarget, SplitDigestPoint, SplitDigestTarget}; use crate::utils::TryIntoBool; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::{field::types::Sample, iop::witness::PartialWitness}; @@ -260,48 +192,4 @@ mod test { assert_eq!(is_merge_case_circuit, is_merge_case_point); } } - - #[derive(Clone, Debug)] - struct TestTableDimension { - digest: Digest, - dimension: TableDimension, - } - - struct TestTableDimensionWire { - digest: DigestTarget, - dimension: TableDimensionWire, - } - - impl UserCircuit for TestTableDimension { - type Wires = TestTableDimensionWire; - - fn build(b: &mut CBuilder) -> Self::Wires { - let digest = b.add_virtual_curve_target(); - let dimension: TableDimensionWire = b.add_virtual_bool_target_safe().into(); - let final_digest = dimension.conditional_row_digest(b, digest); - b.register_curve_public_input(final_digest); - - TestTableDimensionWire { digest, dimension } - } - - fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - pw.set_curve_target(wires.digest, self.digest.to_weierstrass()); - self.dimension.assign_wire(pw, &wires.dimension); - } - } - - #[test] - fn test_dimension_wire() { - let cases = vec![TableDimension::Single, TableDimension::Compound]; - for dimension in cases { - let circuit = TestTableDimension { - digest: Point::rand(), - dimension, - }; - let proof = run_circuit::(circuit.clone()); - let combined = Digest::from_fields(&proof.public_inputs); - let expected = dimension.conditional_row_digest(circuit.digest); - assert_eq!(combined, expected); - } - } } diff --git a/mp2-common/src/eth.rs b/mp2-common/src/eth.rs index ee8eda75b..cdc72b845 100644 --- a/mp2-common/src/eth.rs +++ b/mp2-common/src/eth.rs @@ -1,27 +1,116 @@ //! 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, - primitives::{Address, B256}, + network::{eip2718::Encodable2718, BlockResponse}, + primitives::{Address, Log, B256, U256}, providers::{Provider, RootProvider}, - rlp::Encodable as AlloyEncodable, - rpc::types::{Block, EIP1186AccountProofResponse}, + rlp::{Decodable, Encodable as AlloyEncodable}, + rpc::{ + json_rpc::RpcError, + types::{ + Block, BlockTransactions, EIP1186AccountProofResponse, ReceiptEnvelope, Transaction, + TransactionReceipt, + }, + }, transports::Transport, }; -use anyhow::{bail, Result}; -use eth_trie::{EthTrie, MemoryDB, Trie}; + +use eth_trie::{EthTrie, MemoryDB, Trie, TrieError}; use ethereum_types::H256; -use log::warn; -use rlp::Rlp; +use itertools::Itertools; +use log::{debug, warn}; + +use rlp::{DecoderError, Encodable, Rlp}; use serde::{Deserialize, Serialize}; -use std::{array::from_fn as create_array, sync::Arc}; -use crate::{mpt_sequential::utils::bytes_to_nibbles, rlp::MAX_KEY_NIBBLE_LEN, utils::keccak256}; +use std::{ + array::from_fn as create_array, + collections::{BTreeSet, HashMap}, + fmt::{Debug, Display, Formatter}, + sync::Arc, +}; + +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. +/// It should always be a multiple of 32 since Solidity event data encodes every object in 32 byte chunks +/// regardless of its true size. +const MAX_RECEIPT_DATA_SIZE: usize = 32; + +/// The size of an event topic rlp encoded. +const ENCODED_TOPIC_SIZE: usize = 33; + +/// The number of bytes the transaction type takes up in a Receipts RLP encoding. +const TX_TYPE_BYTES: usize = 1; + +/// Error enum encompassing different errors that can arise in this module. +#[derive(Debug)] +pub enum MP2EthError { + /// Error occuring from a [`RpcError`], but not necessarily one we should retry. + RpcError(String), + /// An error that occurs when trying to fetch data from an RPC node, used so that we can know we should retry the call in this case. + FetchError, + /// An error that arises from a method within the [`rlp`] crate. + RlpError(DecoderError), + /// An error arising from rlp decoding methods in the [`alloy::rlp`] crate. + AlloyRlpError(alloy::rlp::Error), + /// An error arising from methods in the [`eth_trie`] crate. + TrieError(TrieError), + /// Any other error arising from the functions in this module. + InternalError(String), +} + +impl std::error::Error for MP2EthError {} + +impl Display for MP2EthError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + MP2EthError::RpcError(s) => write!(f,"Error returned when making an RPC call {{ inner: {:?} }}", s), + MP2EthError::FetchError => write!(f, "Error occured when trying to fetch data from an RPC node"), + MP2EthError::RlpError(e) => write!(f,"Error returned when performing Rlp encoding or decoding function {{ inner: {:?} }}", e), + MP2EthError::AlloyRlpError(e) => write!(f, "Error when decding to alloy type: {:?}", e), + MP2EthError::TrieError(e) => write!(f, "Error returned when construct or querying an MPT {{ inner: {:?} }}", e), + MP2EthError::InternalError(s) => write!(f, "Error occured in eth related code: {}", s) + } + } +} + +impl From> for MP2EthError { + fn from(value: RpcError) -> Self { + MP2EthError::RpcError(format!("{:?}", value)) + } +} + +impl From for MP2EthError { + fn from(value: DecoderError) -> Self { + MP2EthError::RlpError(value) + } +} + +impl From for MP2EthError { + fn from(value: TrieError) -> Self { + MP2EthError::TrieError(value) + } +} + +impl From for MP2EthError { + fn from(value: alloy::rlp::Error) -> Self { + MP2EthError::AlloyRlpError(value) + } +} + +pub trait Rlpable { fn block_hash(&self) -> Vec { keccak256(&self.rlp()) } @@ -51,6 +140,44 @@ pub fn extract_child_hashes(rlp_data: &[u8]) -> Vec> { hashes } +/// Enum used to distinguish between different types of node in an MPT. +#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub enum NodeType { + Branch, + Extension, + Leaf, +} + +/// Function that returns the [`NodeType`] of an RLP encoded MPT node +pub fn node_type(rlp_data: &[u8]) -> Result { + let rlp = Rlp::new(rlp_data); + + let item_count = rlp.item_count()?; + + if item_count == 17 { + Ok(NodeType::Branch) + } else if item_count == 2 { + // The first item is the encoded path, if it begins with a 2 or 3 it is a leaf, else it is an extension node + let first_item = rlp.at(0)?; + + // We want the first byte + let first_byte = first_item.as_raw()[0]; + + // The we divide by 16 to get the first nibble + match first_byte / 16 { + 0 | 1 => Ok(NodeType::Extension), + 2 | 3 => Ok(NodeType::Leaf), + _ => Err(MP2EthError::InternalError( + "Expected compact encoding beginning with 0,1,2 or 3".to_string(), + )), + } + } else { + Err(MP2EthError::InternalError(format!( + "RLP encoded Node item count was {item_count}, expected either 17 or 2" + ))) + } +} + pub fn left_pad32(slice: &[u8]) -> [u8; 32] { left_pad::<32>(slice) } @@ -88,22 +215,76 @@ pub fn left_pad(slice: &[u8]) -> [u8; N] { } } -/// Query the latest block. -pub async fn query_latest_block(provider: &RootProvider) -> Result { +/// Query a specific block. +pub async fn query_block( + provider: &RootProvider, + id: BlockNumberOrTag, +) -> Result { // Query the MPT proof with retries. - for i in 0..RETRY_NUM { - if let Ok(response) = provider - .get_block_by_number(BlockNumberOrTag::Latest, true.into()) - .await - { + for i in 0..RETRY_NUM - 1 { + if let Ok(response) = provider.get_block_by_number(id, true.into()).await { // Has one block at least. - return Ok(response.unwrap()); + return response.ok_or(MP2EthError::RpcError( + "Call to get block successful but returned None".to_string(), + )); } else { warn!("Failed to query the block - {i} time") } } - bail!("Failed to query the block "); + // For the final attempt we return the error + let resp = provider.get_block_by_number(id, true.into()).await; + + match resp { + Ok(option) => match option { + Some(block) => Ok(block), + None => Err(MP2EthError::RpcError( + "Get block by number call did not error but returned a None value".to_string(), + )), + }, + Err(_) => { + warn!("Failed to query the block - {} time", RETRY_NUM - 1); + Err(MP2EthError::FetchError) + } + } +} + +/// Query a specific block for its receipts. +pub async fn query_block_receipts( + provider: &RootProvider, + id: BlockNumberOrTag, +) -> Result, MP2EthError> { + // Query the MPT proof with retries. + for i in 0..RETRY_NUM - 1 { + if let Ok(response) = provider.get_block_receipts(id.into()).await { + // Has one block at least. + return response.ok_or(MP2EthError::InternalError( + "Call to get block receipts successful but returned None".to_string(), + )); + } else { + warn!("Failed to query the block receipts - {i} time") + } + } + + // For the final attempt we return the error + let resp = provider.get_block_receipts(id.into()).await; + + match resp { + Ok(option) => match option { + Some(block) => Ok(block), + None => Err(MP2EthError::RpcError( + "Get Receipts by block number call did not error but returned a None value" + .to_string(), + )), + }, + Err(_) => { + warn!( + "Failed to query the block receipts - {} time", + RETRY_NUM - 1 + ); + Err(MP2EthError::FetchError) + } + } } pub struct ProofQuery { @@ -111,7 +292,251 @@ pub struct ProofQuery { pub(crate) slot: StorageSlot, } -#[derive(Clone, Debug, Serialize, Deserialize)] +/// 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, Hash, PartialEq, Eq)] +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_WORDS], +} + +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_WORDS 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_WORDS * MAX_RECEIPT_DATA_SIZE); + + let address_size = 21; + let topics_size = (1 + NO_TOPICS) * ENCODED_TOPIC_SIZE + topics_header_len; + let data_size = MAX_DATA_WORDS * MAX_RECEIPT_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_WORDS] = create_array(|i| { + header_size + address_size + topics_size + data_header_len + (i * MAX_RECEIPT_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, + } + } + + /// Function used to return the offset from the start of an Receipt Trie Leaf Node to the log relevant to [`EventLogInfo`] + pub fn get_log_offset(&self, node: &[u8]) -> Result { + let node_rlp = rlp::Rlp::new(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()?[TX_TYPE_BYTES..]); + + // 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_log_offset = std::iter::successors(Some(0usize), |i| Some(i + 1)) + .map_while(|i| logs_rlp.at_with_offset(i).ok()) + .find_map(|(log_rlp, log_off)| { + let mut bytes = log_rlp.as_raw(); + let log = Log::decode(&mut bytes).ok()?; + + if log.address == self.address + && log + .data + .topics() + .contains(&B256::from(self.event_signature)) + { + Some(logs_offset + log_off) + } else { + None + } + }) + .ok_or(MP2EthError::InternalError( + "There were no relevant logs in this transaction".to_string(), + ))?; + + Ok(relevant_log_offset) + } + + /// 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<(Vec, B256), MP2EthError> { + let receipts = query_block_receipts(provider, block).await?; + + let memdb = Arc::new(MemoryDB::new(true)); + let mut receipts_trie = EthTrie::new(memdb.clone()); + + let mut indices = BTreeSet::::new(); + receipts.into_iter().try_for_each(|receipt| { + let tx_index_u64 = receipt.transaction_index.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"), + }; + // To receipt method is infallible so unwrap is safe here + let receipt = receipt_primitive.as_receipt().unwrap(); + let relevant = receipt.logs.iter().find(|log| { + let address_check = log.address == self.address; + let topics = log.topics(); + if topics.is_empty() { + false + } else { + let sig_check = topics[0].0 == self.event_signature; + sig_check && address_check + } + }); + + if relevant.is_some() { + indices.insert(tx_index_u64); + } + + let body_rlp = receipt_primitive.encoded_2718(); + + receipts_trie.insert(&tx_index, &body_rlp) + })?; + let mpt_root = receipts_trie.root_hash()?; + + Ok(( + indices + .iter() + .map(|&tx_index| { + let key = tx_index.rlp_bytes(); + + let proof = receipts_trie.get_proof(&key[..])?; + + Ok(ReceiptProofInfo { + mpt_proof: proof, + mpt_root, + tx_index, + }) + }) + .collect::, MP2EthError>>()?, + B256::from(mpt_root.0), + )) + } +} + +/// Represent an intermediate or leaf node of a storage slot in contract. +/// +/// It has a `parent` node, and its ancestor (root) must be a simple or mapping slot. +/// Any intermediate nodes could be represented as: +/// - For mapping entry, it has a parent node and the mapping key. +/// - For Struct entry, it has a parent node and the EVM offset. +// NOTE: This is not strict, since the parent of a Slot mapping entry must type of +// mapping (cannot be a Struct). +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum StorageSlotNode { + /// Mapping entry including a parent node and the mapping key + Mapping(Box, Vec), + /// Struct entry including a parent node and EVM offset + Struct(Box, u32), +} + +impl StorageSlotNode { + pub fn new_mapping(parent: StorageSlot, mapping_key: Vec) -> Result { + let parent = Box::new(parent); + if !matches!( + *parent, + StorageSlot::Mapping(_, _) | StorageSlot::Node(Self::Mapping(_, _)) + ) { + return Err(MP2EthError::InternalError( + "The parent of a Slot mapping entry must be type of mapping".to_string(), + )); + } + + Ok(Self::Mapping(parent, mapping_key)) + } + + pub fn new_struct(parent: StorageSlot, evm_offset: u32) -> Self { + let parent = Box::new(parent); + + Self::Struct(parent, evm_offset) + } + + pub fn parent(&self) -> &StorageSlot { + match self { + Self::Mapping(parent, _) => parent, + Self::Struct(parent, _) => parent, + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum StorageSlot { /// simple storage slot like a uin256 etc that fits in 32bytes /// Argument is the slot location in the contract @@ -121,12 +546,26 @@ pub enum StorageSlot { /// Second argument is the slot location inthe contract /// (mapping_key, mapping_slot) Mapping(Vec, usize), + /// Represent an intermediate or leaf node of a storage slot in contract. + /// It has a `parent` node, and its ancestor (root) must be a simple or mapping slot. + Node(StorageSlotNode), } + impl StorageSlot { pub fn slot(&self) -> u8 { match self { StorageSlot::Simple(slot) => *slot as u8, StorageSlot::Mapping(_, slot) => *slot as u8, + StorageSlot::Node(node) => node.parent().slot(), + } + } + pub fn evm_offset(&self) -> u32 { + match self { + // Only the Struct storage has the EVM offset. + StorageSlot::Node(StorageSlotNode::Struct(_, evm_offset)) => *evm_offset, + StorageSlot::Simple(_) + | StorageSlot::Mapping(_, _) + | StorageSlot::Node(StorageSlotNode::Mapping(_, _)) => 0, } } pub fn location(&self) -> B256 { @@ -136,11 +575,31 @@ impl StorageSlot { // H( pad32(address), pad32(mapping_slot)) let padded_mkey = left_pad32(mapping_key); let padded_slot = left_pad32(&[*mapping_slot as u8]); - let concat = padded_mkey + let inputs = padded_mkey.into_iter().chain(padded_slot).collect_vec(); + B256::from_slice(&keccak256(&inputs)) + } + StorageSlot::Node(StorageSlotNode::Mapping(parent, mapping_key)) => { + // location = keccak256(left_pad32(mapping_key) || parent_location) + let padded_mapping_key = left_pad32(mapping_key); + let parent_location = parent.location(); + let inputs = padded_mapping_key .into_iter() - .chain(padded_slot) - .collect::>(); - B256::from_slice(&keccak256(&concat)) + .chain(parent_location.0) + .collect_vec(); + B256::from_slice(&keccak256(&inputs)) + } + StorageSlot::Node(StorageSlotNode::Struct(parent, evm_offset)) => { + // location = parent_location + evm_offset + let parent_location = U256::from_be_slice(parent.location().as_slice()); + let location: [_; U256::BYTES] = parent_location + .checked_add(U256::from(*evm_offset)) + .unwrap() + .to_be_bytes(); + debug!( + "Storage slot struct: parent_location = {}, evm_offset = {}", + parent_location, evm_offset, + ); + B256::from_slice(&location) } } } @@ -158,10 +617,31 @@ impl StorageSlot { match self { StorageSlot::Simple(_) => true, StorageSlot::Mapping(_, _) => false, + StorageSlot::Node(node) => node.parent().is_simple_slot(), + } + } + /// Get the mapping key path from the outer key to the inner. + pub fn mapping_keys(&self) -> Vec> { + match self { + StorageSlot::Simple(_) => vec![], + StorageSlot::Mapping(mapping_key, _) => { + vec![mapping_key.clone()] + } + StorageSlot::Node(StorageSlotNode::Mapping(parent, mapping_key)) => { + // [parent_mapping_keys || mapping_key] + let mut mapping_keys = parent.mapping_keys(); + mapping_keys.push(mapping_key.clone()); + + mapping_keys + } + StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => parent.mapping_keys(), } } } impl ProofQuery { + pub fn new(contract: Address, slot: StorageSlot) -> Self { + Self { contract, slot } + } pub fn new_simple_slot(address: Address, slot: usize) -> Self { Self { contract: address, @@ -178,11 +658,17 @@ impl ProofQuery { &self, provider: &RootProvider, block: BlockNumberOrTag, - ) -> Result { + ) -> Result { // Query the MPT proof with retries. - for i in 0..RETRY_NUM { + let location = self.slot.location(); + for i in 0..RETRY_NUM - 1 { + debug!( + "Querying MPT proof:\n\tslot = {:?}, location = {:?}", + self.slot, + U256::from_be_slice(location.as_slice()), + ); match provider - .get_proof(self.contract, vec![self.slot.location()]) + .get_proof(self.contract, vec![location]) .block_id(block.into()) .await { @@ -191,11 +677,20 @@ impl ProofQuery { } } - bail!("Failed to query the MPT proof {RETRY_NUM} in total"); + match provider + .get_proof(self.contract, vec![location]) + .block_id(block.into()) + .await + { + Ok(response) => Ok(response), + Err(_) => Err(MP2EthError::FetchError), + } } /// Returns the raw value from the storage proof, not the one "interpreted" by the /// JSON RPC so we can see how the encoding is done. - pub fn verify_storage_proof(proof: &EIP1186AccountProofResponse) -> Result> { + pub fn verify_storage_proof( + proof: &EIP1186AccountProofResponse, + ) -> Result, MP2EthError> { let memdb = Arc::new(MemoryDB::new(true)); let tx_trie = EthTrie::new(Arc::clone(&memdb)); let proof_key_bytes = proof.storage_proof[0].key.as_b256(); @@ -209,18 +704,17 @@ impl ProofQuery { .iter() .map(|b| b.to_vec()) .collect(), - ); - // key must be valid, proof must be valid and value must exist - if is_valid.is_err() { - bail!("proof is not valid"); - } - if let Some(ext_value) = is_valid.unwrap() { + )?; + + if let Some(ext_value) = is_valid { Ok(ext_value) } else { - bail!("proof says the value associated with that key does not exist"); + Err(MP2EthError::InternalError( + "proof says the value associated with that key does not exist".to_string(), + )) } } - pub fn verify_state_proof(&self, res: &EIP1186AccountProofResponse) -> Result<()> { + pub fn verify_state_proof(&self, res: &EIP1186AccountProofResponse) -> Result<(), MP2EthError> { let memdb = Arc::new(MemoryDB::new(true)); let tx_trie = EthTrie::new(Arc::clone(&memdb)); @@ -234,25 +728,46 @@ impl ProofQuery { state_root_hash, &mpt_key, res.account_proof.iter().map(|b| b.to_vec()).collect(), - ); + )?; - if is_valid.is_err() { - bail!("Account proof is invalid"); - } - if is_valid.unwrap().is_none() { - bail!("Account proof says the value associated with that key does not exist"); + if is_valid.is_none() { + return Err(MP2EthError::InternalError( + "Account proof says the value associated with that key does not exist".to_string(), + )); } // The length of acount node must be 104 bytes (8 + 32 + 32 + 32) as: // [nonce (U64), balance (U256), storage_hash (H256), code_hash (H256)] - let account_node = res.account_proof.last().unwrap(); - assert_eq!(account_node.len(), 104); + let account_node = res.account_proof.last().ok_or(MP2EthError::InternalError( + "Account proof response was empty".to_string(), + ))?; + if account_node.len() != 104 { + Err(MP2EthError::InternalError(format!("The length of acount node must be 104 bytes (8 + 32 + 32 + 32), retrieved node length: {}", account_node.len()))) + } else { + Ok(()) + } + } +} - Ok(()) +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(MP2EthError::InternalError( + "No proof found when verifying".to_string(), + ))?; + + let rlp_receipt = rlp::Rlp::new(&valid[1..]); + ReceiptWithBloom::decode(&mut rlp_receipt.as_raw()).map_err(|e| e.into()) } } -impl BlockUtil for alloy::rpc::types::Block { +impl Rlpable for alloy::rpc::types::Block { fn rlp(&self) -> Vec { let mut out = Vec::new(); self.header.encode(&mut out); @@ -260,7 +775,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 +789,140 @@ impl BlockUtil for alloy::rpc::types::Header { } } +#[allow(dead_code)] +pub struct BlockUtil { + /// The actual [`Block`] that the rest of the data relates to + pub block: Block, + /// The transactions and Receipts in the block paired together + pub txs: Vec, + /// The Receipts Trie + pub receipts_trie: EthTrie, + /// The Transactions Trie + 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 = query_block(t, id).await?; + + let receipts = query_block_receipts(t, id).await?; + let BlockTransactions::Full(all_tx) = block.transactions() else { + return Err(MP2EthError::InternalError( + "Could not recover full transactions from Block".to_string(), + )); + }; + // 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. + #[cfg(test)] + fn check(&mut self) -> Result<(), MP2EthError> { + 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 anyhow::{anyhow, Context, Result}; + use eth_trie::Nibbles; use ethereum_types::U64; use ethers::{ providers::{Http, Middleware}, @@ -283,38 +931,232 @@ 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_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()?; + + // 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 event = test_info.info(); + 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] = 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 == event.address + && log + .data + .topics() + .contains(&B256::from(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 + event.size]; + let decoded_log = Log::decode(&mut buf)?; + let raw_bytes: [u8; 20] = last_node + [*log_offset + event.add_rel_offset..*log_offset + event.add_rel_offset + 20] + .to_vec() + .try_into() + .unwrap(); + assert_eq!(decoded_log.address, event.address); + assert_eq!(raw_bytes, event.address); + let topics = decoded_log.topics(); + assert_eq!(topics[0].0, expected_sig); + let raw_bytes: [u8; 32] = last_node + [*log_offset + event.sig_rel_offset..*log_offset + 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")] @@ -426,6 +1268,40 @@ mod test { Ok(()) } + #[tokio::test] + async fn test_pidgy_pinguin_mapping_slot() -> Result<()> { + // first pinguin holder https://dune.com/queries/2450476/4027653 + // This was outdated so the following is the updated address + // holder: 0xee5ac9c6db07c26e71207a41e64df42e1a2b05cf + // NFT id https://opensea.io/assets/ethereum/0xbd3531da5cf5857e7cfaa92426877b022e612cf8/1116 + let mapping_value = + Address::from_str("0xee5ac9c6db07c26e71207a41e64df42e1a2b05cf").unwrap(); + let nft_id: u32 = 1116; + let mapping_key = left_pad32(&nft_id.to_be_bytes()); + let url = get_mainnet_url(); + let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); + + // extracting from + // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol + // assuming it's using ERC731Enumerable that inherits ERC721 + let mapping_slot = 2; + // pudgy pinguins + let pudgy_address = Address::from_str("0xBd3531dA5CF5857e7CfAA92426877b022e612cf8")?; + let query = ProofQuery::new_mapping_slot(pudgy_address, mapping_slot, mapping_key.to_vec()); + let res = query + .query_mpt_proof(&provider, BlockNumberOrTag::Latest) + .await?; + let raw_address = ProofQuery::verify_storage_proof(&res)?; + // the value is actually RLP encoded ! + let decoded_address: Vec = rlp::decode(&raw_address).unwrap(); + let leaf_node: Vec> = rlp::decode_list(res.storage_proof[0].proof.last().unwrap()); + println!("leaf_node[1].len() = {}", leaf_node[1].len()); + // this is read in the same order + let found_address = Address::from_slice(&decoded_address.into_iter().collect::>()); + assert_eq!(found_address, mapping_value); + Ok(()) + } + #[tokio::test] async fn test_kashish_contract_proof_query() -> Result<()> { // https://sepolia.etherscan.io/address/0xd6a2bFb7f76cAa64Dad0d13Ed8A9EFB73398F39E#code @@ -471,7 +1347,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(); @@ -534,7 +1410,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() diff --git a/mp2-common/src/keccak.rs b/mp2-common/src/keccak.rs index e29ba48a9..a3a97dc4d 100644 --- a/mp2-common/src/keccak.rs +++ b/mp2-common/src/keccak.rs @@ -23,7 +23,9 @@ use serde::{Deserialize, Serialize}; use crate::{ array::{Array, Vector, VectorWire}, - utils::{keccak256, less_than, Endianness, FromTargets, PackerTarget, ToTargets}, + utils::{ + keccak256, less_than, less_than_unsafe, Endianness, FromTargets, PackerTarget, ToTargets, + }, }; /// Length of a hash in bytes. @@ -87,7 +89,7 @@ pub struct KeccakCircuit { /// outside the circuit that requires the original input data. #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct KeccakWires { - input_array: VectorWire, + pub input_array: VectorWire, diff: Target, // 256/u32 = 8 pub output_array: OutputHash, @@ -126,14 +128,14 @@ impl KeccakCircuit { a: &VectorWire, ) -> KeccakWires { let diff_target = b.add_virtual_target(); - let end_padding = b.add(a.real_len, diff_target); + let end_padding_offset = b.add(a.real_len, diff_target); let one = b.one(); - let end_padding = b.sub(end_padding, one); // inclusive range - // little endian so we start padding from the end of the byte - let single_pad = b.constant(F::from_canonical_usize(0x81)); // 1000 0001 + let end_padding = b.sub(end_padding_offset, one); // inclusive range + // little endian so we start padding from the end of the byte + let begin_pad = b.constant(F::from_canonical_usize(0x01)); // 0000 0001 let end_pad = b.constant(F::from_canonical_usize(0x80)); // 1000 0000 - // TODO : make that const generic + let padded_node = a .arr .arr @@ -141,31 +143,17 @@ impl KeccakCircuit { .enumerate() .map(|(i, byte)| { let i_target = b.constant(F::from_canonical_usize(i)); - // condition if we are within the data range ==> i < length - let is_data = less_than(b, i_target, a.real_len, 32); + // vector wires are filled with zeroes beyond a.real_len + // so we can just take the value no matter what and add padding if appropriate. + // condition if we start the padding ==> i == length let is_start_padding = b.is_equal(i_target, a.real_len); // condition if we are done with the padding ==> i == length + diff - 1 let is_end_padding = b.is_equal(i_target, end_padding); - // condition if we only need to add one byte 1000 0001 to pad - // because we work on u8 data, we know we're at least adding 1 byte and in - // this case it's 0x81 = 1000 0001 - // i == length == diff - 1 - let is_start_and_end = b.and(is_start_padding, is_end_padding); - - // nikko XXX: Is this sound ? I think so but not 100% sure. - // I think it's ok to not use `quin_selector` or `b.random_acess` because - // if the prover gives another byte target, then the resulting hash would be invalid, - let item_data = b.mul(is_data.target, *byte); - let item_start_padding = b.mul(is_start_padding.target, begin_pad); - let item_end_padding = b.mul(is_end_padding.target, end_pad); - let item_start_and_end = b.mul(is_start_and_end.target, single_pad); - // if all of these conditions are false, then item will be 0x00,i.e. the padding - let mut item = item_data; - item = b.add(item, item_start_padding); - item = b.add(item, item_end_padding); - item = b.add(item, item_start_and_end); - item + + // Adds the padding to the byte based on the calculated conditions. + let item = b.mul_add(is_start_padding.target, begin_pad, *byte); + b.mul_add(is_end_padding.target, end_pad, item) }) .collect::>(); @@ -176,7 +164,7 @@ impl KeccakCircuit { // to only look at a certain portion of our data, each bool says if the hash function // will update its state for this block or not. let rate_bytes = b.constant(F::from_canonical_usize(KECCAK256_R / 8)); - let end_padding_offset = b.add(end_padding, one); + let nb_blocks = b.div(end_padding_offset, rate_bytes); // - 1 because keccak always take first block so we don't count it let nb_actual_blocks = b.sub(nb_blocks, one); @@ -184,13 +172,17 @@ impl KeccakCircuit { let blocks = (0..total_num_blocks) .map(|i| { let i_target = b.constant(F::from_canonical_usize(i)); - less_than(b, i_target, nb_actual_blocks, 8) + if i == 0 { + less_than(b, i_target, nb_actual_blocks, 8) + } else { + less_than_unsafe(b, i_target, nb_actual_blocks, 8) + } }) .collect::>(); let hash_target = HashInputTarget { input: BigUintTarget { - limbs: node_u32_target.clone(), + limbs: node_u32_target, }, input_bits: 0, blocks, diff --git a/mp2-common/src/lib.rs b/mp2-common/src/lib.rs index ff5e2d806..672cec0a7 100644 --- a/mp2-common/src/lib.rs +++ b/mp2-common/src/lib.rs @@ -11,8 +11,6 @@ use plonky2::plonk::{ config::GenericConfig, proof::ProofWithPublicInputs, }; -#[cfg(not(feature = "original_poseidon"))] -use poseidon2_plonky2::poseidon2_goldilock::Poseidon2GoldilocksConfig; use serde::{Deserialize, Serialize}; pub mod array; @@ -38,9 +36,9 @@ pub mod utils; pub const D: usize = 2; #[cfg(feature = "original_poseidon")] -pub type C = PoseidonGoldilocksConfig; +pub type C = plonky2::plonk::config::PoseidonGoldilocksConfig; #[cfg(not(feature = "original_poseidon"))] -pub type C = Poseidon2GoldilocksConfig; +pub type C = poseidon2_plonky2::poseidon2_goldilock::Poseidon2GoldilocksConfig; pub type F = >::F; pub type CHasher = >::Hasher; diff --git a/mp2-common/src/mpt_sequential/key.rs b/mp2-common/src/mpt_sequential/key.rs index d7129fd84..1426ce548 100644 --- a/mp2-common/src/mpt_sequential/key.rs +++ b/mp2-common/src/mpt_sequential/key.rs @@ -1,6 +1,11 @@ //! MPT key gadget -use crate::{array::Array, keccak::PACKED_HASH_LEN, rlp::MAX_KEY_NIBBLE_LEN}; +use crate::{ + array::Array, + keccak::PACKED_HASH_LEN, + rlp::MAX_KEY_NIBBLE_LEN, + utils::{less_than, less_than_or_equal_to_unsafe}, +}; use core::array::from_fn as create_array; use eth_trie::Nibbles; use plonky2::{ @@ -15,6 +20,8 @@ use plonky2::{ use plonky2_crypto::u32::arithmetic_u32::U32Target; use serde::{Deserialize, Serialize}; +pub type MPTKeyWire = MPTKeyWireGeneric; + /// Calculate the pointer from the MPT key. pub fn mpt_key_ptr(mpt_key: &[u8]) -> usize { let nibbles = Nibbles::from_compact(mpt_key); @@ -24,16 +31,16 @@ pub fn mpt_key_ptr(mpt_key: &[u8]) -> usize { /// 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 +79,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 +87,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 +148,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 +161,62 @@ 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)), } } + + /// This function folds the MPT Key down into a single value, it is used in receipts to recover the transaction index. + pub fn fold_key, const D: usize>( + &self, + b: &mut CircuitBuilder, + ) -> Target { + let t = b._true(); + let zero = b.zero(); + let one = b.one(); + + // First we check that the pointer is at most 15, other wise the result will not fit in a Target + // (without overflow) + let sixteen = b.constant(F::from_canonical_u8(16)); + let check = less_than(b, self.pointer, sixteen, 5); + b.connect(check.target, t.target); + + // We have to check if the first two nibbles sum to precisely 128, we should + // always have at least two nibbles otherwise the key was empty. + let first_nibbles = &self.key.arr[..2]; + let tmp = b.mul(first_nibbles[0], sixteen); + let tmp = b.add(tmp, first_nibbles[1]); + + let one_two_eight = b.constant(F::from_canonical_u8(128)); + + let first_byte_128 = b.is_equal(one_two_eight, tmp); + + // If the pointer is 1 then we should make sure we return zero as the value + let pointer_is_one = b.is_equal(self.pointer, one); + let byte_selector = b.and(pointer_is_one, first_byte_128); + + let initial = b.select(byte_selector, zero, tmp); + + let combiner = b.constant(F::from_canonical_u32(1u32 << 8)); + // We fold over the remaining nibbles of the key + self.key + .arr + .chunks(2) + .enumerate() + .skip(1) + .fold(initial, |acc, (i, chunk)| { + // First we multiply the accumulator by 2^8, then recreate the current byte by multiplying the large_nibble by 16 and adding the current small_nibble + let tmp = b.mul(chunk[0], sixteen); + let tmp = b.add(tmp, chunk[1]); + + let tmp_acc = b.mul(acc, combiner); + let tmp = b.add(tmp_acc, tmp); + + // Convert the index to a target + let index = b.constant(F::from_canonical_usize(2 * i)); + + // If the index is lees than the pointer we return tmp, otherwise we return acc. + let selector = less_than_or_equal_to_unsafe(b, index, self.pointer, 8); + b.select(selector, tmp, acc) + }) + } } diff --git a/mp2-common/src/mpt_sequential/leaf_or_extension.rs b/mp2-common/src/mpt_sequential/leaf_or_extension.rs index 96b3b6355..385eaec92 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 hash of this node + 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..4f68c7c3c 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,11 @@ 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, MPTKeyWire, MPTKeyWireGeneric}; +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 +46,12 @@ 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. We choose this value as any larger would +/// result in an additional keccak permutation, thus increasing the circuit size. +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 +64,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 +84,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 +147,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 +188,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 +203,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]; @@ -236,10 +258,10 @@ where /// Assign the nodes to the wires. The reason we have the output wires /// as well is due to the keccak circuit that requires some special assignement /// from the raw vectors. - pub fn assign_wires, const D: usize>( + pub fn assign, 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 +324,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 +339,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 +353,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 branch. +/// * 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 +508,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 +553,7 @@ mod test { [(); HASH_LEN / 4]:, { type Wires = ( - InputWires, + InputWires, OutputWires, Array, // root Array, // value @@ -497,7 +589,7 @@ mod test { } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - self.c.assign_wires(pw, &wires.0, &wires.1).unwrap(); + self.c.assign(pw, &wires.0, &wires.1).unwrap(); wires.2.assign( pw, &create_array(|i| F::from_canonical_u8(self.exp_root[i])), @@ -531,12 +623,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 +640,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 +665,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 +705,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 +765,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 +853,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 +966,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 +1038,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/mpt_sequential/utils.rs b/mp2-common/src/mpt_sequential/utils.rs index a7c0be269..99da94b8c 100644 --- a/mp2-common/src/mpt_sequential/utils.rs +++ b/mp2-common/src/mpt_sequential/utils.rs @@ -57,13 +57,57 @@ pub fn left_pad_leaf_value< let value_len_80 = b.sub(value[0], byte_80); let value_len = b.select(is_single_byte, one, value_len_80); let offset = b.select(is_single_byte, zero, one); - value - // WARNING: this is a hack to avoid another const generic but - // what we should really do here is extract RLP_VALUE_LEN-1 because we - // consider 1 extra byte for the RLP header always (which may or may not exist) - .extract_array::(b, offset) - .into_vec(value_len) - .normalize_left::<_, _, PADDED_LEN>(b) + + // So the value is just in the first byte if is_single_byte is true + // Hence the first index we take is offset + value_len - 1 and then we continue until we hit + // offset + let tmp = b.add(offset, value_len); + let start = b.sub(tmp, one); + + // In the case that prefix is exactly 128 the value is precisely zero so we should not extract anything + let mut last_byte_found = b.is_equal(value_len_80, zero); + + let mut result_bytes = [zero; PADDED_LEN]; + + // Need the length to be a power of two + let ram_value = if !value.arr.len().is_power_of_two() { + let new_size = value.arr.len().next_power_of_two(); + let mut value_vec = value.arr.to_vec(); + value_vec.resize(new_size, zero); + value_vec + } else { + value.arr.to_vec() + }; + + result_bytes + .iter_mut() + .rev() + .enumerate() + .for_each(|(i, out_byte)| { + let index = b.constant(F::from_canonical_usize(i)); + let inner_offset = b.sub(start, index); + // Set to 0 if found the last byte. + let inner_offset = b.select(last_byte_found, zero, inner_offset); + + // Since VALUE_LEN is a constant that is determined at compile time this conditional won't + // cause any issues with the circuit. + let byte: Target = if RLP_VALUE_LEN <= 64 { + b.random_access(inner_offset, ram_value.clone()) + } else { + value.random_access_large_array(b, inner_offset) + }; + + // Now if `last_byte_found` is true we add zero, otherwise add `byte` + let to_add = b.select(last_byte_found, zero, byte); + + *out_byte = b.add(*out_byte, to_add); + // is_last_byte = offset == last_byte_offset + let is_last_byte = b.is_equal(inner_offset, offset); + // last_byte_found |= is_last_byte + last_byte_found = b.or(last_byte_found, is_last_byte); + }); + + Array::::from_array(result_bytes) } pub fn visit_proof(proof: &[Vec]) { diff --git a/mp2-common/src/poseidon.rs b/mp2-common/src/poseidon.rs index b64755fd6..cf2a84eda 100644 --- a/mp2-common/src/poseidon.rs +++ b/mp2-common/src/poseidon.rs @@ -35,6 +35,9 @@ pub type H = >::Hasher; pub type P = >::AlgebraicPermutation; pub type HashPermutation = >::Permutation; +/// The result of hash to integer has 4 Uint32 (128 bits). +pub const HASH_TO_INT_LEN: usize = 4; + /// The flattened length of Poseidon hash, each original field is splitted from an /// Uint64 into two Uint32. pub const FLATTEN_POSEIDON_LEN: usize = NUM_HASH_OUT_ELTS * 2; diff --git a/mp2-common/src/proof.rs b/mp2-common/src/proof.rs index 4f9154f05..f9705f2f8 100644 --- a/mp2-common/src/proof.rs +++ b/mp2-common/src/proof.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// The generic type `T` allows to specify the /// specific inputs of each circuits besides the proofs that need to be /// recursively verified, while the proofs are serialized in byte format. -#[derive(Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct ProofInputSerialized { pub input: T, pub serialized_child_proofs: Vec>, diff --git a/mp2-common/src/rlp.rs b/mp2-common/src/rlp.rs index 741f9e38e..ed41277ea 100644 --- a/mp2-common/src/rlp.rs +++ b/mp2-common/src/rlp.rs @@ -1,6 +1,6 @@ -use crate::array::{Array, VectorWire}; +use crate::array::{extract_value, Array, VectorWire}; -use crate::utils::{greater_than_or_equal_to_unsafe, less_than, less_than_unsafe, num_to_bits}; +use crate::utils::{less_than, less_than_unsafe, num_to_bits}; use plonky2::field::extension::Extendable; use plonky2::hash::hash_types::RichField; use plonky2::iop::target::{BoolTarget, Target}; @@ -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); @@ -138,54 +146,29 @@ pub fn decode_compact_encoding, const D: usize, con cond, ) } + // Returns the length from the RLP prefix in case of long string or long list // data is the full data starting from the "type" byte of RLP encoding // data length needs to be a power of 2 // non power of 2 lengths are padded leading zeros pub fn data_len, const D: usize>( b: &mut CircuitBuilder, - data: &[Target], + data: &[Target; MAX_LEN_BYTES], len_of_len: Target, - offset: Target, ) -> Target { - let mut res = b.zero(); - let one = b.one(); + let zero = b.zero(); + let mut last_byte_found = b._false(); let const_256 = b.constant(F::from_canonical_u64(256)); - - for i in 0..MAX_LEN_BYTES { - let i_tgt = b.constant(F::from_canonical_u8(i as u8)); - // make sure we don't read out more than the actual len - let len_of_len_pred = less_than_unsafe(b, i_tgt, len_of_len, 8); - // this part offset i to read from the array - let i_offset = b.add(i_tgt, offset); - // i+1 because first byte is the RLP type - let i_plus_1 = b.add(i_offset, one); - let item = quin_selector(b, data, i_plus_1); - - // shift result by one byte - let multiplicand = b.mul(const_256, res); - // res += 2^i * arr[i+1] only if we're in right range - let sum = b.add(multiplicand, item); - let multiplicand_2 = b.mul(sum, len_of_len_pred.target); - - let not_len_of_len_pred_target = b.not(len_of_len_pred); - let multiplicand_3 = b.mul(not_len_of_len_pred_target.target, res); - // res = (2^i * arr[i+1]) * (i < len_len) + res * (i >= len_len) - res = b.add(multiplicand_2, multiplicand_3); - } - - res -} -// We read the RLP header but knowing it is a value that is always <55bytes long -// we can hardcode the type of RLP header it is and directly get the real number len -// in this case, the header marker is 0x80 that we can directly take out from first byte -pub fn short_string_len, const D: usize>( - b: &mut CircuitBuilder, - header: &Target, -) -> Target { - let byte_80 = b.constant(F::from_canonical_usize(128)); - b.sub(*header, byte_80) + data.iter().enumerate().fold(zero, |acc, (j, &byte)| { + let j_tgt = b.constant(F::from_canonical_usize(j)); + + let at_end = b.is_equal(j_tgt, len_of_len); + last_byte_found = b.or(last_byte_found, at_end); + let sum = b.mul_add(const_256, acc, byte); + b.select(last_byte_found, acc, sum) + }) } + /// It returns the RLP header information starting at data[offset]. The header.offset /// is absolute from the 0-index of data (not from the `offset` index) pub fn decode_header, const D: usize>( @@ -196,7 +179,7 @@ pub fn decode_header, const D: usize>( let one = b.one(); let zero = b.zero(); - let prefix = quin_selector(b, data, offset); + let prefix = extract_value(b, data, offset); let byte_80 = b.constant(F::from_canonical_usize(128)); let byte_b7 = b.constant(F::from_canonical_usize(183)); @@ -205,10 +188,12 @@ pub fn decode_header, const D: usize>( let byte_f7 = b.constant(F::from_canonical_usize(247)); let byte_f8 = b.constant(F::from_canonical_usize(248)); + // We check less than or equal to 128 because the single byte 0 is encoded as a string of length zero in RLP let prefix_less_0x80 = less_than(b, prefix, byte_80, 8); - let prefix_less_0xb8 = less_than(b, prefix, byte_b8, 8); - let prefix_less_0xc0 = less_than(b, prefix, byte_c0, 8); - let prefix_less_0xf8 = less_than(b, prefix, byte_f8, 8); + // Prefix has been range checked now so we can use unsafe variant + let prefix_less_0xb8 = less_than_unsafe(b, prefix, byte_b8, 8); + let prefix_less_0xc0 = less_than_unsafe(b, prefix, byte_c0, 8); + let prefix_less_0xf8 = less_than_unsafe(b, prefix, byte_f8, 8); // This part determines at which offset should we read the data let prefix_plus_one = b.add(prefix, one); @@ -229,19 +214,27 @@ pub fn decode_header, const D: usize>( // i.e. if it's a single byte value, no offset we directly read value let offset_data = b._if(prefix_less_0x80, zero, select_3); - // read the lenght encoded depending on the type + // read the length encoded depending on the type + // To avoid repeatedly indexing into the data slice we extract MAX_LEN_BYTES from `offset` + 1 + // as offset includes the type encoding + let poss_length_bytes: [Target; MAX_LEN_BYTES] = std::array::from_fn(|i| { + let index = b.add_const(offset, F::from_canonical_usize(i + 1)); + extract_value(b, data, index) + }); let prefix_minus_f7 = b.sub(prefix, byte_f7); - let long_list_len = data_len(b, data, prefix_minus_f7, offset); + + let long_list_len = data_len(b, &poss_length_bytes, prefix_minus_f7); + // let long_list_len = data_len(b, data, prefix_minus_f7, offset); let short_list_len = b.sub(prefix, byte_c0); let select_1 = b._if(prefix_less_0xf8, short_list_len, long_list_len); let prefix_minus_b7 = b.sub(prefix, byte_b7); - let long_str_len = data_len(b, data, prefix_minus_b7, offset); + let long_str_len = data_len(b, &poss_length_bytes, prefix_minus_b7); let select_2 = b._if(prefix_less_0xc0, long_str_len, select_1); let short_str_len = b.sub(prefix, byte_80); let select_3 = b._if(prefix_less_0xb8, short_str_len, select_2); let len = b._if(prefix_less_0x80, one, select_3); - let data_type = greater_than_or_equal_to_unsafe(b, prefix, byte_c0, 8).target; + let data_type = b.not(prefix_less_0xc0).target; let final_offset = b.add(offset, offset_data); RlpHeader { @@ -256,6 +249,7 @@ pub fn decode_header, const D: usize>( /// The offsets decoded in the returned list are starting from the 0-index of `data` /// not from the `offset` index. /// If N is less than the actual number of items, then the number of fields will be N. +/// This is achieved by using the list header to pad the missing entries. /// Otherwise, the number of fields returned is determined by the header the RLP list. pub fn decode_fixed_list, const D: usize, const N: usize>( b: &mut CircuitBuilder, @@ -279,13 +273,10 @@ pub fn decode_fixed_list, const D: usize, const N: for i in 0..N { // stop when you've looked at exactly the same number of bytes than // the RLP list header indicates - let at_the_end = b.is_equal(offset, end_idx); - // offset always equals offset after we've reached end_idx so before_the_end - // is only true when we haven't reached the end yet - let before_the_end = b.not(at_the_end); - + let before_the_end = b.is_not_equal(offset, end_idx); + let offset_to_use = b.select(before_the_end, offset, zero); // read the header starting from the offset - let header = decode_header(b, data, offset); + let header = decode_header(b, data, offset_to_use); let new_offset = b.add(header.offset, header.len); dec_off[i] = header.offset; @@ -295,8 +286,7 @@ pub fn decode_fixed_list, const D: usize, const N: // move offset to the next field in the list // updates offset such that is is either < end_idx or after that // always equals to end_idx - let diff = b.sub(new_offset, offset); - offset = b.mul_add(before_the_end.target, diff, offset); + offset = b.select(before_the_end, new_offset, offset); num_fields = b.add(num_fields, before_the_end.target); } @@ -308,34 +298,6 @@ pub fn decode_fixed_list, const D: usize, const N: } } -/// Returns an element of the array at index n -/// TODO: replace with random_access from plonky2 and compare constraints -pub fn quin_selector, const D: usize>( - b: &mut CircuitBuilder, - arr: &[Target], - n: Target, -) -> Target { - let mut nums: Vec = vec![]; - - for (i, el) in arr.iter().enumerate() { - let i_target = b.constant(F::from_canonical_usize(i)); - let is_eq = b.is_equal(i_target, n); - // (i == n (idx) ) * element - let product = b.mul(is_eq.target, *el); - nums.push(product); - } - // SUM_i (i == n (idx) ) * element - // -> sum = element - calculate_total(b, &nums) -} - -fn calculate_total, const D: usize>( - b: &mut CircuitBuilder, - arr: &[Target], -) -> Target { - b.add_many(arr) -} - #[cfg(test)] mod tests { use std::array::from_fn as create_array; @@ -355,13 +317,11 @@ 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}; - use super::quin_selector; - /// Returns an array of length `M` from the array `arr` starting at index `offset` pub fn extract_array, const D: usize, const M: usize>( b: &mut CircuitBuilder, @@ -382,7 +342,7 @@ mod tests { let j = b.mul(lt.target, i_plus_n_target); // out_val = arr[((i+n)<=n+M) * (i+n)] - *out_val = quin_selector(b, arr, j); + *out_val = super::extract_value(b, arr, j); } out @@ -548,7 +508,11 @@ mod tests { let len_of_len = builder.constant(F::from_canonical_u64(2)); let zero = builder.zero(); - let res = super::data_len(&mut builder, &data, len_of_len, zero); + let poss_length_bytes: [Target; MAX_LEN_BYTES] = std::array::from_fn(|i| { + let index = builder.add_const(zero, F::from_canonical_usize(i + 1)); + super::extract_value(&mut builder, &data, index) + }); + let res = super::data_len(&mut builder, &poss_length_bytes, len_of_len); builder.connect(res, ret_target); builder.register_public_inputs(&data); @@ -792,7 +756,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-common/src/serialization/circuit_data_serialization.rs b/mp2-common/src/serialization/circuit_data_serialization.rs index 07895189a..44b28202f 100644 --- a/mp2-common/src/serialization/circuit_data_serialization.rs +++ b/mp2-common/src/serialization/circuit_data_serialization.rs @@ -1,5 +1,7 @@ use std::marker::PhantomData; +use plonky2::field::extension::quintic::QuinticExtension; +use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::plonk::circuit_data::VerifierCircuitData; use plonky2::{ field::extension::Extendable, @@ -61,7 +63,7 @@ use plonky2_crypto::{ }, }; use plonky2_ecgfp5::{ - curve::base_field::InverseOrZero, + curve::{base_field::InverseOrZero, curve::Point}, gadgets::base_field::{QuinticQuotientGenerator, QuinticSqrtGenerator}, }; use poseidon2_plonky2::poseidon2_gate::{Poseidon2Gate, Poseidon2Generator}; @@ -71,6 +73,29 @@ use crate::u256::UInt256DivGenerator; use super::{FromBytes, SerializationError, ToBytes}; +impl ToBytes for Point { + fn to_bytes(&self) -> Vec { + let mut buffer = Vec::new(); + let encoded = self.encode(); + buffer + .write_field_ext::(encoded) + .expect("Writing to a byte-vector cannot fail."); + buffer + } +} + +impl FromBytes for Point { + fn from_bytes(bytes: &[u8]) -> Result { + let mut buffer = Buffer::new(bytes); + let compact: QuinticExtension = + buffer.read_field_ext::()?; + + Point::decode(compact).ok_or(SerializationError( + "Could not decode quintic extension to point".to_string(), + )) + } +} + impl> ToBytes for MerkleTree { fn to_bytes(&self) -> Vec { let mut buffer = Vec::new(); @@ -367,4 +392,20 @@ pub(super) mod tests { assert_eq!(decoded_mt.0, mt.0); } + + #[test] + fn test_point_serialization() { + let point = Point::rand(); + + #[derive(Serialize, Deserialize)] + struct TestPointSerialization( + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] Point, + ); + + let p = TestPointSerialization(point); + let encoded = bincode::serialize(&p).unwrap(); + let decoded_p: TestPointSerialization = bincode::deserialize(&encoded).unwrap(); + + assert_eq!(decoded_p.0, point); + } } diff --git a/mp2-common/src/storage_key.rs b/mp2-common/src/storage_key.rs index 942bc592d..b12ca87fd 100644 --- a/mp2-common/src/storage_key.rs +++ b/mp2-common/src/storage_key.rs @@ -7,11 +7,15 @@ use crate::{ array::{Array, Vector, VectorWire}, eth::{left_pad32, StorageSlot}, - keccak::{ByteKeccakWires, InputData, KeccakCircuit, KeccakWires, HASH_LEN}, + keccak::{ByteKeccakWires, InputData, KeccakCircuit, KeccakWires, OutputByteHash, HASH_LEN}, mpt_sequential::{MPTKeyWire, PAD_LEN}, + serialization::circuit_data_serialization::SerializableRichField, types::{MAPPING_KEY_LEN, MAPPING_LEAF_VALUE_LEN}, - utils::keccak256, + u256::{CircuitBuilderU256, UInt256Target, NUM_LIMBS}, + utils::{keccak256, Endianness, PackerTarget}, }; +use alloy::primitives::{B256, U256}; +use itertools::Itertools; use plonky2::{ field::extension::Extendable, hash::hash_types::RichField, @@ -21,7 +25,9 @@ use plonky2::{ }, plonk::circuit_builder::CircuitBuilder, }; +use plonky2_crypto::u32::arithmetic_u32::{CircuitBuilderU32, U32Target}; use serde::{Deserialize, Serialize}; +use std::{array, iter::repeat}; /// One input element length to Keccak const INPUT_ELEMENT_LEN: usize = 32; @@ -30,42 +36,68 @@ const INPUT_TUPLE_LEN: usize = 2 * INPUT_ELEMENT_LEN; /// The whole padded length for the inputs const INPUT_PADDED_LEN: usize = PAD_LEN(INPUT_TUPLE_LEN); +/// Wires associated with the MPT key from the Keccak computation of location +/// It's used for mapping slot of single value (no EVM offset). #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -/// Wires associated with the MPT key from the keccak computation of location pub struct KeccakMPTWires { - /// Actual keccak wires created for the computation of the "location" for - /// the storage slot + /// Actual Keccak wires created for the computation of the base for the storage slot pub keccak_location: ByteKeccakWires, - /// Actual keccak wires created for the computation of the final MPT key - /// from the location. THIS is the one to use to look up a key in the + /// Actual Keccak wires created for the computation of the final MPT key + /// from the location. this is the one to use to look up a key in the /// associated MPT trie. pub keccak_mpt_key: KeccakWires<{ PAD_LEN(HASH_LEN) }>, - /// The MPT key derived in circuit from the storage slot, in NIBBLES + /// The MPT key derived in circuit from the storage slot in nibbles /// TODO: it represents the same information as "exp" but in nibbles. /// It doesn't need to be assigned, but is used in the higher level circuits pub mpt_key: MPTKeyWire, } +/// Wires associated with the MPT key from the Keccak computation of location +/// It's used for mapping slot of Struct (has EVM offset). +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct KeccakStructMPTWires { + /// Keccak base information + pub base: KeccakMPTWires, + /// The location in bytes + /// It's used to check if the packed location is correct. + pub location_bytes: OutputByteHash, +} + +// The Keccak MPT computation +// It's used for mapping slot of single value (no EVM offset). struct KeccakMPT; impl KeccakMPT { + /// Build the Keccak MPT with no offset (offset = 0). fn build, const D: usize>( b: &mut CircuitBuilder, inputs: VectorWire, ) -> KeccakMPTWires { let keccak_location = KeccakCircuit::<{ INPUT_PADDED_LEN }>::hash_to_bytes(b, &inputs); - // keccak(location) - take the output and copy it in a slice large - // enough for padding. - let mut padded_location = [b.zero(); PAD_LEN(HASH_LEN)]; - padded_location[0..HASH_LEN].copy_from_slice(&keccak_location.output.arr); + let location_bytes = keccak_location.output.arr; + + Self::build_location(b, keccak_location, location_bytes) + } + + fn build_location, const D: usize>( + b: &mut CircuitBuilder, + keccak_location: ByteKeccakWires, + location_bytes: [Target; HASH_LEN], + ) -> KeccakMPTWires { + // keccak(location) + let zero = b.zero(); + let arr = location_bytes + .into_iter() + .chain(repeat(zero).take(PAD_LEN(HASH_LEN) - HASH_LEN)) + .collect_vec() + .try_into() + .unwrap(); let hash_len = b.constant(F::from_canonical_usize(HASH_LEN)); let keccak_mpt_key = KeccakCircuit::<{ PAD_LEN(HASH_LEN) }>::hash_vector( b, &VectorWire { real_len: hash_len, - arr: Array { - arr: padded_location, - }, + arr: Array { arr }, }, ); @@ -85,9 +117,9 @@ impl KeccakMPT { pw: &mut PartialWitness, wires: &KeccakMPTWires, inputs: Vec, - location: Vec, + location: [u8; HASH_LEN], ) { - // Assign the keccak necessary values for keccak_location. + // Assign the Keccak necessary values for base. KeccakCircuit::<{ INPUT_PADDED_LEN }>::assign_byte_keccak( pw, &wires.keccak_location, @@ -96,8 +128,8 @@ impl KeccakMPT { &Vector::from_vec(&inputs).expect("Can't create vector input for keccak_location"), ), ); - - // Assign the keccak necessary values for keccak_mpt = H(keccak_location). + // Assign the keccak necessary values for Keccak MPT: + // keccak(location) KeccakCircuit::<{ PAD_LEN(HASH_LEN) }>::assign( pw, &wires.keccak_mpt_key, @@ -108,6 +140,64 @@ impl KeccakMPT { } } +// The Keccak Struct MPT computation +// It's used for mapping slot of Struct (has EVM offset). +struct KeccakStructMPT; + +impl KeccakStructMPT { + /// Build the Keccak MPT for Struct (has EVM offset). + fn build + Extendable, const D: usize>( + b: &mut CircuitBuilder, + inputs: VectorWire, + offset: Target, + ) -> KeccakStructMPTWires { + let location_bytes = OutputByteHash::new(b); + + // location = keccak(inputs) + offset + let keccak_base = KeccakCircuit::<{ INPUT_PADDED_LEN }>::hash_to_bytes(b, &inputs); + // Do range-check on the output, since these bytes are converted for Uint256 computation + // (not fed as input to another Keccak directly). + keccak_base.output.assert_bytes(b); + let base = keccak_base.output.arr.pack(b, Endianness::Big); + let base = UInt256Target::new_from_be_target_limbs(&base).unwrap(); + let offset = UInt256Target::new_from_target_unsafe(b, offset); + let (packed_location, overflow) = b.add_u256(&base, &offset); + b.assert_zero(overflow.0); + + // Ensure the packed location is correct. + location_bytes + .pack(b, Endianness::Big) + .enforce_equal(b, &packed_location.into()); + + let base = KeccakMPT::build_location(b, keccak_base, location_bytes.arr); + + KeccakStructMPTWires { + base, + location_bytes, + } + } + + fn assign( + pw: &mut PartialWitness, + wires: &KeccakStructMPTWires, + inputs: Vec, + base: [u8; HASH_LEN], + offset: u32, + ) { + // location = keccak_base + offset + let location = U256::from_be_bytes(base) + .checked_add(U256::from(offset)) + .expect("Keccak base plus offset is overflow for location computation"); + let location = location.to_be_bytes(); + + KeccakMPT::assign(pw, &wires.base, inputs, location); + + wires + .location_bytes + .assign(pw, &location.map(F::from_canonical_u8)); + } +} + /// Circuit gadget that proves the correct derivation of a MPT key from a simple /// storage slot. /// Deriving a MPT key from simple slot is done like: @@ -135,7 +225,8 @@ impl From for SimpleSlot { } } -/// Wires associated with the MPT key derivation logic of simple storage slot +/// Wires associated with the MPT key derivation logic of simple storage slot of single value +// It's used for simple slot of single value (no EVM offset). #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct SimpleSlotWires { /// Simple storage slot which is assumed to fit in a single byte @@ -147,34 +238,85 @@ pub struct SimpleSlotWires { pub mpt_key: MPTKeyWire, } +/// Wires associated with the MPT key derivation logic of simple storage slot of Struct +// It's used for simple slot of Struct (has EVM offset). +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct SimpleStructSlotWires { + /// Slot base information + pub base: SimpleSlotWires, + /// The location in bytes + /// It's used to check if the packed location is correct. + pub location_bytes: OutputByteHash, +} + // TODO: refactor to extract common functions with MappingSlot. impl SimpleSlot { - /// Derive the MPT key in circuit according to simple storage slot. + /// Derive the MPT key in circuit according to simple storage slot of single value. /// /// Remember the rules to get the MPT key is as follow: - /// * location = pad32(slot) - /// * mpt_key = keccak256(location) - /// + /// location = pad32(slot) + /// mpt_key = keccak256(location) /// Note the simple slot wire and the contract address wires are NOT range /// checked, because they are expected to be given by the verifier. /// /// If that assumption is not true, then the caller should call /// `b.range_check(slot, 8)` to ensure its byteness. - pub fn build, const D: usize>( + pub fn build_single, const D: usize>( b: &mut CircuitBuilder, ) -> SimpleSlotWires { + let zero = b.zero(); let slot = b.add_virtual_target(); + let mut location = [zero; INPUT_ELEMENT_LEN]; + location[INPUT_ELEMENT_LEN - 1] = slot; - // keccak(left_pad32(slot)) - let mut arr = [b.zero(); INPUT_PADDED_LEN]; - arr[INPUT_ELEMENT_LEN - 1] = slot; - let inputs = VectorWire:: { - real_len: b.constant(F::from_canonical_usize(INPUT_ELEMENT_LEN)), - arr: Array { arr }, - }; - // Build for keccak MPT. - let keccak_mpt = KeccakCircuit::::hash_vector(b, &inputs); - // MPT KEY is expressed in nibbles + Self::build_location(b, slot, location) + } + + /// Derive the MPT key with a specified offset in circuit according to simple + /// storage slot of Struct. + /// + /// The rules to get the MPT key with offset is as follow: + /// location = left_pad32(slot) + offset + /// mpt_key = keccak256(location) + /// Note the simple slot wire and the contract address wires are NOT range + /// checked, because they are expected to be given by the verifier. + /// If that assumption is not true, then the caller should call + /// `b.range_check(slot, 8)` to ensure its byteness. + pub fn build_struct, const D: usize>( + b: &mut CircuitBuilder, + offset: Target, + ) -> SimpleStructSlotWires { + let zero = b.zero(); + let slot = b.add_virtual_target(); + // We assume the offset and addition must be within the range of Uint32: + // addition = offset + slot + let (addition, overflow) = b.add_u32(U32Target(offset), U32Target(slot)); + b.assert_zero(overflow.0); + let mut packed_location = [U32Target(zero); NUM_LIMBS]; + packed_location[NUM_LIMBS - 1] = addition; + + // Ensure the packed location is correct. + let location_bytes = OutputByteHash::new(b); + location_bytes + .pack(b, Endianness::Big) + .enforce_equal(b, &packed_location.into()); + + let base = Self::build_location(b, slot, location_bytes.arr); + + SimpleStructSlotWires { + base, + location_bytes, + } + } + + pub fn build_location, const D: usize>( + b: &mut CircuitBuilder, + slot: Target, + location: [Target; INPUT_ELEMENT_LEN], + ) -> SimpleSlotWires { + // Build the Keccak MPT. + let keccak_mpt = Self::build_keccak_mpt(b, location); + // Transform the MPT key to nibbles. let mpt_key = MPTKeyWire::init_from_u32_targets(b, &keccak_mpt.output_array); SimpleSlotWires { @@ -184,25 +326,81 @@ impl SimpleSlot { } } - pub fn assign(&self, pw: &mut PartialWitness, wires: &SimpleSlotWires) { - match self.0 { - StorageSlot::Simple(slot) => { - // safe downcasting because it's assumed to be u8 in constructor - pw.set_target(wires.slot, F::from_canonical_u8(slot as u8)) - } + /// Build the Keccak MPT. + fn build_keccak_mpt, const D: usize>( + b: &mut CircuitBuilder, + location: [Target; INPUT_ELEMENT_LEN], + ) -> KeccakWires { + // keccak(location) + let zero = b.zero(); + let arr = location + .into_iter() + .chain(repeat(zero).take(INPUT_PADDED_LEN - INPUT_ELEMENT_LEN)) + .collect_vec() + .try_into() + .unwrap(); + let inputs = VectorWire:: { + real_len: b.constant(F::from_canonical_usize(INPUT_ELEMENT_LEN)), + arr: Array { arr }, + }; + // Build for keccak MPT. + KeccakCircuit::::hash_vector(b, &inputs) + } + + pub fn assign_single(&self, pw: &mut PartialWitness, wires: &SimpleSlotWires) { + self.assign_with_offset(pw, wires, 0); + } + + pub fn assign_struct( + &self, + pw: &mut PartialWitness, + wires: &SimpleStructSlotWires, + offset: u32, + ) { + let location_bytes = self.assign_with_offset(pw, &wires.base, offset); + + // Assign the location bytes. + let location_bytes = location_bytes + .into_iter() + .map(F::from_canonical_u8) + .collect_vec() + .try_into() + .unwrap(); + wires.location_bytes.assign(pw, &location_bytes); + } + + // Assign with a specified offset. + // The offset could be zero for a single value. + // Return the location bytes as the final input. + fn assign_with_offset( + &self, + pw: &mut PartialWitness, + wires: &SimpleSlotWires, + offset: u32, + ) -> Vec { + let slot = match self.0 { + // Safe downcasting because it's assumed to be u8 in constructor. + StorageSlot::Simple(slot) => slot as u8, _ => panic!("Invalid storage slot type"), // should not happen using constructor - } - let input = self.0.location().as_slice().to_vec(); + }; + pw.set_target(wires.slot, F::from_canonical_u8(slot)); + // Should be same with the slot number if offset is zero. + let location = offset + .checked_add(slot.into()) + .expect("Simple slot plus offset is overflow"); + let location_bytes = B256::left_padding_from(&location.to_be_bytes()).to_vec(); KeccakCircuit::assign( pw, &wires.keccak_mpt, - // unwrap safe because input always fixed 32 bytes - &InputData::Assigned(&Vector::from_vec(&input).unwrap()), + // Unwrap safe because input always fixed 32 bytes. + &InputData::Assigned(&Vector::from_vec(&location_bytes).unwrap()), ); + + location_bytes } } -/// Circuit gadget that proves the correct derivation of a MPT key from a given mapping slot and storage slot. +/// Circuit gadget that proves the correct derivation of a MPT key from a given mapping slot. /// Deriving a MPT key from mapping slot is done like: /// 1. location = keccak(left_pad32(key), left_pad32(slot)) /// 2. mpt_key = keccak(location) @@ -222,10 +420,9 @@ impl MappingSlot { } } -/// Contains the wires associated with the storage slot's mpt key -/// derivation logic. -/// NOTE: currently specific only for mapping slots. -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +/// Contains the wires associated with the storage slot's MPT key derivation logic. +/// It's used for mapping slot of single value (no EVM offset). +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct MappingSlotWires { /// "input" mapping key which is maxed out at 32 bytes pub mapping_key: Array, @@ -235,19 +432,53 @@ pub struct MappingSlotWires { pub keccak_mpt: KeccakMPTWires, } +/// Contains the wires associated with the storage slot's MPT key derivation logic. +/// It's used for mapping slot of Struct (has EVM offset). +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MappingStructSlotWires { + /// "input" mapping key which is maxed out at 32 bytes + pub mapping_key: Array, + /// "input" mapping slot which is assumed to fit in a single byte + pub mapping_slot: Target, + /// Wires associated with the MPT key + pub keccak_mpt: KeccakStructMPTWires, +} + +/// Contains the wires associated with the MPT key derivation logic of mappings where the value +/// stored in each mapping entry is another mapping (referred to as mapping of mappings). +/// +/// In this case, we refer to the key for the first-layer mapping entry as the outer key, +/// while the key for the mapping stored in the entry mapping is referred to as inner key. +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MappingOfMappingsSlotWires { + /// Mapping slot number which is assumed to fit in a single byte + pub mapping_slot: Target, + /// 32 bytes value of the key associated to the node in the mapping + pub outer_key: Array, + /// 32 bytes value of the key associated to the second-layer mapping + /// We are extracting the mapping entry as `mapping[outer_key][inner_key]`. + pub inner_key: Array, + /// Keccak computed result referred to the inner mapping slot as + /// `keccak256(left_pad32(outer_key) || left_pad32(mapping_slot))` + pub inner_mapping_slot: ByteKeccakWires, + /// Wires associated with the MPT key + pub keccak_mpt: KeccakStructMPTWires, +} + /// Size of the input to the digest and hash function pub(crate) const MAPPING_INPUT_TOTAL_LEN: usize = MAPPING_KEY_LEN + MAPPING_LEAF_VALUE_LEN; /// Value but with the padding taken into account. const MAPPING_INPUT_PADDED_LEN: usize = PAD_LEN(MAPPING_INPUT_TOTAL_LEN); impl MappingSlot { - /// Derives the mpt_key in circuit according to which type of storage slot + /// Derives the mpt_key in circuit according to which type of storage slot of single value. + /// /// Remember the rules to get the mpt key is as follow: - /// * location = keccak256(pad32(mapping_key), pad32(mapping_slot)) - /// * mpt_key = keccak256(location) - /// Note the mapping slot wire is NOT range checked, because it is expected to - /// be given by the verifier. If that assumption is not true, then the caller - /// should call `b.range_check(mapping_slot,8)` to ensure its byteness. - pub fn mpt_key, const D: usize>( + /// location = keccak256(pad32(mapping_key), pad32(mapping_slot)) + /// mpt_key = keccak256(location) + /// Note the mapping slot wire is NOT range checked, because it is expected to + /// be given by the verifier. If that assumption is not true, then the caller + /// should call `b.range_check(mapping_slot,8)` to ensure its byteness. + pub fn build_single, const D: usize>( b: &mut CircuitBuilder, ) -> MappingSlotWires { let mapping_key = Array::::new(b); @@ -272,165 +503,471 @@ impl MappingSlot { keccak_mpt, } } - pub fn assign(&self, pw: &mut PartialWitness, wires: &MappingSlotWires) { - // first assign the "inputs" - let padded_mkey = left_pad32(&self.mapping_key); - let padded_slot = left_pad32(&[self.mapping_slot]); - // the "padding" is done in circuit for slot - pw.set_target(wires.mapping_slot, F::from_canonical_u8(self.mapping_slot)); - // already give 32 bytes for the mapping key - wires.mapping_key.assign_bytes(pw, &padded_mkey); - // Then compute the entire expected array to derive the mpt key - // H ( pad32(mapping_key), pad32(mapping_slot)) - let inputs = padded_mkey - .into_iter() - .chain(padded_slot) - .collect::>(); - // then compute the expected resulting hash for mpt key derivation. - let location = keccak256(&inputs); + + /// Derive the MPT key with a specified offset in circuit according to mapping slot of Struct. + /// + /// The rules to get the mpt key with offset is as follow: + /// location = keccak256(pad32(mapping_key), pad32(mapping_slot)) + offset + /// mpt_key = keccak256(location) + /// Note the mapping slot wire is NOT range checked, because it is expected to + /// be given by the verifier. If that assumption is not true, then the caller + /// should call `b.range_check(mapping_slot, 8)` to ensure its byteness. + pub fn build_struct + Extendable, const D: usize>( + b: &mut CircuitBuilder, + offset: Target, + ) -> MappingStructSlotWires { + let mapping_key = Array::::new(b); + mapping_key.assert_bytes(b); + let mapping_slot = b.add_virtual_target(); + let mut input = [b.zero(); MAPPING_INPUT_PADDED_LEN]; + input[0..MAPPING_KEY_LEN].copy_from_slice(&mapping_key.arr); + input[2 * MAPPING_KEY_LEN - 1] = mapping_slot; + + // keccak(left_pad32(mapping_key), left_pad32(mapping_slot)) + let inputs = VectorWire:: { + real_len: b.constant(F::from_canonical_usize(MAPPING_INPUT_TOTAL_LEN)), + arr: Array { arr: input }, + }; + // Build for keccak MPT. + // location = keccak(inputs) + offset + let keccak_mpt = KeccakStructMPT::build(b, inputs, offset); + + MappingStructSlotWires { + mapping_key, + mapping_slot, + keccak_mpt, + } + } + + /// Derive the MPT key with an inner mapping key and offset in circuit according to + /// mapping slot. + /// + /// The rules to get the mpt key with offset is as follow: + /// inner_mapping_slot = keccak256(left_pad32(outer_key) || left_pad32(mapping_slot)) + /// location = keccak256(left_pad32(inner_key) || inner_mapping_slot) + offset + /// mpt_key = keccak256(location) + /// Note the mapping slot wire is NOT range checked, because it is expected to + /// be given by the verifier. If that assumption is not true, then the caller + /// should call `b.range_check(mapping_slot, 8)` to ensure its byteness. + pub fn build_mapping_of_mappings< + F: SerializableRichField + Extendable, + const D: usize, + >( + b: &mut CircuitBuilder, + offset: Target, + ) -> MappingOfMappingsSlotWires { + let mapping_slot = b.add_virtual_target(); + let [inner_key, outer_key] = array::from_fn(|_| { + let key = Array::::new(b); + key.assert_bytes(b); + + key + }); + + // inner_mapping_slot = keccak256(left_pad32(outer_key) || left_pad32(mapping_slot)) + let mut arr = [b.zero(); MAPPING_INPUT_PADDED_LEN]; + arr[0..MAPPING_KEY_LEN].copy_from_slice(&outer_key.arr); + arr[2 * MAPPING_KEY_LEN - 1] = mapping_slot; + let inputs = VectorWire:: { + real_len: b.constant(F::from_canonical_usize(MAPPING_INPUT_TOTAL_LEN)), + arr: Array { arr }, + }; + let inner_mapping_slot = KeccakCircuit::<{ INPUT_PADDED_LEN }>::hash_to_bytes(b, &inputs); + + // inputs = left_pad32(inner_key) || inner_mapping_slot + let mut arr = [b.zero(); MAPPING_INPUT_PADDED_LEN]; + arr[..MAPPING_KEY_LEN].copy_from_slice(&inner_key.arr); + arr[MAPPING_KEY_LEN..2 * MAPPING_KEY_LEN].copy_from_slice(&inner_mapping_slot.output.arr); + let inputs = VectorWire:: { + real_len: b.constant(F::from_canonical_usize(MAPPING_INPUT_TOTAL_LEN)), + arr: Array { arr }, + }; + + // location = keccak(inputs) + offset + let keccak_mpt = KeccakStructMPT::build(b, inputs, offset); + + MappingOfMappingsSlotWires { + mapping_slot, + inner_key, + outer_key, + inner_mapping_slot, + keccak_mpt, + } + } + + pub fn assign_single( + &self, + pw: &mut PartialWitness, + wires: &MappingSlotWires, + ) { + let (inputs, location) = + self.assign_slot_and_mapping_key(pw, wires.mapping_slot, &wires.mapping_key); + KeccakMPT::assign(pw, &wires.keccak_mpt, inputs, location); } + + pub fn assign_struct( + &self, + pw: &mut PartialWitness, + wires: &MappingStructSlotWires, + offset: u32, + ) { + let (inputs, location_base) = + self.assign_slot_and_mapping_key(pw, wires.mapping_slot, &wires.mapping_key); + + KeccakStructMPT::assign(pw, &wires.keccak_mpt, inputs, location_base, offset); + } + + pub fn assign_mapping_of_mappings( + &self, + pw: &mut PartialWitness, + wires: &MappingOfMappingsSlotWires, + inner_key: &[u8], + offset: u32, + ) { + let (inputs, inner_mapping_slot) = + self.assign_slot_and_mapping_key(pw, wires.mapping_slot, &wires.outer_key); + + let inner_key = left_pad32(inner_key); + wires.inner_key.assign_bytes(pw, &inner_key); + + // Assign the keccak values for inner mapping slot. + KeccakCircuit::<{ INPUT_PADDED_LEN }>::assign_byte_keccak( + pw, + &wires.inner_mapping_slot, + // No need to create a new input wire array since we create it in circuit. + &InputData::Assigned( + &Vector::from_vec(&inputs) + .expect("Cannot create vector input for inner mapping slot"), + ), + ); + // location = keccak(left_pad32(inner_key) || inner_mapping_slot) + let inputs = inner_key + .into_iter() + .chain(inner_mapping_slot) + .collect_vec(); + let base = keccak256(&inputs).try_into().unwrap(); + KeccakStructMPT::assign(pw, &wires.keccak_mpt, inputs, base, offset); + } + + // Assign the slot and mapping key. + // Return the input and keccak output as base location. + fn assign_slot_and_mapping_key( + &self, + pw: &mut PartialWitness, + mapping_slot: Target, + mapping_key: &Array, + ) -> (Vec, [u8; HASH_LEN]) { + // Pad the slot and mapping key. + let padded_slot = left_pad32(&[self.mapping_slot]); + let padded_key = left_pad32(&self.mapping_key); + + // Assign the mapping slot. + pw.set_target(mapping_slot, F::from_canonical_u8(self.mapping_slot)); + + // Assign the mapping key. + mapping_key.assign_bytes(pw, &padded_key); + + // Compute the entire expected array to derive the MPT key: + // keccak(left_pad32(mapping_key), left_pad32(mapping_slot)) + let inputs = padded_key.into_iter().chain(padded_slot).collect_vec(); + + // Then compute the expected resulting hash for MPT key derivation. + let base_location = keccak256(&inputs).try_into().unwrap(); + + (inputs, base_location) + } } #[cfg(test)] mod test { - use super::{MappingSlot, MappingSlotWires, SimpleSlot, SimpleSlotWires}; + use super::*; use crate::{ array::Array, - eth::StorageSlot, - keccak::{HASH_LEN, PACKED_HASH_LEN}, + eth::{StorageSlot, StorageSlotNode}, mpt_sequential::utils::bytes_to_nibbles, rlp::MAX_KEY_NIBBLE_LEN, - utils::{keccak256, Endianness, Packer, ToFields}, + types::CBuilder, C, D, F, }; - use mp2_test::circuit::{run_circuit, UserCircuit}; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::random_vector, + }; use plonky2::{ - field::extension::Extendable, - hash::hash_types::RichField, + field::types::Field, + iop::witness::WitnessWrite, iop::{target::Target, witness::PartialWitness}, - plonk::circuit_builder::CircuitBuilder, }; - use plonky2_crypto::u32::arithmetic_u32::U32Target; + use rand::{thread_rng, Rng}; + use std::array; + + #[derive(Clone, Debug)] + struct TestSimpleSlot { + slot: u8, + } + + impl UserCircuit for TestSimpleSlot { + type Wires = (SimpleSlotWires, Array); + + fn build(b: &mut CBuilder) -> Self::Wires { + let wires = SimpleSlot::build_single(b); + let exp_key = Array::new(b); + wires.mpt_key.key.enforce_equal(b, &exp_key); + + (wires, exp_key) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + let storage_slot = StorageSlot::Simple(self.slot as usize); + let circuit = SimpleSlot::new(self.slot); + circuit.assign_single(pw, &wires.0); + wires.1.assign_bytes(pw, &storage_slot.mpt_nibbles()); + } + } + + #[test] + fn test_simple_single_slot() { + let rng = &mut thread_rng(); + let slot = rng.gen(); + + let circuit = TestSimpleSlot { slot }; + run_circuit::(circuit); + } + + #[derive(Clone, Debug)] + struct TestSimpleStructSlot { + slot: u8, + evm_offset: u32, + } + + impl UserCircuit for TestSimpleStructSlot { + // EVM offset + simple slot + expected MPT key + type Wires = ( + Target, + SimpleStructSlotWires, + Array, + ); + + fn build(b: &mut CBuilder) -> Self::Wires { + let evm_offset = b.add_virtual_target(); + let slot = SimpleSlot::build_struct(b, evm_offset); + let exp_key = Array::new(b); + slot.base.mpt_key.key.enforce_equal(b, &exp_key); + + (evm_offset, slot, exp_key) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + let circuit = SimpleSlot::new(self.slot); + + let parent = StorageSlot::Simple(self.slot as usize); + let storage_slot = + StorageSlot::Node(StorageSlotNode::new_struct(parent, self.evm_offset)); + + pw.set_target(wires.0, F::from_canonical_u32(self.evm_offset)); + circuit.assign_struct(pw, &wires.1, self.evm_offset); + wires.2.assign_bytes(pw, &storage_slot.mpt_nibbles()); + } + } + + #[test] + fn test_simple_struct_slot() { + let rng = &mut thread_rng(); + let slot = rng.gen(); + let evm_offset = rng.gen(); + + let circuit = TestSimpleStructSlot { slot, evm_offset }; + run_circuit::(circuit); + } #[derive(Clone, Debug)] struct TestMappingSlot { - m: MappingSlot, - // 64 nibbles - exp_mpt_key_nibbles: Vec, - exp_keccak_location: Vec, - } - impl UserCircuit for TestMappingSlot - where - F: RichField + Extendable, - { + mapping_slot: MappingSlot, + exp_mpt_key: Vec, + } + + impl UserCircuit for TestMappingSlot { type Wires = ( + // Mapping slot MappingSlotWires, - // exp mpt key in nibbles + // Expected MPT key in nibbles Array, - // exp keccak location - Array, - // exp mpt key bytes - Array, ); - fn build(b: &mut CircuitBuilder) -> Self::Wires { - let mapping_slot_wires = MappingSlot::mpt_key(b); - let exp_key = Array::::new(b); - let good_key = mapping_slot_wires + fn build(b: &mut CBuilder) -> Self::Wires { + let mapping_slot = MappingSlot::build_single(b); + let exp_mpt_key = Array::::new(b); + + mapping_slot .keccak_mpt .mpt_key .key - .equals(b, &exp_key); - let tru = b._true(); - b.connect(tru.target, good_key.target); - let exp_keccak_location = Array::::new(b); - let good_keccak_location = mapping_slot_wires - .keccak_mpt - .keccak_location - .output - .equals(b, &exp_keccak_location); - b.connect(tru.target, good_keccak_location.target); - let exp_keccak_mpt = Array::::new(b); - let good_keccak_mpt = mapping_slot_wires - .keccak_mpt - .keccak_mpt_key - .output_array - .equals(b, &exp_keccak_mpt); - b.connect(tru.target, good_keccak_mpt.target); - ( - mapping_slot_wires, - exp_key, - exp_keccak_location, - exp_keccak_mpt, - ) + .enforce_equal(b, &exp_mpt_key); + + (mapping_slot, exp_mpt_key) } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - // assign the expected mpt key we should see + self.mapping_slot.assign_single(pw, &wires.0); wires .1 - .assign_bytes(pw, &self.exp_mpt_key_nibbles.clone().try_into().unwrap()); - // assign the expected location we should see + .assign_bytes(pw, &self.exp_mpt_key.clone().try_into().unwrap()); + } + } + + #[test] + fn test_mapping_single_slot() { + let rng = &mut thread_rng(); + + let slot = rng.gen(); + let mapping_key = random_vector(16); + let storage_slot = StorageSlot::Mapping(mapping_key.clone(), slot); + let mpt_key = storage_slot.mpt_key_vec(); + + let circuit = TestMappingSlot { + mapping_slot: MappingSlot { + mapping_key, + mapping_slot: slot as u8, + }, + exp_mpt_key: bytes_to_nibbles(&mpt_key), + }; + run_circuit::(circuit); + } + + #[derive(Clone, Debug)] + struct TestMappingStructSlot { + evm_offset: u32, + mapping_slot: MappingSlot, + exp_mpt_key: Vec, + } + + impl UserCircuit for TestMappingStructSlot { + type Wires = ( + // EVM offset + Target, + // Mapping slot + MappingStructSlotWires, + // Expected MPT key in nibbles + Array, + ); + + fn build(b: &mut CBuilder) -> Self::Wires { + let evm_offset = b.add_virtual_target(); + let mapping_slot = MappingSlot::build_struct(b, evm_offset); + let exp_mpt_key = Array::::new(b); + + mapping_slot + .keccak_mpt + .base + .mpt_key + .key + .enforce_equal(b, &exp_mpt_key); + + (evm_offset, mapping_slot, exp_mpt_key) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + pw.set_target(wires.0, F::from_canonical_u32(self.evm_offset)); + self.mapping_slot + .assign_struct(pw, &wires.1, self.evm_offset); wires .2 - .assign_bytes(pw, &self.exp_keccak_location.clone().try_into().unwrap()); - let exp_mpt_key_bytes = keccak256(&self.exp_keccak_location); - wires.3.assign( - pw, - &exp_mpt_key_bytes - .pack(Endianness::Little) - .to_fields() - .try_into() - .unwrap(), - ); - self.m.assign(pw, &wires.0); + .assign_bytes(pw, &self.exp_mpt_key.clone().try_into().unwrap()); } } #[test] - fn test_mapping_slot_key_derivation() { - let mapping_key = hex::decode("1234").unwrap(); - let mapping_slot = 2; - let slot = StorageSlot::Mapping(mapping_key.clone(), mapping_slot); - let mpt_key = slot.mpt_key_vec(); - let circuit = TestMappingSlot { - m: MappingSlot { + fn test_mapping_struct_slot() { + let rng = &mut thread_rng(); + + let slot = rng.gen(); + let evm_offset = rng.gen(); + let mapping_key = random_vector(16); + let parent = StorageSlot::Mapping(mapping_key.clone(), slot as usize); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent, evm_offset)); + let mpt_key = storage_slot.mpt_key_vec(); + + let circuit = TestMappingStructSlot { + evm_offset, + mapping_slot: MappingSlot { mapping_key, - mapping_slot: mapping_slot as u8, + mapping_slot: slot, }, - exp_mpt_key_nibbles: bytes_to_nibbles(&mpt_key), - exp_keccak_location: slot.location().as_slice().to_vec(), + exp_mpt_key: bytes_to_nibbles(&mpt_key), }; run_circuit::(circuit); } #[derive(Clone, Debug)] - struct TestSimpleSlot { - slot: u8, + struct TestMappingOfMappingsSlot { + evm_offset: u32, + inner_key: Vec, + mapping_slot: MappingSlot, + exp_mpt_key: Vec, } - impl UserCircuit for TestSimpleSlot { - type Wires = (SimpleSlotWires, Array); + impl UserCircuit for TestMappingOfMappingsSlot { + type Wires = ( + // EVM offset + Target, + // Mapping of mappings slot + MappingOfMappingsSlotWires, + // Expected MPT key in nibbles + Array, + ); - fn build(c: &mut CircuitBuilder) -> Self::Wires { - let wires = SimpleSlot::build(c); - let exp_key = Array::new(c); - wires.mpt_key.key.enforce_equal(c, &exp_key); - (wires, exp_key) + fn build(b: &mut CBuilder) -> Self::Wires { + let evm_offset = b.add_virtual_target(); + let mapping_slot = MappingSlot::build_mapping_of_mappings(b, evm_offset); + let exp_mpt_key = Array::::new(b); + + mapping_slot + .keccak_mpt + .base + .mpt_key + .key + .enforce_equal(b, &exp_mpt_key); + + (evm_offset, mapping_slot, exp_mpt_key) } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { - let eth_slot = StorageSlot::Simple(self.slot as usize); - let circuit = SimpleSlot::new(self.slot); - circuit.assign(pw, &wires.0); - wires.1.assign_bytes(pw, ð_slot.mpt_nibbles()); + pw.set_target(wires.0, F::from_canonical_u32(self.evm_offset)); + self.mapping_slot.assign_mapping_of_mappings( + pw, + &wires.1, + &self.inner_key, + self.evm_offset, + ); + wires + .2 + .assign_bytes(pw, &self.exp_mpt_key.clone().try_into().unwrap()); } } #[test] - fn test_simple_slot() { - let circuit = TestSimpleSlot { slot: 8 }; + fn test_mapping_of_mappings_slot() { + let rng = &mut thread_rng(); + + let slot = rng.gen(); + let evm_offset = rng.gen(); + let [outer_key, inner_key] = array::from_fn(|_| random_vector(16)); + let grand = StorageSlot::Mapping(outer_key.clone(), slot as usize); + let parent = + StorageSlot::Node(StorageSlotNode::new_mapping(grand, inner_key.clone()).unwrap()); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent, evm_offset)); + let mpt_key = storage_slot.mpt_key_vec(); + + let circuit = TestMappingOfMappingsSlot { + evm_offset, + inner_key, + mapping_slot: MappingSlot { + mapping_key: outer_key, + mapping_slot: slot, + }, + exp_mpt_key: bytes_to_nibbles(&mpt_key), + }; run_circuit::(circuit); } } diff --git a/mp2-common/src/types.rs b/mp2-common/src/types.rs index 383cf6110..516e27ba8 100644 --- a/mp2-common/src/types.rs +++ b/mp2-common/src/types.rs @@ -1,6 +1,9 @@ //! Custom types -use crate::{array::Array, D, F}; +use crate::{ + array::{Array, L32}, + D, F, +}; use anyhow::ensure; use derive_more::Deref; use plonky2::{ @@ -76,6 +79,11 @@ pub const MAX_BLOCK_LEN: usize = 650; /// value **not** RLP encoded,i.e. without the 1-byte RLP header. pub const MAPPING_LEAF_VALUE_LEN: usize = 32; +pub const MAPPING_LEAF_VALUE_LEN_PACKED: usize = L32(MAPPING_LEAF_VALUE_LEN); + +/// The length of an EVM word +pub const EVM_WORD_LEN: usize = 32; + impl From<[u8; 32]> for HashOutput { fn from(value: [u8; 32]) -> Self { Self(value) @@ -108,3 +116,21 @@ impl From> for HashOutput { value.to_bytes().try_into().unwrap() } } + +impl From<&HashOut> for HashOutput { + fn from(value: &HashOut) -> Self { + value.to_bytes().try_into().unwrap() + } +} + +impl From for HashOut { + fn from(value: HashOutput) -> Self { + Self::from_bytes(&value.0) + } +} + +impl From<&HashOutput> for HashOut { + fn from(value: &HashOutput) -> Self { + Self::from_bytes(&value.0) + } +} diff --git a/mp2-common/src/u256.rs b/mp2-common/src/u256.rs index ae6b79cff..685693908 100644 --- a/mp2-common/src/u256.rs +++ b/mp2-common/src/u256.rs @@ -60,7 +60,7 @@ pub fn is_less_than_or_equal_to_u256_arr(left: &[U256], right: &[U256]) -> (bool } /// Circuit representation of u256 -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, Copy)] pub struct UInt256Target([U32Target; NUM_LIMBS]); impl PartialEq for UInt256Target { @@ -231,7 +231,7 @@ impl, const D: usize> CircuitBuilderU256 } fn add_virtual_u256(&mut self) -> UInt256Target { - self.add_virtual_u256_arr::<1>()[0].clone() + self.add_virtual_u256_arr::<1>()[0] } fn add_virtual_u256_arr(&mut self) -> [UInt256Target; N] { @@ -530,8 +530,8 @@ impl, const D: usize> CircuitBuilderU256 ) -> UInt256Target { // first check if `cond` is a constant match self.target_as_constant(cond.target) { - Some(val) if val == F::ZERO => return right.clone(), - Some(val) if val == F::ONE => return left.clone(), + Some(val) if val == F::ZERO => return *right, + Some(val) if val == F::ONE => return *left, _ => (), }; let limbs = create_array(|i| { @@ -739,10 +739,10 @@ impl UInt256Target { let quotient = b.add_virtual_u256(); let remainder = b.add_virtual_u256(); b.add_simple_generator(UInt256DivGenerator { - dividend: self.clone(), - divisor: other.clone(), - quotient: quotient.clone(), - remainder: remainder.clone(), + dividend: *self, + divisor: *other, + quotient, + remainder, is_div, }); // enforce that remainder < other, if other != 0 and is_div == true; @@ -771,9 +771,9 @@ impl UInt256Target { // otherwise, prod = quotient*other, as we need to later check that quotient*other + remainder == self let mul_input = if let Some(val) = b.target_as_constant(is_div.target) { if val == F::ONE { - quotient.clone() + quotient } else { - self.clone() + *self } } else { b.select_u256(is_div, "ient, self) @@ -838,6 +838,15 @@ impl From> for UInt256Target { } } +impl From for Array { + fn from(value: UInt256Target) -> Self { + let mut arr = value.0; + arr.reverse(); + + Self::from_array(arr) + } +} + impl<'a> From<&'a UInt256Target> for Vec { fn from(value: &'a UInt256Target) -> Self { value.0.iter().map(|u32_t| u32_t.0).rev().collect_vec() diff --git a/mp2-common/src/utils.rs b/mp2-common/src/utils.rs index 3a4193ecb..af0e59d63 100644 --- a/mp2-common/src/utils.rs +++ b/mp2-common/src/utils.rs @@ -12,15 +12,20 @@ use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::VerifierCircuitData; use plonky2::plonk::config::{GenericConfig, GenericHashOut, Hasher}; use plonky2_crypto::u32::arithmetic_u32::U32Target; +use plonky2_ecdsa::gadgets::biguint::BigUintTarget; use plonky2_ecgfp5::gadgets::{base_field::QuinticExtensionTarget, curve::CurveTarget}; use sha3::Digest; use sha3::Keccak256; -use crate::array::Targetable; -use crate::poseidon::{HashableField, H}; use crate::serialization::circuit_data_serialization::SerializableRichField; -use crate::{group_hashing::EXTENSION_DEGREE, types::HashOutput, ProofTuple}; +use crate::{ + array::Targetable, + group_hashing::EXTENSION_DEGREE, + poseidon::{HashableField, H}, + types::HashOutput, + ProofTuple, +}; const TWO_POWER_8: usize = 256; const TWO_POWER_16: usize = 65536; @@ -398,6 +403,7 @@ impl ToFields for HashOut { self.elements.to_vec() } } + pub trait Fieldable { fn to_field(&self) -> F; } @@ -471,6 +477,12 @@ impl ToTargets for &[Target] { } } +impl ToTargets for BigUintTarget { + fn to_targets(&self) -> Vec { + self.limbs.iter().map(|u| u.0).collect() + } +} + pub trait TargetsConnector { fn connect_targets(&mut self, e1: T, e2: T); fn is_equal_targets(&mut self, e1: T, e2: T) -> BoolTarget; @@ -552,6 +564,7 @@ pub trait PackableRichField: RichField {} impl PackableRichField for GoldilocksField {} +#[derive(Clone, Copy, Debug)] pub enum Endianness { Big, Little, @@ -793,7 +806,6 @@ impl, const D: usize> SliceConnector for CircuitBui #[cfg(test)] mod test { - use super::{bits_to_num, Packer, ToFields}; use crate::utils::{ greater_than, greater_than_or_equal_to, less_than, less_than_or_equal_to, num_to_bits, @@ -808,7 +820,6 @@ mod test { use plonky2::iop::witness::{PartialWitness, WitnessWrite}; use plonky2::plonk::circuit_builder::CircuitBuilder; use plonky2::plonk::circuit_data::CircuitConfig; - use rand::{thread_rng, Rng, RngCore}; #[test] 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/cells_tree.rs b/mp2-test/src/cells_tree.rs index 513ed28fa..5f7b5fd8c 100644 --- a/mp2-test/src/cells_tree.rs +++ b/mp2-test/src/cells_tree.rs @@ -22,15 +22,15 @@ use plonky2::{ use rand::{thread_rng, Rng}; use ryhope::{ storage::{memory::InMemory, updatetree::UpdateTree, EpochKvStorage, TreeTransactionalStorage}, - tree::{sbbst, TreeTopology}, + tree::{sbbst::IncrementalTree, TreeTopology}, InitSettings, MerkleTreeKvDb, NodePayload, }; use serde::{Deserialize, Serialize}; use std::iter; -pub type CellTree = sbbst::Tree; +pub type CellTree = IncrementalTree; pub type CellTreeKey = ::Key; -type CellStorage = InMemory; +type CellStorage = InMemory; pub type MerkleCellTree = MerkleTreeKvDb; /// Test node of the cells tree @@ -116,7 +116,7 @@ impl NodePayload for TestCell { pub async fn build_cell_tree( row: Vec, ) -> Result<(MerkleCellTree, UpdateTree<::Key>)> { - let mut cell_tree = MerkleCellTree::new(InitSettings::Reset(sbbst::Tree::empty()), ()) + let mut cell_tree = MerkleCellTree::new(InitSettings::Reset(IncrementalTree::empty()), ()) .await .unwrap(); let update_tree = cell_tree diff --git a/mp2-test/src/circuit.rs b/mp2-test/src/circuit.rs index 262d4384e..f810dac93 100644 --- a/mp2-test/src/circuit.rs +++ b/mp2-test/src/circuit.rs @@ -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..bd945a4ac 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, B256, U256}, + providers::{ext::AnvilApi, Provider, ProviderBuilder}, + sol, +}; use eth_trie::{EthTrie, MemoryDB, Trie}; -use rand::{thread_rng, Rng}; + +use mp2_common::eth::{EventLogInfo, ReceiptProofInfo}; +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,243 @@ pub fn generate_random_storage_mpt( } (trie, keys[right_key_idx].to_vec()) } + +#[derive(Debug, Clone)] +pub struct ReceiptTestInfo { + /// The event which we have returned proofs for + pub event: EventLogInfo, + /// The proofs for receipts relating to `self.query` + pub proofs: Vec, + /// The root of the Receipt Trie at this block (in case there are no relevant events) + pub receipts_root: B256, +} + +impl + ReceiptTestInfo +{ + /// Getter for the proofs + pub fn proofs(&self) -> Vec { + self.proofs.clone() + } + /// Getter for the query + pub fn info(&self) -> &EventLogInfo { + &self.event + } +} +/// 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 tx_req = match i % 4 { + 0 | 1 => random, + 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_WORDS) { + (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_WORDS) { + (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 event = EventLogInfo::::new( + *event_contract.address(), + &events[0].signature(), + ); + + let (proofs, receipts_root) = event + .query_receipt_proofs(rpc.root(), BlockNumberOrTag::Number(block_number)) + .await + .unwrap(); + + ReceiptTestInfo { + event, + proofs, + receipts_root, + } + }) +} diff --git a/mp2-v1/Cargo.toml b/mp2-v1/Cargo.toml index d7b9b3856..e136bb278 100644 --- a/mp2-v1/Cargo.toml +++ b/mp2-v1/Cargo.toml @@ -19,6 +19,7 @@ log.workspace = true paste.workspace = true plonky2.workspace = true plonky2_crypto.workspace = true +plonky2_ecdsa.workspace = true plonky2_ecgfp5.workspace = true rand.workspace = true rlp.workspace = true diff --git a/mp2-v1/Makefile b/mp2-v1/Makefile index f82a9538b..1c73912da 100644 --- a/mp2-v1/Makefile +++ b/mp2-v1/Makefile @@ -12,15 +12,12 @@ TEST_BINDINGS_OUT_PATH=$(TEST_CONTRACT_PATH)/out/$(TEST_BINDINGS_FOLDER) # Generate the integration test contract bindings. bindings: - rm -rf $(TEST_BINDINGS_MOD_PATH) $(TEST_BINDINGS_OUT_PATH) # Generate new bindings. forge install --root $(TEST_CONTRACT_PATH) - forge bind --alloy --module --root $(TEST_CONTRACT_PATH) - -# Move the bindings module to the integration test location. - mv -f $(TEST_BINDINGS_OUT_PATH) $(TEST_BINDINGS_MOD_PATH) + forge bind --bindings-path $(TEST_BINDINGS_MOD_PATH) --alloy --module --root $(TEST_CONTRACT_PATH) --extra-output abi --overwrite cargo fmt + # Declare phony targets .PHONY: bindings diff --git a/mp2-v1/src/api.rs b/mp2-v1/src/api.rs index 524950133..7aa537a3e 100644 --- a/mp2-v1/src/api.rs +++ b/mp2-v1/src/api.rs @@ -1,5 +1,5 @@ //! Main APIs and related structures - +#![allow(clippy::identity_op)] use std::iter::once; use crate::{ @@ -10,16 +10,25 @@ use crate::{ self, compute_metadata_digest as length_metadata_digest, LengthCircuitInput, }, values_extraction::{ - self, compute_leaf_mapping_metadata_digest, compute_leaf_single_metadata_digest, - identifier_block_column, identifier_for_mapping_key_column, - identifier_for_mapping_value_column, identifier_single_var_column, + self, + gadgets::{ + column_info::{ExtractedColumnInfo, InputColumnInfo}, + metadata_gadget::TableMetadata, + }, + identifier_block_column, identifier_for_inner_mapping_key_column, + identifier_for_outer_mapping_key_column, identifier_for_value_column, INNER_KEY_ID_PREFIX, + KEY_ID_PREFIX, OUTER_KEY_ID_PREFIX, }, + MAX_RECEIPT_LEAF_NODE_LEN, }; + use alloy::primitives::Address; use anyhow::Result; use itertools::Itertools; +use log::debug; use mp2_common::{ digest::Digest, + group_hashing::map_to_curve_point, poseidon::H, types::HashOutput, utils::{Fieldable, ToFields}, @@ -28,7 +37,6 @@ use plonky2::{ iop::target::Target, plonk::config::{GenericHashOut, Hasher}, }; -use plonky2_ecgfp5::curve::curve::Point; use serde::{Deserialize, Serialize}; /// Struct containing the expected input MPT Extension/Branch node @@ -37,15 +45,31 @@ pub struct InputNode { pub node: Vec, } +// TODO: Specify `NODE_LEN = MAX_LEAF_NODE_LEN` in the generic parameter, +// but it could not work for using `MAPPING_LEAF_NODE_LEN` constant directly. +/// We use `512` in as the `NODE_LEN` in [`values_extraction::CircuitInput`] to represent +/// the maximum length of a Receipt Trie leaf node. The Storage trie leaf node size is now hard coded into +/// the circuits. +type ValuesExtractionInput = + values_extraction::CircuitInput<512, MAX_EXTRACTED_COLUMNS>; +/// We use `512` in as the `NODE_LEN` in [`values_extraction::PublicParameters`] to represent +/// the maximum length of a Receipt Trie leaf node. The Storage trie leaf node size is now hard coded into +/// the circuits. +type ValuesExtractionParameters = + values_extraction::PublicParameters<512, MAX_EXTRACTED_COLUMNS>; +fn sanity_check() { + assert_eq!(MAX_RECEIPT_LEAF_NODE_LEN, 512); +} + /// Set of inputs necessary to generate proofs for each circuit employed in the /// pre-processing stage of LPN -pub enum CircuitInput { +pub enum CircuitInput { /// Contract extraction input ContractExtraction(contract_extraction::CircuitInput), /// Length extraction input LengthExtraction(LengthCircuitInput), /// Values extraction input - ValuesExtraction(values_extraction::CircuitInput), + ValuesExtraction(ValuesExtractionInput), /// Block extraction necessary input BlockExtraction(block_extraction::CircuitInput), /// Final extraction input @@ -62,16 +86,16 @@ pub enum CircuitInput { #[derive(Serialize, Deserialize)] /// Parameters defining all the circuits employed for the pre-processing stage of LPN -pub struct PublicParameters { +pub struct PublicParameters { contract_extraction: contract_extraction::PublicParameters, length_extraction: length_extraction::PublicParameters, - values_extraction: values_extraction::PublicParameters, + values_extraction: ValuesExtractionParameters, block_extraction: block_extraction::PublicParameters, final_extraction: final_extraction::PublicParameters, tree_creation: verifiable_db::api::PublicParameters>, } -impl PublicParameters { +impl PublicParameters { pub fn get_params_info(&self) -> Result> { self.tree_creation.get_params_info() } @@ -80,11 +104,19 @@ impl PublicParameters { pub fn empty_cell_tree_proof(&self) -> Result> { self.tree_creation.empty_cell_tree_proof() } + + pub fn get_value_extraction_params( + &self, + ) -> &ValuesExtractionParameters { + &self.values_extraction + } } /// Instantiate the circuits employed for the pre-processing stage of LPN, /// returning their corresponding parameters -pub fn build_circuits_params() -> PublicParameters { +pub fn build_circuits_params( +) -> PublicParameters { + sanity_check(); log::info!("Building contract_extraction parameters..."); let contract_extraction = contract_extraction::build_circuits_params(); log::info!("Building length_extraction parameters..."); @@ -117,7 +149,10 @@ pub fn build_circuits_params() -> PublicParameters { /// Generate a proof for a circuit in the set of circuits employed in the /// pre-processing stage of LPN, employing `CircuitInput` to specify for which /// circuit the proof should be generated -pub fn generate_proof(params: &PublicParameters, input: CircuitInput) -> Result> { +pub fn generate_proof( + params: &PublicParameters, + input: CircuitInput, +) -> Result> { match input { CircuitInput::ContractExtraction(input) => { contract_extraction::generate_proof(¶ms.contract_extraction, input) @@ -146,6 +181,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( @@ -174,13 +212,152 @@ pub fn generate_proof(params: &PublicParameters, input: CircuitInput) -> Result< pub type MetadataHash = HashOutput; /// Enumeration to be employed to provide input slots for metadata hash computation +#[derive(Debug, Clone)] pub enum SlotInputs { - /// slots of a set of simple variables - Simple(Vec), - /// slot of a mapping variable without an associated length slot to determine the number of entries - Mapping(u8), - /// slots of a mapping variable and of a slot containing the length of the mapping - MappingWithLength(u8, u8), + /// Slots of a set of simple variables or Struct + /// The slot number should be same for the fields of one Struct. + Simple(Vec), + /// Slot of a mapping variable or Struct + /// It should be only one input for mapping to simple value, and multiple inputs + /// for the fields of a Struct. The slot number should be always same for both + /// mapping to simple value or a Struct. + Mapping(Vec), + /// Slot of a mapping of mappings variable or Struct + /// It's similiar as mapping type, the mapping value could be simple value or a Struct. + /// The slot number should be always same. + MappingOfMappings(Vec), + /// Slots of a mapping variable and of a slot containing the length of the mapping + MappingWithLength(Vec, u8), +} + +impl SlotInputs { + pub fn to_column_metadata( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> TableMetadata { + let (slot, extracted_columns) = match self { + SlotInputs::Simple(ref inner) + | SlotInputs::Mapping(ref inner) + | SlotInputs::MappingOfMappings(ref inner) + | SlotInputs::MappingWithLength(ref inner, ..) => ( + inner[0].slot, + compute_table_info(inner.to_vec(), contract_address, chain_id, extra.clone()), + ), + }; + + let num_mapping_keys = match self { + SlotInputs::Simple(..) => 0usize, + SlotInputs::Mapping(..) | SlotInputs::MappingWithLength(..) => 1, + SlotInputs::MappingOfMappings(..) => 2, + }; + + let input_columns = match num_mapping_keys { + 0 => vec![], + 1 => { + let identifier = identifier_for_outer_mapping_key_column( + slot, + contract_address, + chain_id, + extra.clone(), + ); + + let input_column = InputColumnInfo::new(&[slot], identifier, KEY_ID_PREFIX); + vec![input_column] + } + 2 => { + let outer_identifier = identifier_for_outer_mapping_key_column( + slot, + contract_address, + chain_id, + extra.clone(), + ); + let inner_identifier = identifier_for_inner_mapping_key_column( + slot, + contract_address, + chain_id, + extra.clone(), + ); + vec![ + InputColumnInfo::new(&[slot], outer_identifier, OUTER_KEY_ID_PREFIX), + InputColumnInfo::new(&[slot], inner_identifier, INNER_KEY_ID_PREFIX), + ] + } + _ => vec![], + }; + + TableMetadata::new(&input_columns, &extracted_columns) + } +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Copy)] +pub struct SlotInput { + /// Slot information of the variable + pub(crate) slot: u8, + /// The offset in bytes where to extract this column in a given EVM word + pub(crate) byte_offset: usize, + /// The length (in bits) of the field to extract in the EVM word + pub(crate) length: usize, + /// At which EVM word is this column extracted from. For simple variables, + /// this value should always be 0. For structs that spans more than one EVM word + // that value should be depending on which section of the struct we are in. + pub(crate) evm_word: u32, +} + +impl From for SlotInput { + fn from(value: ExtractedColumnInfo) -> Self { + let extraction_id = value.extraction_id(); + let slot = extraction_id[0].0 as u8; + + SlotInput { + slot, + byte_offset: value.byte_offset().0 as usize, + length: value.length().0 as usize, + evm_word: value.location_offset().0 as u32, + } + } +} + +impl From<&ExtractedColumnInfo> for SlotInput { + fn from(value: &ExtractedColumnInfo) -> Self { + let extraction_id = value.extraction_id(); + let slot = extraction_id[0].0 as u8; + + SlotInput { + slot, + byte_offset: value.byte_offset().0 as usize, + length: value.length().0 as usize, + evm_word: value.location_offset().0 as u32, + } + } +} + +impl SlotInput { + pub fn new(slot: u8, byte_offset: usize, length: usize, evm_word: u32) -> Self { + Self { + slot, + byte_offset, + length, + evm_word, + } + } + + pub fn slot(&self) -> u8 { + self.slot + } + + pub fn byte_offset(&self) -> usize { + self.byte_offset + } + + pub fn length(&self) -> usize { + self.length + } + + pub fn evm_word(&self) -> u32 { + self.evm_word + } } /// Compute metadata hash for a "merge" table. Right now it supports only merging tables from the @@ -192,9 +369,9 @@ pub fn merge_metadata_hash( table_a: SlotInputs, table_b: SlotInputs, ) -> MetadataHash { - let md_a = value_metadata(table_a, &contract, chain_id, extra.clone()); - let md_b = value_metadata(table_b, &contract, chain_id, extra); - let combined = md_a + md_b; + let (md_a, _) = value_metadata(table_a, &contract, chain_id, extra.clone()); + let (md_b, _) = value_metadata(table_b, &contract, chain_id, extra); + let combined = map_to_curve_point(&md_a.to_fields()) + map_to_curve_point(&md_b.to_fields()); let contract_digest = contract_metadata_digest(&contract); // the block id is only added at the index tree level, the rest is combined at the final // extraction level. @@ -203,28 +380,53 @@ pub fn merge_metadata_hash( // NOTE: the block id is added at the end of the digest computation only once - this returns only // the part without the block id -fn value_metadata(inputs: SlotInputs, contract: &Address, chain_id: u64, extra: Vec) -> Digest { - match inputs { - SlotInputs::Simple(slots) => slots.iter().fold(Point::NEUTRAL, |acc, &slot| { - let id = identifier_single_var_column(slot, contract, chain_id, extra.clone()); - let digest = compute_leaf_single_metadata_digest(id, slot); - acc + digest - }), - SlotInputs::Mapping(slot) => metadata_digest_mapping(contract, chain_id, extra, slot), - SlotInputs::MappingWithLength(mapping_slot, length_slot) => { - let mapping_digest = metadata_digest_mapping(contract, chain_id, extra, mapping_slot); - let length_digest = length_metadata_digest(length_slot, mapping_slot); - mapping_digest + length_digest +fn value_metadata( + inputs: SlotInputs, + contract: &Address, + chain_id: u64, + extra: Vec, +) -> (Digest, Digest) { + let column_metadata = inputs.to_column_metadata(contract, chain_id, extra.clone()); + + let md = column_metadata.digest(); + + let length_digest = match inputs { + SlotInputs::Simple(..) | SlotInputs::Mapping(..) | SlotInputs::MappingOfMappings(..) => { + Digest::NEUTRAL } - } + SlotInputs::MappingWithLength(mapping_inputs, length_slot) => { + assert!(!mapping_inputs.is_empty()); + let mapping_slot = mapping_inputs[0].slot; + length_metadata_digest(length_slot, mapping_slot) + } + }; + (md, length_digest) } -fn metadata_digest_mapping(address: &Address, chain_id: u64, extra: Vec, slot: u8) -> Digest { - let key_id = identifier_for_mapping_key_column(slot, address, chain_id, extra.clone()); - let value_id = identifier_for_mapping_value_column(slot, address, chain_id, extra.clone()); - compute_leaf_mapping_metadata_digest(key_id, value_id, slot) + +/// Compute the table information for the value columns. +pub fn compute_table_info( + inputs: Vec, + address: &Address, + chain_id: u64, + extra: Vec, +) -> Vec { + inputs + .into_iter() + .map(|input| { + let id = identifier_for_value_column(&input, address, chain_id, extra.clone()); + + ExtractedColumnInfo::new( + &[input.slot], + id, + input.byte_offset, + input.length, + input.evm_word, + ) + }) + .collect_vec() } -fn combine_digest_and_block(digest: Digest) -> HashOutput { +pub fn combine_digest_and_block(digest: Digest) -> HashOutput { let block_id = identifier_block_column(); let inputs = digest .to_fields() @@ -242,9 +444,17 @@ pub fn metadata_hash( extra: Vec, ) -> MetadataHash { // closure to compute the metadata digest associated to a mapping variable - let value_digest = value_metadata(slot_input, contract_address, chain_id, extra); + let (md_digest, length_digest) = value_metadata(slot_input, contract_address, chain_id, extra); + // Correspond to the computation of final extraction base circuit. + let md_digest = map_to_curve_point(&md_digest.to_fields()); // add contract digest let contract_digest = contract_metadata_digest(contract_address); + debug!( + "METADATA_HASH ->\n\tvalues_ext_md = {:?}\n\tcontract_md = {:?}\n\tfinal_ex_md(contract + values_ex) = {:?}", + md_digest.to_weierstrass(), + contract_digest.to_weierstrass(), + (contract_digest + md_digest).to_weierstrass(), + ); // compute final hash - combine_digest_and_block(contract_digest + value_digest) + combine_digest_and_block(contract_digest + md_digest + length_digest) } 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..3fa135261 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,8 @@ 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, _) = + 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 +107,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..8e9176fff 100644 --- a/mp2-v1/src/final_extraction/api.rs +++ b/mp2-v1/src/final_extraction/api.rs @@ -1,4 +1,4 @@ -use mp2_common::{self, default_config, digest::TableDimension, proof::ProofWithVK, C, D, F}; +use mp2_common::{self, default_config, proof::ProofWithVK, C, D, F}; use plonky2::{iop::target::Target, plonk::circuit_data::VerifierCircuitData}; use recursion_framework::{ circuit_builder::{CircuitWithUniversalVerifier, CircuitWithUniversalVerifierBuilder}, @@ -11,6 +11,7 @@ use super::{ base_circuit::BaseCircuitInput, lengthed_circuit::LengthedRecursiveWires, merge_circuit::{MergeTable, MergeTableRecursiveWires}, + receipt_circuit::{ReceiptCircuitInput, ReceiptCircuitProofInputs, ReceiptCircuitProofWires}, 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,10 +53,11 @@ pub struct PublicParameters { simple: CircuitWithUniversalVerifier, lengthed: CircuitWithUniversalVerifier, merge: CircuitWithUniversalVerifier, + receipt: CircuitWithUniversalVerifier, circuit_set: RecursiveCircuits, } -const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 2; +const FINAL_EXTRACTION_CIRCUIT_SET_SIZE: usize = 4; pub(super) const NUM_IO: usize = PublicInputs::::TOTAL_LEN; impl PublicParameters { @@ -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, } } @@ -108,8 +114,6 @@ impl PublicParameters { let merge = MergeTable { is_table_a_multiplier: input.is_table_a_multiplier, - dimension_a: input.table_a_dimension, - dimension_b: input.table_b_dimension, }; let merge_inputs = MergeCircuit { base, merge }; let proof = self @@ -124,14 +128,11 @@ impl PublicParameters { contract_circuit_set: &RecursiveCircuits, value_circuit_set: &RecursiveCircuits, ) -> Result> { - let simple_inputs = SimpleCircuit::new( - BaseCircuitProofInputs::new_from_proofs( - input.base, - contract_circuit_set.clone(), - value_circuit_set.clone(), - ), - input.dimension, - ); + let simple_inputs = SimpleCircuit::new(BaseCircuitProofInputs::new_from_proofs( + input.base, + contract_circuit_set.clone(), + value_circuit_set.clone(), + )); let proof = self .circuit_set .generate_proof(&self.simple, [], [], simple_inputs)?; @@ -160,6 +161,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 } @@ -167,7 +181,6 @@ impl PublicParameters { pub struct SimpleCircuitInput { base: BaseCircuitInput, - dimension: TableDimension, } pub struct LengthedCircuitInput { @@ -178,8 +191,6 @@ pub struct LengthedCircuitInput { pub struct MergeCircuitInput { base: BaseCircuitInput, is_table_a_multiplier: bool, - table_a_dimension: TableDimension, - table_b_dimension: TableDimension, } impl CircuitInput { @@ -202,8 +213,6 @@ impl CircuitInput { Ok(Self::MergeTable(MergeCircuitInput { base, is_table_a_multiplier: true, - table_a_dimension: TableDimension::Single, - table_b_dimension: TableDimension::Compound, })) } /// Instantiate inputs for simple variables circuit. Coumpound must be set to true @@ -214,10 +223,9 @@ impl CircuitInput { block_proof: Vec, contract_proof: Vec, value_proof: Vec, - dimension: TableDimension, ) -> Result { let base = BaseCircuitInput::new(block_proof, contract_proof, vec![value_proof])?; - Ok(Self::Simple(SimpleCircuitInput { base, dimension })) + Ok(Self::Simple(SimpleCircuitInput { base })) } /// Instantiate inputs for circuit dealing with compound types with a length slot pub fn new_lengthed_input( @@ -230,12 +238,18 @@ 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)] mod tests { use mp2_common::{ - digest::TableDimension, proof::{serialize_proof, ProofWithVK}, C, D, F, }; @@ -247,6 +261,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 +285,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 +300,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(), @@ -296,30 +319,27 @@ mod tests { ) .into(); // test generation of proof for simple circuit for both compound and simple types - for dimension in [TableDimension::Single, TableDimension::Compound] { - let circuit_input = CircuitInput::new_simple_input( - serialize_proof(&block_proof).unwrap(), - contract_proof.serialize().unwrap(), - value_proof.serialize().unwrap(), - dimension, - ) - .unwrap(); + let circuit_input = CircuitInput::new_simple_input( + serialize_proof(&block_proof).unwrap(), + contract_proof.serialize().unwrap(), + value_proof.serialize().unwrap(), + ) + .unwrap(); - let proof = ProofWithVK::deserialize( - ¶ms - .generate_simple_proof( - match circuit_input { - CircuitInput::Simple(input) => input, - _ => unreachable!(), - }, - contract_params.get_recursive_circuit_set(), - values_params.get_recursive_circuit_set(), - ) - .unwrap(), - ) - .unwrap(); - proof_pis.check_proof_public_inputs(proof.proof(), dimension, None); - } + let proof = ProofWithVK::deserialize( + ¶ms + .generate_simple_proof( + match circuit_input { + CircuitInput::Simple(input) => input, + _ => unreachable!(), + }, + contract_params.get_recursive_circuit_set(), + values_params.get_recursive_circuit_set(), + ) + .unwrap(), + ) + .unwrap(); + proof_pis.check_proof_public_inputs(proof.proof(), None); // test proof generation for types with length circuit let length_proof: ProofWithVK = ( length_proof.clone(), @@ -347,6 +367,32 @@ mod tests { .unwrap(), ) .unwrap(); - proof_pis.check_proof_public_inputs(proof.proof(), TableDimension::Compound, Some(len_dm)); + proof_pis.check_proof_public_inputs(proof.proof(), 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..ce2474eab 100644 --- a/mp2-v1/src/final_extraction/base_circuit.rs +++ b/mp2-v1/src/final_extraction/base_circuit.rs @@ -74,10 +74,11 @@ impl BaseCircuit { } b.connect(contract_pi.mpt_key().pointer, minus_one); - let mut base_dm = value_pis[0].metadata_digest_target(); - for vp in value_pis.iter().skip(1) { - base_dm = b.add_curve_point(&[base_dm, vp.metadata_digest_target()]); - } + let value_dms = value_pis + .iter() + .map(|vp| b.map_to_curve_point(vp.metadata_digest_raw())) + .collect_vec(); + let base_dm = b.add_curve_point(&value_dms); let final_dm = b.add_curve_point(&[base_dm, contract_pi.metadata_digest()]); // enforce block_pi.state_root == contract_pi.state_root @@ -238,7 +239,6 @@ pub(crate) mod test { use anyhow::Result; use itertools::Itertools; use mp2_common::{ - digest::TableDimension, group_hashing::map_to_curve_point, keccak::PACKED_HASH_LEN, rlp::MAX_KEY_NIBBLE_LEN, @@ -385,7 +385,6 @@ pub(crate) mod test { pub(crate) fn check_proof_public_inputs( &self, proof: &ProofWithPublicInputs, - dimension: TableDimension, length_dm: Option, ) { let proof_pis = PublicInputs::from_slice(&proof.public_inputs); @@ -397,13 +396,7 @@ pub(crate) mod test { // check digests let value_pi = values_extraction::PublicInputs::new(&self.values_pi); - if let TableDimension::Compound = dimension { - assert_eq!(proof_pis.value_point(), value_pi.values_digest()); - } else { - // in this case, dv is D(value_dv) - let exp_dv = map_to_curve_point(&value_pi.values_digest().to_fields()); - assert_eq!(proof_pis.value_point(), exp_dv.to_weierstrass()); - } + assert_eq!(proof_pis.value_point(), value_pi.values_digest()); // metadata is addition of contract and value // ToDo: make it a trait once we understand it's sound let weierstrass_to_point = |wp: WeierstrassPoint| { @@ -414,7 +407,7 @@ pub(crate) mod test { }; let contract_pi = contract_extraction::PublicInputs::from_slice(&self.contract_pi); let contract_dm = weierstrass_to_point(contract_pi.metadata_point()).unwrap(); - let value_dm = weierstrass_to_point(value_pi.metadata_digest()).unwrap(); + let value_dm = map_to_curve_point(value_pi.metadata_digest_raw()); let expected_dm = if let Some(len_dm) = length_dm { let len_dm = weierstrass_to_point(len_dm).unwrap(); contract_dm + value_dm + len_dm @@ -441,6 +434,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 +462,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/lengthed_circuit.rs b/mp2-v1/src/final_extraction/lengthed_circuit.rs index f9e23fa68..f8a6a29eb 100644 --- a/mp2-v1/src/final_extraction/lengthed_circuit.rs +++ b/mp2-v1/src/final_extraction/lengthed_circuit.rs @@ -160,7 +160,6 @@ mod test { use super::*; use base_circuit::test::{ProofsPi, ProofsPiTarget}; - use mp2_common::digest::TableDimension; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::iop::witness::WitnessWrite; @@ -215,6 +214,6 @@ mod test { let len_pi = length_extraction::PublicInputs::::from_slice(&test_circuit.len_pi); let len_dm = len_pi.metadata_point(); let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Compound, Some(len_dm)); + pis.check_proof_public_inputs(&proof, Some(len_dm)); } } diff --git a/mp2-v1/src/final_extraction/merge_circuit.rs b/mp2-v1/src/final_extraction/merge_circuit.rs index 962c601ca..01ede464a 100644 --- a/mp2-v1/src/final_extraction/merge_circuit.rs +++ b/mp2-v1/src/final_extraction/merge_circuit.rs @@ -6,7 +6,7 @@ use super::{ BaseCircuitProofInputs, PublicInputs, }; use mp2_common::{ - digest::{SplitDigestTarget, TableDimension, TableDimensionWire}, + digest::SplitDigestTarget, serialization::{deserialize, serialize}, types::CBuilder, utils::ToTargets, @@ -32,16 +32,12 @@ use verifiable_db::extraction::ExtractionPI; #[derive(Clone, Debug)] pub struct MergeTable { pub(crate) is_table_a_multiplier: bool, - pub(crate) dimension_a: TableDimension, - pub(crate) dimension_b: TableDimension, } #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct MergeTableWires { #[serde(deserialize_with = "deserialize", serialize_with = "serialize")] is_table_a_multiplier: BoolTarget, - dimension_a: TableDimensionWire, - dimension_b: TableDimensionWire, } impl MergeTable { @@ -60,17 +56,8 @@ impl MergeTable { let table_a = values_extraction::PublicInputs::new(table_a); let table_b = values_extraction::PublicInputs::new(table_b); - // prepare the table digest if they're compound or not - // At final extraction, if we're extracting a single type table, then we need to digest one - // more time the value proof digest. The value proof digest gives us SUM D(column) but at - // this stage we want D ( SUM D(column)). - // NOTE: in practice at first we only gonna have one table being the single table with a - // single row and the other one being a mapping. But this implementation should allow for - // mappings X mappings, or arrays X mappings etc. - let table_a_dimension = TableDimensionWire(b.add_virtual_bool_target_safe()); - let table_b_dimension = TableDimensionWire(b.add_virtual_bool_target_safe()); - let digest_a = table_a_dimension.conditional_row_digest(b, table_a.values_digest_target()); - let digest_b = table_b_dimension.conditional_row_digest(b, table_b.values_digest_target()); + let digest_a = table_a.values_digest_target(); + let digest_b = table_b.values_digest_target(); // Combine the two digest depending on which table is the multiplier let is_table_a_multiplier = b.add_virtual_bool_target_safe(); @@ -96,13 +83,9 @@ impl MergeTable { .register_args(b); MergeTableWires { is_table_a_multiplier, - dimension_a: table_a_dimension, - dimension_b: table_b_dimension, } } fn assign(&self, pw: &mut PartialWitness, wires: &MergeTableWires) { - self.dimension_a.assign_wire(pw, &wires.dimension_a); - self.dimension_b.assign_wire(pw, &wires.dimension_b); pw.set_bool_target(wires.is_table_a_multiplier, self.is_table_a_multiplier); } } @@ -164,7 +147,9 @@ mod test { use super::*; use base_circuit::test::{ProofsPi, ProofsPiTarget}; use mp2_common::{ - digest::SplitDigestPoint, group_hashing::weierstrass_to_point as wp, C, D, F, + digest::SplitDigestPoint, + group_hashing::{map_to_curve_point, weierstrass_to_point as wp}, + C, D, F, }; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::iop::witness::WitnessWrite; @@ -213,8 +198,6 @@ mod test { fn test_final_merge_circuit() { let pis_a = ProofsPi::random(); let pis_b = pis_a.generate_new_random_value(); - let table_a_dimension = TableDimension::Single; - let table_b_dimension = TableDimension::Compound; let table_a_multiplier = true; let test_circuit = TestMergeCircuit { @@ -222,19 +205,14 @@ mod test { pis_b: pis_b.values_pi.clone(), circuit: MergeTable { is_table_a_multiplier: table_a_multiplier, - dimension_a: table_a_dimension, - dimension_b: table_b_dimension, }, }; let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // first compute the right digest for each table according to their dimension - let table_a_digest = - table_a_dimension.conditional_row_digest(wp(&pis_a.value_inputs().values_digest())); - let table_b_digest = - table_b_dimension.conditional_row_digest(wp(&pis_b.value_inputs().values_digest())); + let table_a_digest = wp(&pis_a.value_inputs().values_digest()); + let table_b_digest = wp(&pis_b.value_inputs().values_digest()); // then do the splitting according to how we want to merge them (i.e. which is the // multiplier) let split_a = @@ -246,9 +224,10 @@ mod test { let final_digest = split_total.combine_to_row_digest(); // testing the digest values assert_eq!(final_digest, wp(&pi.value_point())); - let combined_metadata = wp(&pis_a.value_inputs().metadata_digest()) - + wp(&pis_b.value_inputs().metadata_digest()) - + wp(&pis_a.contract_inputs().metadata_point()); + let [metadata_a, metadata_b] = + [&pis_a, &pis_b].map(|pi| map_to_curve_point(pi.value_inputs().metadata_digest_raw())); + let combined_metadata = + metadata_a + metadata_b + wp(&pis_a.contract_inputs().metadata_point()); assert_eq!(combined_metadata, wp(&pi.metadata_point())); let block_pi = pis_a.block_inputs(); assert_eq!(pi.bn, block_pi.bn); 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/public_inputs.rs b/mp2-v1/src/final_extraction/public_inputs.rs index e0bb67c21..7244e7105 100644 --- a/mp2-v1/src/final_extraction/public_inputs.rs +++ b/mp2-v1/src/final_extraction/public_inputs.rs @@ -6,7 +6,7 @@ use mp2_common::{ public_inputs::{PublicInputCommon, PublicInputRange}, types::{CBuilder, CURVE_TARGET_LEN}, u256::{self, UInt256Target}, - utils::{FromFields, FromTargets, ToTargets}, + utils::{FromFields, FromTargets, ToTargets, TryIntoBool}, F, }; use plonky2::iop::target::{BoolTarget, Target}; @@ -110,6 +110,10 @@ impl PublicInputs<'_, F> { pub fn block_number(&self) -> u64 { U256::from_fields(self.bn).to() } + /// Get the merge flag + pub fn merge_flag(&self) -> bool { + self.merge[0].try_into_bool().unwrap() + } } impl<'a, T> PublicInputs<'a, T> { 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..b0a9f24aa --- /dev/null +++ b/mp2-v1/src/final_extraction/receipt_circuit.rs @@ -0,0 +1,385 @@ +use mp2_common::{ + default_config, + keccak::OutputHash, + proof::{deserialize_proof, verify_proof_fixed_circuit, ProofWithVK}, + public_inputs::PublicInputCommon, + serialization::{deserialize, serialize}, + 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 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; + +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.receipt_root == value_pi.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); + } +} + +impl CircuitLogicWires for ReceiptCircuitProofWires { + 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 { + let verification = ReceiptCircuitProofInputs::build(builder, &builder_parameters); + ReceiptExtractionCircuit::build( + builder, + verification.get_block_public_inputs(), + verification.get_value_public_inputs(), + ); + verification + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> anyhow::Result<()> { + inputs.assign_proof_targets(pw, self)?; + 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, + rlp::MAX_KEY_NIBBLE_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(MAX_KEY_NIBBLE_LEN); + 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/final_extraction/simple_circuit.rs b/mp2-v1/src/final_extraction/simple_circuit.rs index 6bd039daf..f12801998 100644 --- a/mp2-v1/src/final_extraction/simple_circuit.rs +++ b/mp2-v1/src/final_extraction/simple_circuit.rs @@ -1,10 +1,5 @@ -use derive_more::{From, Into}; -use mp2_common::{ - digest::{TableDimension, TableDimensionWire}, - public_inputs::PublicInputCommon, - utils::ToTargets, - D, F, -}; +use derive_more::From; +use mp2_common::{public_inputs::PublicInputCommon, utils::ToTargets, D, F}; use plonky2::{ iop::{target::Target, witness::PartialWitness}, plonk::circuit_builder::CircuitBuilder, @@ -22,11 +17,8 @@ use super::{ /// This circuit contains the logic to prove the final extraction of a simple /// variable (like uint256) or a mapping without an associated length slot. -#[derive(Clone, Debug, From, Into)] -pub struct SimpleCircuit(TableDimension); - -#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] -pub struct SimpleWires(TableDimensionWire); +#[derive(Clone, Debug, From)] +pub struct SimpleCircuit; impl SimpleCircuit { fn build( @@ -34,15 +26,12 @@ impl SimpleCircuit { block_pi: &[Target], contract_pi: &[Target], value_pi: &[Target], - ) -> SimpleWires { + ) { // only one value proof to verify for this circuit let base_wires = base_circuit::BaseCircuit::build(b, block_pi, contract_pi, vec![value_pi]); let value_pi = values_extraction::PublicInputs::::new(value_pi); - let dv = value_pi.values_digest_target(); - // Compute the final value digest depending on the table dimension - let dimension: TableDimensionWire = b.add_virtual_bool_target_safe().into(); - let final_dv = dimension.conditional_row_digest(b, dv); + let final_dv = value_pi.values_digest_target(); PublicInputs::new( &base_wires.bh, &base_wires.prev_bh, @@ -52,11 +41,6 @@ impl SimpleCircuit { &[b._false().target], ) .register_args(b); - SimpleWires(dimension) - } - - fn assign(&self, pw: &mut PartialWitness, wires: &SimpleWires) { - self.0.assign_wire(pw, &wires.0); } } @@ -64,20 +48,15 @@ impl SimpleCircuit { pub(crate) struct SimpleCircuitRecursiveWires { /// NOTE: assumed to be containing a single value inside, in the vec. base: BaseCircuitProofWires, - simple_wires: SimpleWires, } pub struct SimpleCircuitInput { base: BaseCircuitProofInputs, - simple: SimpleCircuit, } impl SimpleCircuitInput { - pub(crate) fn new(base: BaseCircuitProofInputs, dimension: TableDimension) -> Self { - Self { - base, - simple: dimension.into(), - } + pub(crate) fn new(base: BaseCircuitProofInputs) -> Self { + Self { base } } } @@ -95,21 +74,17 @@ impl CircuitLogicWires for SimpleCircuitRecursiveWires { ) -> Self { // only one proof to verify for this simple circuit let base = BaseCircuitProofInputs::build(builder, &builder_parameters, 1); - let wires = SimpleCircuit::build( + SimpleCircuit::build( builder, base.get_block_public_inputs(), base.get_contract_public_inputs(), base.get_value_public_inputs(), ); - Self { - base, - simple_wires: wires, - } + Self { base } } fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> anyhow::Result<()> { inputs.base.assign_proof_targets(pw, &self.base)?; - inputs.simple.assign(pw, &self.simple_wires); Ok(()) } } @@ -123,12 +98,10 @@ mod test { #[derive(Clone, Debug)] struct TestSimpleCircuit { - circuit: SimpleCircuit, pis: ProofsPi, } struct TestSimpleWires { - circuit: SimpleWires, pis: ProofsPiTarget, } @@ -136,33 +109,23 @@ mod test { type Wires = TestSimpleWires; fn build(c: &mut plonky2::plonk::circuit_builder::CircuitBuilder) -> Self::Wires { let pis = ProofsPiTarget::new(c); - let wires = SimpleCircuit::build(c, &pis.blocks_pi, &pis.contract_pi, &pis.values_pi); - TestSimpleWires { - circuit: wires, - pis, - } + SimpleCircuit::build(c, &pis.blocks_pi, &pis.contract_pi, &pis.values_pi); + TestSimpleWires { pis } } fn prove(&self, pw: &mut plonky2::iop::witness::PartialWitness, wires: &Self::Wires) { wires.pis.assign(pw, &self.pis); - self.circuit.assign(pw, &wires.circuit) } } #[test] fn test_final_simple_circuit() { let pis = ProofsPi::random(); - let test_circuit = TestSimpleCircuit { - pis: pis.clone(), - circuit: TableDimension::Compound.into(), - }; + let test_circuit = TestSimpleCircuit { pis: pis.clone() }; let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Compound, None); + pis.check_proof_public_inputs(&proof, None); - let test_circuit = TestSimpleCircuit { - pis: pis.clone(), - circuit: TableDimension::Single.into(), - }; + let test_circuit = TestSimpleCircuit { pis: pis.clone() }; let proof = run_circuit::(test_circuit); - pis.check_proof_public_inputs(&proof, TableDimension::Single, None); + pis.check_proof_public_inputs(&proof, None); } } diff --git a/mp2-v1/src/indexing/block.rs b/mp2-v1/src/indexing/block.rs index 6556a93bd..7966d0845 100644 --- a/mp2-v1/src/indexing/block.rs +++ b/mp2-v1/src/indexing/block.rs @@ -1,14 +1,60 @@ //! Module to handle the block number as a primary index -use ryhope::tree::{sbbst, TreeTopology}; - -/// The index tree when the primary index is the block number of a blockchain is a sbbst since it -/// is a highly optimized tree for monotonically increasing index. It produces very little -/// tree-manipulating operations on update, and therefore, requires the least amount of reproving -/// when adding a new index. -/// NOTE: when dealing with another type of index, i.e. a general index such as what can happen on -/// a result table, then this tree does not work anymore. -pub type BlockTree = sbbst::Tree; +use anyhow::anyhow; +use ryhope::{ + storage::{pgsql::PgsqlStorage, RoEpochKvStorage}, + tree::{sbbst, TreeTopology}, + MerkleTreeKvDb, +}; + +use crate::query::planner::TreeFetcher; + +use super::index::IndexNode; + +/// The index tree when the primary index is an epoch in a time-series DB, like the block number for a blockchain. +/// It is a sbbst since it is a highly optimized tree for monotonically increasing index. +/// It produces very little tree-manipulating operations on update, and therefore, requires the least amount +/// of reproving when adding a new index. +/// NOTE: it is still required that monotonically increasing indexes are inserted in the tree, +/// i.e. a general index such as what can happen on a result table wouldn't work with this tree. +pub type BlockTree = sbbst::EpochTree; /// The key used to refer to a table where the block number is the primary index. pub type BlockTreeKey = ::Key; /// Just an alias that give more meaning depending on the context pub type BlockPrimaryIndex = BlockTreeKey; + +pub type IndexStorage = PgsqlStorage, false>; +pub type MerkleIndexTree = MerkleTreeKvDb, IndexStorage>; + +/// Get the previous epoch of `epoch` in `tree` +pub async fn get_previous_epoch( + tree: &MerkleIndexTree, + epoch: BlockPrimaryIndex, +) -> anyhow::Result> { + let current_epoch = tree.current_epoch().await?; + let epoch_ctx = tree + .node_context(&epoch) + .await? + .ok_or(anyhow!("epoch {epoch} not found in the tree"))?; + + Ok(tree + .get_predecessor(&epoch_ctx, current_epoch) + .await + .map(|(ctx, _)| ctx.node_id)) +} + +/// Get the next epoch of `epoch` in `tree` +pub async fn get_next_epoch( + tree: &MerkleIndexTree, + epoch: BlockPrimaryIndex, +) -> anyhow::Result> { + let current_epoch = tree.current_epoch().await?; + let epoch_ctx = tree + .node_context(&epoch) + .await? + .ok_or(anyhow!("epoch {epoch} not found in the tree"))?; + + Ok(tree + .get_successor(&epoch_ctx, current_epoch) + .await + .map(|(ctx, _)| ctx.node_id)) +} diff --git a/mp2-v1/src/indexing/cell.rs b/mp2-v1/src/indexing/cell.rs index 7ad8461a2..3e6da557f 100644 --- a/mp2-v1/src/indexing/cell.rs +++ b/mp2-v1/src/indexing/cell.rs @@ -25,13 +25,13 @@ use super::ColumnID; /// By default the cells tree is a sbbst tree since it is fixed for a given table and this is the /// simplest/fastest tree. -pub type CellTree = sbbst::Tree; +pub type CellTree = sbbst::IncrementalTree; /// The key used to refer to a cell in the tree pub type CellTreeKey = ::Key; /// The storage of cell tree is "in memory" since it is never really saved on disk. Rather, it is /// always reconstructed on the fly given it is very small. Moreover, storing it on disk would /// require as many sql tables as there would be rows, making this solution highly unpracticable. -pub type CellStorage = InMemory>; +pub type CellStorage = InMemory, false>; /// The cells tree is a Merkle tree with cryptographically secure hash function committing to its /// content. pub type MerkleCellTree = @@ -50,14 +50,16 @@ pub async fn new_tree< + Serialize + for<'a> Deserialize<'a>, >() -> MerkleCellTree { - MerkleCellTree::new(InitSettings::Reset(sbbst::Tree::empty()), ()) + MerkleCellTree::new(InitSettings::Reset(sbbst::IncrementalTree::empty()), ()) .await .unwrap() } /// Cell is the information stored in a specific cell of a specific row. /// A row node in the row tree contains a vector of such cells. -#[derive(Clone, Default, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord)] +#[derive( + Clone, Copy, Default, Debug, Serialize, Deserialize, Hash, PartialEq, Eq, PartialOrd, Ord, +)] pub struct Cell { /// The unique identifier of the cell, derived from the contract it comes /// from and its slot in its storage. diff --git a/mp2-v1/src/indexing/mod.rs b/mp2-v1/src/indexing/mod.rs index 90de676e0..29e8d4480 100644 --- a/mp2-v1/src/indexing/mod.rs +++ b/mp2-v1/src/indexing/mod.rs @@ -1,6 +1,15 @@ +use anyhow::Result; + use crate::indexing::{index::IndexNode, row::RowPayload}; use alloy::primitives::U256; +use block::MerkleIndexTree; use mp2_common::types::HashOutput; +use row::MerkleRowTree; +use ryhope::{ + storage::pgsql::{SqlServerConnection, SqlStorageSettings}, + tree::scapegoat, + InitSettings, UserEpoch, +}; pub mod block; pub mod cell; @@ -9,6 +18,77 @@ pub mod row; pub type ColumnID = u64; +/// Build `MerkleIndexTree` and `MerkleRowTree` trees from tables +/// `index_table_name` and `row_table_name` in the DB with URL `db_url`. +pub async fn load_trees( + db_url: &str, + index_table_name: String, + row_table_name: String, +) -> Result<(MerkleIndexTree, MerkleRowTree)> { + let index_tree = MerkleIndexTree::new( + InitSettings::MustExist, + SqlStorageSettings { + source: SqlServerConnection::NewConnection(db_url.to_string()), + table: index_table_name.clone(), + external_mapper: None, + }, + ) + .await?; + let row_tree = MerkleRowTree::new( + InitSettings::MustExist, + SqlStorageSettings { + table: row_table_name, + source: SqlServerConnection::NewConnection(db_url.to_string()), + external_mapper: Some(index_table_name), + }, + ) + .await?; + + Ok((index_tree, row_tree)) +} + +/// Build `MerkleIndexTree` and `MerkleRowTree` trees starting from +/// `genesis_block`. The tables employed in the DB with URL `db_url` +/// to store the trees are `index_table_name` and `row_table_name`, +/// respectively. The following additional parameters are required: +/// - `alpha`: Parameter of the Scapegoat tree employed for the `MerkleRowTree` +/// - `reset_if_exist`: if true, an existing tree would be deleted +pub async fn build_trees( + db_url: &str, + index_table_name: String, + row_table_name: String, + genesis_block: UserEpoch, + alpha: scapegoat::Alpha, + reset_if_exist: bool, +) -> Result<(MerkleIndexTree, MerkleRowTree)> { + let db_settings_index = SqlStorageSettings { + source: SqlServerConnection::NewConnection(db_url.to_string()), + table: index_table_name.clone(), + external_mapper: None, + }; + let db_settings_row = SqlStorageSettings { + source: SqlServerConnection::NewConnection(db_url.to_string()), + table: row_table_name, + external_mapper: Some(index_table_name), + }; + + let index_tree = ryhope::new_index_tree( + genesis_block as UserEpoch, + db_settings_index, + reset_if_exist, + ) + .await?; + let row_tree = ryhope::new_row_tree( + genesis_block as UserEpoch, + alpha, + db_settings_row, + reset_if_exist, + ) + .await?; + + Ok((index_tree, row_tree)) +} + // NOTE this might be good to have on public API ? // cc/ @andrus pub trait LagrangeNode { diff --git a/mp2-v1/src/indexing/row.rs b/mp2-v1/src/indexing/row.rs index 04910f9ec..102cad543 100644 --- a/mp2-v1/src/indexing/row.rs +++ b/mp2-v1/src/indexing/row.rs @@ -1,4 +1,4 @@ -use super::{cell::CellTreeKey, ColumnID}; +use super::{block::BlockPrimaryIndex, cell::CellTreeKey, ColumnID}; use alloy::primitives::U256; use anyhow::Result; use derive_more::{Deref, From}; @@ -14,12 +14,19 @@ use plonky2::{ hash::hash_types::HashOut, plonk::config::{GenericHashOut, Hasher}, }; -use ryhope::{storage::pgsql::ToFromBytea, tree::scapegoat, NodePayload}; +use ryhope::{ + storage::pgsql::{PgsqlStorage, ToFromBytea}, + tree::scapegoat, + MerkleTreeKvDb, NodePayload, +}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub type RowTree = scapegoat::Tree; pub type RowTreeKeyNonce = Vec; +pub type RowStorage = PgsqlStorage, true>; +pub type MerkleRowTree = MerkleTreeKvDb, RowStorage>; + pub trait ToNonce { fn to_nonce(&self) -> RowTreeKeyNonce; } @@ -201,6 +208,9 @@ impl RowPayloa } } + pub fn column_value(&self, column_id: ColumnID) -> Option { + self.cells.get(&column_id).map(|c| c.value) + } pub fn secondary_index_value(&self) -> U256 { self.cells .get(&self.secondary_index_column) 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/length_extraction/leaf.rs b/mp2-v1/src/length_extraction/leaf.rs index a01dfe65e..531cbd6f5 100644 --- a/mp2-v1/src/length_extraction/leaf.rs +++ b/mp2-v1/src/length_extraction/leaf.rs @@ -87,7 +87,7 @@ impl LeafLengthCircuit { // we don't range check the variable and length slots as they are part of the DM public // commitment let variable_slot = cb.add_virtual_target(); - let length_slot = SimpleSlot::build(cb); + let length_slot = SimpleSlot::build_single(cb); let length_mpt = MPTLeafOrExtensionNode::build_and_advance_key::< _, @@ -124,7 +124,7 @@ impl LeafLengthCircuit { GFp::from_canonical_u8(self.variable_slot), ); - self.length_slot.assign(pw, &wires.length_slot); + self.length_slot.assign_single(pw, &wires.length_slot); wires.length_mpt.assign( pw, &Vector::from_vec(&self.length_node).expect("invalid node length"), diff --git a/mp2-v1/src/lib.rs b/mp2-v1/src/lib.rs index 3e9cb8414..4c70520c4 100644 --- a/mp2-v1/src/lib.rs +++ b/mp2-v1/src/lib.rs @@ -8,15 +8,30 @@ #![feature(generic_arg_infer)] // stylistic feature #![feature(async_closure)] -use mp2_common::mpt_sequential::PAD_LEN; - +use mp2_common::{array::L32, mpt_sequential::PAD_LEN}; +/// The maximum length of an MPT Branch Node that we accept, any larger would cause additional keccak permutation to run +/// resulting in having to have a number of different circuits for different size MPT branch nodes. pub const MAX_BRANCH_NODE_LEN: usize = 532; +/// The maximum length of an MPT Branch Node after its been padded for keccak hashing pub const MAX_BRANCH_NODE_LEN_PADDED: usize = PAD_LEN(532); /// rlp( rlp(max key 32b) + rlp(max value 32b) ) + 1 for compact encoding /// see test_len() pub const MAX_EXTENSION_NODE_LEN: usize = 69; +/// The size of a MPT extension node after it has been padded for keccak hashing. pub const MAX_EXTENSION_NODE_LEN_PADDED: usize = PAD_LEN(69); +/// rlp( rlp(max key 32b) + rlp(max value 32b) ) + 1 for compact encoding pub const MAX_LEAF_NODE_LEN: usize = MAX_EXTENSION_NODE_LEN; +/// The size of a Storage MPT leaf node after it has been padded for keccak hashing +pub const MAX_LEAF_NODE_LEN_PADDED: usize = PAD_LEN(MAX_LEAF_NODE_LEN); +/// The maximum size in bytes of a value stored inside an MPT leaf node +pub const MAX_LEAF_VALUE_LEN: usize = 32; +/// This is the length of Storage leaf value packed into u32 elements. +pub const L32_LEAF_VALUE_LEN: usize = L32(MAX_LEAF_VALUE_LEN); +/// The maximum size of receipt leaf that we accept in the code, any larger causes additiona keccak hashing to occur resulting in +/// different circuits. +pub const MAX_RECEIPT_LEAF_NODE_LEN: usize = 512; +/// This is the maxoimum number fo columns that are extracted from a log in a receipt, it corresponds to three topics and two EVM words of additional data +pub const MAX_RECEIPT_COLUMNS: usize = 5; pub mod api; pub mod block_extraction; @@ -26,3 +41,9 @@ pub mod indexing; pub mod length_extraction; pub mod query; pub mod values_extraction; + +#[cfg(test)] +pub(crate) mod tests { + /// Testing maximum columns + pub(crate) const TEST_MAX_COLUMNS: usize = 32; +} diff --git a/mp2-v1/src/query/batching_planner.rs b/mp2-v1/src/query/batching_planner.rs index ee61c4ffc..6b6cc87d1 100644 --- a/mp2-v1/src/query/batching_planner.rs +++ b/mp2-v1/src/query/batching_planner.rs @@ -8,7 +8,7 @@ use itertools::Itertools; use parsil::symbols::ContextProvider; use ryhope::{ storage::{updatetree::UpdateTree, WideLineage}, - Epoch, + UserEpoch, }; use serde::{Deserialize, Serialize}; use verifiable_db::query::{ @@ -26,7 +26,7 @@ use crate::{ query::planner::TreeFetcher, }; -use super::planner::NonExistenceInput; +use super::planner::NonExistenceInputRow; async fn compute_input_for_row>>( tree: &T, @@ -36,12 +36,12 @@ async fn compute_input_for_row RowInput { let row_path = tree - .compute_path(row_key, index_value as Epoch) + .compute_path(row_key, index_value as UserEpoch) .await .unwrap_or_else(|| panic!("node with key {:?} not found in cache", row_key)); let path = NodePath::new(row_path, index_path.clone()); let (_, row_payload) = tree - .fetch_ctx_and_payload_at(row_key, index_value as Epoch) + .fetch_ctx_and_payload_at(row_key, index_value as UserEpoch) .await .unwrap_or_else(|| panic!("node with key {:?} not found in cache", row_key)); // build row cells @@ -92,8 +92,8 @@ pub async fn generate_chunks_and_update_tree< row_cache: WideLineage>, index_cache: WideLineage>, column_ids: &ColumnIDs, - non_existence_inputs: NonExistenceInput<'_, C>, - epoch: Epoch, + non_existence_inputs: NonExistenceInputRow<'_, C>, + epoch: UserEpoch, ) -> Result<( HashMap, Vec>, UTForChunks, @@ -108,12 +108,12 @@ async fn generate_chunks( row_cache: WideLineage>, index_cache: WideLineage>, column_ids: &ColumnIDs, - non_existence_inputs: NonExistenceInput<'_, C>, + non_existence_inputs: NonExistenceInputRow<'_, C>, ) -> Result>> { let index_keys_by_epochs = index_cache.keys_by_epochs(); assert_eq!(index_keys_by_epochs.len(), 1); let row_keys_by_epochs = row_cache.keys_by_epochs(); - let current_epoch = *index_keys_by_epochs.keys().next().unwrap() as Epoch; + let current_epoch = *index_keys_by_epochs.keys().next().unwrap() as UserEpoch; let sorted_index_values = index_keys_by_epochs[¤t_epoch] .iter() .cloned() @@ -125,7 +125,7 @@ async fn generate_chunks( .await .unwrap_or_else(|| panic!("node with key {index_value} not found in index tree cache")); let proven_rows = if let Some(matching_rows) = - row_keys_by_epochs.get(&(index_value as Epoch)) + row_keys_by_epochs.get(&(index_value as UserEpoch)) { let sorted_rows = matching_rows.iter().collect::>(); stream::iter(sorted_rows.iter()) @@ -137,13 +137,13 @@ async fn generate_chunks( .await } else { let proven_node = non_existence_inputs - .find_row_node_for_non_existence(index_value) + .find_node_for_non_existence(index_value) .await - .unwrap_or_else(|_| { - panic!("node for non-existence not found for index value {index_value}") + .unwrap_or_else(|e| { + panic!("node for non-existence not found for index value {index_value}: {e:?}") }); let row_input = compute_input_for_row( - non_existence_inputs.row_tree, + non_existence_inputs.tree, &proven_node, index_value, &index_path, @@ -447,7 +447,7 @@ impl UTForChunksBuilder { /// to the proving task for that chunk fn build_update_tree_with_base_chunks( self, - epoch: Epoch, + epoch: UserEpoch, ) -> ( HashMap, Vec>, UTForChunks, diff --git a/mp2-v1/src/query/planner.rs b/mp2-v1/src/query/planner.rs index 7426da087..a24fa56f7 100644 --- a/mp2-v1/src/query/planner.rs +++ b/mp2-v1/src/query/planner.rs @@ -1,22 +1,25 @@ use alloy::primitives::U256; -use anyhow::Context; +use anyhow::{ensure, Context}; use bb8::Pool; use bb8_postgres::PostgresConnectionManager; use core::hash::Hash; use futures::stream::TryStreamExt; use itertools::Itertools; use mp2_common::types::HashOutput; -use parsil::{bracketer::bracket_secondary_index, symbols::ContextProvider, ParsilSettings}; +use parsil::{ + bracketer::{bracket_primary_index, bracket_secondary_index}, + symbols::ContextProvider, + ParsilSettings, +}; use ryhope::{ storage::{ - pgsql::{PgsqlStorage, ToFromBytea}, - updatetree::UpdateTree, - FromSettings, PayloadStorage, TransactionalStorage, TreeStorage, WideLineage, + pgsql::ToFromBytea, updatetree::UpdateTree, FromSettings, PayloadStorage, + TransactionalStorage, TreeStorage, WideLineage, }, tree::{MutableTree, NodeContext, TreeTopology}, - Epoch, MerkleTreeKvDb, NodePayload, + MerkleTreeKvDb, NodePayload, UserEpoch, }; -use std::{fmt::Debug, future::Future}; +use std::{fmt::Debug, future::Future, marker::PhantomData}; use tokio_postgres::{row::Row as PsqlRow, types::ToSql, NoTls}; use verifiable_db::query::{ api::TreePathInputs, @@ -24,14 +27,12 @@ use verifiable_db::query::{ }; use crate::indexing::{ - block::BlockPrimaryIndex, - row::{RowPayload, RowTree, RowTreeKey}, + block::{BlockPrimaryIndex, MerkleIndexTree}, + index::IndexNode, + row::{MerkleRowTree, RowPayload, RowTreeKey}, LagrangeNode, }; -/// There is only the PSQL storage fully supported for the non existence case since one needs to -/// executor particular requests on the DB in this case. -pub type DBRowStorage = PgsqlStorage>; /// The type of connection to psql backend pub type DBPool = Pool>; @@ -39,58 +40,172 @@ pub struct NonExistenceInfo { pub proving_plan: UpdateTree, } +pub type NonExistenceInputRow<'a, C> = + NonExistenceInput<'a, C, RowTreeKey, RowPayload, MerkleRowTree, true>; +pub type NonExistenceInputIndex<'a, C> = NonExistenceInput< + 'a, + C, + BlockPrimaryIndex, + IndexNode, + MerkleIndexTree, + false, +>; #[derive(Clone)] -pub struct NonExistenceInput<'a, C: ContextProvider> { - pub(crate) row_tree: &'a MerkleTreeKvDb, DBRowStorage>, +pub struct NonExistenceInput< + 'a, + C: ContextProvider, + K: Debug + Clone + Eq + PartialEq, + V: LagrangeNode, + T: TreeFetcher, + const ROWS_TREE: bool, +> { + pub(crate) tree: &'a T, pub(crate) table_name: String, pub(crate) pool: &'a DBPool, pub(crate) settings: &'a ParsilSettings, pub(crate) bounds: QueryBounds, + _k: PhantomData, + _v: PhantomData, } -impl<'a, C: ContextProvider> NonExistenceInput<'a, C> { +impl< + 'a, + C: ContextProvider, + K: Debug + Clone + Eq + PartialEq + ToFromBytea, + V: LagrangeNode, + T: TreeFetcher, + const ROWS_TREE: bool, + > NonExistenceInput<'a, C, K, V, T, ROWS_TREE> +{ pub fn new( - row_tree: &'a MerkleTreeKvDb, DBRowStorage>, + tree: &'a T, table_name: String, pool: &'a DBPool, settings: &'a ParsilSettings, bounds: &'a QueryBounds, ) -> Self { Self { - row_tree, + tree, table_name, pool, settings, bounds: bounds.clone(), + _k: PhantomData, + _v: PhantomData, } } - pub async fn find_row_node_for_non_existence( + pub async fn find_node_for_non_existence( &self, primary: BlockPrimaryIndex, - ) -> anyhow::Result { - let (query_for_min, query_for_max) = bracket_secondary_index( - &self.table_name, - self.settings, - primary as Epoch, - &self.bounds, + ) -> anyhow::Result { + let (preliminary_query, query_for_min, query_for_max) = if ROWS_TREE { + bracket_secondary_index( + &self.table_name, + self.settings, + primary as UserEpoch, + &self.bounds, + ) + } else { + bracket_primary_index(&self.table_name, primary as UserEpoch, &self.bounds) + }; + + let params = execute_row_query(self.pool, &preliminary_query, &[]).await?; + ensure!( + params.len() == 1, + "Preliminary query returned more than one row" ); + let param = params[0].get::<_, U256>(0); // try first with lower node than secondary min query bound - let to_be_proven_node = - match find_node_for_proof(self.pool, self.row_tree, query_for_min, primary, true) + let to_be_proven_node = match self + .find_node_for_proof(query_for_min.map(|q| (q, param)), primary, true) + .await? + { + Some(node) => node, + None => self + .find_node_for_proof(query_for_max.map(|q| (q, param)), primary, false) .await? - { - Some(node) => node, - None => { - find_node_for_proof(self.pool, self.row_tree, query_for_max, primary, false) - .await? - .expect("No valid node found to prove non-existence, something is wrong") - } - }; + .expect("No valid node found to prove non-existence, something is wrong"), + }; Ok(to_be_proven_node) } + + async fn find_node_for_proof( + &self, + query_with_param: Option<(String, U256)>, + primary: BlockPrimaryIndex, + is_min_query: bool, + ) -> anyhow::Result> { + let rows = if let Some((query, param)) = query_with_param { + execute_row_query(self.pool, &query, &[param]).await? + } else { + return Ok(None); + }; + if rows.is_empty() { + // no node found, return None + return Ok(None); + } + let row_key = rows[0] + .get::<_, Option>>(0) + .map(K::from_bytea) + .context("unable to parse row key tree") + .expect(""); + // among the nodes with the same index value of the node with `row_key`, we need to find + // the one that satisfies the following property: all its successor nodes have values bigger + // than `max_query_secondary`, and all its predecessor nodes have values smaller than + // `min_query_secondary`. Such a node can be found differently, depending on the case: + // - if `is_min_query = true`, then we are looking among nodes with the highest value smaller + // than `min_query_secondary` bound (call this value `min_value`); + // therefore, we need to find the "last" node among the nodes with value `min_value`, that + // is the node whose successor (if exists) has a value bigger than `min_value`. Since there + // are no nodes in the tree in the range [`min_query_secondary, max_query_secondary`], then + // the value of the successor of the "last" node is necessarily bigger than `max_query_secondary`, + // and so it implies that we found the node satisfying the property mentioned above + // - if `is_min_query = false`, then we are looking among nodes with the smallest value higher + // than `max_query_secondary` bound (call this value `max_value`); + // therefore, we need to find the "first" node among the nodes with value `max_value`, that + // is the node whose predecessor (if exists) has a value smaller than `max_value`. Since there + // are no nodes in the tree in the range [`min_query_secondary, max_query_secondary`], then + // the value of the predecessor of the "first" node is necessarily smaller than `min_query_secondary`, + // and so it implies that we found the node satisfying the property mentioned above + let (mut node_ctx, node_value) = self + .tree + .fetch_ctx_and_payload_at(&row_key, primary as UserEpoch) + .await + .unwrap(); + let value = node_value.value(); + + if is_min_query { + // starting from the node with key `row_key`, we iterate over its successor nodes in the tree, + // until we found a node that either has no successor or whose successor stores a value different + // from the value `value` stored in the node with key `row_key`; the node found is the one to be + // employed to generate the non-existence proof + let mut successor_ctx = + get_successor_node_with_same_value(self.tree, &node_ctx, value, primary).await; + while successor_ctx.is_some() { + node_ctx = successor_ctx.unwrap(); + successor_ctx = + get_successor_node_with_same_value(self.tree, &node_ctx, value, primary).await; + } + } else { + // starting from the node with key `row_key`, we iterate over its predecessor nodes in the tree, + // until we found a node that either has no predecessor or whose predecessor stores a value different + // from the value `value` stored in the node with key `row_key`; the node found is the one to be + // employed to generate the non-existence proof + let mut predecessor_ctx = + get_predecessor_node_with_same_value(self.tree, &node_ctx, value, primary).await; + while predecessor_ctx.is_some() { + node_ctx = predecessor_ctx.unwrap(); + predecessor_ctx = + get_predecessor_node_with_same_value(self.tree, &node_ctx, value, primary) + .await; + } + } + + Ok(Some(node_ctx.node_id)) + } } pub trait TreeFetcher: Sized { @@ -100,13 +215,13 @@ pub trait TreeFetcher: Sized fn fetch_ctx_and_payload_at( &self, k: &K, - epoch: Epoch, + epoch: UserEpoch, ) -> impl Future, V)>> + Send; fn compute_path( &self, node_key: &K, - epoch: Epoch, + epoch: UserEpoch, ) -> impl Future> { async move { let (node_ctx, node_payload) = self.fetch_ctx_and_payload_at(node_key, epoch).await?; @@ -152,7 +267,7 @@ pub trait TreeFetcher: Sized &self, node_ctx: NodeContext, node_payload: V, - at: Epoch, + at: UserEpoch, ) -> impl Future { async move { let child_hash = async |k: Option| -> Option { @@ -183,7 +298,7 @@ pub trait TreeFetcher: Sized fn get_successor( &self, node_ctx: &NodeContext, - epoch: Epoch, + epoch: UserEpoch, ) -> impl Future, V)>> where K: Clone + Debug + Eq + PartialEq, @@ -217,7 +332,7 @@ pub trait TreeFetcher: Sized } else { // we don't found the right child node in the tree, which means that the // successor might be out of range, so we return None - return None; + None } } else { // find successor among the ancestors of current node: we go up in the path @@ -264,7 +379,7 @@ pub trait TreeFetcher: Sized fn get_predecessor( &self, node_ctx: &NodeContext, - epoch: Epoch, + epoch: UserEpoch, ) -> impl Future, V)>> where K: Clone + Debug + Eq + PartialEq, @@ -298,7 +413,7 @@ pub trait TreeFetcher: Sized } else { // we don't found the left child node in the tree, which means that the // predecessor might be out of range, so we return None - return None; + None } } else { // find predecessor among the ancestors of current node: we go up in the path @@ -349,7 +464,11 @@ where { const IS_WIDE_LINEAGE: bool = true; - async fn fetch_ctx_and_payload_at(&self, k: &K, epoch: Epoch) -> Option<(NodeContext, V)> { + async fn fetch_ctx_and_payload_at( + &self, + k: &K, + epoch: UserEpoch, + ) -> Option<(NodeContext, V)> { self.ctx_and_payload_at(epoch, k) } } @@ -369,7 +488,7 @@ impl< async fn fetch_ctx_and_payload_at( &self, k: &T::Key, - epoch: Epoch, + epoch: UserEpoch, ) -> Option<(NodeContext, V)> { self.try_fetch_with_context_at(k, epoch) .await @@ -386,7 +505,7 @@ impl< async fn fetch_existing_node_from_tree>( tree: &T, k: &K, - epoch: Epoch, + epoch: UserEpoch, ) -> Option<(NodeContext, V)> where K: Clone + Debug + Eq + PartialEq, @@ -408,14 +527,17 @@ where // this method returns the `NodeContext` of the successor of the node provided as input, // if the successor exists in the row tree and it stores the same value of the input node (i.e., `value`); // returns `None` otherwise, as it means that the input node can be used to prove non-existence -async fn get_successor_node_with_same_value( - row_tree: &MerkleTreeKvDb, DBRowStorage>, - node_ctx: &NodeContext, +async fn get_successor_node_with_same_value< + K: Debug + Clone + Eq + PartialEq, + V: LagrangeNode, + T: TreeFetcher, +>( + tree: &T, + node_ctx: &NodeContext, value: U256, primary: BlockPrimaryIndex, -) -> Option> { - row_tree - .get_successor(node_ctx, primary as Epoch) +) -> Option> { + tree.get_successor(node_ctx, primary as UserEpoch) .await .and_then(|(successor_ctx, successor_payload)| { if successor_payload.value() != value { @@ -431,14 +553,17 @@ async fn get_successor_node_with_same_value( // this method returns the `NodeContext` of the predecessor of the node provided as input, // if the predecessor exists in the row tree and it stores the same value of the input node (i.e., `value`); // returns `None` otherwise, as it means that the input node can be used to prove non-existence -async fn get_predecessor_node_with_same_value( - row_tree: &MerkleTreeKvDb, DBRowStorage>, - node_ctx: &NodeContext, +async fn get_predecessor_node_with_same_value< + K: Debug + Clone + Eq + PartialEq, + V: LagrangeNode, + T: TreeFetcher, +>( + tree: &T, + node_ctx: &NodeContext, value: U256, primary: BlockPrimaryIndex, -) -> Option> { - row_tree - .get_predecessor(node_ctx, primary as Epoch) +) -> Option> { + tree.get_predecessor(node_ctx, primary as UserEpoch) .await .and_then(|(predecessor_ctx, predecessor_payload)| { if predecessor_payload.value() != value { @@ -451,78 +576,6 @@ async fn get_predecessor_node_with_same_value( }) } -async fn find_node_for_proof( - db: &DBPool, - row_tree: &MerkleTreeKvDb, DBRowStorage>, - query: Option, - primary: BlockPrimaryIndex, - is_min_query: bool, -) -> anyhow::Result> { - if query.is_none() { - return Ok(None); - } - let rows = execute_row_query(db, &query.unwrap(), &[]).await?; - if rows.is_empty() { - // no node found, return None - return Ok(None); - } - let row_key = rows[0] - .get::<_, Option>>(0) - .map(RowTreeKey::from_bytea) - .context("unable to parse row key tree") - .expect(""); - // among the nodes with the same index value of the node with `row_key`, we need to find - // the one that satisfies the following property: all its successor nodes have values bigger - // than `max_query_secondary`, and all its predecessor nodes have values smaller than - // `min_query_secondary`. Such a node can be found differently, depending on the case: - // - if `is_min_query = true`, then we are looking among nodes with the highest value smaller - // than `min_query_secondary` bound (call this value `min_value`); - // therefore, we need to find the "last" node among the nodes with value `min_value`, that - // is the node whose successor (if exists) has a value bigger than `min_value`. Since there - // are no nodes in the tree in the range [`min_query_secondary, max_query_secondary`], then - // the value of the successor of the "last" node is necessarily bigger than `max_query_secondary`, - // and so it implies that we found the node satisfying the property mentioned above - // - if `is_min_query = false`, then we are looking among nodes with the smallest value higher - // than `max_query_secondary` bound (call this value `max_value`); - // therefore, we need to find the "first" node among the nodes with value `max_value`, that - // is the node whose predecessor (if exists) has a value smaller than `max_value`. Since there - // are no nodes in the tree in the range [`min_query_secondary, max_query_secondary`], then - // the value of the predecessor of the "first" node is necessarily smaller than `min_query_secondary`, - // and so it implies that we found the node satisfying the property mentioned above - let (mut node_ctx, node_value) = row_tree - .fetch_with_context_at(&row_key, primary as Epoch) - .await? - .unwrap(); - let value = node_value.value(); - - if is_min_query { - // starting from the node with key `row_key`, we iterate over its successor nodes in the tree, - // until we found a node that either has no successor or whose successor stores a value different - // from the value `value` stored in the node with key `row_key`; the node found is the one to be - // employed to generate the non-existence proof - let mut successor_ctx = - get_successor_node_with_same_value(row_tree, &node_ctx, value, primary).await; - while successor_ctx.is_some() { - node_ctx = successor_ctx.unwrap(); - successor_ctx = - get_successor_node_with_same_value(row_tree, &node_ctx, value, primary).await; - } - } else { - // starting from the node with key `row_key`, we iterate over its predecessor nodes in the tree, - // until we found a node that either has no predecessor or whose predecessor stores a value different - // from the value `value` stored in the node with key `row_key`; the node found is the one to be - // employed to generate the non-existence proof - let mut predecessor_ctx = - get_predecessor_node_with_same_value(row_tree, &node_ctx, value, primary).await; - while predecessor_ctx.is_some() { - node_ctx = predecessor_ctx.unwrap(); - predecessor_ctx = - get_predecessor_node_with_same_value(row_tree, &node_ctx, value, primary).await; - } - } - - Ok(Some(node_ctx.node_id)) -} pub async fn execute_row_query2( pool: &DBPool, query: &str, @@ -569,7 +622,7 @@ async fn get_node_info_from_ctx_and_payload< tree: &T, node_ctx: NodeContext, node_payload: V, - at: Epoch, + at: UserEpoch, ) -> (NodeInfo, Option, Option) { // this looks at the value of a child node (left and right), and fetches the grandchildren // information to be able to build their respective node info. @@ -642,7 +695,7 @@ pub async fn get_node_info< >( tree: &T, k: &K, - at: Epoch, + at: UserEpoch, ) -> (NodeInfo, Option, Option) { let (node_ctx, node_payload) = tree .fetch_ctx_and_payload_at(k, at) diff --git a/mp2-v1/src/values_extraction/api.rs b/mp2-v1/src/values_extraction/api.rs index a1bcaa6a8..679848382 100644 --- a/mp2-v1/src/values_extraction/api.rs +++ b/mp2-v1/src/values_extraction/api.rs @@ -2,23 +2,35 @@ use super::{ branch::{BranchCircuit, BranchWires}, + dummy::{DummyNodeCircuit, DummyNodeWires}, extension::{ExtensionNodeCircuit, ExtensionNodeWires}, + gadgets::{ + column_info::{ExtractedColumnInfo, InputColumnInfo}, + metadata_gadget::TableMetadata, + }, leaf_mapping::{LeafMappingCircuit, LeafMappingWires}, + leaf_mapping_of_mappings::{LeafMappingOfMappingsCircuit, LeafMappingOfMappingsWires}, + leaf_receipt::{ReceiptLeafCircuit, ReceiptLeafWires}, leaf_single::{LeafSingleCircuit, LeafSingleWires}, public_inputs::PublicInputs, + ColumnId, MappingKey, INNER_KEY_ID_PREFIX, KEY_ID_PREFIX, OUTER_KEY_ID_PREFIX, }; -use crate::{api::InputNode, MAX_BRANCH_NODE_LEN, MAX_LEAF_NODE_LEN}; +use crate::{api::InputNode, MAX_BRANCH_NODE_LEN, MAX_RECEIPT_COLUMNS}; +use alloy::primitives::B256; use anyhow::{bail, ensure, Result}; use log::debug; use mp2_common::{ default_config, + eth::EventLogInfo, mpt_sequential::PAD_LEN, + poseidon::H, proof::{ProofInputSerialized, ProofWithVK}, storage_key::{MappingSlot, SimpleSlot}, C, D, F, }; use paste::paste; -use plonky2::{field::types::PrimeField64, hash::hash_types::HashOut}; +use plonky2::{field::types::PrimeField64, hash::hash_types::HashOut, plonk::config::Hasher}; +use plonky2_ecgfp5::curve::curve::Point; #[cfg(test)] use recursion_framework::framework_testing::{ new_universal_circuit_builder_for_testing, TestingRecursiveCircuits, @@ -30,30 +42,47 @@ use recursion_framework::{ use serde::{Deserialize, Serialize}; use std::array; -type LeafSingleWire = LeafSingleWires; -type LeafMappingWire = LeafMappingWires; type ExtensionInput = ProofInputSerialized; type BranchInput = ProofInputSerialized; const NUM_IO: usize = PublicInputs::::TOTAL_LEN; /// CircuitInput is a wrapper around the different specialized circuits that can /// be used to prove a MPT node recursively. -#[derive(Serialize, Deserialize)] -pub enum CircuitInput { - LeafSingle(LeafSingleCircuit), - LeafMapping(LeafMappingCircuit), +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum CircuitInput +where + [(); PAD_LEN(LEAF_LEN)]:, +{ + LeafSingle(LeafSingleCircuit), + LeafMapping(LeafMappingCircuit), + LeafMappingOfMappings(LeafMappingOfMappingsCircuit), + LeafReceipt(ReceiptLeafCircuit), Extension(ExtensionInput), - BranchSingle(BranchInput), - BranchMapping(BranchInput), + Branch(BranchInput), + Dummy(DummyNodeCircuit), } -impl CircuitInput { +impl + CircuitInput +where + [(); PAD_LEN(LEAF_LEN)]:, +{ /// Create a circuit input for proving a leaf MPT node of single variable. - pub fn new_single_variable_leaf(node: Vec, slot: u8, column_id: u64) -> Self { + pub fn new_single_variable_leaf( + node: Vec, + slot: u8, + evm_word: u32, + table_info: Vec, + ) -> Self { + let metadata = TableMetadata::new(&[], &table_info); + + let slot = SimpleSlot::new(slot); + CircuitInput::LeafSingle(LeafSingleCircuit { node, - slot: SimpleSlot::new(slot), - id: column_id, + slot, + metadata, + offset: evm_word, }) } @@ -61,18 +90,70 @@ impl CircuitInput { pub fn new_mapping_variable_leaf( node: Vec, slot: u8, - mapping_key: Vec, + mapping_key: MappingKey, key_id: u64, - value_id: u64, + evm_word: u32, + table_info: Vec, ) -> Self { + let input_column = InputColumnInfo::new(&[slot], key_id, KEY_ID_PREFIX); + + let metadata = TableMetadata::new(&[input_column], &table_info); + + let slot = MappingSlot::new(slot, mapping_key); + CircuitInput::LeafMapping(LeafMappingCircuit { node, - slot: MappingSlot::new(slot, mapping_key), - key_id, - value_id, + slot, + metadata, + offset: evm_word, }) } + /// Create a circuit input for proving a leaf MPT node of mappings where the + /// value stored in a mapping entry is another mapping. + pub fn new_mapping_of_mappings_leaf( + node: Vec, + slot: u8, + outer_key_data: (MappingKey, ColumnId), + inner_key_data: (MappingKey, ColumnId), + evm_word: u32, + table_info: Vec, + ) -> Self { + // We calculate so called "Input" columns here. These are columns that involve data not explicitly extractable from an MPT node + // but are used in proving we are looking at the correct node. For instance mapping keys are used to calculate the position of a leaf node + // that we need to extract from, but only the output of a keccak hash of some combination of them is included in the node, hence we feed them in as witness. + let outer_input_column = + InputColumnInfo::new(&[slot], outer_key_data.1, OUTER_KEY_ID_PREFIX); + let inner_input_column = + InputColumnInfo::new(&[slot], inner_key_data.1, INNER_KEY_ID_PREFIX); + + let metadata = TableMetadata::new(&[outer_input_column, inner_input_column], &table_info); + + let slot = MappingSlot::new(slot, outer_key_data.0); + + CircuitInput::LeafMappingOfMappings(LeafMappingOfMappingsCircuit { + node, + slot, + inner_key: inner_key_data.0, + metadata, + evm_word: evm_word as u8, + }) + } + + /// Create a circuit input for proving a leaf MPT node of a transaction receipt. + pub fn new_receipt_leaf( + node: &[u8], + tx_index: u64, + event: &EventLogInfo, + ) -> Self { + CircuitInput::LeafReceipt( + ReceiptLeafCircuit::::new::( + node, tx_index, event, + ) + .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 { @@ -81,19 +162,19 @@ impl CircuitInput { }) } - /// Create a circuit input for proving a branch MPT node of single variable. - pub fn new_single_variable_branch(node: Vec, child_proofs: Vec>) -> Self { - CircuitInput::BranchSingle(ProofInputSerialized { + /// Create a circuit input for proving a branch MPT node. + pub fn new_branch(node: Vec, child_proofs: Vec>) -> Self { + CircuitInput::Branch(ProofInputSerialized { input: InputNode { node }, serialized_child_proofs: child_proofs, }) } - /// Create a circuit input for proving a branch MPT node of mapping variable. - pub fn new_mapping_variable_branch(node: Vec, child_proofs: Vec>) -> Self { - CircuitInput::BranchMapping(ProofInputSerialized { - input: InputNode { node }, - serialized_child_proofs: child_proofs, + /// Create a circuit input for proving a dummy node. + pub fn new_dummy(trie_root: B256, metadata_digest: Point) -> Self { + CircuitInput::Dummy(DummyNodeCircuit { + root_hash: trie_root, + metadata_digest, }) } } @@ -103,10 +184,18 @@ impl CircuitInput { /// Most notably, it holds them in a way to use the recursion framework allowing /// us to specialize circuits according to the situation. #[derive(Eq, PartialEq, Serialize, Deserialize)] -pub struct PublicParameters { - leaf_single: CircuitWithUniversalVerifier, - leaf_mapping: CircuitWithUniversalVerifier, +pub struct PublicParameters +where + [(); PAD_LEN(LEAF_LEN)]:, +{ + leaf_single: CircuitWithUniversalVerifier>, + leaf_mapping: CircuitWithUniversalVerifier>, + leaf_mapping_of_mappings: + CircuitWithUniversalVerifier>, + leaf_receipt: + CircuitWithUniversalVerifier>, extension: CircuitWithUniversalVerifier, + dummy: CircuitWithUniversalVerifier, #[cfg(not(test))] branches: BranchCircuits, #[cfg(test)] @@ -119,17 +208,24 @@ pub struct PublicParameters { /// Public API employed to build the MPT circuits, which are returned in /// serialized form. -pub fn build_circuits_params() -> PublicParameters { +pub fn build_circuits_params( +) -> PublicParameters +where + [(); PAD_LEN(LEAF_LEN)]:, +{ PublicParameters::build() } /// Public API employed to generate a proof for the circuit specified by /// `CircuitInput`, employing the `circuit_params` generated with the /// `build_circuits_params` API. -pub fn generate_proof( - circuit_params: &PublicParameters, - circuit_type: CircuitInput, -) -> Result> { +pub fn generate_proof( + circuit_params: &PublicParameters, + circuit_type: CircuitInput, +) -> Result> +where + [(); PAD_LEN(LEAF_LEN)]:, +{ circuit_params.generate_proof(circuit_type)?.serialize() } @@ -143,7 +239,6 @@ pub trait BranchMacroTrait { set: &RecursiveCircuits, branch_node: InputNode, child_proofs: Vec, - is_simple_aggregation: bool, ) -> Result; } @@ -190,7 +285,6 @@ macro_rules! impl_branch_circuits { set: &RecursiveCircuits, branch_node: InputNode, child_proofs: Vec, - is_simple_aggregation: bool, ) -> Result { // first, determine manually the common prefix, the ptr and the mapping slot // from the public inputs of the children proofs. @@ -244,7 +338,6 @@ macro_rules! impl_branch_circuits { common_prefix, expected_pointer: pointer, n_proof_valid: $i, - is_simple_aggregation, } ).map(|p| (p, self.[< b $i >].get_verifier_data().clone()).into()) }, @@ -268,7 +361,6 @@ macro_rules! impl_branch_circuits { common_prefix, expected_pointer: pointer, n_proof_valid: num_real_proofs, - is_simple_aggregation, }, ).map(|p| (p, self.[< b $i>].get_verifier_data().clone()).into()) } @@ -285,10 +377,15 @@ 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; - -impl PublicParameters { +/// 3 branch circuits + 1 extension + 1 leaf single + 1 leaf mapping + 1 leaf mapping of mappings + 1 leaf receipt + 1 dummy circuit +const MAPPING_CIRCUIT_SET_SIZE: usize = 9; + +impl + PublicParameters +where + [(); PAD_LEN(LEAF_LEN)]:, + [(); >::HASH_SIZE]:, +{ /// Generates the circuit parameters for the MPT circuits. fn build() -> Self { let config = default_config(); @@ -305,15 +402,26 @@ impl PublicParameters { debug!("Building leaf single circuit"); let leaf_single = - circuit_builder.build_circuit::>(()); + circuit_builder.build_circuit::>(()); debug!("Building leaf mapping circuit"); let leaf_mapping = - circuit_builder.build_circuit::>(()); + circuit_builder.build_circuit::>(()); + + debug!("Building leaf mapping of mappings circuit"); + let leaf_mapping_of_mappings = 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::(()); + debug!("Building dummy circuit"); + let dummy = circuit_builder.build_circuit::(()); + debug!("Building branch circuits"); #[cfg(not(test))] let branches = BranchCircuits::new(&circuit_builder); @@ -323,7 +431,10 @@ impl PublicParameters { let mut circuits_set = vec![ leaf_single.get_verifier_data().circuit_digest, leaf_mapping.get_verifier_data().circuit_digest, + leaf_mapping_of_mappings.get_verifier_data().circuit_digest, + leaf_receipt.get_verifier_data().circuit_digest, extension.get_verifier_data().circuit_digest, + dummy.get_verifier_data().circuit_digest, ]; circuits_set.extend(branches.circuit_set()); assert_eq!(circuits_set.len(), MAPPING_CIRCUIT_SET_SIZE); @@ -331,7 +442,10 @@ impl PublicParameters { PublicParameters { leaf_single, leaf_mapping, + leaf_mapping_of_mappings, + leaf_receipt, extension, + dummy, branches, #[cfg(not(test))] set: RecursiveCircuits::new_from_circuit_digests(circuits_set), @@ -340,7 +454,10 @@ impl PublicParameters { } } - fn generate_proof(&self, circuit_type: CircuitInput) -> Result { + fn generate_proof( + &self, + circuit_type: CircuitInput, + ) -> Result { let set = &self.get_circuit_set(); match circuit_type { CircuitInput::LeafSingle(leaf) => set @@ -349,7 +466,15 @@ impl PublicParameters { CircuitInput::LeafMapping(leaf) => set .generate_proof(&self.leaf_mapping, [], [], leaf) .map(|p| (p, self.leaf_mapping.get_verifier_data().clone()).into()), - + CircuitInput::LeafMappingOfMappings(leaf) => set + .generate_proof(&self.leaf_mapping_of_mappings, [], [], leaf) + .map(|p| (p, self.leaf_mapping_of_mappings.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::Dummy(dummy) => set + .generate_proof(&self.dummy, [], [], dummy) + .map(|p| (p, self.dummy.get_verifier_data().clone()).into()), CircuitInput::Extension(ext) => { let mut child_proofs = ext.get_child_proofs()?; let (child_proof, child_vk) = child_proofs @@ -368,19 +493,13 @@ impl PublicParameters { ) .map(|p| (p, self.extension.get_verifier_data().clone()).into()) } - CircuitInput::BranchSingle(branch) => { + CircuitInput::Branch(branch) => { let child_proofs = branch.get_child_proofs()?; self.branches - .generate_proof(set, branch.input, child_proofs, true) - } - CircuitInput::BranchMapping(branch) => { - let child_proofs = branch.get_child_proofs()?; - self.branches - .generate_proof(set, branch.input, child_proofs, false) + .generate_proof(set, branch.input, child_proofs) } } } - pub(crate) fn get_circuit_set(&self) -> &RecursiveCircuits { #[cfg(not(test))] let set = &self.set; @@ -393,67 +512,294 @@ impl PublicParameters { #[cfg(test)] mod tests { - use super::{ - super::{ - compute_leaf_mapping_metadata_digest, compute_leaf_mapping_values_digest, + super::{public_inputs, StorageSlotInfo}, + *, + }; + use crate::{ + tests::TEST_MAX_COLUMNS, + values_extraction::{ + compute_leaf_mapping_metadata_digest, compute_leaf_mapping_of_mappings_metadata_digest, + compute_leaf_mapping_of_mappings_values_digest, compute_leaf_mapping_values_digest, compute_leaf_single_metadata_digest, compute_leaf_single_values_digest, - identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, public_inputs, }, - *, + MAX_RECEIPT_LEAF_NODE_LEN, }; use alloy::primitives::Address; use eth_trie::{EthTrie, MemoryDB, Trie}; + use itertools::Itertools; + use log::info; use mp2_common::{ - eth::StorageSlot, + eth::{StorageSlot, StorageSlotNode}, + group_hashing::weierstrass_to_point, mpt_sequential::utils::bytes_to_nibbles, - types::{GFp, ADDRESS_LEN}, + types::MAPPING_LEAF_VALUE_LEN, }; - use mp2_test::{mpt_sequential::generate_random_storage_mpt, utils::random_vector}; - use plonky2::field::types::Field; + use mp2_test::{ + mpt_sequential::{generate_random_storage_mpt, generate_receipt_test_info}, + utils::random_vector, + }; + use plonky2::field::types::{Field, Sample}; use plonky2_ecgfp5::curve::curve::Point; - use serial_test::serial; + use rand::{thread_rng, Rng}; use std::{str::FromStr, sync::Arc}; - const TEST_SLOT: u8 = 10; - const TEST_CONTRACT_ADDRESS: &str = "0xd6a2bfb7f76caa64dad0d13ed8a9efb73398f39e"; + type CircuitInput = super::CircuitInput; + type PublicParameters = super::PublicParameters; #[derive(Debug)] - struct TestData { + struct TestEthTrie { trie: EthTrie, mpt_keys: Vec>, - /// Key of mapping slot, or none for simple slot - mapping_key: Option>, } #[test] - fn test_values_extraction_single_variable_apis() { - test_apis(true); + fn test_values_extraction_api_single_variable() { + const TEST_SLOTS: [u8; 2] = [5, 10]; + + let _ = env_logger::try_init(); + + let storage_slot1 = StorageSlot::Simple(TEST_SLOTS[0] as usize); + let storage_slot2 = StorageSlot::Simple(TEST_SLOTS[1] as usize); + + let table_info = TEST_SLOTS + .into_iter() + .map(|slot| { + ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(slot), + ], + F::ZERO, + ) + }) + .collect_vec(); + + let test_slots = [ + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), + ]; + + test_api(test_slots); + } + + #[test] + fn test_values_extraction_api_single_struct() { + const TEST_SLOT: u8 = 2; + const TEST_EVM_WORDS: [u32; 2] = [10, 20]; + + let _ = env_logger::try_init(); + + let parent_slot = StorageSlot::Simple(TEST_SLOT as usize); + let storage_slot1 = StorageSlot::Node(StorageSlotNode::new_struct( + parent_slot.clone(), + TEST_EVM_WORDS[0], + )); + let storage_slot2 = + StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); + + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::from_canonical_u32(evm_word), + ) + }) + .collect_vec(); + + let test_slots = [ + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), + ]; + + test_api(test_slots); + } + + #[test] + fn test_values_extraction_api_mapping_variable() { + const TEST_SLOT: u8 = 2; + + let _ = env_logger::try_init(); + + let mapping_key1 = vec![10]; + let mapping_key2 = vec![20]; + let storage_slot1 = StorageSlot::Mapping(mapping_key1, TEST_SLOT as usize); + let storage_slot2 = StorageSlot::Mapping(mapping_key2, TEST_SLOT as usize); + + // The first and second column infos are same (only for testing). + let table_info = [0u32; 2] + .into_iter() + .map(|evm_word| { + ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::from_canonical_u32(evm_word), + ) + }) + .collect_vec(); + + let test_slots = [ + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), + ]; + + test_api(test_slots); } #[test] - fn test_values_extraction_mapping_variable_apis() { - test_apis(false); + fn test_values_extraction_api_mapping_struct() { + const TEST_SLOT: u8 = 2; + const TEST_EVM_WORDS: [u32; 2] = [10, 20]; + + let _ = env_logger::try_init(); + + let parent_slot = StorageSlot::Mapping(vec![10, 20], TEST_SLOT as usize); + let storage_slot1 = StorageSlot::Node(StorageSlotNode::new_struct( + parent_slot.clone(), + TEST_EVM_WORDS[0], + )); + let storage_slot2 = + StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); + + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::from_canonical_u32(evm_word), + ) + }) + .collect_vec(); + + let test_slots = [ + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), + ]; + + test_api(test_slots); } #[test] - #[serial] - fn test_values_extraction_single_variable_circuits() { - test_circuits(true, 6); + fn test_values_extraction_api_mapping_of_mappings() { + const TEST_SLOT: u8 = 2; + const TEST_EVM_WORDS: [u32; 2] = [10, 20]; + + let _ = env_logger::try_init(); + + let grand_slot = StorageSlot::Mapping(vec![10, 20], TEST_SLOT as usize); + let parent_slot = + StorageSlot::Node(StorageSlotNode::new_mapping(grand_slot, vec![30, 40]).unwrap()); + let storage_slot1 = StorageSlot::Node(StorageSlotNode::new_struct( + parent_slot.clone(), + TEST_EVM_WORDS[0], + )); + let storage_slot2 = + StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORDS[1])); + + let table_info = TEST_EVM_WORDS + .into_iter() + .map(|evm_word| { + ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::from_canonical_u32(evm_word), + ) + }) + .collect_vec(); + + let test_slots = [ + StorageSlotInfo::new(storage_slot1, table_info.clone()), + StorageSlotInfo::new(storage_slot2, table_info), + ]; + + test_api(test_slots); } #[test] - #[serial] - fn test_values_extraction_mapping_variable_circuits() { - test_circuits(false, 6); + fn test_values_extraction_api_branch_with_multiple_children() { + const TEST_SLOT: u8 = 2; + const NUM_CHILDREN: usize = 6; + + let _ = env_logger::try_init(); + + let storage_slot = StorageSlot::Simple(TEST_SLOT as usize); + let table_info = { + vec![ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::ZERO, + )] + }; + let test_slot = StorageSlotInfo::new(storage_slot, table_info); + + test_branch_with_multiple_children(NUM_CHILDREN, test_slot); } #[test] - #[serial] fn test_values_extraction_api_serialization() { - let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); - let chain_id = 10; + const TEST_SLOT: u8 = 10; + const TEST_EVM_WORD: u32 = 5; + const TEST_OUTER_KEY: [u8; 2] = [10, 20]; + const TEST_INNER_KEY: [u8; 3] = [30, 40, 50]; + + let _ = env_logger::try_init(); + + let rng = &mut thread_rng(); // Test serialization for public parameters. let params = PublicParameters::build(); @@ -475,164 +821,197 @@ mod tests { encoded_proof }; - // Test for leaf single variable circuit. - let mut test_data = generate_storage_trie_and_keys(true, TEST_SLOT, 2); - let proof = test_data.trie.get_proof(&test_data.mpt_keys[0]).unwrap(); - test_circuit_input(CircuitInput::LeafSingle(LeafSingleCircuit { - node: proof.last().unwrap().to_vec(), - slot: SimpleSlot::new(TEST_SLOT), - id: identifier_single_var_column(TEST_SLOT, &contract_address, chain_id, vec![]), - })); - - // Test for leaf mapping variable circuit. - let mut test_data = generate_storage_trie_and_keys(false, TEST_SLOT, 2); - let proof = test_data.trie.get_proof(&test_data.mpt_keys[0]).unwrap(); - let encoded = test_circuit_input(CircuitInput::LeafMapping(LeafMappingCircuit { - node: proof.last().unwrap().to_vec(), - slot: MappingSlot::new(TEST_SLOT, test_data.mapping_key.unwrap().clone()), - key_id: identifier_for_mapping_key_column( - TEST_SLOT, - &contract_address, - chain_id, - vec![], - ), - value_id: identifier_for_mapping_value_column( - TEST_SLOT, - &contract_address, - chain_id, - vec![], - ), - })); + // Construct the table info for testing. + let table_info = { + vec![ExtractedColumnInfo::sample( + true, + &[ + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::ZERO, + F::from_canonical_u8(TEST_SLOT), + ], + F::from_canonical_u32(TEST_EVM_WORD), + )] + }; - // Test for branch circuit. + // Test for single variable leaf. + let parent_slot = StorageSlot::Simple(TEST_SLOT as usize); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct( + parent_slot.clone(), + TEST_EVM_WORD, + )); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); + let mut test_trie = generate_test_trie(1, &test_slot); + let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); + test_circuit_input(CircuitInput::new_single_variable_leaf( + proof.last().unwrap().to_vec(), + TEST_SLOT, + TEST_EVM_WORD, + table_info.clone(), + )); + + // Test for mapping variable leaf. + let parent_slot = StorageSlot::Mapping(TEST_OUTER_KEY.to_vec(), TEST_SLOT as usize); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct( + parent_slot.clone(), + TEST_EVM_WORD, + )); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); + let mut test_trie = generate_test_trie(1, &test_slot); + let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); + let key_id = rng.gen(); + test_circuit_input(CircuitInput::new_mapping_variable_leaf( + proof.last().unwrap().to_vec(), + TEST_SLOT, + TEST_OUTER_KEY.to_vec(), + key_id, + TEST_EVM_WORD, + table_info.clone(), + )); + + // Test for mapping of mappings leaf. + let grand_slot = StorageSlot::Mapping(TEST_OUTER_KEY.to_vec(), TEST_SLOT as usize); + let parent_slot = StorageSlot::Node( + StorageSlotNode::new_mapping(grand_slot, TEST_INNER_KEY.to_vec()).unwrap(), + ); + let storage_slot = + StorageSlot::Node(StorageSlotNode::new_struct(parent_slot, TEST_EVM_WORD)); + let test_slot = StorageSlotInfo::new(storage_slot, table_info.clone()); + let mut test_trie = generate_test_trie(2, &test_slot); + let proof = test_trie.trie.get_proof(&test_trie.mpt_keys[0]).unwrap(); + let outer_key_id = rng.gen(); + let inner_key_id = rng.gen(); + let encoded = test_circuit_input(CircuitInput::new_mapping_of_mappings_leaf( + proof.last().unwrap().to_vec(), + TEST_SLOT, + (TEST_OUTER_KEY.to_vec(), outer_key_id), + (TEST_INNER_KEY.to_vec(), inner_key_id), + TEST_EVM_WORD, + table_info, + )); + + // Test for branch. let branch_node = proof[proof.len() - 2].to_vec(); - test_circuit_input(CircuitInput::BranchMapping(BranchInput { + test_circuit_input(CircuitInput::Branch(BranchInput { input: InputNode { node: branch_node.clone(), }, serialized_child_proofs: vec![encoded], })); + + // Test for dummy + let dummy_hash = B256::random(); + let dummy_md = Point::rand(); + test_circuit_input(CircuitInput::new_dummy(dummy_hash, dummy_md)); } - fn generate_storage_trie_and_keys( - is_simple_aggregation: bool, - slot: u8, - num_children: usize, - ) -> TestData { - let (mut trie, _) = generate_random_storage_mpt::<3, 32>(); - let (mapping_key, slot) = if is_simple_aggregation { - (None, StorageSlot::Simple(slot as usize)) - } else { - let mapping_key = random_vector(20); - ( - Some(mapping_key.clone()), - StorageSlot::Mapping(mapping_key, slot as usize), - ) - }; - let mut mpt = slot.mpt_key_vec(); - let mpt_len = mpt.len(); - let last_byte = mpt[mpt_len - 1]; - let first_nibble = last_byte & 0xF0; - let second_nibble = last_byte & 0x0F; - println!( - "key: {}, last: {}, first: {}, second: {}", - hex::encode(&mpt), - last_byte, - first_nibble, - second_nibble - ); - let mut mpt_keys = Vec::new(); - // only change the last nibble - for i in 0..num_children { - mpt[mpt_len - 1] = first_nibble + ((second_nibble + i as u8) & 0x0F); - mpt_keys.push(mpt.clone()); - } - println!( - "key1: {:?}, key2: {:?}", - hex::encode(&mpt_keys[0]), - hex::encode(&mpt_keys[1]) - ); - let v: Vec = rlp::encode(&random_vector(32)).to_vec(); - mpt_keys + fn test_api(test_slots: [StorageSlotInfo; 2]) { + info!("Generating MPT proofs"); + let memdb = Arc::new(MemoryDB::new(true)); + let mut trie = EthTrie::new(memdb.clone()); + let mpt_keys = test_slots .iter() - .for_each(|mpt| trie.insert(mpt, &v).unwrap()); + .map(|test_slot| { + let mpt_key = test_slot.slot.mpt_key(); + let value = random_vector(MAPPING_LEAF_VALUE_LEN); + trie.insert(&mpt_key, &rlp::encode(&value)).unwrap(); + mpt_key + }) + .collect_vec(); trie.root_hash().unwrap(); + let mpt_proofs = mpt_keys + .into_iter() + .map(|key| trie.get_proof(&key).unwrap()) + .collect_vec(); + // Get the branch node. + let node_len = mpt_proofs[0].len(); + // Ensure both are located in the same branch. + assert_eq!(node_len, mpt_proofs[1].len()); + let branch_node = mpt_proofs[0][node_len - 2].clone(); + assert_eq!(branch_node, mpt_proofs[1][node_len - 2]); + + info!("Generating parameters"); + let params = build_circuits_params(); - TestData { - trie, - mapping_key, - mpt_keys, - } + let leaf_proofs = test_slots + .into_iter() + .zip_eq(mpt_proofs) + .enumerate() + .map(|(i, (test_slot, mut leaf_proof))| { + info!("Proving leaf {i}"); + prove_leaf(¶ms, leaf_proof.pop().unwrap(), test_slot) + }) + .collect(); + + info!("Proving branch"); + let _branch_proof = prove_branch(¶ms, branch_node, leaf_proofs); } - fn test_apis(is_simple_aggregation: bool) { - let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); - let chain_id = 10; - - let memdb = Arc::new(MemoryDB::new(true)); - let mut trie = EthTrie::new(memdb.clone()); - - let key1 = [1u8; 4]; - let val1 = [2u8; ADDRESS_LEN]; - let slot1 = if is_simple_aggregation { - StorageSlot::Simple(TEST_SLOT as usize) + /// Generate a branch proof. + fn prove_branch( + params: &PublicParameters, + node: Vec, + leaf_proofs: Vec>, + ) -> Vec { + let input = CircuitInput::new_branch(node, leaf_proofs); + generate_proof(params, input).unwrap() + } + #[test] + fn test_receipt_api() { + let receipt_proof_infos = generate_receipt_test_info::<1, 0>(); + let receipt_proofs = receipt_proof_infos.proofs(); + let event = receipt_proof_infos.info(); + // We need two nodes that are children of the same branch so we compare the last but two nodes for each of them until we find a case that works + let (info_one, info_two) = if let Some((one, two)) = receipt_proofs + .iter() + .enumerate() + .find_map(|(i, current_proof)| { + let current_node_second_to_last = + current_proof.mpt_proof[current_proof.mpt_proof.len() - 2].clone(); + receipt_proofs + .iter() + .skip(i + 1) + .find(|find_info| { + find_info.mpt_proof[find_info.mpt_proof.len() - 2].clone() + == current_node_second_to_last + }) + .map(|matching| (current_proof, matching)) + }) { + (one, two) } else { - StorageSlot::Mapping(key1.to_vec(), TEST_SLOT as usize) + panic!("No relevant events with same branch node parent") }; - let mpt_key1 = slot1.mpt_key(); - let key2 = [3u8; 4]; - let val2 = [4u8; ADDRESS_LEN]; - let slot2 = if is_simple_aggregation { - // Must be a different slot value for single variables. - StorageSlot::Simple(TEST_SLOT as usize + 1) - } else { - // Must be the same slot value for mapping variables. - StorageSlot::Mapping(key2.to_vec(), TEST_SLOT as usize) - }; - let mpt_key2 = slot2.mpt_key(); + let proof_length_1 = info_one.mpt_proof.len(); + let proof_length_2 = info_two.mpt_proof.len(); - trie.insert(&mpt_key1, &rlp::encode(&val1.as_slice())) - .unwrap(); - trie.insert(&mpt_key2, &rlp::encode(&val2.as_slice())) - .unwrap(); - trie.root_hash().unwrap(); + let list_one = rlp::decode_list::>(&info_one.mpt_proof[proof_length_1 - 2]); + let list_two = rlp::decode_list::>(&info_two.mpt_proof[proof_length_2 - 2]); - let proof1 = trie.get_proof(&mpt_key1).unwrap(); - let proof2 = trie.get_proof(&mpt_key2).unwrap(); - assert_eq!(proof1[0], proof2[0]); + assert_eq!(list_one, list_two); + assert!(list_one.len() == 17); - // Make sure node above is really a branch node. - assert!(rlp::decode_list::>(&proof1[0]).len() == 17); println!("Generating params..."); let params = build_circuits_params(); println!("Proving leaf 1..."); - let leaf_input1 = if is_simple_aggregation { - let column_id = - identifier_single_var_column(TEST_SLOT, &contract_address, chain_id, vec![]); - CircuitInput::new_single_variable_leaf(proof1[1].clone(), TEST_SLOT, column_id) - } else { - let key_id = - identifier_for_mapping_key_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let value_id = - identifier_for_mapping_value_column(TEST_SLOT, &contract_address, chain_id, vec![]); - - CircuitInput::new_mapping_variable_leaf( - proof1[1].clone(), - TEST_SLOT, - key1.to_vec(), - key_id, - value_id, - ) - }; + let leaf_input_1 = CircuitInput::new_receipt_leaf( + info_one.mpt_proof.last().unwrap(), + info_one.tx_index, + event, + ); let now = std::time::Instant::now(); - let leaf_proof1 = generate_proof(¶ms, leaf_input1).unwrap(); + 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(); - assert_eq!(ptr, GFp::ZERO); + println!("pointer: {}", ptr); } println!( "Proof for leaf 1 generated in {} ms", @@ -640,42 +1019,25 @@ mod tests { ); println!("Proving leaf 2..."); - let leaf_input2 = if is_simple_aggregation { - let column_id = - identifier_single_var_column(TEST_SLOT + 1, &contract_address, chain_id, vec![]); - CircuitInput::new_single_variable_leaf(proof2[1].clone(), TEST_SLOT + 1, column_id) - } else { - let key_id = - identifier_for_mapping_key_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let value_id = - identifier_for_mapping_value_column(TEST_SLOT, &contract_address, chain_id, vec![]); - CircuitInput::new_mapping_variable_leaf( - proof2[1].clone(), - TEST_SLOT, - key2.to_vec(), - key_id, - value_id, - ) - }; + let leaf_input_2 = CircuitInput::new_receipt_leaf( + info_two.mpt_proof.last().unwrap(), + info_two.tx_index, + event, + ); let now = std::time::Instant::now(); - let leaf_proof2 = generate_proof(¶ms, leaf_input2).unwrap(); + 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 = if is_simple_aggregation { - CircuitInput::new_single_variable_branch( - proof1[0].clone(), - vec![leaf_proof1, leaf_proof2], - ) - } else { - CircuitInput::new_mapping_variable_branch( - proof1[0].clone(), - vec![leaf_proof1, leaf_proof2], - ) - }; + let branch_input = CircuitInput::new_branch( + info_two.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!( @@ -684,82 +1046,248 @@ mod tests { ); } - fn test_circuits(is_simple_aggregation: bool, num_children: usize) { + #[test] + fn test_dummy_api() { + println!("Generating params..."); + let params = build_circuits_params(); + + let dummy_hash = B256::random(); + let dummy_md = Point::rand(); + + println!("Proving dummy circuit"); + let dummy_input = CircuitInput::new_dummy(dummy_hash, dummy_md); + + let now = std::time::Instant::now(); + generate_proof(¶ms, dummy_input).unwrap(); + println!( + "Proof for dummy node generated in {} ms", + now.elapsed().as_millis() + ); + } + + /// Generate a leaf proof. + fn prove_leaf(params: &PublicParameters, node: Vec, test_slot: StorageSlotInfo) -> Vec { + // RLP(RLP(compact(partial_key_in_nibble)), RLP(value)) + let leaf_tuple: Vec> = rlp::decode_list(&node); + assert_eq!(leaf_tuple.len(), 2); + let value: [u8; 32] = leaf_tuple[1][1..].to_vec().try_into().unwrap(); + + let evm_word = test_slot.evm_word(); + + let table_info = test_slot.table_info(); + + // Build the identifier extra data, it's used to compute the key IDs. + const TEST_CONTRACT_ADDRESS: &str = "0x105dD0eF26b92a3698FD5AaaF688577B9Cafd970"; + const TEST_CHAIN_ID: u64 = 1000; + let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); - let chain_id = 10; - let id = identifier_single_var_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let key_id = - identifier_for_mapping_key_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let value_id = - identifier_for_mapping_value_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let params = PublicParameters::build(); - let mut test_data = - generate_storage_trie_and_keys(is_simple_aggregation, TEST_SLOT, num_children); - - let trie = &mut test_data.trie; - let mpt1 = test_data.mpt_keys[0].as_slice(); - let mpt2 = test_data.mpt_keys[1].as_slice(); - let p1 = trie.get_proof(mpt1).unwrap(); - let p2 = trie.get_proof(mpt2).unwrap(); - - // They should share the same branch node. - assert_eq!(p1.len(), p2.len()); - assert_eq!(p1[p1.len() - 2], p2[p2.len() - 2]); - - let l1_inputs = if is_simple_aggregation { - let column_id = - identifier_single_var_column(TEST_SLOT, &contract_address, chain_id, vec![]); - CircuitInput::new_single_variable_leaf( - p1.last().unwrap().to_vec(), - TEST_SLOT, - column_id, - ) - } else { - let key_id = - identifier_for_mapping_key_column(TEST_SLOT, &contract_address, chain_id, vec![]); - let value_id = - identifier_for_mapping_value_column(TEST_SLOT, &contract_address, chain_id, vec![]); - CircuitInput::new_mapping_variable_leaf( - p1.last().unwrap().to_vec(), - TEST_SLOT, - test_data.mapping_key.clone().unwrap(), - key_id, - value_id, - ) + let metadata = test_slot.table_columns(&contract_address, TEST_CHAIN_ID, vec![]); + + let (expected_metadata_digest, expected_values_digest, circuit_input) = match &test_slot + .slot + { + // Simple variable slot + StorageSlot::Simple(slot) => { + let metadata_digest = compute_leaf_single_metadata_digest(&test_slot); + let values_digest = compute_leaf_single_values_digest(&test_slot, value); + + let circuit_input = CircuitInput::new_single_variable_leaf( + node, + *slot as u8, + evm_word, + table_info.to_vec(), + ); + + (metadata_digest, values_digest, circuit_input) + } + // Mapping variable + StorageSlot::Mapping(mapping_key, slot) => { + let key_id = metadata.input_columns()[0].identifier().to_canonical_u64(); + let metadata_digest = compute_leaf_mapping_metadata_digest(&test_slot, key_id); + let values_digest = compute_leaf_mapping_values_digest( + &test_slot, + value, + mapping_key.clone(), + key_id, + ); + + let outer_key_id = metadata.input_columns()[0].identifier().0; + + let circuit_input = CircuitInput::new_mapping_variable_leaf( + node, + *slot as u8, + mapping_key.clone(), + outer_key_id, + evm_word, + table_info.to_vec(), + ); + + (metadata_digest, values_digest, circuit_input) + } + StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => match *parent.clone() { + // Simple Struct + StorageSlot::Simple(slot) => { + let metadata_digest = compute_leaf_single_metadata_digest(&test_slot); + let values_digest = compute_leaf_single_values_digest(&test_slot, value); + + let circuit_input = CircuitInput::new_single_variable_leaf( + node, + slot as u8, + evm_word, + table_info.to_vec(), + ); + + (metadata_digest, values_digest, circuit_input) + } + // Mapping Struct + StorageSlot::Mapping(mapping_key, slot) => { + let key_id = metadata.input_columns()[0].identifier().to_canonical_u64(); + let metadata_digest = compute_leaf_mapping_metadata_digest(&test_slot, key_id); + let values_digest = compute_leaf_mapping_values_digest( + &test_slot, + value, + mapping_key.clone(), + key_id, + ); + + let circuit_input = CircuitInput::new_mapping_variable_leaf( + node, + slot as u8, + mapping_key.clone(), + key_id, + evm_word, + table_info.to_vec(), + ); + + (metadata_digest, values_digest, circuit_input) + } + // Mapping of mappings Struct + StorageSlot::Node(StorageSlotNode::Mapping(grand, inner_mapping_key)) => { + match *grand { + StorageSlot::Mapping(outer_mapping_key, slot) => { + let input_columns = metadata.input_columns(); + let outer_key_id = input_columns[0].identifier().to_canonical_u64(); + let inner_key_id = input_columns[1].identifier().to_canonical_u64(); + let outer_mapping_data = (outer_mapping_key, outer_key_id); + let inner_mapping_data = (inner_mapping_key, inner_key_id); + let metadata_digest = compute_leaf_mapping_of_mappings_metadata_digest( + &test_slot, + outer_key_id, + inner_key_id, + ); + let values_digest = compute_leaf_mapping_of_mappings_values_digest( + &test_slot, + value, + outer_mapping_data.clone(), + inner_mapping_data.clone(), + ); + + let circuit_input = CircuitInput::new_mapping_of_mappings_leaf( + node, + slot as u8, + outer_mapping_data, + inner_mapping_data, + evm_word, + table_info.to_vec(), + ); + + (metadata_digest, values_digest, circuit_input) + } + _ => unreachable!(), + } + } + _ => unreachable!(), + }, + _ => unreachable!(), }; - // Generate a leaf then a branch proof with only this leaf. - println!("[+] Generating leaf proof 1..."); - let leaf1_proof_buff = generate_proof(¶ms, l1_inputs).unwrap(); - let leaf1_proof = ProofWithVK::deserialize(&leaf1_proof_buff).unwrap(); - let pub1 = leaf1_proof.proof.public_inputs[..NUM_IO].to_vec(); + let proof = generate_proof(params, circuit_input).unwrap(); + + // Check the metadata digest of public inputs. + let decoded_proof = ProofWithVK::deserialize(&proof).unwrap(); + let pi = PublicInputs::new(&decoded_proof.proof.public_inputs); + assert_eq!( + pi.metadata_digest(), + expected_metadata_digest.to_weierstrass() + ); + assert_eq!(pi.values_digest(), expected_values_digest.to_weierstrass()); + + proof + } + + /// Generate a MPT trie with sepcified number of children. + fn generate_test_trie(num_children: usize, storage_slot: &StorageSlotInfo) -> TestEthTrie { + let (mut trie, _) = generate_random_storage_mpt::<3, 32>(); + + let mut mpt_key = storage_slot.slot.mpt_key_vec(); + let mpt_len = mpt_key.len(); + let last_byte = mpt_key[mpt_len - 1]; + let first_nibble = last_byte & 0xF0; + let second_nibble = last_byte & 0x0F; + + // Generate the test MPT keys. + let mut mpt_keys = Vec::new(); + for i in 0..num_children { + // Only change the last nibble. + mpt_key[mpt_len - 1] = first_nibble + ((second_nibble + i as u8) & 0x0F); + mpt_keys.push(mpt_key.clone()); + } + + // Add the MPT keys to the trie. + let value = rlp::encode(&random_vector(32)).to_vec(); + mpt_keys + .iter() + .for_each(|key| trie.insert(key, &value).unwrap()); + trie.root_hash().unwrap(); + + TestEthTrie { trie, mpt_keys } + } + + /// Test the proof generation of one branch with the specified number of children. + fn test_branch_with_multiple_children(num_children: usize, test_slot: StorageSlotInfo) { + info!("Generating test trie"); + let mut test_trie = generate_test_trie(num_children, &test_slot); + + let mpt_key1 = test_trie.mpt_keys[0].as_slice(); + let mpt_key2 = test_trie.mpt_keys[1].as_slice(); + let proof1 = test_trie.trie.get_proof(mpt_key1).unwrap(); + let proof2 = test_trie.trie.get_proof(mpt_key2).unwrap(); + let node_len = proof1.len(); + // Get the branch node. + let branch_node = proof1[node_len - 2].clone(); + // Ensure both are located in the same branch. + assert_eq!(node_len, proof2.len()); + assert_eq!(branch_node, proof2[node_len - 2]); + + info!("Generating parameters"); + let params = build_circuits_params(); + + // Generate the branch proof with one leaf. + println!("Generating leaf proof"); + let leaf_proof_buf1 = prove_leaf(¶ms, proof1[node_len - 1].clone(), test_slot); + let leaf_proof1 = ProofWithVK::deserialize(&leaf_proof_buf1).unwrap(); + let pub1 = leaf_proof1.proof.public_inputs[..NUM_IO].to_vec(); let pi1 = PublicInputs::new(&pub1); assert_eq!(pi1.proof_inputs.len(), NUM_IO); let (_, comp_ptr) = pi1.mpt_key_info(); assert_eq!(comp_ptr, F::from_canonical_usize(63)); - - let branch_node = p1[p1.len() - 2].to_vec(); - println!("[+] Generating branch proof 1..."); - let branch_inputs = if is_simple_aggregation { - CircuitInput::new_single_variable_branch(branch_node.clone(), vec![leaf1_proof_buff]) - } else { - CircuitInput::new_mapping_variable_branch(branch_node.clone(), vec![leaf1_proof_buff]) - }; - let branch1_buff = generate_proof(¶ms, branch_inputs).unwrap(); - let branch1 = ProofWithVK::deserialize(&branch1_buff).unwrap(); + println!("Generating branch proof with one leaf"); + let branch_proof = + prove_branch(¶ms, branch_node.clone(), vec![leaf_proof_buf1.clone()]); + let branch_proof = ProofWithVK::deserialize(&branch_proof).unwrap(); let exp_vk = params.branches.b1.get_verifier_data(); - assert_eq!(branch1.verifier_data(), exp_vk); + assert_eq!(branch_proof.verifier_data(), exp_vk); - // Generate a fake proof to test branch circuit. - let gen_fake_proof = |mpt| { + // Generate a fake proof for testing branch circuit. + let gen_fake_proof = |mpt_key| { let mut pub2 = pub1.clone(); assert_eq!(pub2.len(), NUM_IO); pub2[public_inputs::K_RANGE].copy_from_slice( - &bytes_to_nibbles(mpt) + &bytes_to_nibbles(mpt_key) .into_iter() .map(F::from_canonical_u8) - .collect::>(), + .collect_vec(), ); assert_eq!(pub2.len(), pub1.len()); @@ -781,105 +1309,53 @@ mod tests { .unwrap(); let vk = params.set.verifier_data_for_input_proofs::<1>()[0].clone(); ProofWithVK::from((fake_proof[0].clone(), vk)) + .serialize() + .unwrap() }; - // Check validity of public input of `branch2` proof. - let check_public_input = |num_children, proof: &ProofWithVK| { - let branch_pub = PublicInputs::new(&proof.proof().public_inputs[..NUM_IO]); - - let value: Vec = rlp::decode(&trie.get(mpt1).unwrap().unwrap()).unwrap(); - let [leaf_values_digest, leaf_metadata_digest] = if is_simple_aggregation { - let dv = compute_leaf_single_values_digest(id, &value); - let dm = compute_leaf_single_metadata_digest(id, TEST_SLOT); - - [dv, dm] - } else { - let dv = compute_leaf_mapping_values_digest( - key_id, - value_id, - &test_data.mapping_key.clone().unwrap(), - &value, - ); - let dm = compute_leaf_mapping_metadata_digest(key_id, value_id, TEST_SLOT); + // Check the public input of branch proof. + let check_branch_public_inputs = |num_children, branch_proof: &ProofWithVK| { + let [leaf_pi, branch_pi] = [&leaf_proof1, branch_proof] + .map(|proof| PublicInputs::new(&proof.proof().public_inputs[..NUM_IO])); - [dv, dm] - }; - - let values_digest = + let leaf_metadata_digest = leaf_pi.metadata_digest(); + let leaf_values_digest = weierstrass_to_point(&leaf_pi.values_digest()); + let branch_values_digest = (0..num_children).fold(Point::NEUTRAL, |acc, _| acc + leaf_values_digest); - let metadata_digest = if is_simple_aggregation { - (0..num_children).fold(Point::NEUTRAL, |acc, _| acc + leaf_metadata_digest) - } else { - leaf_metadata_digest - }; - assert_eq!(branch_pub.values_digest(), values_digest.to_weierstrass()); + assert_eq!(branch_pi.metadata_digest(), leaf_metadata_digest); assert_eq!( - branch_pub.metadata_digest(), - metadata_digest.to_weierstrass() + branch_pi.values_digest(), + branch_values_digest.to_weierstrass(), + "Value digests did not agree pi: {:?}, calculated: {:?}", + branch_pi.values_digest(), + branch_values_digest.to_weierstrass(), ); - assert_eq!(branch_pub.n(), F::from_canonical_usize(num_children)); - - let (k1, p1) = pi1.mpt_key_info(); - let (kb, pb) = branch_pub.mpt_key_info(); - let p1 = p1.to_canonical_u64() as usize; - let pb = pb.to_canonical_u64() as usize; - assert_eq!(p1 - 1, pb); - assert_eq!(k1[..pb], kb[..pb]); + assert_eq!(branch_pi.n(), F::from_canonical_usize(num_children)); }; - // Generate a branch proof with two leafs inputs now but using the - // testing framework. We simulate another leaf at the right key, so we - // just modify the nibble at the pointer. - // Generate fake dummy proofs but with expected public inputs. - println!("[+] Generating leaf proof 2..."); - let leaf2_proof = gen_fake_proof(mpt2); - - println!("[+] Generating branch proof 2..."); - let branch_input = BranchInput { - input: InputNode { - node: branch_node.clone(), - }, - serialized_child_proofs: vec![ - bincode::serialize(&leaf1_proof).unwrap(), - bincode::serialize(&leaf2_proof).unwrap(), - ], - }; - let branch_input = if is_simple_aggregation { - CircuitInput::BranchSingle(branch_input) - } else { - CircuitInput::BranchMapping(branch_input) - }; - let branch2 = params.generate_proof(branch_input).unwrap(); + info!("Generating branch with two leaves"); + let leaf_proof_buf2 = gen_fake_proof(mpt_key2); + let branch_proof = prove_branch( + ¶ms, + branch_node.clone(), + vec![leaf_proof_buf1.clone(), leaf_proof_buf2.clone()], + ); + let branch_proof = ProofWithVK::deserialize(&branch_proof).unwrap(); let exp_vk = params.branches.b4.get_verifier_data().clone(); - assert_eq!(branch2.verifier_data(), &exp_vk); - check_public_input(2, &branch2); - - // Generate num_children-2 fake proofs to test branch circuit with - // num_children proofs. - let mut serialized_child_proofs = vec![ - bincode::serialize(&leaf1_proof).unwrap(), - bincode::serialize(&leaf2_proof).unwrap(), - ]; + assert_eq!(branch_proof.verifier_data(), &exp_vk); + check_branch_public_inputs(2, &branch_proof); + + // Generate `num_children - 2`` fake proofs. + let mut leaf_proofs = vec![leaf_proof_buf1, leaf_proof_buf2]; for i in 2..num_children { - serialized_child_proofs.push( - bincode::serialize(&gen_fake_proof(test_data.mpt_keys[i].as_slice())).unwrap(), - ) + let leaf_proof = gen_fake_proof(test_trie.mpt_keys[i].as_slice()); + leaf_proofs.push(leaf_proof); } - println!("[+] Generating branch proof {}...", num_children); - let branch_input = BranchInput { - input: InputNode { - node: branch_node.clone(), - }, - serialized_child_proofs, - }; - let branch_input = if is_simple_aggregation { - CircuitInput::BranchSingle(branch_input) - } else { - CircuitInput::BranchMapping(branch_input) - }; - let branch_proof = params.generate_proof(branch_input).unwrap(); + info!("Generating branch proof with {num_children} leaves"); + let branch_proof = prove_branch(¶ms, branch_node, leaf_proofs); + let branch_proof = ProofWithVK::deserialize(&branch_proof).unwrap(); let exp_vk = params.branches.b9.get_verifier_data().clone(); assert_eq!(branch_proof.verifier_data(), &exp_vk); - check_public_input(num_children, &branch_proof); + check_branch_public_inputs(num_children, &branch_proof); } } diff --git a/mp2-v1/src/values_extraction/branch.rs b/mp2-v1/src/values_extraction/branch.rs index afc352859..0498144f4 100644 --- a/mp2-v1/src/values_extraction/branch.rs +++ b/mp2-v1/src/values_extraction/branch.rs @@ -4,19 +4,19 @@ use super::public_inputs::{PublicInputs, PublicInputsArgs}; use anyhow::Result; use mp2_common::{ array::{Array, Vector, VectorWire}, + group_hashing::CircuitBuilderGroupHashing, 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}, - serialization::{deserialize, serialize}, + rlp::{decode_fixed_list, MAX_ITEMS_IN_LIST, MAX_KEY_NIBBLE_LEN}, types::{CBuilder, GFp}, - utils::{less_than, Endianness, PackerTarget}, + utils::{Endianness, PackerTarget}, D, }; use plonky2::{ field::types::Field, iop::{ - target::{BoolTarget, Target}, + target::Target, witness::{PartialWitness, WitnessWrite}, }, plonk::proof::ProofWithPublicInputsTarget, @@ -37,9 +37,6 @@ where node: VectorWire, root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, n_proof_valid: Target, - /// The flag is true for `simple` aggregation type, and false for `multiple` type. - #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] - is_simple_aggregation: BoolTarget, } #[derive(Clone, Debug)] @@ -48,7 +45,6 @@ pub struct BranchCircuit { pub(crate) common_prefix: Vec, pub(crate) expected_pointer: usize, pub(crate) n_proof_valid: usize, - pub(crate) is_simple_aggregation: bool, } impl BranchCircuit @@ -60,14 +56,16 @@ 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(); let ffalse = b._false(); let n_proof_valid = b.add_virtual_target(); - let is_simple_aggregation = b.add_virtual_bool_target_safe(); // Build the node and ensure it only includes bytes. let node = VectorWire::::new(b); @@ -87,9 +85,7 @@ where // addition of all children. let mut values_digest = b.curve_zero(); - // Accumulate the metadata digests of each child proof for `simple` - // aggregation type, or the digests of each child proof are same for - // `multiple` type. + // Initialize the metadata digest. let mut metadata_digest = b.curve_zero(); // we already decode the RLP headers here since we need it to verify the @@ -97,39 +93,33 @@ where let headers = decode_fixed_list::<_, D, MAX_ITEMS_IN_LIST>(b, &node.arr.arr, zero); let zero_point = b.curve_zero(); + let mut should_process = b._false(); let mut seen_nibbles = vec![]; for (i, proof_inputs) in inputs.iter().enumerate() { let it = b.constant(GFp::from_canonical_usize(i)); - let should_process = less_than(b, it, n_proof_valid, 5); + let proof_limit = b.is_equal(it, n_proof_valid); + should_process = b.or(should_process, proof_limit); // Accumulate the values digest. let child_digest = proof_inputs.values_digest_target(); - let child_digest = b.curve_select(should_process, child_digest, zero_point); + let child_digest = b.curve_select(should_process, zero_point, child_digest); values_digest = b.curve_add(values_digest, child_digest); let child_digest = proof_inputs.metadata_digest_target(); if i > 0 { - // Check if the metadata digests are same for `multiple` aggregation type. - let is_equal = b.curve_eq(metadata_digest, child_digest); - let should_check = b.not(is_simple_aggregation); - let should_check = b.or(is_equal, should_check); - b.connect(is_equal.target, should_check.target); - - // Accumulate the metadata digests for `simple` aggregation type. - let should_acc = b.and(should_process, is_simple_aggregation); - let child_digest = b.curve_select(should_acc, child_digest, zero_point); - metadata_digest = b.curve_add(metadata_digest, child_digest); + // Ensure the metadata digests of all child proofs are same. + b.connect_curve_points(metadata_digest, child_digest); } else { metadata_digest = child_digest; } // Add the number of leaves this proof has processed. - let maybe_n = b.select(should_process, proof_inputs.n(), zero); + let maybe_n = b.select(should_process, zero, proof_inputs.n()); n = b.add(n, maybe_n); 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); @@ -137,7 +127,7 @@ where // Make sure we don't process twice the same proof for same nibble. seen_nibbles.iter().for_each(|sn| { let is_equal = b.is_equal(*sn, nibble); - let should_be_false = b.select(should_process, is_equal.target, ffalse.target); + let should_be_false = b.select(should_process, ffalse.target, is_equal.target); b.connect(should_be_false, ffalse.target); }); seen_nibbles.push(nibble); @@ -176,7 +166,6 @@ where common_prefix, root, n_proof_valid, - is_simple_aggregation, } } @@ -197,7 +186,6 @@ where wires.n_proof_valid, GFp::from_canonical_usize(self.n_proof_valid), ); - pw.set_bool_target(wires.is_simple_aggregation, self.is_simple_aggregation); } } @@ -297,44 +285,25 @@ mod tests { } #[test] - fn test_values_extraction_branch_circuit_simple_type_without_padding() { - const NODE_LEN: usize = 100; - const N_REAL: usize = 2; - const N_PADDING: usize = 0; - - test_branch_circuit::(true); - } - - #[test] - fn test_values_extraction_branch_circuit_simple_type_with_padding() { - const NODE_LEN: usize = 100; - const N_REAL: usize = 2; - const N_PADDING: usize = 1; - - test_branch_circuit::(true); - } - - #[test] - fn test_values_extraction_branch_circuit_multiple_type_without_padding() { + fn test_values_extraction_branch_circuit_without_padding() { const NODE_LEN: usize = 100; const N_REAL: usize = 2; const N_PADDING: usize = 0; - test_branch_circuit::(false); + test_branch_circuit::(); } #[test] - fn test_values_extraction_branch_circuit_multiple_type_with_padding() { + fn test_values_extraction_branch_circuit_with_padding() { const NODE_LEN: usize = 100; const N_REAL: usize = 2; const N_PADDING: usize = 1; - test_branch_circuit::(false); + test_branch_circuit::(); } - fn test_branch_circuit( - is_simple_aggregation: bool, - ) where + fn test_branch_circuit() + where [(); PAD_LEN(NODE_LEN)]:, [(); N_REAL + N_PADDING]:, { @@ -397,19 +366,13 @@ mod tests { let leaf = proof.last().unwrap(); let ptr = compute_key_ptr(leaf); - let metadata = if is_simple_aggregation { - random_vector(20) - } else { - // Set the same metadata digests for `multiple` aggregation type. - metadata.clone() - }; let pi = compute_pi(ptr, &child.key, &child.value, leaf, &metadata); assert_eq!(pi.len(), PublicInputs::::TOTAL_LEN); child.proof = proof.clone(); child.leaf = leaf.clone(); child.ptr = ptr; - child.metadata = metadata; + child.metadata = metadata.clone(); child.pi = pi; } let node = children[0].proof[1].clone(); @@ -420,7 +383,6 @@ mod tests { common_prefix: bytes_to_nibbles(&children[0].key), expected_pointer: children[0].ptr, n_proof_valid: N_REAL, - is_simple_aggregation, }; // Extend the children public inputs by repeatedly copying the last real one as paddings. @@ -465,21 +427,11 @@ mod tests { } // Check metadata digest { - let branch_acc = children - .iter() - .skip(1) - .map(|child| compute_digest(child.metadata.clone())) - .fold( - compute_digest(children[0].metadata.clone()), - |acc, digest| { - if is_simple_aggregation { - acc + digest - } else { - assert_eq!(acc, digest); - acc - } - }, - ); + let branch_acc = compute_digest(children[0].metadata.clone()); + children[1..].iter().for_each(|child| { + let child_acc = compute_digest(child.metadata.clone()); + assert_eq!(child_acc, branch_acc); + }); assert_eq!(pi.metadata_digest(), branch_acc.to_weierstrass()); } diff --git a/mp2-v1/src/values_extraction/dummy.rs b/mp2-v1/src/values_extraction/dummy.rs new file mode 100644 index 000000000..c05208ae6 --- /dev/null +++ b/mp2-v1/src/values_extraction/dummy.rs @@ -0,0 +1,222 @@ +//! Module containing circuit code for a dummy value extraction circuit when an MPT contains no relevant data +//! for the object we are indexing. + +use super::public_inputs::{PublicInputs, PublicInputsArgs}; +use alloy::primitives::B256; +use anyhow::Result; +use mp2_common::{ + array::Array, + keccak::{OutputHash, PACKED_HASH_LEN}, + mpt_sequential::MPTKeyWire, + public_inputs::PublicInputCommon, + rlp::MAX_KEY_NIBBLE_LEN, + serialization::{deserialize, serialize}, + types::{CBuilder, GFp}, + utils::{Endianness, Packer, ToFields, ToTargets}, + D, +}; +use plonky2::{ + field::types::{Field, PrimeField64}, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::proof::ProofWithPublicInputsTarget, +}; +use plonky2_ecgfp5::{curve::curve::Point, gadgets::curve::CircuitBuilderEcGFp5}; +use recursion_framework::circuit_builder::CircuitLogicWires; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct DummyNodeWires { + root: Array, + metadata_digest: Vec, + key: MPTKeyWire, +} + +/// Circuit to proving the processing of an extension node +#[derive(Clone, Debug, Copy, Serialize, Deserialize)] +pub struct DummyNodeCircuit { + pub(crate) root_hash: B256, + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + pub(crate) metadata_digest: Point, +} + +impl DummyNodeCircuit { + pub fn build(b: &mut CBuilder) -> DummyNodeWires { + // Build the key wire which will have all zeroes for nibbles and the pointer set to F::NEG_ONE + let key = MPTKeyWire::new(b); + + // Build the output hash array + let root = OutputHash::new(b); + + // Build the metadata target + let dm = b.add_virtual_curve_target(); + + // Expose the public inputs. + PublicInputsArgs { + h: &root, + k: &key, + dv: b.curve_zero(), + dm, + n: b.zero(), + } + .register(b); + + DummyNodeWires { + root: root.downcast_to_targets(), + metadata_digest: dm.to_targets(), + key, + } + } + + pub fn assign(&self, pw: &mut PartialWitness, wires: &DummyNodeWires) { + // Set the root + let packed_root = self + .root_hash + .0 + .pack(Endianness::Little) + .into_iter() + .map(GFp::from_canonical_u32) + .collect::>(); + pw.set_target_arr(&wires.root.arr, &packed_root); + + pw.set_target_arr( + &wires.metadata_digest, + &self.metadata_digest.to_weierstrass().to_fields(), + ); + + // First get field negative one in usize form + let ptr = GFp::NEG_ONE.to_canonical_u64() as usize; + wires.key.assign(pw, &[0; MAX_KEY_NIBBLE_LEN], ptr); + } +} + +/// Num of children = 1 +impl CircuitLogicWires for DummyNodeWires { + type CircuitBuilderParams = (); + + type Inputs = DummyNodeCircuit; + + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + + fn circuit_logic( + builder: &mut CBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; 0], + _builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + DummyNodeCircuit::build(builder) + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { + inputs.assign(pw, self); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::{super::public_inputs::tests::new_extraction_public_inputs, *}; + + use mp2_common::{ + rlp::MAX_KEY_NIBBLE_LEN, + utils::{Endianness, Packer}, + C, D, F, + }; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{ + field::types::{Field, Sample}, + iop::{target::Target, witness::WitnessWrite}, + plonk::circuit_builder::CircuitBuilder, + }; + + #[derive(Clone, Debug)] + struct TestDummyNodeCircuit<'a> { + c: DummyNodeCircuit, + exp_pi: PublicInputs<'a, F>, + } + + impl UserCircuit for TestDummyNodeCircuit<'_> { + // Extension node wires + child public inputs + type Wires = (DummyNodeWires, Vec); + + fn build(b: &mut CircuitBuilder) -> Self::Wires { + let exp_pi = b.add_virtual_targets(PublicInputs::::TOTAL_LEN); + let ext_wires = DummyNodeCircuit::build(b); + + (ext_wires, exp_pi) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.c.assign(pw, &wires.0); + + assert_eq!(wires.1.len(), PublicInputs::::TOTAL_LEN); + assert_eq!( + self.exp_pi.proof_inputs.len(), + PublicInputs::::TOTAL_LEN + ); + pw.set_target_arr(&wires.1, self.exp_pi.proof_inputs) + } + } + + #[test] + fn test_values_extraction_dummy_node_circuit() { + // Prepare the public inputs + let random_hash = B256::random(); + let md = Point::rand(); + let random_md = md.to_weierstrass(); + let key = vec![0u8; MAX_KEY_NIBBLE_LEN]; + let ptr = GFp::NEG_ONE.to_canonical_u64() as usize; + let values_digest = Point::NEUTRAL.to_weierstrass(); + + let exp_pi = new_extraction_public_inputs( + &random_hash.0.pack(Endianness::Little), + &key, + ptr, + &values_digest, + &random_md, + 0, + ); + + let exp_pi = PublicInputs::new(&exp_pi); + + // Quick test to see if we can convert back to public inputs. + assert_eq!(random_hash.0.pack(Endianness::Little), exp_pi.root_hash()); + let (exp_key, exp_ptr) = exp_pi.mpt_key_info(); + assert_eq!( + key.iter() + .cloned() + .map(F::from_canonical_u8) + .collect::>(), + exp_key, + ); + assert_eq!(exp_ptr, GFp::NEG_ONE); + assert_eq!(Point::NEUTRAL.to_weierstrass(), exp_pi.values_digest()); + assert_eq!(random_md, exp_pi.metadata_digest()); + assert_eq!(GFp::ZERO, exp_pi.n()); + + let circuit = TestDummyNodeCircuit { + c: DummyNodeCircuit { + root_hash: random_hash, + metadata_digest: md, + }, + exp_pi: exp_pi.clone(), + }; + let proof = run_circuit::(circuit); + let pi = PublicInputs::new(&proof.public_inputs); + + { + let exp_hash = random_hash.0.pack(Endianness::Little); + assert_eq!(pi.root_hash(), exp_hash); + } + { + let (key, ptr) = pi.mpt_key_info(); + assert_eq!(key, exp_key); + + assert_eq!(ptr, exp_ptr); + } + assert_eq!(pi.values_digest(), exp_pi.values_digest()); + assert_eq!(pi.metadata_digest(), exp_pi.metadata_digest()); + assert_eq!(pi.n(), exp_pi.n()); + } +} diff --git a/mp2-v1/src/values_extraction/gadgets/column_info.rs b/mp2-v1/src/values_extraction/gadgets/column_info.rs new file mode 100644 index 000000000..f7d9084d3 --- /dev/null +++ b/mp2-v1/src/values_extraction/gadgets/column_info.rs @@ -0,0 +1,594 @@ +//! Column information for values extraction + +use itertools::{zip_eq, Itertools}; +use mp2_common::{ + array::Array, + eth::{left_pad, left_pad32}, + group_hashing::{map_to_curve_point, CircuitBuilderGroupHashing}, + keccak::PACKED_HASH_LEN, + poseidon::H, + types::{CBuilder, MAPPING_LEAF_VALUE_LEN}, + utils::{Endianness, Packer}, + CHasher, F, +}; +use plonky2::{ + field::types::{Field, Sample}, + hash::hash_types::{HashOut, HashOutTarget}, + iop::{ + target::{BoolTarget, Target}, + witness::WitnessWrite, + }, + plonk::config::Hasher, +}; +use plonky2_crypto::u32::arithmetic_u32::U32Target; +use plonky2_ecgfp5::{curve::curve::Point, gadgets::curve::CurveTarget}; +use rand::{thread_rng, Rng}; +use serde::{Deserialize, Serialize}; +use std::iter::once; + +/// Trait defining common functionality between [`InputColumnInfo`] and [`ExtractedColumnInfo`] +pub trait ColumnInfo { + /// Getter for the column identifier as a field element + fn identifier_field(&self) -> F; + + /// Getter for the identifier as a [`u64`] + fn identifier(&self) -> u64; +} + +/// This struct is used for information in MPT nodes that isn't explicitly extractable from the node itself, but is used +/// to prove that we are looking at the correct node. For instance with mapping keys the value stored for a mapping in slot `s` with key +/// `k` is `keccak(keccak(s) || k)` where we use `||` to represent concatenation. +/// +/// The metadata for these columns is also calculated slight differently so we seperate them from [`ExtractedColumnInfo`] since we never have to +/// index into an array to get the value stored in a cell of one of these columns, thus reducing cost when calculating the values digest. +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct InputColumnInfo { + /// This is the information used to identify the data relative to the contract, + /// for storage extraction its the slot, for receipts its the event signature for example + pub extraction_identifier: [F; PACKED_HASH_LEN], + /// Column identifier + pub identifier: F, + /// Prefix used in computing mpt metadata + pub metadata_prefix: [u8; 32], +} + +impl InputColumnInfo { + /// Construct a new instance of [`ColumnInfo`] + pub fn new(extraction_identifier: &[u8], identifier: u64, metadata_prefix: &[u8]) -> Self { + let extraction_vec = left_pad32(extraction_identifier).pack(Endianness::Big); + let extraction_identifier = extraction_vec + .into_iter() + .map(F::from_canonical_u32) + .collect::>() + .try_into() + .expect("This should never fail"); + let identifier = F::from_canonical_u64(identifier); + + Self { + extraction_identifier, + identifier, + metadata_prefix: left_pad::<32>(metadata_prefix), + } + } + + /// Compute the MPT metadata. + pub fn mpt_metadata(&self) -> HashOut { + // key_column_md = H( "\0KEY" || slot) + let inputs = [ + self.metadata_prefix().as_slice(), + self.extraction_id().as_slice(), + ] + .concat(); + H::hash_no_pad(&inputs) + } + + /// Compute the column information digest. + pub fn digest(&self) -> Point { + let metadata = self.mpt_metadata(); + + // digest = D(mpt_metadata || info.identifier) + let inputs = [metadata.elements.as_slice(), &[self.identifier()]].concat(); + + map_to_curve_point(&inputs) + } + + pub fn extraction_id(&self) -> [F; 8] { + self.extraction_identifier + } + + pub fn identifier(&self) -> F { + self.identifier + } + + pub fn metadata_prefix(&self) -> Vec { + self.metadata_prefix + .as_slice() + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32) + .collect() + } + + pub fn value_digest(&self, value: &[u8]) -> Point { + let bytes = left_pad32(value); + + let inputs = once(self.identifier()) + .chain( + bytes + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32), + ) + .collect_vec(); + map_to_curve_point(&inputs) + } +} + +/// This struct stores all the infomation that corresponds to data we actually extract from a MPT Leaf Node. +/// For instance in a storage leaf `self.extraction_identifier` would be the slot, `self.identifier` is this columns identifier in the table +/// `self.byte_offset` is how far from the start of the value stored in the node this extracted data begins, `self.length` is the number of bytes the data takes up +/// and `self.location_offset` is used in storage for signaling that this data is extracted from an object that may span multiple EVM words. +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Copy)] +pub struct ExtractedColumnInfo { + /// This is the information used to identify the data relative to the contract, + /// for storage extraction its the slot, for receipts its the event signature for example + pub extraction_identifier: [F; PACKED_HASH_LEN], + /// Column identifier + pub identifier: F, + /// The offset in bytes where to extract this column from some predetermined start point, + /// for storage this would be the byte offset from the start of the given EVM word, for Receipts + /// this would be either the offset from the start of the receipt or from the start of the + /// relevant log + pub byte_offset: F, + /// The length in bytes of the field to extract in the EVM word + pub length: F, + /// For storage this is the EVM word, for receipts this is either 1 or 0 and indicates whether to + /// use the relevant log offset or not. + pub location_offset: F, +} + +impl PartialOrd for ExtractedColumnInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for ExtractedColumnInfo { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.location_offset + .0 + .cmp(&other.location_offset.0) + .then(self.byte_offset.0.cmp(&other.byte_offset.0)) + } +} + +impl ExtractedColumnInfo { + /// Construct a new instance of [`ColumnInfo`] + pub fn new( + extraction_identifier: &[u8], + identifier: u64, + byte_offset: usize, + length: usize, + location_offset: u32, + ) -> Self { + let mut extraction_vec = extraction_identifier.pack(Endianness::Little); + extraction_vec.resize(PACKED_HASH_LEN, 0u32); + extraction_vec.reverse(); + let extraction_identifier = extraction_vec + .into_iter() + .map(F::from_canonical_u32) + .collect::>() + .try_into() + .expect("This should never fail"); + let identifier = F::from_canonical_u64(identifier); + let [byte_offset, length] = [byte_offset, length].map(F::from_canonical_usize); + let location_offset = F::from_canonical_u32(location_offset); + + Self { + extraction_identifier, + identifier, + byte_offset, + length, + location_offset, + } + } + + /// Create a sample column info. It could be used in integration tests. + pub fn sample_storage( + extraction_identifier: &[F; PACKED_HASH_LEN], + location_offset: F, + ) -> Self { + let rng = &mut thread_rng(); + + let length: usize = rng.gen_range(1..=MAPPING_LEAF_VALUE_LEN); + let max_byte_offset = MAPPING_LEAF_VALUE_LEN - length; + let byte_offset = F::from_canonical_usize(rng.gen_range(0..=max_byte_offset)); + let length = F::from_canonical_usize(length); + let identifier = F::rand(); + + Self { + extraction_identifier: *extraction_identifier, + identifier, + byte_offset, + length, + location_offset, + } + } + + /// Sample a new [`ExtractedColumnInfo`] at random, if `flag` is `true` then it will be for storage extraction, + /// if false it will be for receipt extraction. + pub fn sample( + flag: bool, + extraction_identifier: &[F; PACKED_HASH_LEN], + location_offset: F, + ) -> Self { + if flag { + ExtractedColumnInfo::sample_storage(extraction_identifier, location_offset) + } else { + unimplemented!() + } + } + + /// Compute the MPT metadata. + pub fn mpt_metadata(&self) -> HashOut { + // metadata = H(info.extraction_id || info.location_offset || info.byte_offset || info.length) + let inputs = [ + self.extraction_id().as_slice(), + &[self.location_offset(), self.byte_offset(), self.length()], + ] + .concat(); + + H::hash_no_pad(&inputs) + } + + /// Compute the column information digest. + pub fn digest(&self) -> Point { + let metadata = self.mpt_metadata(); + + // digest = D(mpt_metadata || info.identifier) + let inputs = [metadata.elements.as_slice(), &[self.identifier()]].concat(); + + map_to_curve_point(&inputs) + } + + pub fn extraction_id(&self) -> [F; 8] { + self.extraction_identifier + } + + pub fn identifier(&self) -> F { + self.identifier + } + + pub fn byte_offset(&self) -> F { + self.byte_offset + } + + pub fn length(&self) -> F { + self.length + } + + pub fn location_offset(&self) -> F { + self.location_offset + } + + pub fn extract_value(&self, value: &[u8]) -> [u8; 32] { + left_pad32( + &value[self.byte_offset().0 as usize + ..self.byte_offset().0 as usize + self.length.0 as usize], + ) + } + + pub fn value_digest(&self, value: &[u8]) -> Point { + // If the column identifier is zero then its a dummy column. This is because the column identifier + // is always computed as the output of a hash which is EXTREMELY unlikely to be exactly zero. + + let bytes = self.extract_value(value); + + let inputs = once(self.identifier()) + .chain( + bytes + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32), + ) + .collect_vec(); + map_to_curve_point(&inputs) + } + + pub fn receipt_value_digest(&self, value: &[u8], offset: usize) -> Point { + let start = offset + self.byte_offset().0 as usize; + let bytes = left_pad32(&value[start..start + self.length.0 as usize]); + + let inputs = once(self.identifier()) + .chain( + bytes + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32), + ) + .collect_vec(); + map_to_curve_point(&inputs) + } +} + +impl ColumnInfo for InputColumnInfo { + fn identifier_field(&self) -> F { + self.identifier + } + + fn identifier(&self) -> u64 { + self.identifier.0 + } +} + +impl ColumnInfo for ExtractedColumnInfo { + fn identifier_field(&self) -> F { + self.identifier + } + + fn identifier(&self) -> u64 { + self.identifier.0 + } +} +/// Column info +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Copy)] +pub struct ExtractedColumnInfoTarget { + /// This is the information used to identify the data relative to the contract, + /// for storage extraction its the slot, for receipts its the event signature for example + pub(crate) extraction_identifier: [Target; PACKED_HASH_LEN], + /// Column identifier + pub(crate) identifier: Target, + /// The offset in bytes where to extract this column from some predetermined start point, + /// for storage this would be the byte offset from the start of the given EVM word, for Receipts + /// this would be either the offset from the start of the receipt or from the start of the + /// relevant log + pub(crate) byte_offset: Target, + /// The length in bytes of the field to extract in the EVM word + pub(crate) length: Target, + /// For storage this is the EVM word, for receipts this is zero + pub(crate) location_offset: Target, +} + +impl ExtractedColumnInfoTarget { + /// Compute the MPT metadata. + pub fn mpt_metadata(&self, b: &mut CBuilder) -> HashOutTarget { + // metadata = H(info.extraction_id || info.location_offset || info.byte_offset || info.length) + let inputs = [ + self.extraction_id().as_slice(), + &[self.location_offset(), self.byte_offset(), self.length()], + ] + .concat(); + + b.hash_n_to_hash_no_pad::(inputs) + } + + /// Compute the column information digest. + pub fn digest(&self, b: &mut CBuilder) -> CurveTarget { + let metadata = self.mpt_metadata(b); + + // digest = D(mpt_metadata || info.identifier) + let inputs = [metadata.elements.as_slice(), &[self.identifier()]].concat(); + + b.map_to_curve_point(&inputs) + } + + pub fn extraction_id(&self) -> [Target; PACKED_HASH_LEN] { + self.extraction_identifier + } + + pub fn identifier(&self) -> Target { + self.identifier + } + + pub fn byte_offset(&self) -> Target { + self.byte_offset + } + + pub fn length(&self) -> Target { + self.length + } + + pub fn location_offset(&self) -> Target { + self.location_offset + } + + /// Functionality used to conditionally extract data from a slice. + /// `conditional` represents whether the value should actually be extracted or not, it should be set to `false` if actual extraction occurs + /// `start` is the first index we look at. + pub fn extract_value( + &self, + b: &mut CBuilder, + conditional: BoolTarget, + value: &Array, + start: Target, + ) -> Array { + let zero = b.zero(); + let mut last_byte_found = conditional; + // Even if the constant `VALUE_LEN` is larger than 32 this is the maximum size in bytes + // of data that we extract per column + let mut result_bytes = [zero; 32]; + result_bytes + .iter_mut() + .rev() + .enumerate() + .for_each(|(i, out_byte)| { + // offset = info.byte_offset + i + let index = b.constant(F::from_canonical_usize(i)); + let offset = b.sub(start, index); + // Set to 0 if found the last byte. + let offset = b.select(last_byte_found, zero, offset); + + // Since VALUE_LEN is a constant that is determined at compile time this conditional won't + // cause any issues with the circuit. + let byte = if VALUE_LEN < 64 { + b.random_access(offset, value.arr.to_vec()) + } else { + value.random_access_large_array(b, offset) + }; + + // Now if `last_byte_found` is true we add zero, otherwise add `byte` + let to_add = b.select(last_byte_found, zero, byte); + + *out_byte = b.add(*out_byte, to_add); + // is_last_byte = offset == last_byte_offset + let is_last_byte = b.is_equal(offset, self.byte_offset); + // last_byte_found |= is_last_byte + last_byte_found = b.or(last_byte_found, is_last_byte); + }); + + let result_arr = Array::::from_array(result_bytes); + + Array::::pack(&result_arr, b, Endianness::Big) + } +} + +/// Column info +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct InputColumnInfoTarget { + /// This is the information used to identify the data relative to the contract, + /// for storage extraction its the slot, for receipts its the event signature for example + pub extraction_identifier: [Target; PACKED_HASH_LEN], + /// Column identifier + pub identifier: Target, +} + +impl InputColumnInfoTarget { + /// Compute the MPT metadata. + pub fn mpt_metadata( + &self, + b: &mut CBuilder, + metadata_prefix: &[Target; PACKED_HASH_LEN], + extraction_id: &[Target; PACKED_HASH_LEN], + ) -> HashOutTarget { + // key_column_md = H( "\0KEY" || slot) + let inputs = [metadata_prefix.as_slice(), extraction_id.as_slice()].concat(); + + b.hash_n_to_hash_no_pad::(inputs) + } + + /// Compute the column information digest. + pub fn digest( + &self, + b: &mut CBuilder, + metadata_prefix: &[Target; PACKED_HASH_LEN], + extraction_id: &[Target; PACKED_HASH_LEN], + ) -> CurveTarget { + let metadata = self.mpt_metadata(b, metadata_prefix, extraction_id); + + // digest = D(mpt_metadata || info.identifier) + let inputs = [metadata.elements.as_slice(), &[self.identifier()]].concat(); + + b.map_to_curve_point(&inputs) + } + + pub fn extraction_id(&self) -> [Target; 8] { + self.extraction_identifier + } + + pub fn identifier(&self) -> Target { + self.identifier + } +} + +pub trait CircuitBuilderColumnInfo { + /// Add a virtual extracted column info target. + fn add_virtual_extracted_column_info(&mut self) -> ExtractedColumnInfoTarget; + + /// Add a virtual input column info target. + fn add_virtual_input_column_info(&mut self) -> InputColumnInfoTarget; +} + +impl CircuitBuilderColumnInfo for CBuilder { + fn add_virtual_extracted_column_info(&mut self) -> ExtractedColumnInfoTarget { + let extraction_identifier: [Target; PACKED_HASH_LEN] = self.add_virtual_target_arr(); + + let [identifier, byte_offset, length, location_offset] = self.add_virtual_target_arr(); + + ExtractedColumnInfoTarget { + extraction_identifier, + identifier, + byte_offset, + length, + location_offset, + } + } + + fn add_virtual_input_column_info(&mut self) -> InputColumnInfoTarget { + let extraction_identifier: [Target; PACKED_HASH_LEN] = self.add_virtual_target_arr(); + + let identifier = self.add_virtual_target(); + + InputColumnInfoTarget { + extraction_identifier, + identifier, + } + } +} + +pub trait WitnessWriteColumnInfo { + fn set_extracted_column_info_target( + &mut self, + target: &ExtractedColumnInfoTarget, + value: &ExtractedColumnInfo, + ); + + fn set_extracted_column_info_target_arr( + &mut self, + targets: &[ExtractedColumnInfoTarget], + values: &[ExtractedColumnInfo], + ) { + zip_eq(targets, values) + .for_each(|(target, value)| self.set_extracted_column_info_target(target, value)); + } + + fn set_input_column_info_target( + &mut self, + target: &InputColumnInfoTarget, + value: &InputColumnInfo, + ); + + fn set_input_column_info_target_arr( + &mut self, + targets: &[InputColumnInfoTarget], + values: &[InputColumnInfo], + ) { + zip_eq(targets, values) + .for_each(|(target, value)| self.set_input_column_info_target(target, value)); + } +} + +impl> WitnessWriteColumnInfo for T { + fn set_extracted_column_info_target( + &mut self, + target: &ExtractedColumnInfoTarget, + value: &ExtractedColumnInfo, + ) { + target + .extraction_identifier + .iter() + .zip(value.extraction_identifier.iter()) + .for_each(|(t, v)| self.set_target(*t, *v)); + [ + (target.identifier, value.identifier), + (target.byte_offset, value.byte_offset), + (target.length, value.length), + (target.location_offset, value.location_offset), + ] + .into_iter() + .for_each(|(t, v)| self.set_target(t, v)); + } + + fn set_input_column_info_target( + &mut self, + target: &InputColumnInfoTarget, + value: &InputColumnInfo, + ) { + target + .extraction_identifier + .iter() + .zip(value.extraction_identifier.iter()) + .for_each(|(t, v)| self.set_target(*t, *v)); + + self.set_target(target.identifier, value.identifier()); + } +} diff --git a/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs b/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs new file mode 100644 index 000000000..6118e290a --- /dev/null +++ b/mp2-v1/src/values_extraction/gadgets/metadata_gadget.rs @@ -0,0 +1,669 @@ +//! The metadata gadget is used to ensure the correct extraction from the set of all identifiers. + +use super::column_info::{ + CircuitBuilderColumnInfo, ExtractedColumnInfo, ExtractedColumnInfoTarget, InputColumnInfo, + InputColumnInfoTarget, WitnessWriteColumnInfo, +}; + +use itertools::Itertools; +use mp2_common::{ + array::{Array, Targetable}, + eth::{left_pad32, EventLogInfo, StorageSlot}, + group_hashing::CircuitBuilderGroupHashing, + keccak::PACKED_HASH_LEN, + poseidon::{empty_poseidon_hash, hash_to_int_value, H}, + serialization::{ + deserialize_array, deserialize_long_array, serialize_array, serialize_long_array, + }, + types::{CBuilder, HashOutput}, + utils::{Endianness, Packer, ToFields}, + F, +}; +use plonky2::{ + field::types::Field, + hash::hash_types::HashOut, + iop::{ + target::{BoolTarget, Target}, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::config::Hasher, +}; +use plonky2_crypto::u32::arithmetic_u32::U32Target; +use plonky2_ecgfp5::{ + curve::{curve::Point, scalar_field::Scalar}, + gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, +}; +use rand::{thread_rng, Rng}; +use serde::{Deserialize, Serialize}; +use std::{array, borrow::Borrow, iter::once}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// This struct stores the [`InputColumnInfo`] and [`ExtractedColumnInfo`] for an object that we wish to index. +/// `input_columns` are columns whose values must be provided to an extraction circuit as witness directly, for instance mapping keys for storage variables +/// or the transaction index for receipts. There will be fixed amount of them per object type that we are indexing so we can safely store them as a vec. +/// `extracted_columns` are columns whose values are stored in the value part of an MPT node. +/// `num_actual_columns` is the number of columns that aren't dummy columns. We need this since a circuit has to always have the same number of columns but not every table will need all of them. +/// +/// We use this struct so we can store all information about the columns of a table easily and use it to calculate value and metadata digests. +pub struct TableMetadata { + /// Columns that aren't extracted from the node, like the mapping keys + pub(crate) input_columns: Vec, + /// The extracted column info + pub(crate) extracted_columns: Vec, + /// Actual column number + pub(crate) num_actual_columns: usize, +} + +impl TableMetadata { + /// Create a new instance of [`TableMetadata`] from a slice of [`InputColumnInfo`] and a slice of [`ExtractedColumnInfo`] we assume that the columns are sorted into a predetermined order. + pub fn new( + input_columns: &[InputColumnInfo], + extracted_columns: &[ExtractedColumnInfo], + ) -> TableMetadata { + let num_actual_columns = extracted_columns.len() + input_columns.len(); + + TableMetadata { + input_columns: input_columns.to_vec(), + extracted_columns: extracted_columns.to_vec(), + num_actual_columns, + } + } + + /// Create a sample MPT metadata. It could be used in testing. + pub fn sample( + flag: bool, + input_prefixes: &[&[u8]], + extraction_identifier: &[u8], + location_offset: F, + ) -> Self { + let rng = &mut thread_rng(); + + let input_columns = input_prefixes + .iter() + .map(|prefix| InputColumnInfo::new(extraction_identifier, rng.gen(), prefix)) + .collect::>(); + + let num_actual_columns = rng.gen_range(1..=NUM_EXTRACTED_COLUMNS); + + let mut extraction_vec = extraction_identifier.pack(Endianness::Little); + extraction_vec.resize(8, 0u32); + extraction_vec.reverse(); + let extraction_id: [F; 8] = extraction_vec + .into_iter() + .map(F::from_canonical_u32) + .collect::>() + .try_into() + .expect("This should never fail"); + + let extracted_columns = (0..num_actual_columns) + .map(|_| ExtractedColumnInfo::sample(flag, &extraction_id, location_offset)) + .collect::>(); + + TableMetadata::new(&input_columns, &extracted_columns) + } + + /// Get the input columns + pub fn input_columns(&self) -> &[InputColumnInfo] { + self.input_columns.as_slice() + } + + /// Get the columns we actually extract from + pub fn extracted_columns(&self) -> &[ExtractedColumnInfo] { + &self.extracted_columns[..self.num_actual_columns - self.input_columns.len()] + } + + /// Compute the metadata digest. + pub fn digest(&self) -> Point { + let input_iter = self + .input_columns() + .iter() + .map(|column| column.digest()) + .collect::>(); + + let extracted_iter = self + .extracted_columns() + .iter() + .map(|column| column.digest()) + .collect::>(); + + input_iter + .into_iter() + .chain(extracted_iter) + .fold(Point::NEUTRAL, |acc, b| acc + b) + } + + /// Computes the value digest for a provided value array and the unique row_id + pub fn input_value_digest>(&self, input_vals: &[T]) -> (Point, HashOutput) { + // Make sure we have the same number of input values and columns + assert_eq!(input_vals.len(), self.input_columns.len()); + + let point = self + .input_columns() + .iter() + .zip(input_vals.iter()) + .fold(Point::NEUTRAL, |acc, (column, value)| { + acc + column.value_digest(value.borrow()) + }); + + let row_id_input = input_vals + .iter() + .flat_map(|key| { + key.borrow() + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32) + }) + .collect::>(); + + (point, H::hash_no_pad(&row_id_input).into()) + } + + pub fn extracted_value_digest(&self, value: &[u8], slot: &StorageSlot) -> Point { + let mut slot_extraction_id = [F::ZERO; 8]; + slot_extraction_id[7] = F::from_canonical_u8(slot.slot()); + let location_offset = F::from_canonical_u32(slot.evm_offset()); + self.extracted_columns() + .iter() + .fold(Point::NEUTRAL, |acc, column| { + let correct_extraction_id = slot_extraction_id == column.extraction_id(); + let correct_location = location_offset == column.location_offset(); + if correct_location && correct_extraction_id { + acc + column.value_digest(value) + } else { + acc + } + }) + } + + fn extracted_receipt_value_digest( + &self, + value: &[u8], + event: &EventLogInfo, + ) -> Point { + // Get the relevant log offset + let relevant_log_offset = event + .get_log_offset(value) + .expect("No relevant log in the provided value"); + + self.extracted_columns() + .iter() + .fold(Point::NEUTRAL, |acc, column| { + acc + column.receipt_value_digest(value, relevant_log_offset) + }) + } + + pub fn num_actual_columns(&self) -> usize { + self.num_actual_columns + } + + /// Create a new instance of [`TableMetadata`] from an [`EventLogInfo`]. Events + /// always have two input columns relating to the transaction index and gas used for the transaction. + pub fn from_event_info( + event: &EventLogInfo, + ) -> TableMetadata { + TableMetadata::from(*event) + } + + /// Function to calculate the full receipt value digest from a receipt leaf node and [`EventLogInfo`] + pub fn receipt_value_digest( + &self, + tx_index: u64, + value: &[u8], + event: &EventLogInfo, + ) -> Point { + let mut tx_index_input = [0u8; 32]; + tx_index_input[31] = tx_index as u8; + + // The actual receipt data is item 1 in the list + let node_rlp = rlp::Rlp::new(value); + let receipt_rlp = node_rlp.at(1).unwrap(); + + // 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().unwrap()[1..]); + + // The logs themselves start are the item at index 3 in this list + let gas_used_rlp = receipt_list.at(1).unwrap(); + + let gas_used_bytes = left_pad32(gas_used_rlp.data().unwrap()); + + let (input_d, row_unique_data) = + self.input_value_digest(&[&tx_index_input, &gas_used_bytes]); + let extracted_vd = self.extracted_receipt_value_digest(value, event); + + let total = input_d + extracted_vd; + + // row_id = H2int(row_unique_data || num_actual_columns) + let inputs = HashOut::from(row_unique_data) + .to_fields() + .into_iter() + .chain(std::iter::once(F::from_canonical_usize( + self.num_actual_columns, + ))) + .collect::>(); + let hash = H::hash_no_pad(&inputs); + let row_id = hash_to_int_value(hash); + + // values_digest = values_digest * row_id + let row_id = Scalar::from_noncanonical_biguint(row_id); + + total * row_id + } + + /// Computes storage values digest + pub(crate) fn storage_values_digest( + &self, + input_vals: &[[u8; 32]], + value: &[u8], + slot: &StorageSlot, + ) -> Point { + let (input_vd, row_unique) = self.input_value_digest(input_vals); + + let extract_vd = self.extracted_value_digest(value, slot); + + let inputs = if self.input_columns().is_empty() { + empty_poseidon_hash() + .to_fields() + .into_iter() + .chain(once(F::from_canonical_usize( + self.input_columns().len() + self.extracted_columns().len(), + ))) + .collect_vec() + } else { + HashOut::from(row_unique) + .to_fields() + .into_iter() + .chain(once(F::from_canonical_usize( + self.input_columns().len() + self.extracted_columns().len(), + ))) + .collect_vec() + }; + let hash = H::hash_no_pad(&inputs); + let row_id = hash_to_int_value(hash); + + // values_digest = values_digest * row_id + let row_id = Scalar::from_noncanonical_biguint(row_id); + if slot.evm_offset() == 0 { + (extract_vd + input_vd) * row_id + } else { + extract_vd * row_id + } + } +} + +impl TableMetadata { + pub(crate) fn build( + b: &mut CBuilder, + num_input_columns: usize, + ) -> TableMetadataTarget { + let real_columns = array::from_fn(|_| b.add_virtual_bool_target_safe()); + + let num_actual_columns = b.add_many(real_columns.iter().map(|bool_tar| bool_tar.target)); + let num_actual_columns = b.add_const( + num_actual_columns, + F::from_canonical_usize(num_input_columns), + ); + TableMetadataTarget { + input_columns: (0..num_input_columns) + .map(|_| b.add_virtual_input_column_info()) + .collect::>(), + extracted_columns: array::from_fn(|_| b.add_virtual_extracted_column_info()), + real_columns, + num_actual_columns, + } + } + + pub(crate) fn assign( + pw: &mut PartialWitness, + columns_metadata: &TableMetadata, + metadata_target: &TableMetadataTarget, + ) { + // First we check that we are trying to assign from a `TableMetadata` with the correct + // number of columns + assert_eq!( + columns_metadata.input_columns.len(), + metadata_target.input_columns.len() + ); + + assert!(columns_metadata.extracted_columns.len() <= MAX_EXTRACTED_COLUMNS); + + pw.set_input_column_info_target_arr( + metadata_target.input_columns.as_slice(), + columns_metadata.input_columns.as_slice(), + ); + + let padded_extracted_columns = columns_metadata + .extracted_columns + .iter() + .copied() + .chain(std::iter::repeat(columns_metadata.extracted_columns[0])) + .take(MAX_EXTRACTED_COLUMNS) + .collect::>(); + pw.set_extracted_column_info_target_arr( + metadata_target.extracted_columns.as_slice(), + padded_extracted_columns.as_slice(), + ); + + metadata_target + .real_columns + .iter() + .enumerate() + .for_each(|(i, &b_target)| { + pw.set_bool_target(b_target, i < columns_metadata.extracted_columns.len()) + }); + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub(crate) struct TableMetadataTarget { + /// Information about all input columns of the table + pub(crate) input_columns: Vec, + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + /// Information about all extracted columns of the table + pub(crate) extracted_columns: [ExtractedColumnInfoTarget; MAX_EXTRACTED_COLUMNS], + /// An Array signaling whether an extracted column is real or not + #[serde( + serialize_with = "serialize_array", + deserialize_with = "deserialize_array" + )] + pub(crate) real_columns: [BoolTarget; MAX_EXTRACTED_COLUMNS], + /// The number of actual columns + pub(crate) num_actual_columns: Target, +} + +type ReceiptExtractedOutput = ( + Array, + Array, + CurveTarget, + CurveTarget, +); + +impl TableMetadataTarget { + #[cfg(test)] + pub fn metadata_digest( + &self, + b: &mut CBuilder, + metadata_prefixes: &[&[Target; PACKED_HASH_LEN]], + extraction_id: &[Target; PACKED_HASH_LEN], + ) -> CurveTarget { + let input_points = self + .input_columns + .iter() + .zip_eq(metadata_prefixes.iter()) + .map(|(column, metadata_prefix)| column.digest(b, metadata_prefix, extraction_id)) + .collect::>(); + + let curve_zero = b.curve_zero(); + let extracted_points = self + .extracted_columns + .iter() + .zip(self.real_columns.iter()) + .map(|(column, &selector)| { + let poss_digest = column.digest(b); + b.select_curve_point(selector, poss_digest, curve_zero) + }) + .collect::>(); + + let points = [input_points, extracted_points].concat(); + + b.add_curve_point(&points) + } + + /// Computes the value digest and metadata digest for the input columns from the supplied inputs. + /// Outputs are ordered as `(MetadataDigest, ValueDigest)`. + pub(crate) fn inputs_digests( + &self, + b: &mut CBuilder, + input_values: &[Array], + metadata_prefixes: &[&[Target; PACKED_HASH_LEN]], + extraction_id: &[Target; PACKED_HASH_LEN], + ) -> (CurveTarget, CurveTarget) { + let (metadata_points, value_points): (Vec, Vec) = self + .input_columns + .iter() + .zip_eq(input_values.iter()) + .zip_eq(metadata_prefixes) + .map(|((column, input_val), metadata_prefix)| { + let inputs = once(column.identifier) + .chain(input_val.arr.iter().map(|t| t.to_target())) + .collect_vec(); + ( + column.digest(b, metadata_prefix, extraction_id), + b.map_to_curve_point(&inputs), + ) + }) + .unzip(); + + ( + b.add_curve_point(&metadata_points), + b.add_curve_point(&value_points), + ) + } + + /// Computes the value digest and metadata digest for the extracted columns from the supplied value + /// Outputs are ordered as `(MetadataDigest, ValueDigest)`. + /// The inputs `location_no_offset` and `location` represent the MPT key for the slot of this variable without an evm word offset + /// and the MPT key of the current leaf node respectively. To determine whether we should extract a value or not we check to see if + /// `location_no_offset + column.loction_offset == location`, if this is true we extract, if false we dummy the value. + pub(crate) fn extracted_digests( + &self, + b: &mut CBuilder, + value: &Array, + offset: Target, + extraction_id: &[Target; PACKED_HASH_LEN], + ) -> (CurveTarget, CurveTarget) { + let one = b.one(); + + let curve_zero = b.curve_zero(); + + let ex_id_arr = Array::::from(*extraction_id); + + let (metadata_points, value_points): (Vec, Vec) = self + .extracted_columns + .into_iter() + .zip(self.real_columns) + .map(|(column, selector)| { + // Calculate the column digest + let column_digest = column.digest(b); + + // Now we work out if the column is to be extracted, if it is we will take the value we recover from `value[column.byte_offset..column.byte_offset + column.length]` + // left padded. + let correct_offset = b.is_equal(offset, column.location_offset()); + + // We check that we have the correct base extraction id + let column_ex_id_arr = + Array::::from(column.extraction_id()); + let correct_extraction_id = column_ex_id_arr.equals(b, &ex_id_arr); + + // We only extract if we are in the correct location AND `column.is_extracted` is true + let correct_location = b.and(correct_offset, correct_extraction_id); + + // We also make sure we should actually extract for this column, otherwise we have issues + // when indexing into the array. + let correct = b.and(selector, correct_location); + + // last_byte_found lets us know whether we continue extracting or not. + // Hence if we want to extract values `extract` will be true so `last_byte_found` should be false + let last_byte_found = b.not(correct); + + // We iterate over the result bytes in reverse order, the first element that we want to access + // from `value` is `value[MAPPING_LEAF_VALUE_LEN - column.byte_offset - column.length]` and then + // we keep extracting until we reach `value[column.byte_offset]`. + + let last_byte_offset = b.add(column.byte_offset, column.length); + + let start = b.sub(last_byte_offset, one); + + let result_packed = column.extract_value(b, last_byte_found, value, start); + + let inputs = once(column.identifier) + .chain(result_packed.arr.iter().map(|t| t.to_target())) + .collect_vec(); + let value_digest = b.map_to_curve_point(&inputs); + let value_selector = b.not(correct); + + ( + b.curve_select(selector, column_digest, curve_zero), + b.curve_select(value_selector, curve_zero, value_digest), + ) + }) + .unzip(); + + ( + b.add_curve_point(&metadata_points), + b.add_curve_point(&value_points), + ) + } + + /// Computes the value digest and metadata digest for the extracted columns from the supplied value + /// Outputs are ordered as `(MetadataDigest, ValueDigest)`. + pub(crate) fn extracted_receipt_digests( + &self, + b: &mut CBuilder, + value: &Array, + log_offset: Target, + address_offset: Target, + signature_offset: Target, + ) -> ReceiptExtractedOutput { + let one = b.one(); + let curve_zero = b.curve_zero(); + + let address_start = b.add(log_offset, address_offset); + let address = value.extract_array_large::<_, _, 20>(b, address_start); + + let signature_start = b.add(log_offset, signature_offset); + let signature = value.extract_array_large::<_, _, 32>(b, signature_start); + + let (metadata_points, value_points): (Vec, Vec) = self + .extracted_columns + .into_iter() + .zip(self.real_columns) + .map(|(column, selector)| { + // Calculate the column digest + let column_digest = column.digest(b); + // If selector is true (from self.real_columns) we need it to be false when we feed it into `column.extract_value()` later. + let selector = b.not(selector); + + let location = b.add(log_offset, column.byte_offset()); + + // We iterate over the result bytes in reverse order, the first element that we want to access + // from `value` is `value[location + column.length - 1]` and then + // we keep extracting until we reach `value[location]`. + + let last_byte_offset = b.add(location, column.length); + + let start = b.sub(last_byte_offset, one); + + // Extract the value if selector is false + let result_packed = column.extract_value(b, selector, value, start); + + let inputs = once(column.identifier) + .chain(result_packed.arr.iter().map(|t| t.to_target())) + .collect_vec(); + let value_digest = b.map_to_curve_point(&inputs); + ( + b.curve_select(selector, curve_zero, column_digest), + b.curve_select(selector, curve_zero, value_digest), + ) + }) + .unzip(); + + ( + address, + signature, + b.add_curve_point(&metadata_points), + b.add_curve_point(&value_points), + ) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::tests::TEST_MAX_COLUMNS; + use mp2_common::{C, D}; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2_ecgfp5::gadgets::curve::PartialWitnessCurve; + + #[derive(Clone, Debug)] + struct TestMedataCircuit { + columns_metadata: TableMetadata, + slot: u8, + expected_num_actual_columns: usize, + expected_metadata_digest: Point, + } + + impl UserCircuit for TestMedataCircuit { + // Metadata target + slot + expected number of actual columns + expected metadata digest + type Wires = ( + TableMetadataTarget, + Target, + Target, + CurveTarget, + ); + + fn build(b: &mut CBuilder) -> Self::Wires { + let metadata_target = TableMetadata::build(b, 0); + let slot = b.add_virtual_target(); + let zero = b.zero(); + let expected_num_actual_columns = b.add_virtual_target(); + let expected_metadata_digest = b.add_virtual_curve_target(); + let extraction_id = [zero, zero, zero, zero, zero, zero, zero, slot]; + let metadata_digest = metadata_target.metadata_digest(b, &[], &extraction_id); + + b.connect_curve_points(metadata_digest, expected_metadata_digest); + + b.connect( + metadata_target.num_actual_columns, + expected_num_actual_columns, + ); + + ( + metadata_target, + slot, + expected_num_actual_columns, + expected_metadata_digest, + ) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + TableMetadata::assign(pw, &self.columns_metadata, &wires.0); + + pw.set_target(wires.1, F::from_canonical_u8(self.slot)); + pw.set_target( + wires.2, + F::from_canonical_usize(self.expected_num_actual_columns), + ); + pw.set_curve_target(wires.3, self.expected_metadata_digest.to_weierstrass()); + } + } + + #[test] + fn test_values_extraction_metadata_gadget() { + let rng = &mut thread_rng(); + + let slot = rng.gen(); + let evm_word = rng.gen(); + + let metadata = TableMetadata::sample::( + true, + &[], + &[slot], + F::from_canonical_u32(evm_word), + ); + + let expected_num_actual_columns = metadata.num_actual_columns(); + let expected_metadata_digest = metadata.digest(); + + let test_circuit = TestMedataCircuit { + columns_metadata: metadata, + slot, + expected_num_actual_columns, + expected_metadata_digest, + }; + + let _ = run_circuit::(test_circuit); + } +} diff --git a/mp2-v1/src/values_extraction/gadgets/mod.rs b/mp2-v1/src/values_extraction/gadgets/mod.rs new file mode 100644 index 000000000..c8a4684de --- /dev/null +++ b/mp2-v1/src/values_extraction/gadgets/mod.rs @@ -0,0 +1,2 @@ +pub mod column_info; +pub mod metadata_gadget; diff --git a/mp2-v1/src/values_extraction/leaf_mapping.rs b/mp2-v1/src/values_extraction/leaf_mapping.rs index 6b2c9c647..52efc4fee 100644 --- a/mp2-v1/src/values_extraction/leaf_mapping.rs +++ b/mp2-v1/src/values_extraction/leaf_mapping.rs @@ -1,20 +1,22 @@ //! Module handling the mapping entries inside a storage trie -use crate::MAX_LEAF_NODE_LEN; +use crate::values_extraction::public_inputs::{PublicInputs, PublicInputsArgs}; +use anyhow::Result; -use super::public_inputs::{PublicInputs, PublicInputsArgs}; use mp2_common::{ array::{Array, Vector, VectorWire}, + eth::left_pad32, group_hashing::CircuitBuilderGroupHashing, - keccak::{InputData, KeccakCircuit, KeccakWires}, + keccak::{InputData, KeccakCircuit, KeccakWires, PACKED_HASH_LEN}, mpt_sequential::{ utils::left_pad_leaf_value, MPTLeafOrExtensionNode, MAX_LEAF_VALUE_LEN, PAD_LEN, }, + poseidon::hash_to_int_target, public_inputs::PublicInputCommon, - storage_key::{MappingSlot, MappingSlotWires}, - types::{CBuilder, GFp, MAPPING_KEY_LEN, MAPPING_LEAF_VALUE_LEN}, - utils::{Endianness, PackerTarget}, - D, + storage_key::{MappingSlot, MappingStructSlotWires}, + types::{CBuilder, GFp, MAPPING_LEAF_VALUE_LEN}, + utils::{Endianness, Packer, ToTargets}, + CHasher, D, F, }; use plonky2::{ field::types::Field, @@ -22,81 +24,110 @@ use plonky2::{ target::Target, witness::{PartialWitness, WitnessWrite}, }, - plonk::circuit_builder::CircuitBuilder, + plonk::proof::ProofWithPublicInputsTarget, }; + +use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; -use std::iter; +use std::iter::once; + +use super::{ + gadgets::metadata_gadget::{TableMetadata, TableMetadataTarget}, + KEY_ID_PREFIX, +}; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct LeafMappingWires -where - [(); PAD_LEN(NODE_LEN)]:, -{ - node: VectorWire, - root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, - slot: MappingSlotWires, - value: Array, - key_id: Target, - value_id: Target, +pub struct LeafMappingWires { + /// Full node from the MPT proof + pub(crate) node: VectorWire, + /// Leaf value + pub(crate) value: Array, + /// MPT root + pub(crate) root: KeccakWires<{ PAD_LEN(69) }>, + /// Storage mapping variable slot + pub(crate) slot: MappingStructSlotWires, + /// MPT metadata + metadata: TableMetadataTarget, + /// The offset from the base slot + offset: Target, } /// Circuit to prove the correct derivation of the MPT key from a mapping slot #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LeafMappingCircuit { +pub struct LeafMappingCircuit { pub(crate) node: Vec, pub(crate) slot: MappingSlot, - pub(crate) key_id: u64, - pub(crate) value_id: u64, + pub(crate) metadata: TableMetadata, + pub(crate) offset: u32, } -impl LeafMappingCircuit -where - [(); PAD_LEN(NODE_LEN)]:, -{ - pub fn build(b: &mut CBuilder) -> LeafMappingWires { - let slot = MappingSlot::mpt_key(b); - let key_id = b.add_virtual_target(); - let value_id = b.add_virtual_target(); +impl LeafMappingCircuit { + pub fn build(b: &mut CBuilder) -> LeafMappingWires { + let zero = b.zero(); - // Range check for the slot byte since we don't export it as a public input for now. - b.range_check(slot.mapping_slot, 8); + let metadata = TableMetadata::build(b, 1); + let offset = b.add_virtual_target(); + let slot = MappingSlot::build_struct(b, offset); // Build the node wires. - let wires = - MPTLeafOrExtensionNode::build_and_advance_key::<_, D, NODE_LEN, MAX_LEAF_VALUE_LEN>( - b, - &slot.keccak_mpt.mpt_key, - ); + let wires = MPTLeafOrExtensionNode::build_and_advance_key::<_, D, 69, MAX_LEAF_VALUE_LEN>( + b, + &slot.keccak_mpt.base.mpt_key, + ); let node = wires.node; let root = wires.root; // Left pad the leaf value. - let value = left_pad_leaf_value(b, &wires.value); - - // Compute the metadata digest - D(key_id || value_id || slot). - let metadata_digest = b.map_to_curve_point(&[key_id, value_id, slot.mapping_slot]); - - // Compute the values digest - D(D(key_id || key) + D(value_id || value)). - assert_eq!(slot.mapping_key.arr.len(), MAPPING_KEY_LEN); - assert_eq!(value.arr.len(), MAPPING_LEAF_VALUE_LEN); - let [packed_key, packed_value] = - [&slot.mapping_key, &value].map(|arr| arr.arr.pack(b, Endianness::Big)); - let inputs: Vec<_> = iter::once(key_id).chain(packed_key).collect(); - let k_digest = b.map_to_curve_point(&inputs); - let inputs: Vec<_> = iter::once(value_id).chain(packed_value).collect(); - let v_digest = b.map_to_curve_point(&inputs); - // D(key_id || key) + D(value_id || value) - let add_digest = b.curve_add(k_digest, v_digest); - let inputs: Vec<_> = add_digest - .0 - .0 + let value: Array = left_pad_leaf_value(b, &wires.value); + + // Compute the metadata digest and the value digest + let packed_mapping_key = slot.mapping_key.pack(b, Endianness::Big); + + let key_prefix: [Target; PACKED_HASH_LEN] = left_pad32(KEY_ID_PREFIX) + .pack(Endianness::Big) + .iter() + .map(|num| b.constant(F::from_canonical_u32(*num))) + .collect::>() + .try_into() + .expect("This should never fail"); + + let extraction_id = [zero, zero, zero, zero, zero, zero, zero, slot.mapping_slot]; + let (input_metadata_digest, input_value_digest) = metadata.inputs_digests( + b, + &[packed_mapping_key.clone()], + &[&key_prefix], + &extraction_id, + ); + let (extracted_metadata_digest, extracted_value_digest) = + metadata.extracted_digests::(b, &value, offset, &extraction_id); + + let selector = b.is_equal(zero, offset); + let curve_zero = b.curve_zero(); + let selected_input_value_digest = b.curve_select(selector, input_value_digest, curve_zero); + let value_digest = + b.add_curve_point(&[selected_input_value_digest, extracted_value_digest]); + let metadata_digest = + b.add_curve_point(&[input_metadata_digest, extracted_metadata_digest]); + + // Compute the unique data to identify a row is the mapping key. + // row_unique_data = H(pack(left_pad32(key)) + let row_unique_data = b.hash_n_to_hash_no_pad::( + packed_mapping_key.downcast_to_targets().arr.to_vec(), + ); + // row_id = H2int(row_unique_data || num_actual_columns) + let inputs = row_unique_data + .to_targets() .into_iter() - .flat_map(|ext| ext.0) - .chain(iter::once(add_digest.0 .1.target)) + .chain(once(metadata.num_actual_columns)) .collect(); - let values_digest = b.map_to_curve_point(&inputs); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id = hash_to_int_target(b, hash); + + // values_digest = values_digest * row_id + let row_id = b.biguint_to_nonnative(&row_id); + let values_digest = b.curve_scalar_mul(value_digest, &row_id); // Only one leaf in this node. let n = b.one(); @@ -113,50 +144,52 @@ where LeafMappingWires { node, + value, root, slot, - value, - key_id, - value_id, + metadata, + offset, } } - pub fn assign(&self, pw: &mut PartialWitness, wires: &LeafMappingWires) { - let pad_node = - Vector::::from_vec(&self.node).expect("invalid node given"); - wires.node.assign(pw, &pad_node); - KeccakCircuit::<{ PAD_LEN(NODE_LEN) }>::assign( + pub fn assign( + &self, + pw: &mut PartialWitness, + wires: &LeafMappingWires, + ) { + let padded_node = + Vector::::from_vec(&self.node).expect("Invalid node"); + wires.node.assign(pw, &padded_node); + KeccakCircuit::<{ PAD_LEN(69) }>::assign( pw, &wires.root, - &InputData::Assigned(&pad_node), + &InputData::Assigned(&padded_node), ); - self.slot.assign(pw, &wires.slot); - pw.set_target(wires.key_id, GFp::from_canonical_u64(self.key_id)); - pw.set_target(wires.value_id, GFp::from_canonical_u64(self.value_id)); + + self.slot.assign_struct(pw, &wires.slot, self.offset); + TableMetadata::assign(pw, &self.metadata, &wires.metadata); + pw.set_target(wires.offset, F::from_canonical_u32(self.offset)); } } /// Num of children = 0 -impl CircuitLogicWires for LeafMappingWires { +impl CircuitLogicWires + for LeafMappingWires +{ type CircuitBuilderParams = (); + type Inputs = LeafMappingCircuit; - type Inputs = LeafMappingCircuit; - - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; fn circuit_logic( - builder: &mut CircuitBuilder, - _verified_proofs: [&plonky2::plonk::proof::ProofWithPublicInputsTarget; 0], + builder: &mut CBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; 0], _builder_parameters: Self::CircuitBuilderParams, ) -> Self { LeafMappingCircuit::build(builder) } - fn assign_input( - &self, - inputs: Self::Inputs, - pw: &mut PartialWitness, - ) -> anyhow::Result<()> { + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { inputs.assign(pw, self); Ok(()) } @@ -164,20 +197,18 @@ impl CircuitLogicWires for LeafMappingWires { #[cfg(test)] mod tests { - use super::{ - super::{ - compute_leaf_mapping_metadata_digest, compute_leaf_mapping_values_digest, - identifier_for_mapping_key_column, identifier_for_mapping_value_column, - }, - *, + use super::*; + use crate::{ + tests::TEST_MAX_COLUMNS, + values_extraction::{storage_value_digest, StorageSlotInfo, KEY_ID_PREFIX}, }; - use alloy::primitives::Address; use eth_trie::{Nibbles, Trie}; use mp2_common::{ array::Array, - eth::StorageSlot, + eth::{StorageSlot, StorageSlotNode}, mpt_sequential::utils::bytes_to_nibbles, rlp::MAX_KEY_NIBBLE_LEN, + types::MAPPING_LEAF_VALUE_LEN, utils::{keccak256, Endianness, Packer}, C, D, F, }; @@ -189,32 +220,25 @@ mod tests { use plonky2::{ field::types::Field, iop::{target::Target, witness::PartialWitness}, - plonk::circuit_builder::CircuitBuilder, }; - use std::str::FromStr; + use rand::{thread_rng, Rng}; - const TEST_CONTRACT_ADDRESS: &str = "0x105dD0eF26b92a3698FD5AaaF688577B9Cafd970"; + type LeafCircuit = LeafMappingCircuit; + type LeafWires = LeafMappingWires; #[derive(Clone, Debug)] - struct TestLeafMappingCircuit { - c: LeafMappingCircuit, + struct TestLeafMappingCircuit { + c: LeafCircuit, exp_value: Vec, } - impl UserCircuit for TestLeafMappingCircuit - where - [(); PAD_LEN(NODE_LEN)]:, - { + impl UserCircuit for TestLeafMappingCircuit { // Leaf wires + expected extracted value - type Wires = ( - LeafMappingWires, - Array, - ); + type Wires = (LeafWires, Array); - fn build(b: &mut CircuitBuilder) -> Self::Wires { + fn build(b: &mut CBuilder) -> Self::Wires { + let leaf_wires = LeafCircuit::build(b); let exp_value = Array::::new(b); - - let leaf_wires = LeafMappingCircuit::::build(b); leaf_wires.value.enforce_equal(b, &exp_value); (leaf_wires, exp_value) @@ -228,33 +252,50 @@ mod tests { } } - #[test] - fn test_values_extraction_leaf_mapping_circuit() { - const NODE_LEN: usize = 80; - - let mapping_slot = 2_u8; - let mapping_key = hex::decode("1234").unwrap(); - let slot = StorageSlot::Mapping(mapping_key.clone(), mapping_slot as usize); - let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); - let key_id = identifier_for_mapping_key_column(mapping_slot, &contract_address, 1, vec![]); - let value_id = - identifier_for_mapping_value_column(mapping_slot, &contract_address, 1, vec![]); - + fn test_circuit_for_storage_slot(mapping_key: &[u8; 32], storage_slot: StorageSlot) { let (mut trie, _) = generate_random_storage_mpt::<3, MAPPING_LEAF_VALUE_LEN>(); let value = random_vector(MAPPING_LEAF_VALUE_LEN); let encoded_value: Vec = rlp::encode(&value).to_vec(); - trie.insert(&slot.mpt_key(), &encoded_value).unwrap(); + // Ensure we added one byte of RLP header. + assert_eq!(encoded_value.len(), MAPPING_LEAF_VALUE_LEN + 1); + trie.insert(&storage_slot.mpt_key(), &encoded_value) + .unwrap(); trie.root_hash().unwrap(); - - let proof = trie.get_proof(&slot.mpt_key_vec()).unwrap(); + let proof = trie.get_proof(&storage_slot.mpt_key_vec()).unwrap(); let node = proof.last().unwrap().clone(); - let c = LeafMappingCircuit:: { + let slot = storage_slot.slot(); + let evm_word = storage_slot.evm_offset(); + + // Compute the metadata digest. + let table_metadata = TableMetadata::sample::( + true, + &[KEY_ID_PREFIX], + &[slot], + F::from_canonical_u32(evm_word), + ); + + let metadata_digest = table_metadata.digest(); + let slot_info = StorageSlotInfo::new( + storage_slot.clone(), + table_metadata.extracted_columns.clone(), + ); + let values_digest = storage_value_digest( + &table_metadata, + &[mapping_key], + &value.clone().try_into().unwrap(), + &slot_info, + ); + + let slot = MappingSlot::new(slot, mapping_key.to_vec()); + + let c = LeafMappingCircuit:: { node: node.clone(), - slot: MappingSlot::new(mapping_slot, mapping_key.clone()), - key_id, - value_id, + slot: slot.clone(), + metadata: table_metadata, + offset: evm_word, }; + let test_circuit = TestLeafMappingCircuit { c, exp_value: value.clone(), @@ -262,15 +303,16 @@ mod tests { let proof = run_circuit::(test_circuit); let pi = PublicInputs::new(&proof.public_inputs); - + // Check root hash { let exp_hash = keccak256(&node).pack(Endianness::Little); assert_eq!(pi.root_hash(), exp_hash); } + // Check MPT key { let (key, ptr) = pi.mpt_key_info(); - let exp_key = slot.mpt_key_vec(); + let exp_key = storage_slot.mpt_key_vec(); let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) .into_iter() .map(F::from_canonical_u8) @@ -282,17 +324,29 @@ mod tests { let exp_ptr = F::from_canonical_usize(MAX_KEY_NIBBLE_LEN - 1 - nib.nibbles().len()); assert_eq!(exp_ptr, ptr); } - // Check values digest - { - let exp_digest = - compute_leaf_mapping_values_digest(key_id, value_id, &mapping_key, &value); - assert_eq!(pi.values_digest(), exp_digest.to_weierstrass()); - } - // Check metadata digest - { - let exp_digest = compute_leaf_mapping_metadata_digest(key_id, value_id, mapping_slot); - assert_eq!(pi.metadata_digest(), exp_digest.to_weierstrass()); - } assert_eq!(pi.n(), F::ONE); + // Check metadata digest + assert_eq!(pi.metadata_digest(), metadata_digest.to_weierstrass()); + // Check values digest + assert_eq!(pi.values_digest(), values_digest.to_weierstrass()); + } + + #[test] + fn test_values_extraction_leaf_mapping_variable() { + let rng = &mut thread_rng(); + let mapping_key: [u8; 32] = std::array::from_fn(|_| rng.gen()); + let storage_slot = StorageSlot::Mapping(mapping_key.to_vec(), 2); + + test_circuit_for_storage_slot(&mapping_key, storage_slot); + } + + #[test] + fn test_values_extraction_leaf_mapping_struct() { + let rng = &mut thread_rng(); + let mapping_key: [u8; 32] = std::array::from_fn(|_| rng.gen()); + let parent = StorageSlot::Mapping(mapping_key.to_vec(), 5); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent, 20)); + + test_circuit_for_storage_slot(&mapping_key, storage_slot); } } diff --git a/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs b/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs new file mode 100644 index 000000000..52150e00b --- /dev/null +++ b/mp2-v1/src/values_extraction/leaf_mapping_of_mappings.rs @@ -0,0 +1,387 @@ +//! This circuit allows to extract data from mappings where the value stored in each mapping entry +//! is another mapping. In this case, we refer to the key for the first-layer mapping entry as the +//! outer key, while the key for the mapping stored in the entry mapping is referred to as inner key. + +use crate::{ + values_extraction::{ + gadgets::metadata_gadget::TableMetadataTarget, + public_inputs::{PublicInputs, PublicInputsArgs}, + }, + MAX_LEAF_NODE_LEN, +}; +use anyhow::Result; + +use mp2_common::{ + array::{Array, Vector, VectorWire}, + eth::left_pad32, + group_hashing::CircuitBuilderGroupHashing, + keccak::{InputData, KeccakCircuit, KeccakWires, PACKED_HASH_LEN}, + mpt_sequential::{utils::left_pad_leaf_value, MPTLeafOrExtensionNode, PAD_LEN}, + poseidon::hash_to_int_target, + public_inputs::PublicInputCommon, + storage_key::{MappingOfMappingsSlotWires, MappingSlot}, + types::{CBuilder, GFp, MAPPING_LEAF_VALUE_LEN}, + utils::{Endianness, Packer, ToTargets}, + CHasher, D, F, +}; +use plonky2::{ + field::types::Field, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::proof::ProofWithPublicInputsTarget, +}; +use plonky2_crypto::u32::arithmetic_u32::U32Target; +use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; +use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; +use recursion_framework::circuit_builder::CircuitLogicWires; +use serde::{Deserialize, Serialize}; +use std::iter::once; + +use super::{gadgets::metadata_gadget::TableMetadata, INNER_KEY_ID_PREFIX, OUTER_KEY_ID_PREFIX}; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct LeafMappingOfMappingsWires { + /// Full node from the MPT proof + pub(crate) node: VectorWire, + /// Leaf value + pub(crate) value: Array, + /// MPT root + pub(crate) root: KeccakWires<{ PAD_LEN(MAX_LEAF_NODE_LEN) }>, + /// Mapping slot associating wires including outer and inner mapping keys + pub(crate) slot: MappingOfMappingsSlotWires, + /// MPT metadata + metadata: TableMetadataTarget, + offset: Target, +} + +/// Circuit to prove the correct derivation of the MPT key from mappings where +/// the value stored in each mapping entry is another mapping +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LeafMappingOfMappingsCircuit { + pub(crate) node: Vec, + pub(crate) slot: MappingSlot, + pub(crate) inner_key: Vec, + pub(crate) metadata: TableMetadata, + pub(crate) evm_word: u8, +} + +impl LeafMappingOfMappingsCircuit { + pub fn build(b: &mut CBuilder) -> LeafMappingOfMappingsWires { + let offset = b.add_virtual_target(); + let metadata = TableMetadata::build(b, 2); + let slot = MappingSlot::build_mapping_of_mappings(b, offset); + + let zero = b.zero(); + + // Build the node wires. + let wires = MPTLeafOrExtensionNode::build_and_advance_key::<_, D, 69, 33>( + b, + &slot.keccak_mpt.base.mpt_key, + ); + let node: VectorWire = wires.node; + let root = wires.root; + + // Left pad the leaf value. + let value: Array = left_pad_leaf_value(b, &wires.value); + + // Compute the metadata digest and the value digest + let input_values: [Array; 2] = + [&slot.outer_key, &slot.inner_key] + .map(|key| Array::::pack(key, b, Endianness::Big)); + // Add the key prefixes to the circuit as constants + let outer_key_prefix: [Target; PACKED_HASH_LEN] = left_pad32(OUTER_KEY_ID_PREFIX) + .pack(Endianness::Big) + .iter() + .map(|num| b.constant(F::from_canonical_u32(*num))) + .collect::>() + .try_into() + .expect("This should never fail"); + let inner_key_prefix: [Target; PACKED_HASH_LEN] = left_pad32(INNER_KEY_ID_PREFIX) + .pack(Endianness::Big) + .iter() + .map(|num| b.constant(F::from_canonical_u32(*num))) + .collect::>() + .try_into() + .expect("This should never fail"); + let extraction_id = [zero, zero, zero, zero, zero, zero, zero, slot.mapping_slot]; + let (input_metadata_digest, input_value_digest) = metadata.inputs_digests( + b, + &input_values, + &[&outer_key_prefix, &inner_key_prefix], + &extraction_id, + ); + let (extracted_metadata_digest, extracted_value_digest) = + metadata.extracted_digests::(b, &value, offset, &extraction_id); + + let metadata_digest = + b.add_curve_point(&[input_metadata_digest, extracted_metadata_digest]); + + let input_selector = b.is_equal(zero, offset); + let curve_zero = b.curve_zero(); + let input_value_digest = b.curve_select(input_selector, input_value_digest, curve_zero); + let value_digest = b.add_curve_point(&[input_value_digest, extracted_value_digest]); + + // Compute the unique data to identify a row is the mapping key: + // row_unique_data = H(outer_key || inner_key) + let inputs = input_values + .iter() + .flat_map(|arr| arr.downcast_to_targets().arr) + .collect::>(); + let row_unique_data = b.hash_n_to_hash_no_pad::(inputs); + // row_id = H2int(row_unique_data || num_actual_columns) + let inputs = row_unique_data + .to_targets() + .into_iter() + .chain(once(metadata.num_actual_columns)) + .collect(); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id = hash_to_int_target(b, hash); + + // values_digest = values_digest * row_id + let row_id = b.biguint_to_nonnative(&row_id); + let values_digest = b.curve_scalar_mul(value_digest, &row_id); + + // Only one leaf in this node. + let n = b.one(); + + // Register the public inputs. + PublicInputsArgs { + h: &root.output_array, + k: &wires.key, + dv: values_digest, + dm: metadata_digest, + n, + } + .register(b); + + LeafMappingOfMappingsWires { + node, + value, + root, + slot, + metadata, + offset, + } + } + + pub fn assign( + &self, + pw: &mut PartialWitness, + wires: &LeafMappingOfMappingsWires, + ) { + let padded_node = + Vector::::from_vec(&self.node).expect("Invalid node"); + wires.node.assign(pw, &padded_node); + KeccakCircuit::<{ PAD_LEN(69) }>::assign( + pw, + &wires.root, + &InputData::Assigned(&padded_node), + ); + + self.slot.assign_mapping_of_mappings( + pw, + &wires.slot, + &self.inner_key, + self.evm_word as u32, + ); + TableMetadata::assign(pw, &self.metadata, &wires.metadata); + pw.set_target(wires.offset, F::from_canonical_u8(self.evm_word)); + } +} + +/// Num of children = 0 +impl CircuitLogicWires + for LeafMappingOfMappingsWires +{ + type CircuitBuilderParams = (); + type Inputs = LeafMappingOfMappingsCircuit; + + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + + fn circuit_logic( + builder: &mut CBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; 0], + _builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + LeafMappingOfMappingsCircuit::build(builder) + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { + inputs.assign(pw, self); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + tests::TEST_MAX_COLUMNS, + values_extraction::{ + storage_value_digest, StorageSlotInfo, INNER_KEY_ID_PREFIX, OUTER_KEY_ID_PREFIX, + }, + }; + use eth_trie::{Nibbles, Trie}; + use mp2_common::{ + array::Array, + eth::{StorageSlot, StorageSlotNode}, + mpt_sequential::utils::bytes_to_nibbles, + rlp::MAX_KEY_NIBBLE_LEN, + types::MAPPING_LEAF_VALUE_LEN, + utils::{keccak256, Endianness, Packer}, + C, D, F, + }; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + mpt_sequential::generate_random_storage_mpt, + utils::random_vector, + }; + use plonky2::{ + field::types::Field, + iop::{target::Target, witness::PartialWitness}, + }; + + use rand::{thread_rng, Rng}; + use std::array; + + #[derive(Clone, Debug)] + struct TestNewLeafMappingOfMappingsCircuit { + c: LeafMappingOfMappingsCircuit, + exp_value: Vec, + } + + impl UserCircuit for TestNewLeafMappingOfMappingsCircuit { + // Leaf wires + expected extracted value + type Wires = ( + LeafMappingOfMappingsWires, + Array, + ); + + fn build(b: &mut CBuilder) -> Self::Wires { + let leaf_wires = LeafMappingOfMappingsCircuit::::build(b); + let exp_value = Array::::new(b); + leaf_wires.value.enforce_equal(b, &exp_value); + + (leaf_wires, exp_value) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.c.assign(pw, &wires.0); + wires + .1 + .assign_bytes(pw, &self.exp_value.clone().try_into().unwrap()); + } + } + + fn test_circuit_for_storage_slot( + outer_key: &[u8; 32], + inner_key: &[u8; 32], + storage_slot: StorageSlot, + ) { + let (mut trie, _) = generate_random_storage_mpt::<3, MAPPING_LEAF_VALUE_LEN>(); + let value = random_vector(MAPPING_LEAF_VALUE_LEN); + let encoded_value: Vec = rlp::encode(&value).to_vec(); + // Ensure we added one byte of RLP header. + assert_eq!(encoded_value.len(), MAPPING_LEAF_VALUE_LEN + 1); + trie.insert(&storage_slot.mpt_key(), &encoded_value) + .unwrap(); + trie.root_hash().unwrap(); + let proof = trie.get_proof(&storage_slot.mpt_key_vec()).unwrap(); + let node = proof.last().unwrap().clone(); + + let slot = storage_slot.slot(); + let evm_word = storage_slot.evm_offset(); + // Compute the metadata digest. + let table_metadata = TableMetadata::sample::( + true, + &[OUTER_KEY_ID_PREFIX, INNER_KEY_ID_PREFIX], + &[slot], + F::from_canonical_u32(evm_word), + ); + + let metadata_digest = table_metadata.digest(); + let slot_info = StorageSlotInfo::new( + storage_slot.clone(), + table_metadata.extracted_columns.clone(), + ); + + let values_digest = storage_value_digest( + &table_metadata, + &[outer_key, inner_key], + &value.clone().try_into().unwrap(), + &slot_info, + ); + + let slot = MappingSlot::new(slot, outer_key.to_vec()); + + let new_c = LeafMappingOfMappingsCircuit:: { + node: node.clone(), + slot: slot.clone(), + inner_key: inner_key.to_vec(), + metadata: table_metadata, + evm_word: evm_word as u8, + }; + + let new_test_circuit = TestNewLeafMappingOfMappingsCircuit { + c: new_c, + exp_value: value.clone(), + }; + + let proof = run_circuit::(new_test_circuit); + let pi = PublicInputs::new(&proof.public_inputs); + // Check root hash + { + let exp_hash = keccak256(&node).pack(Endianness::Little); + assert_eq!(pi.root_hash(), exp_hash); + } + // Check MPT key + { + let (key, ptr) = pi.mpt_key_info(); + + let exp_key = storage_slot.mpt_key_vec(); + let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) + .into_iter() + .map(F::from_canonical_u8) + .collect(); + assert_eq!(key, exp_key); + + let leaf_key: Vec> = rlp::decode_list(&node); + let nib = Nibbles::from_compact(&leaf_key[0]); + let exp_ptr = F::from_canonical_usize(MAX_KEY_NIBBLE_LEN - 1 - nib.nibbles().len()); + assert_eq!(exp_ptr, ptr); + } + assert_eq!(pi.n(), F::ONE); + // Check metadata digest + assert_eq!(pi.metadata_digest(), metadata_digest.to_weierstrass()); + + // Check values digest + assert_eq!(pi.values_digest(), values_digest.to_weierstrass()); + } + + #[test] + fn test_values_extraction_leaf_mapping_of_mappings_variable() { + let rng = &mut thread_rng(); + let outer_key: [u8; 32] = array::from_fn(|_| rng.gen()); + let inner_key: [u8; 32] = array::from_fn(|_| rng.gen()); + let parent = StorageSlot::Mapping(outer_key.to_vec(), 2); + let storage_slot = + StorageSlot::Node(StorageSlotNode::new_mapping(parent, inner_key.to_vec()).unwrap()); + + test_circuit_for_storage_slot(&outer_key, &inner_key, storage_slot); + } + + #[test] + fn test_values_extraction_leaf_mapping_of_mappings_struct() { + let rng = &mut thread_rng(); + let outer_key: [u8; 32] = array::from_fn(|_| rng.gen()); + let inner_key: [u8; 32] = array::from_fn(|_| rng.gen()); + let grand = StorageSlot::Mapping(outer_key.to_vec(), 2); + let parent = + StorageSlot::Node(StorageSlotNode::new_mapping(grand, inner_key.to_vec()).unwrap()); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent, 30)); + + test_circuit_for_storage_slot(&outer_key, &inner_key, storage_slot); + } +} 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..84226ae59 --- /dev/null +++ b/mp2-v1/src/values_extraction/leaf_receipt.rs @@ -0,0 +1,517 @@ +//! Module handling the leaf node inside a Receipt Trie + +use super::{ + gadgets::metadata_gadget::{TableMetadata, TableMetadataTarget}, + public_inputs::{PublicInputs, PublicInputsArgs}, + GAS_USED_PREFIX, TX_INDEX_PREFIX, +}; + +use alloy::primitives::Address; +use anyhow::Result; +use mp2_common::{ + array::{extract_value, Array, Targetable, Vector, VectorWire}, + eth::{left_pad32, EventLogInfo}, + group_hashing::CircuitBuilderGroupHashing, + keccak::{InputData, KeccakCircuit, KeccakWires, HASH_LEN, PACKED_HASH_LEN}, + mpt_sequential::{utils::bytes_to_nibbles, MPTKeyWire, MPTReceiptLeafNode, PAD_LEN}, + poseidon::hash_to_int_target, + public_inputs::PublicInputCommon, + rlp::MAX_KEY_NIBBLE_LEN, + types::{CBuilder, GFp}, + utils::{less_than_unsafe, Endianness, Packer, ToTargets}, + CHasher, D, F, +}; +use plonky2::{ + field::types::Field, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::circuit_builder::CircuitBuilder, +}; + +use plonky2_crypto::u32::arithmetic_u32::{CircuitBuilderU32, U32Target}; +use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; +use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; + +use recursion_framework::circuit_builder::CircuitLogicWires; +use rlp::Encodable; +use serde::{Deserialize, Serialize}; + +/// 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; + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub(crate) struct ReceiptLeafWires +where + [(); PAD_LEN(NODE_LEN)]:, +{ + /// The event we are monitoring for + pub(crate) event: EventWires, + /// The node bytes + pub(crate) node: VectorWire, + /// the hash of the node bytes + pub(crate) root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, + /// The offsets of the relevant logs inside the node + pub(crate) relevant_log_offset: Target, + /// The key in the MPT Trie + pub(crate) mpt_key: MPTKeyWire, + /// The table metadata + pub(crate) metadata: TableMetadataTarget, +} + +/// Contains all the information for an [`Event`] in rlp form +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +pub struct EventWires { + /// 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, +} + +/// 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 signature in the rlp encoded log + pub sig_rel_offset: usize, + /// This is the offset in the node to the start of the log that relates to `event_info` + pub relevant_log_offset: usize, + /// The table metadata + pub metadata: TableMetadata, +} + +impl + ReceiptLeafCircuit +where + [(); PAD_LEN(NODE_LEN)]:, +{ + /// Create a new [`ReceiptLeafCircuit`] from a [`ReceiptProofInfo`] and a [`EventLogInfo`] + pub fn new( + last_node: &[u8], + tx_index: u64, + event: &EventLogInfo, + ) -> Result { + // Get the relevant log offset + let relevant_log_offset = event.get_log_offset(last_node)?; + + let EventLogInfo:: { + size, + address, + add_rel_offset, + event_signature, + sig_rel_offset, + .. + } = *event; + + // Construct the table metadata from the event + let metadata = TableMetadata::from(*event); + + Ok(Self { + node: last_node.to_vec(), + tx_index, + size, + address, + rel_add_offset: add_rel_offset, + event_signature, + sig_rel_offset, + relevant_log_offset, + metadata, + }) + } + + pub(crate) fn build(b: &mut CBuilder) -> ReceiptLeafWires { + // Build the event wires + let event_wires = Self::build_event_wires(b); + // Build the metadata + let metadata = TableMetadata::build(b, 2); + let zero = b.zero(); + + let one = b.one(); + let two = b.two(); + + // Add targets for the data specific to this receipt + let relevant_log_offset = b.add_virtual_target(); + + let mpt_key = MPTKeyWire::new(b); + let index = mpt_key.fold_key(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; + + // 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( + node.arr.arr[0], + F::from_canonical_u64(1) - F::from_canonical_u64(247), + ); + // Since header_len_len can be at most 8 bytes its safe for us to just take the first 64 elements of the array here as it will + // always be in this range + let key_header = extract_value(b, &node.arr.arr[..64], header_len_len); + let less_than_val = b.constant(F::from_canonical_u8(128)); + let single_value = less_than_unsafe(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 = node.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 = node.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 = node.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); + // We want gas_used_offset + gas_used_len + one here because we want to stop our sum one + // after the gas_used_offset + gas_used_len + let final_gas_index = b.add(initial_gas_index, gas_used_len); + + let combiner = b.constant(F::from_canonical_u64(1 << 8)); + let mut last_byte_found = b._false(); + 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 = node.arr.random_access_large_array(b, access_index); + + // Check to see if we have reached the index where we stop summing + let at_end = b.is_equal(access_index, final_gas_index); + last_byte_found = b.or(at_end, last_byte_found); + + let tmp = b.mul_add(acc, combiner, array_value); + b.select(last_byte_found, acc, tmp) + }); + + let zero_u32 = b.zero_u32(); + let tx_index_input = Array::::from_array([ + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + U32Target::from_target(index), + ]); + let gas_used_input = Array::::from_array([ + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + zero_u32, + U32Target::from_target(gas_used), + ]); + // Add the key prefixes to the circuit as constants + let tx_index_prefix: [Target; PACKED_HASH_LEN] = left_pad32(TX_INDEX_PREFIX) + .pack(Endianness::Big) + .iter() + .map(|num| b.constant(F::from_canonical_u32(*num))) + .collect::>() + .try_into() + .expect("This should never fail"); + let gas_used_prefix: [Target; PACKED_HASH_LEN] = left_pad32(GAS_USED_PREFIX) + .pack(Endianness::Big) + .iter() + .map(|num| b.constant(F::from_canonical_u32(*num))) + .collect::>() + .try_into() + .expect("This should never fail"); + + let extraction_id_packed = event_wires.event_signature.pack(b, Endianness::Big); + let extraction_id = extraction_id_packed.downcast_to_targets(); + // Extract input values + let (input_metadata_digest, input_value_digest) = metadata.inputs_digests( + b, + &[tx_index_input.clone(), gas_used_input.clone()], + &[&tx_index_prefix, &gas_used_prefix], + &extraction_id.arr, + ); + // Now we verify extracted values + let (address_extract, signature_extract, extracted_metadata_digest, extracted_value_digest) = + metadata.extracted_receipt_digests( + b, + &node.arr, + relevant_log_offset, + event_wires.add_rel_offset, + event_wires.sig_rel_offset, + ); + + address_extract.enforce_equal(b, &event_wires.address); + signature_extract.enforce_equal(b, &event_wires.event_signature); + + let dm = b.add_curve_point(&[input_metadata_digest, extracted_metadata_digest]); + + let value_digest = b.add_curve_point(&[input_value_digest, extracted_value_digest]); + + // Compute the unique data to identify a row is the mapping key. + // row_unique_data = H(tx_index || gas_used) + let row_unique_data = b.hash_n_to_hash_no_pad::( + tx_index_input + .arr + .iter() + .map(|t| t.to_target()) + .chain(gas_used_input.arr.iter().map(|t| t.to_target())) + .collect::>(), + ); + // row_id = H2int(row_unique_data || num_actual_columns) + let inputs = row_unique_data + .to_targets() + .into_iter() + .chain(std::iter::once(metadata.num_actual_columns)) + .collect(); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id = hash_to_int_target(b, hash); + + // values_digest = values_digest * row_id + let row_id = b.biguint_to_nonnative(&row_id); + let dv = b.curve_scalar_mul(value_digest, &row_id); + + // Register the public inputs + PublicInputsArgs { + h: &root.output_array, + k: &wires.key, + dv, + dm, + n: one, + } + .register_args(b); + + ReceiptLeafWires { + event: event_wires, + node, + root, + relevant_log_offset, + mpt_key, + metadata, + } + } + + fn build_event_wires(b: &mut CBuilder) -> EventWires { + // 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(); + + EventWires { + address, + add_rel_offset, + event_signature, + sig_rel_offset, + } + } + + pub(crate) 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.relevant_log_offset, + GFp::from_canonical_usize(self.relevant_log_offset), + ); + let key_encoded = self.tx_index.rlp_bytes(); + let mut nibbles = bytes_to_nibbles(&key_encoded); + let ptr = nibbles.len() - 1; + nibbles.resize(MAX_KEY_NIBBLE_LEN, 0u8); + + let key_nibbles: [u8; MAX_KEY_NIBBLE_LEN] = nibbles + .try_into() + .expect("Couldn't create mpt key with correct length"); + + wires.mpt_key.assign(pw, &key_nibbles, ptr); + + TableMetadata::assign(pw, &self.metadata, &wires.metadata); + } + + pub fn assign_event_wires(&self, pw: &mut PartialWitness, wires: &EventWires) { + 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), + ); + } +} + +/// Num of children = 0 +impl CircuitLogicWires + for ReceiptLeafWires +where + [(); PAD_LEN(NODE_LEN)]:, +{ + 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 super::*; + + use mp2_common::{ + utils::{keccak256, Endianness, 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_WORDS: 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 event = receipt_proof_infos.info(); + + let c = ReceiptLeafCircuit::::new::( + info.mpt_proof.last().unwrap(), + info.tx_index, + event, + ) + .unwrap(); + let metadata = c.metadata.clone(); + + 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 = metadata.receipt_value_digest(info.tx_index, &node, event); + assert_eq!(pi.values_digest(), exp_digest.to_weierstrass()); + } + + // Check metadata digest + { + let exp_digest = metadata.digest(); + assert_eq!(pi.metadata_digest(), exp_digest.to_weierstrass()); + } + } +} diff --git a/mp2-v1/src/values_extraction/leaf_single.rs b/mp2-v1/src/values_extraction/leaf_single.rs index ba28e9d36..951c18971 100644 --- a/mp2-v1/src/values_extraction/leaf_single.rs +++ b/mp2-v1/src/values_extraction/leaf_single.rs @@ -1,20 +1,22 @@ //! Module handling the single variable inside a storage trie - -use crate::MAX_LEAF_NODE_LEN; - -use super::public_inputs::{PublicInputs, PublicInputsArgs}; +#![allow(clippy::identity_op)] +use crate::values_extraction::{ + gadgets::metadata_gadget::{TableMetadata, TableMetadataTarget}, + public_inputs::{PublicInputs, PublicInputsArgs}, +}; +use anyhow::Result; use mp2_common::{ array::{Array, Vector, VectorWire}, - group_hashing::CircuitBuilderGroupHashing, keccak::{InputData, KeccakCircuit, KeccakWires}, mpt_sequential::{ utils::left_pad_leaf_value, MPTLeafOrExtensionNode, MAX_LEAF_VALUE_LEN, PAD_LEN, }, + poseidon::{empty_poseidon_hash, hash_to_int_target}, public_inputs::PublicInputCommon, - storage_key::{SimpleSlot, SimpleSlotWires}, + storage_key::{SimpleSlot, SimpleStructSlotWires}, types::{CBuilder, GFp, MAPPING_LEAF_VALUE_LEN}, - utils::{Endianness, PackerTarget}, - D, + utils::ToTargets, + CHasher, D, F, }; use plonky2::{ field::types::Field, @@ -22,63 +24,78 @@ use plonky2::{ target::Target, witness::{PartialWitness, WitnessWrite}, }, - plonk::circuit_builder::CircuitBuilder, + plonk::proof::ProofWithPublicInputsTarget, }; + +use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; +use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; -use std::iter; +use std::iter::once; #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] -pub struct LeafSingleWires -where - [(); PAD_LEN(NODE_LEN)]:, -{ - node: VectorWire, - root: KeccakWires<{ PAD_LEN(NODE_LEN) }>, - slot: SimpleSlotWires, - value: Array, - id: Target, +pub struct LeafSingleWires { + /// Full node from the MPT proof + node: VectorWire, + /// Leaf value + value: Array, + /// MPT root + root: KeccakWires<{ PAD_LEN(69) }>, + /// Storage single variable slot + slot: SimpleStructSlotWires, + /// MPT metadata + metadata: TableMetadataTarget, + /// Offset from the base slot, + offset: Target, } /// Circuit to prove the correct derivation of the MPT key from a simple slot #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LeafSingleCircuit { +pub struct LeafSingleCircuit { pub(crate) node: Vec, pub(crate) slot: SimpleSlot, - pub(crate) id: u64, + pub(crate) metadata: TableMetadata, + pub(crate) offset: u32, } -impl LeafSingleCircuit -where - [(); PAD_LEN(NODE_LEN)]:, -{ - pub fn build(b: &mut CBuilder) -> LeafSingleWires { - let slot = SimpleSlot::build(b); - let id = b.add_virtual_target(); - - // Range check for the slot byte since we don't export it as a public input for now. - b.range_check(slot.slot, 8); - +impl LeafSingleCircuit { + pub fn build(b: &mut CBuilder) -> LeafSingleWires { + let metadata = TableMetadata::build(b, 0); + let offset = b.add_virtual_target(); + let slot = SimpleSlot::build_struct(b, offset); + let zero = b.zero(); // Build the node wires. - let wires = - MPTLeafOrExtensionNode::build_and_advance_key::<_, D, NODE_LEN, MAX_LEAF_VALUE_LEN>( - b, - &slot.mpt_key, - ); + let wires = MPTLeafOrExtensionNode::build_and_advance_key::<_, D, 69, MAX_LEAF_VALUE_LEN>( + b, + &slot.base.mpt_key, + ); let node = wires.node; let root = wires.root; // Left pad the leaf value. - let value = left_pad_leaf_value(b, &wires.value); + let value: Array = left_pad_leaf_value(b, &wires.value); + + // Compute the metadata digest and the value digest + let (metadata_digest, value_digest) = metadata.extracted_digests::( + b, + &value, + offset, + &[zero, zero, zero, zero, zero, zero, zero, slot.base.slot], + ); - // Compute the metadata digest - D(identifier || slot). - let metadata_digest = b.map_to_curve_point(&[id, slot.slot]); + // row_id = H2int(H("") || num_actual_columns) + let empty_hash = b.constant_hash(*empty_poseidon_hash()); + let inputs = empty_hash + .to_targets() + .into_iter() + .chain(once(metadata.num_actual_columns)) + .collect(); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id = hash_to_int_target(b, hash); - // Compute the values digest - D(identifier || value). - assert_eq!(value.arr.len(), MAPPING_LEAF_VALUE_LEN); - let packed_value = value.arr.pack(b, Endianness::Big); - let inputs: Vec<_> = iter::once(id).chain(packed_value).collect(); - let values_digest = b.map_to_curve_point(&inputs); + // value_digest = value_digest * row_id + let row_id = b.biguint_to_nonnative(&row_id); + let values_digest = b.curve_scalar_mul(value_digest, &row_id); // Only one leaf in this node. let n = b.one(); @@ -95,48 +112,51 @@ where LeafSingleWires { node, + value, root, slot, - value, - id, + metadata, + offset, } } - pub fn assign(&self, pw: &mut PartialWitness, wires: &LeafSingleWires) { - let pad_node = - Vector::::from_vec(&self.node).expect("invalid node given"); - wires.node.assign(pw, &pad_node); - KeccakCircuit::<{ PAD_LEN(NODE_LEN) }>::assign( + pub fn assign( + &self, + pw: &mut PartialWitness, + wires: &LeafSingleWires, + ) { + let padded_node = + Vector::::from_vec(&self.node).expect("Invalid node"); + wires.node.assign(pw, &padded_node); + KeccakCircuit::<{ PAD_LEN(69) }>::assign( pw, &wires.root, - &InputData::Assigned(&pad_node), + &InputData::Assigned(&padded_node), ); - self.slot.assign(pw, &wires.slot); - pw.set_target(wires.id, GFp::from_canonical_u64(self.id)); + self.slot.assign_struct(pw, &wires.slot, self.offset); + TableMetadata::assign(pw, &self.metadata, &wires.metadata); + pw.set_target(wires.offset, GFp::from_canonical_u32(self.offset)); } } /// Num of children = 0 -impl CircuitLogicWires for LeafSingleWires { +impl CircuitLogicWires + for LeafSingleWires +{ type CircuitBuilderParams = (); + type Inputs = LeafSingleCircuit; - type Inputs = LeafSingleCircuit; - - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; fn circuit_logic( - builder: &mut CircuitBuilder, - _verified_proofs: [&plonky2::plonk::proof::ProofWithPublicInputsTarget; 0], + builder: &mut CBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; 0], _builder_parameters: Self::CircuitBuilderParams, ) -> Self { LeafSingleCircuit::build(builder) } - fn assign_input( - &self, - inputs: Self::Inputs, - pw: &mut PartialWitness, - ) -> anyhow::Result<()> { + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { inputs.assign(pw, self); Ok(()) } @@ -144,21 +164,18 @@ impl CircuitLogicWires for LeafSingleWires { #[cfg(test)] mod tests { - - use super::{ - super::{ - compute_leaf_single_metadata_digest, compute_leaf_single_values_digest, - identifier_single_var_column, - }, - *, + use super::*; + use crate::{ + tests::TEST_MAX_COLUMNS, + values_extraction::{storage_value_digest, StorageSlotInfo}, }; - use alloy::primitives::Address; use eth_trie::{Nibbles, Trie}; use mp2_common::{ array::Array, - eth::StorageSlot, + eth::{StorageSlot, StorageSlotNode}, mpt_sequential::utils::bytes_to_nibbles, rlp::MAX_KEY_NIBBLE_LEN, + types::MAPPING_LEAF_VALUE_LEN, utils::{keccak256, Endianness, Packer}, C, D, F, }; @@ -170,32 +187,24 @@ mod tests { use plonky2::{ field::types::Field, iop::{target::Target, witness::PartialWitness}, - plonk::circuit_builder::CircuitBuilder, }; - use std::str::FromStr; - const TEST_CONTRACT_ADDRESS: &str = "0x105dD0eF26b92a3698FD5AaaF688577B9Cafd970"; + type LeafCircuit = LeafSingleCircuit; + type LeafWires = LeafSingleWires; #[derive(Clone, Debug)] - struct TestLeafSingleCircuit { - c: LeafSingleCircuit, + struct TestLeafSingleCircuit { + c: LeafCircuit, exp_value: Vec, } - impl UserCircuit for TestLeafSingleCircuit - where - [(); PAD_LEN(NODE_LEN)]:, - { + impl UserCircuit for TestLeafSingleCircuit { // Leaf wires + expected extracted value - type Wires = ( - LeafSingleWires, - Array, - ); + type Wires = (LeafWires, Array); - fn build(b: &mut CircuitBuilder) -> Self::Wires { + fn build(b: &mut CBuilder) -> Self::Wires { + let leaf_wires = LeafCircuit::build(b); let exp_value = Array::::new(b); - - let leaf_wires = LeafSingleCircuit::::build(b); leaf_wires.value.enforce_equal(b, &exp_value); (leaf_wires, exp_value) @@ -209,32 +218,46 @@ mod tests { } } - #[test] - fn test_values_extraction_leaf_single_circuit() { - const NODE_LEN: usize = 80; - - let simple_slot = 2_u8; - let slot = StorageSlot::Simple(simple_slot as usize); - let contract_address = Address::from_str(TEST_CONTRACT_ADDRESS).unwrap(); - let chain_id = 10; - let id = identifier_single_var_column(simple_slot, &contract_address, chain_id, vec![]); - + fn test_circuit_for_storage_slot(storage_slot: StorageSlot) { let (mut trie, _) = generate_random_storage_mpt::<3, MAPPING_LEAF_VALUE_LEN>(); let value = random_vector(MAPPING_LEAF_VALUE_LEN); let encoded_value: Vec = rlp::encode(&value).to_vec(); - // assert we added one byte of RLP header + // Ensure we added one byte of RLP header. assert_eq!(encoded_value.len(), MAPPING_LEAF_VALUE_LEN + 1); - println!("encoded value {:?}", encoded_value); - trie.insert(&slot.mpt_key(), &encoded_value).unwrap(); + trie.insert(&storage_slot.mpt_key(), &encoded_value) + .unwrap(); trie.root_hash().unwrap(); - - let proof = trie.get_proof(&slot.mpt_key_vec()).unwrap(); + let proof = trie.get_proof(&storage_slot.mpt_key_vec()).unwrap(); let node = proof.last().unwrap().clone(); - let c = LeafSingleCircuit:: { + let slot = storage_slot.slot(); + let evm_word = storage_slot.evm_offset(); + // Compute the metadata digest. + let table_metadata = TableMetadata::sample::( + true, + &[], + &[slot], + F::from_canonical_u32(evm_word), + ); + + let metadata_digest = table_metadata.digest(); + + let slot_info = StorageSlotInfo::new( + storage_slot.clone(), + table_metadata.extracted_columns.clone(), + ); + let values_digest = storage_value_digest( + &table_metadata, + &[], + &value.clone().try_into().unwrap(), + &slot_info, + ); + let slot = SimpleSlot::new(slot); + let c = LeafCircuit { node: node.clone(), - slot: SimpleSlot::new(simple_slot), - id, + slot, + metadata: table_metadata, + offset: evm_word, }; let test_circuit = TestLeafSingleCircuit { c, @@ -243,15 +266,16 @@ mod tests { let proof = run_circuit::(test_circuit); let pi = PublicInputs::new(&proof.public_inputs); - + // Check root hash { let exp_hash = keccak256(&node).pack(Endianness::Little); assert_eq!(pi.root_hash(), exp_hash); } + // Check MPT key { let (key, ptr) = pi.mpt_key_info(); - let exp_key = slot.mpt_key_vec(); + let exp_key = storage_slot.mpt_key_vec(); let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) .into_iter() .map(F::from_canonical_u8) @@ -263,16 +287,25 @@ mod tests { let exp_ptr = F::from_canonical_usize(MAX_KEY_NIBBLE_LEN - 1 - nib.nibbles().len()); assert_eq!(exp_ptr, ptr); } - // Check values digest - { - let exp_digest = compute_leaf_single_values_digest(id, &value); - assert_eq!(pi.values_digest(), exp_digest.to_weierstrass()); - } - // Check metadata digest - { - let exp_digest = compute_leaf_single_metadata_digest(id, simple_slot); - assert_eq!(pi.metadata_digest(), exp_digest.to_weierstrass()); - } assert_eq!(pi.n(), F::ONE); + // Check metadata digest + assert_eq!(pi.metadata_digest(), metadata_digest.to_weierstrass()); + // Check values digest + assert_eq!(pi.values_digest(), values_digest.to_weierstrass()); + } + + #[test] + fn test_values_extraction_leaf_single_variable() { + let storage_slot = StorageSlot::Simple(2); + + test_circuit_for_storage_slot(storage_slot); + } + + #[test] + fn test_values_extraction_leaf_single_struct() { + let parent = StorageSlot::Simple(5); + let storage_slot = StorageSlot::Node(StorageSlotNode::new_struct(parent, 10)); + + test_circuit_for_storage_slot(storage_slot); } } diff --git a/mp2-v1/src/values_extraction/mod.rs b/mp2-v1/src/values_extraction/mod.rs index e40681f5c..09e3c4c5d 100644 --- a/mp2-v1/src/values_extraction/mod.rs +++ b/mp2-v1/src/values_extraction/mod.rs @@ -1,9 +1,18 @@ +use crate::api::SlotInput; + +use gadgets::{ + column_info::{ExtractedColumnInfo, InputColumnInfo}, + metadata_gadget::TableMetadata, +}; +use itertools::Itertools; + use alloy::primitives::Address; use mp2_common::{ - eth::left_pad32, - group_hashing::map_to_curve_point, - poseidon::H, - types::{GFp, MAPPING_KEY_LEN, MAPPING_LEAF_VALUE_LEN}, + digest::Digest, + eth::{left_pad32, EventLogInfo, StorageSlot}, + keccak::HASH_LEN, + poseidon::{empty_poseidon_hash, H}, + types::{HashOutput, MAPPING_LEAF_VALUE_LEN}, utils::{Endianness, Packer, ToFields}, F, }; @@ -11,150 +20,623 @@ use plonky2::{ field::types::{Field, PrimeField64}, plonk::config::Hasher, }; -use plonky2_ecgfp5::curve::curve::Point as Digest; -use std::iter; + +use serde::{Deserialize, Serialize}; +use std::iter::once; pub mod api; mod branch; +mod dummy; mod extension; +pub mod gadgets; mod leaf_mapping; +mod leaf_mapping_of_mappings; +mod leaf_receipt; mod leaf_single; +pub mod planner; pub mod public_inputs; pub use api::{build_circuits_params, generate_proof, CircuitInput, PublicParameters}; pub use public_inputs::PublicInputs; -/// Constant prefixes for key and value IDs. Restrict both prefixes to 3-bytes, -/// so `prefix + slot (u8)` could be converted to an U32. -pub(crate) const KEY_ID_PREFIX: &[u8] = b"KEY"; -pub(crate) const VALUE_ID_PREFIX: &[u8] = b"VAL"; +/// Constant prefixes for the mapping key ID. Restrict to 4-bytes (Uint32). +pub(crate) const KEY_ID_PREFIX: &[u8] = b"\0KEY"; + +/// Constant prefixes for the inner and outer key IDs of mapping slot. +/// Restrict to 8-bytes (Uint64). +pub(crate) const INNER_KEY_ID_PREFIX: &[u8] = b"\0\0IN_KEY"; +pub(crate) const OUTER_KEY_ID_PREFIX: &[u8] = b"\0OUT_KEY"; pub(crate) const BLOCK_ID_DST: &[u8] = b"BLOCK_NUMBER"; -pub fn identifier_block_column() -> u64 { +/// Type for mapping keys +pub type MappingKey = Vec; +/// Type for column ID +pub type ColumnId = u64; + +/// Storage slot information for generating the extraction proof +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] +pub struct StorageSlotInfo { + slot: StorageSlot, + table_info: Vec, +} + +impl StorageSlotInfo { + pub fn new(slot: StorageSlot, table_info: Vec) -> Self { + Self { slot, table_info } + } + + pub fn slot(&self) -> &StorageSlot { + &self.slot + } + + pub fn table_info(&self) -> &[ExtractedColumnInfo] { + &self.table_info + } + + pub fn evm_word(&self) -> u32 { + self.slot.evm_offset() + } + + pub fn outer_key_id( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> Option { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + self.outer_key_id_raw(extra) + } + + pub fn inner_key_id( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> Option { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + self.inner_key_id_raw(extra) + } + + pub fn outer_key_id_raw(&self, extra: Vec) -> Option { + let slot = self.slot().slot(); + let num_mapping_keys = self.slot().mapping_keys().len(); + match num_mapping_keys { + _ if num_mapping_keys == 0 => None, + _ if num_mapping_keys == 1 => Some(identifier_for_mapping_key_column_raw(slot, extra)), + _ if num_mapping_keys == 2 => { + Some(identifier_for_outer_mapping_key_column_raw(slot, extra)) + } + _ => panic!("Unsupport for the nested mapping keys of length greater than 2"), + } + } + + pub fn inner_key_id_raw(&self, extra: Vec) -> Option { + let slot = self.slot().slot(); + let num_mapping_keys = self.slot().mapping_keys().len(); + match num_mapping_keys { + _ if num_mapping_keys < 2 => None, + _ if num_mapping_keys == 2 => { + Some(identifier_for_inner_mapping_key_column_raw(slot, extra)) + } + _ => panic!("Unsupport for the nested mapping keys of length greater than 2"), + } + } + + pub fn slot_inputs( + &self, + ) -> Vec { + self.table_info().iter().map(SlotInput::from).collect() + } + + pub fn table_columns( + &self, + contract_address: &Address, + chain_id: u64, + extra: Vec, + ) -> TableMetadata { + let slot = self.slot().slot(); + let num_mapping_keys = self.slot().mapping_keys().len(); + + let input_columns = match num_mapping_keys { + 0 => vec![], + 1 => { + let identifier = compute_id_with_prefix( + KEY_ID_PREFIX, + slot, + contract_address, + chain_id, + extra.clone(), + ); + let input_column = InputColumnInfo::new(&[slot], identifier, KEY_ID_PREFIX); + vec![input_column] + } + 2 => { + let outer_identifier = compute_id_with_prefix( + OUTER_KEY_ID_PREFIX, + slot, + contract_address, + chain_id, + extra.clone(), + ); + let inner_identifier = compute_id_with_prefix( + INNER_KEY_ID_PREFIX, + slot, + contract_address, + chain_id, + extra.clone(), + ); + vec![ + InputColumnInfo::new(&[slot], outer_identifier, OUTER_KEY_ID_PREFIX), + InputColumnInfo::new(&[slot], inner_identifier, INNER_KEY_ID_PREFIX), + ] + } + _ => vec![], + }; + + TableMetadata::new(&input_columns, self.table_info()) + } +} + +/// Prefix used for making a topic column id. +pub const TOPIC_PREFIX: &[u8] = b"topic"; +/// [`TOPIC_PREFIX`] as a [`str`] +pub const TOPIC_NAME: &str = "topic"; + +/// Prefix used for making a data column id. +pub const DATA_PREFIX: &[u8] = b"data"; +/// [`DATA_PREFIX`] as a [`str`] +pub const DATA_NAME: &str = "data"; + +/// Prefix for transaction index +pub const TX_INDEX_PREFIX: &[u8] = b"tx_index"; +/// [`TX_INDEX_PREFIX`] as a [`str`] +pub const TX_INDEX_NAME: &str = "tx_index"; + +/// Prefix for gas used +pub const GAS_USED_PREFIX: &[u8] = b"gas_used"; +/// [`GAS_USED_PREFIX`] as a [`str`] +pub const GAS_USED_NAME: &str = "gas_used"; + +impl + From> for TableMetadata +{ + fn from(event: EventLogInfo) -> Self { + let extraction_id = event.event_signature; + + let tx_index_column_id = + identifier_for_tx_index_column(&event.event_signature, &event.address, &[]); + + let gas_used_column_id = + identifier_for_gas_used_column(&event.event_signature, &event.address, &[]); + + let tx_index_input_column = InputColumnInfo::new( + extraction_id.as_slice(), + tx_index_column_id, + TX_INDEX_PREFIX, + ); + let gas_used_index_column = InputColumnInfo::new( + extraction_id.as_slice(), + gas_used_column_id, + GAS_USED_PREFIX, + ); + + let topic_columns = event + .topics + .iter() + .enumerate() + .map(|(j, &offset)| { + ExtractedColumnInfo::new( + extraction_id.as_slice(), + identifier_for_topic_column( + &event.event_signature, + &event.address, + &[j as u8 + 1], + ), + offset, + 32, + 0, + ) + }) + .collect::>(); + + let data_columns = event + .data + .iter() + .enumerate() + .map(|(j, &offset)| { + ExtractedColumnInfo::new( + extraction_id.as_slice(), + identifier_for_data_column( + &event.event_signature, + &event.address, + &[j as u8 + 1], + ), + offset, + 32, + 0, + ) + }) + .collect::>(); + + let extracted_columns = [topic_columns, data_columns].concat(); + + TableMetadata::new( + &[tx_index_input_column, gas_used_index_column], + &extracted_columns, + ) + } +} + +pub fn identifier_block_column() -> ColumnId { let inputs: Vec = BLOCK_ID_DST.to_fields(); H::hash_no_pad(&inputs).elements[0].to_canonical_u64() } -/// Calculate `id = Poseidon(slot || contract_address)[0]` for single variable. -pub fn identifier_single_var_column( - slot: u8, +/// Compute identifier for value column. +/// +/// The value column could be either simple or mapping slot. +/// `id = H(slot || byte_offset || length || evm_word || contract_address || chain_id || extra)[0]` +pub fn identifier_for_value_column( + input: &SlotInput, contract_address: &Address, chain_id: u64, extra: Vec, -) -> u64 { - let fields = contract_address +) -> ColumnId { + let extra = contract_address .0 - .iter() - .copied() + .into_iter() .chain(chain_id.to_be_bytes()) .chain(extra) - .collect::>() - .to_fields(); + .collect_vec(); + + identifier_for_value_column_raw(input, extra) +} - let inputs: Vec<_> = iter::once(GFp::from_canonical_u8(slot)) - .chain(fields) - .collect(); +/// Compute identifier for value column in raw mode. +/// The value column could be either simple or mapping slot. +/// `id = H(slot || byte_offset || length || evm_word || extra)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_mapping_key_column`. +pub fn identifier_for_value_column_raw(input: &SlotInput, extra: Vec) -> ColumnId { + let inputs = once(input.slot) + .chain(input.byte_offset.to_be_bytes()) + .chain(input.length.to_be_bytes()) + .chain(input.evm_word.to_be_bytes()) + .chain(extra) + .map(F::from_canonical_u8) + .collect_vec(); H::hash_no_pad(&inputs).elements[0].to_canonical_u64() } -/// Calculate `key_id = Poseidon(KEY || slot || contract_address)[0]` for mapping variable leaf. +/// Compute key indetifier for mapping variable. +/// `key_id = H(KEY || slot || contract_address || chain_id)[0]` pub fn identifier_for_mapping_key_column( slot: u8, contract_address: &Address, chain_id: u64, extra: Vec, -) -> u64 { +) -> ColumnId { compute_id_with_prefix(KEY_ID_PREFIX, slot, contract_address, chain_id, extra) } -/// Calculate `value_id = Poseidon(VAL || slot || contract_address)[0]` for mapping variable leaf. -pub fn identifier_for_mapping_value_column( +/// Compute key indetifier for mapping variable in raw mode. +/// `key_id = H(KEY || slot || contract_address || chain_id)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_mapping_key_column`. +pub fn identifier_for_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(KEY_ID_PREFIX, slot, extra) +} + +/// Compute outer key indetifier for mapping of mappings variable. +/// `outer_key_id = H(OUT_KEY || slot || contract_address || chain_id)[0]` +pub fn identifier_for_outer_mapping_key_column( + slot: u8, + contract_address: &Address, + chain_id: u64, + extra: Vec, +) -> ColumnId { + compute_id_with_prefix(OUTER_KEY_ID_PREFIX, slot, contract_address, chain_id, extra) +} + +/// Compute outer key indetifier for mapping of mappings variable in raw mode. +/// `outer_key_id = H(OUT_KEY || slot || contract_address || chain_id)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_outer_mapping_key_column`. +pub fn identifier_for_outer_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(OUTER_KEY_ID_PREFIX, slot, extra) +} +/// Compute inner key indetifier for mapping of mappings variable. +/// `inner_key_id = H(IN_KEY || slot || contract_address || chain_id)[0]` +pub fn identifier_for_inner_mapping_key_column( slot: u8, contract_address: &Address, chain_id: u64, extra: Vec, -) -> u64 { - compute_id_with_prefix(VALUE_ID_PREFIX, slot, contract_address, chain_id, extra) +) -> ColumnId { + compute_id_with_prefix(INNER_KEY_ID_PREFIX, slot, contract_address, chain_id, extra) +} + +/// Compute inner key indetifier for mapping of mappings variable in raw mode. +/// `inner_key_id = H(IN_KEY || slot || extra)[0]` +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `identifier_for_inner_mapping_key_column`. +pub fn identifier_for_inner_mapping_key_column_raw(slot: u8, extra: Vec) -> u64 { + compute_id_with_prefix_raw(INNER_KEY_ID_PREFIX, slot, extra) +} + +/// Compute tx index identifier for tx index variable. +/// `inner_key_id = H(PREFIX || event_signature || contract_address || chain_id || extra)[0]` +pub fn identifier_for_tx_index_column( + event_signature: &[u8; HASH_LEN], + contract_address: &Address, + extra: &[u8], +) -> ColumnId { + let extra = identifier_raw_extra(contract_address, 0, extra.to_vec()); + + let inputs: Vec = TX_INDEX_PREFIX + .iter() + .copied() + .chain(*event_signature) + .chain(extra) + .collect_vec() + .to_fields(); + + H::hash_no_pad(&inputs).elements[0].to_canonical_u64() +} + +/// Compute gas used identifier for gas used variable. +/// `inner_key_id = H(PREFIX || event_signature || contract_address || chain_id || extra)[0]` +pub fn identifier_for_gas_used_column( + event_signature: &[u8; HASH_LEN], + contract_address: &Address, + extra: &[u8], +) -> ColumnId { + let extra = identifier_raw_extra(contract_address, 0, extra.to_vec()); + + let inputs: Vec = GAS_USED_PREFIX + .iter() + .copied() + .chain(*event_signature) + .chain(extra) + .collect_vec() + .to_fields(); + + H::hash_no_pad(&inputs).elements[0].to_canonical_u64() +} + +/// Compute topic identifier for topic variable. +/// `inner_key_id = H(PREFIX || event_signature || contract_address || chain_id || extra)[0]` +pub fn identifier_for_topic_column( + event_signature: &[u8; HASH_LEN], + contract_address: &Address, + extra: &[u8], +) -> ColumnId { + let extra = identifier_raw_extra(contract_address, 0, extra.to_vec()); + + let inputs: Vec = TOPIC_PREFIX + .iter() + .copied() + .chain(*event_signature) + .chain(extra) + .collect_vec() + .to_fields(); + + H::hash_no_pad(&inputs).elements[0].to_canonical_u64() +} + +/// Compute data identifier for data variable. +/// `inner_key_id = H(PREFIX || event_signature || contract_address || chain_id || extra)[0]` +pub fn identifier_for_data_column( + event_signature: &[u8; HASH_LEN], + contract_address: &Address, + extra: &[u8], +) -> ColumnId { + let extra = identifier_raw_extra(contract_address, 0, extra.to_vec()); + + let inputs: Vec = DATA_PREFIX + .iter() + .copied() + .chain(*event_signature) + .chain(extra) + .collect_vec() + .to_fields(); + + H::hash_no_pad(&inputs).elements[0].to_canonical_u64() } /// Calculate ID with prefix. -fn compute_id_with_prefix( +pub(crate) fn compute_id_with_prefix( prefix: &[u8], slot: u8, contract_address: &Address, chain_id: u64, extra: Vec, -) -> u64 { +) -> ColumnId { + let extra = identifier_raw_extra(contract_address, chain_id, extra); + + compute_id_with_prefix_raw(prefix, slot, extra) +} + +/// Construct the raw extra by contract address, chain ID and extra data. +pub fn identifier_raw_extra(contract_address: &Address, chain_id: u64, extra: Vec) -> Vec { + contract_address + .0 + .into_iter() + .chain(chain_id.to_be_bytes()) + .chain(extra) + .collect() +} + +/// Calculate ID with prefix in raw mode. +/// +/// We could custom the `extra` argument, if it's set to `(contract_address || chain_id || extra)`, +/// It's same with `compute_id_with_prefix`. +fn compute_id_with_prefix_raw(prefix: &[u8], slot: u8, extra: Vec) -> ColumnId { let inputs: Vec = prefix .iter() .cloned() - .chain(iter::once(slot)) - .chain(contract_address.0) - .chain(chain_id.to_be_bytes()) + .chain(once(slot)) .chain(extra) - .collect::>() + .collect_vec() .to_fields(); H::hash_no_pad(&inputs).elements[0].to_canonical_u64() } -/// Calculate `values_digest = D(D(key_id || key) + D(value_id || value))` for mapping variable leaf. -pub fn compute_leaf_mapping_values_digest( - key_id: u64, - value_id: u64, - mapping_key: &[u8], - value: &[u8], -) -> Digest { - assert!(mapping_key.len() <= MAPPING_KEY_LEN); - assert!(value.len() <= MAPPING_LEAF_VALUE_LEN); - - let [packed_key, packed_value] = - [mapping_key, value].map(|arr| left_pad32(arr).pack(Endianness::Big).to_fields()); - - let inputs: Vec<_> = iter::once(GFp::from_canonical_u64(key_id)) - .chain(packed_key) - .collect(); - let k_digest = map_to_curve_point(&inputs); - let inputs: Vec<_> = iter::once(GFp::from_canonical_u64(value_id)) - .chain(packed_value) - .collect(); - let v_digest = map_to_curve_point(&inputs); - // D(key_id || key) + D(value_id || value) - let add_digest = (k_digest + v_digest).to_weierstrass(); - let inputs: Vec<_> = add_digest - .x - .0 +/// Compute the row unique data for single leaf. +pub fn row_unique_data_for_single_leaf() -> HashOutput { + empty_poseidon_hash().into() +} + +/// Compute the row unique data for mapping leaf. +pub fn row_unique_data_for_mapping_leaf(mapping_key: &[u8]) -> HashOutput { + // row_unique_data = H(pack(left_pad32(key)) + let packed_mapping_key = left_pad32(mapping_key) + .pack(Endianness::Big) .into_iter() - .chain(add_digest.y.0) - .chain(iter::once(GFp::from_bool(add_digest.is_inf))) - .collect(); - map_to_curve_point(&inputs) + .map(F::from_canonical_u32) + .collect_vec(); + H::hash_no_pad(&packed_mapping_key).into() } -/// Calculate `values_digest = D(id || value)` for single variable leaf. -pub fn compute_leaf_single_values_digest(id: u64, value: &[u8]) -> Digest { - assert!(value.len() <= MAPPING_LEAF_VALUE_LEN); +/// Compute the row unique data for mapping of mappings leaf. +pub fn row_unique_data_for_mapping_of_mappings_leaf( + outer_mapping_key: &[u8], + inner_mapping_key: &[u8], +) -> HashOutput { + let [packed_outer_key, packed_inner_key] = [outer_mapping_key, inner_mapping_key].map(|key| { + left_pad32(key) + .pack(Endianness::Big) + .into_iter() + .map(F::from_canonical_u32) + }); + // Compute the unique data to identify a row is the mapping key: + // row_unique_data = H(outer_key || inner_key) + let inputs = packed_outer_key.chain(packed_inner_key).collect_vec(); + H::hash_no_pad(&inputs).into() +} - let packed_value = left_pad32(value).pack(Endianness::Big).to_fields(); +/// Function to compute a storage value digest +fn storage_value_digest( + table_metadata: &TableMetadata, + keys: &[&[u8]], + value: &[u8; MAPPING_LEAF_VALUE_LEN], + slot: &StorageSlotInfo, +) -> Digest { + let padded_keys = keys + .iter() + .map(|slice| left_pad32(slice)) + .collect::>(); + // Panic if the number of keys provided is not equal to the number of input columns + assert_eq!( + keys.len(), + table_metadata.input_columns.len(), + "Number of keys: {}, does not equal the number of input columns: {}", + keys.len(), + table_metadata.input_columns.len() + ); + table_metadata.storage_values_digest(padded_keys.as_slice(), value.as_slice(), slot.slot()) +} + +/// Compute the metadata digest for single variable leaf. +pub fn compute_leaf_single_metadata_digest(slot_info: &StorageSlotInfo) -> Digest { + TableMetadata::new(&[], slot_info.table_info()).digest() +} - let inputs: Vec<_> = iter::once(GFp::from_canonical_u64(id)) - .chain(packed_value) - .collect(); - map_to_curve_point(&inputs) +/// Compute the values digest for single variable leaf. +pub fn compute_leaf_single_values_digest( + slot_info: &StorageSlotInfo, + value: [u8; MAPPING_LEAF_VALUE_LEN], +) -> Digest { + let table_metadata = TableMetadata::new(&[], slot_info.table_info()); + storage_value_digest(&table_metadata, &[], &value, slot_info) } -/// Calculate `metadata_digest = D(id || slot)` for single variable leaf. -pub fn compute_leaf_single_metadata_digest(id: u64, slot: u8) -> Digest { - map_to_curve_point(&[GFp::from_canonical_u64(id), GFp::from_canonical_u8(slot)]) +/// Compute the metadata digest for mapping variable leaf. +pub fn compute_leaf_mapping_metadata_digest( + slot_info: &StorageSlotInfo, + key_id: ColumnId, +) -> Digest { + let input_column = InputColumnInfo::new(&[slot_info.slot().slot()], key_id, KEY_ID_PREFIX); + TableMetadata::new(&[input_column], slot_info.table_info()).digest() } -/// Calculate `metadata_digest = D(key_id || value_id || slot)` for mapping variable leaf. -pub fn compute_leaf_mapping_metadata_digest(key_id: u64, value_id: u64, slot: u8) -> Digest { - map_to_curve_point(&[ - GFp::from_canonical_u64(key_id), - GFp::from_canonical_u64(value_id), - GFp::from_canonical_u8(slot), - ]) +/// Compute the values digest for mapping variable leaf. +pub fn compute_leaf_mapping_values_digest( + slot_info: &StorageSlotInfo, + value: [u8; MAPPING_LEAF_VALUE_LEN], + mapping_key: MappingKey, + key_id: ColumnId, +) -> Digest { + let input_column = InputColumnInfo::new(&[slot_info.slot().slot()], key_id, KEY_ID_PREFIX); + let table_metadata = TableMetadata::new(&[input_column], slot_info.table_info()); + storage_value_digest( + &table_metadata, + &[mapping_key.as_slice()], + &value, + slot_info, + ) +} + +/// Compute the metadata digest for mapping of mappings leaf. +pub fn compute_leaf_mapping_of_mappings_metadata_digest( + slot_info: &StorageSlotInfo, + outer_key_id: ColumnId, + inner_key_id: ColumnId, +) -> Digest { + let outer_key_column = InputColumnInfo::new( + &[slot_info.slot().slot()], + outer_key_id, + OUTER_KEY_ID_PREFIX, + ); + let inner_key_column = InputColumnInfo::new( + &[slot_info.slot().slot()], + inner_key_id, + INNER_KEY_ID_PREFIX, + ); + TableMetadata::new( + &[outer_key_column, inner_key_column], + slot_info.table_info(), + ) + .digest() +} + +/// Compute the values digest for mapping of mappings leaf. +pub fn compute_leaf_mapping_of_mappings_values_digest( + slot_info: &StorageSlotInfo, + value: [u8; MAPPING_LEAF_VALUE_LEN], + outer_mapping_data: (MappingKey, ColumnId), + inner_mapping_data: (MappingKey, ColumnId), +) -> Digest { + let (outer_key, outer_key_id) = outer_mapping_data; + let (inner_key, inner_key_id) = inner_mapping_data; + let outer_key_column = InputColumnInfo::new( + &[slot_info.slot().slot()], + outer_key_id, + OUTER_KEY_ID_PREFIX, + ); + let inner_key_column = InputColumnInfo::new( + &[slot_info.slot().slot()], + inner_key_id, + INNER_KEY_ID_PREFIX, + ); + let table_metadata = TableMetadata::new( + &[outer_key_column, inner_key_column], + slot_info.table_info(), + ); + + storage_value_digest( + &table_metadata, + &[outer_key.as_slice(), inner_key.as_slice()], + &value, + slot_info, + ) } diff --git a/mp2-v1/src/values_extraction/planner.rs b/mp2-v1/src/values_extraction/planner.rs new file mode 100644 index 000000000..0325419e7 --- /dev/null +++ b/mp2-v1/src/values_extraction/planner.rs @@ -0,0 +1,587 @@ +//! This code returns an [`UpdateTree`] used to plan how we prove a series of values was extracted from a Merkle Patricia Trie. +use alloy::{ + network::Ethereum, + primitives::{keccak256, B256}, + providers::RootProvider, + transports::Transport, +}; +use anyhow::Result; +use mp2_common::{ + eth::{node_type, EventLogInfo, MP2EthError, NodeType}, + mpt_sequential::PAD_LEN, +}; + +use ryhope::{ + error::RyhopeError, + storage::updatetree::{Next, UpdateTree}, +}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{fmt::Debug, future::Future, hash::Hash}; + +use std::{ + collections::HashMap, + error::Error, + fmt::{Display, Formatter}, + write, +}; + +use super::{ + gadgets::metadata_gadget::TableMetadata, generate_proof, CircuitInput, PublicParameters, +}; + +#[derive(Debug)] +/// Error enum used for Extractable data +pub enum MP2PlannerError { + /// An error that occurs when trying to fetch data from an RPC node, used so that we can know we should retry the call in this case. + FetchError, + /// An error that occurs when the [`UpdateTree`] returns an unexpected output from one of its methods. + UpdateTreeError(String), + /// A conversion from the error type defined in [`mp2_common::eth`] that is not a [`MP2EthError::FetchError`]. + EthError(MP2EthError), + /// An error that occurs from a method in the proving API. + ProvingError(String), + /// Error from within Ryhope + RyhopeError(RyhopeError), +} + +impl Error for MP2PlannerError {} + +impl Display for MP2PlannerError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + MP2PlannerError::FetchError => write!( + f, + "Error occured when trying to fetch data from an RPC node" + ), + MP2PlannerError::UpdateTreeError(s) => write!( + f, + "Error occured when working with the update Tree: {{ inner: {} }}", + s + ), + MP2PlannerError::EthError(e) => write!( + f, + "Error occured in call from mp2_common::eth function {{ inner: {:?} }}", + e + ), + MP2PlannerError::ProvingError(s) => { + write!(f, "Error while proving, extra message: {}", s) + } + MP2PlannerError::RyhopeError(e) => { + write!(f, "Error in Ryhope method {{ inner: {:?} }}", e) + } + } + } +} + +impl From for MP2PlannerError { + fn from(value: MP2EthError) -> Self { + match value { + MP2EthError::FetchError => MP2PlannerError::FetchError, + _ => MP2PlannerError::EthError(value), + } + } +} + +impl From for MP2PlannerError { + fn from(value: RyhopeError) -> Self { + MP2PlannerError::RyhopeError(value) + } +} + +/// Enum used for supplying extra inputs needed to convert [`ProofData`] to [`CircuitInput`]. +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InputEnum { + /// Leaf Variant that contains the extra inputs that depend on the implementation of [`Extractable`] + Leaf(E::ExtraLeafInput), + /// Extension extra input should be a single child proof + Extension(Vec), + /// Branch extra inputs should be a list of child proofs + Branch(Vec>), + /// A dummy input just requires the root hash of the tree and the metadata digest for the extracted item + Dummy(B256), +} + +impl InputEnum { + /// Create a new Branch or extension node with empty input + pub fn empty_non_leaf(node: &[u8]) -> Result { + let node_type = node_type(node)?; + // Match on the node type to make sure we can create an empty version. + match node_type { + NodeType::Branch => Ok(InputEnum::Branch(vec![])), + NodeType::Extension => Ok(InputEnum::Extension(vec![])), + _ => Err(MP2PlannerError::UpdateTreeError( + "Tried to make an empty node from a MPT node that wasn't a Branch or Extension" + .to_string(), + )), + } + } +} + +/// Trait that is implemented for all data that we can provably extract. +pub trait Extractable: Debug { + /// The extra info needed to make a leaf proof for this extraction type. + type ExtraLeafInput: Clone + + Debug + + Serialize + + DeserializeOwned + + PartialEq + + Eq + + Ord + + PartialOrd + + Hash; + /// Method that creates an [`ExtractionUpdatePlan`] that can then be processed either locally or in a distributed fashion. + fn create_update_plan( + &self, + epoch: u64, + provider: &RootProvider, + ) -> impl Future, MP2PlannerError>> + where + Self: Sized; + /// Method that defines how to convert [`ProofData`] into [`CircuitInput`] for this implementation. + fn to_circuit_input( + &self, + proof_data: &ProofData, + ) -> CircuitInput + where + [(); PAD_LEN(LEAF_LEN)]:, + Self: Sized; + /// Method provided for building and processing an [`ExtractionUpdatePlan`] locally. + fn prove_value_extraction< + const MAX_EXTRACTED_COLUMNS: usize, + const LEAF_LEN: usize, + T: Transport + Clone, + >( + &self, + epoch: u64, + pp: &PublicParameters, + provider: &RootProvider, + ) -> impl Future, MP2PlannerError>> + where + [(); PAD_LEN(LEAF_LEN)]:; +} + +/// Struct that stores the MPT node along with any extra data needed for the [`CircuitInput`] API. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct ProofData { + /// The MPT node + node: Vec, + /// Extra inputs as defined by the implementor of [`Extractable`] + extra_inputs: InputEnum, +} + +impl ProofData { + /// Create a new instance of [`ProofData`] + pub fn new(node: Vec, extra_inputs: InputEnum) -> ProofData { + ProofData:: { node, extra_inputs } + } + + /// Create a new instance of [`ProofData`] from a slice of [`u8`] and any extra data required. + pub fn from_slice( + node: &[u8], + extra_inputs: InputEnum, + ) -> Result, MP2PlannerError> { + let node_type = node_type(node)?; + + // Check that the node type matches the extra input type we expect. + if !matches!( + (node_type, &extra_inputs), + (NodeType::Branch, InputEnum::Branch(..)) + | (NodeType::Extension, InputEnum::Extension(..)) + | (NodeType::Leaf, InputEnum::Leaf(..)) + ) { + return Err(MP2PlannerError::ProvingError(format!( + "The node provided: {:?} did not match the extra input type provided: {:?} ", + node_type, extra_inputs + ))); + } + + Ok(ProofData::::new(node.to_vec(), extra_inputs)) + } + + /// Update a [`ProofData`] with a proof represented as a [`Vec`]. This method + /// will error if called on a node whose `extra_inputs` are not either the + /// [`InputEnum::Extension`] or [`InputEnum::Branch`] variant. + pub fn update(&mut self, proof: Vec) -> Result<(), MP2PlannerError> { + match self.extra_inputs { + // If its a branch simply push the proof into the stored vec + InputEnum::Branch(ref mut proofs) => proofs.push(proof), + // For an extension we check that the vec is currently empty, if it is we replace it with + // the provided one. + InputEnum::Extension(ref mut inner_proof) => { + if !proof.is_empty() { + return Err(MP2PlannerError::UpdateTreeError( + "Can't update Extension ProofData if its child proof isn't empty" + .to_string(), + )); + } + *inner_proof = proof; + } + _ => { + return Err(MP2PlannerError::UpdateTreeError( + "Can't update a Proof Data that isn't an Extension or Branch".to_string(), + )) + } + }; + + Ok(()) + } +} + +/// A struct that stores an [`UpdateTree`] of keys and a local cache of [`ProofData`]. +/// This way when a [`WorkplanItem`](ryhope::storage::updatetree::WorkplanItem) is processed we can update the cache so any parent proofs can +/// be processed. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExtractionUpdatePlan { + /// The [`UpdateTree`] that specifies the order proofs should be generated. + pub(crate) update_tree: UpdateTree, + /// The cache of input data, at the beginning only the keys relating to leaf proofs will have all data + /// provided, it should then be updated as these tasks are processed. + pub(crate) proof_cache: HashMap>, +} + +impl ExtractionUpdatePlan { + /// Create a new [`ExtractionUpdatePlan`] from its constituent parts. + pub fn new(update_tree: UpdateTree, proof_cache: HashMap>) -> Self { + Self { + update_tree, + proof_cache, + } + } + /// Method to run the plan to completion locally. For each item in the [`UpdatePlan`](ryhope::storage::updatetree::UpdatePlan) we fetch the data from [`self.proof_cache`](ExtractionUpdatePlan::proof_cache) + /// convert the [`ProofData`] to a [`CircuitInput`] which we then pass to the [`generate_proof`] function defined in [`crate::values_extraction::api`]. We then take the output proof + /// and if the current key has a parent node in [`self.update_tree`](ExtractionUpdatePlan::update_tree) we update the [`ProofData`] stored for this key. If no parent is present we must be at the root of the tree + /// and so we just return the final proof. + pub fn process_locally( + &mut self, + params: &PublicParameters, + extractable: &E, + ) -> Result, MP2PlannerError> + where + [(); PAD_LEN(LEAF_LEN)]:, + { + // Convert the UpdateTree into an UpdatePlan + let mut update_plan = self.update_tree.clone().into_workplan(); + // Instantiate a vector that will eventually be the output. + let mut final_proof = Vec::::new(); + // Run the loop while the UpdatePlan continues to yield tasks. + while let Some(Next::Ready(work_plan_item)) = update_plan.next() { + // Retrieve proof data related to this key + let proof_data = self.proof_cache.get(work_plan_item.k()).ok_or( + MP2PlannerError::UpdateTreeError("Key not present in the proof cache".to_string()), + )?; + // Convert to CircuitInput + let circuit_type = extractable.to_circuit_input(proof_data); + + // Generate the proof + let proof = generate_proof(params, circuit_type).map_err(|e| { + MP2PlannerError::ProvingError(format!( + "Error while generating proof for node {{ inner: {:?} }}", + e + )) + })?; + + // Fetch the parent of this key + let parent = self.update_tree.get_parent_key(work_plan_item.k()); + // Determine next steps based on whether the parent exists + match parent { + Some(parent_key) => { + let proof_data_ref = self.proof_cache.get_mut(&parent_key).unwrap(); + proof_data_ref.update(proof)? + } + None => { + final_proof = proof; + } + } + // Mark the item as done + update_plan.done(&work_plan_item)?; + } + Ok(final_proof) + } +} + +impl Extractable + for EventLogInfo +{ + type ExtraLeafInput = u64; + async fn create_update_plan( + &self, + epoch: u64, + provider: &RootProvider, + ) -> Result, MP2PlannerError> { + // Query for the receipt proofs relating to this event at block number `epoch` + let (proofs, receipt_root) = self.query_receipt_proofs(provider, epoch.into()).await?; + + let mut proof_cache = HashMap::>::new(); + + // Convert the paths into their keys using keccak + if proofs.is_empty() { + let dummy_input = InputEnum::Dummy(receipt_root); + let proof_data = ProofData:: { + node: vec![], + extra_inputs: dummy_input, + }; + + proof_cache.insert(receipt_root, proof_data); + + let update_tree = UpdateTree::::from_path(vec![receipt_root], epoch as i64); + + Ok(ExtractionUpdatePlan::new(update_tree, proof_cache)) + } else { + let key_paths = proofs + .iter() + .map(|input| { + let proof_len = input.mpt_proof.len(); + + // First we add the leaf and its proving data to the cache + let leaf = input + .mpt_proof + .last() + .ok_or(MP2PlannerError::UpdateTreeError( + "MPT proof had no nodes".to_string(), + ))?; + let leaf_key = keccak256(leaf); + let leaf_proof_data = + ProofData::::from_slice(leaf, InputEnum::Leaf(input.tx_index))?; + + proof_cache.insert(leaf_key, leaf_proof_data); + + input + .mpt_proof + .iter() + .take(proof_len - 1) + .map(|proof_vec| { + let proof_key = keccak256(proof_vec); + let proof_input = InputEnum::::empty_non_leaf(proof_vec)?; + let proof_data = ProofData::::from_slice(proof_vec, proof_input)?; + proof_cache.insert(proof_key, proof_data); + Ok(proof_key) + }) + .chain(std::iter::once(Ok(leaf_key))) + .collect::, MP2PlannerError>>() + }) + .collect::>, MP2PlannerError>>()?; + + // Now we make the UpdateTree + let update_tree = UpdateTree::::from_paths(key_paths, epoch as i64); + + // Finally make the plan + Ok(ExtractionUpdatePlan::::new(update_tree, proof_cache)) + } + } + + fn to_circuit_input( + &self, + proof_data: &ProofData, + ) -> CircuitInput + where + [(); PAD_LEN(LEAF_LEN)]:, + Self: Sized, + { + let ProofData { node, extra_inputs } = proof_data; + match extra_inputs { + InputEnum::Branch(child_proofs) => { + CircuitInput::new_branch(node.clone(), child_proofs.clone()) + } + InputEnum::Extension(child_proof) => { + CircuitInput::new_extension(node.clone(), child_proof.clone()) + } + InputEnum::Leaf(tx_index) => CircuitInput::new_receipt_leaf(node, *tx_index, self), + InputEnum::Dummy(block_hash) => { + let metadata = TableMetadata::from_event_info(self); + let metadata_digest = metadata.digest(); + CircuitInput::new_dummy(*block_hash, metadata_digest) + } + } + } + + async fn prove_value_extraction< + const MAX_EXTRACTED_COLUMNS: usize, + const LEAF_LEN: usize, + T: Transport + Clone, + >( + &self, + epoch: u64, + pp: &PublicParameters, + provider: &RootProvider, + ) -> Result, MP2PlannerError> + where + [(); PAD_LEN(LEAF_LEN)]:, + { + let mut extraction_plan = self.create_update_plan(epoch, provider).await?; + + extraction_plan.process_locally(pp, self) + } +} + +#[cfg(test)] +pub mod tests { + + use alloy::{primitives::Address, providers::ProviderBuilder, sol}; + use anyhow::anyhow; + + use mp2_common::{ + digest::Digest, + eth::ReceiptProofInfo, + proof::ProofWithVK, + types::GFp, + utils::{Endianness, Packer}, + }; + use mp2_test::eth::get_mainnet_url; + use plonky2::field::types::Field; + use plonky2_ecgfp5::curve::curve::Point; + use std::str::FromStr; + + use crate::values_extraction::{ + api::build_circuits_params, gadgets::metadata_gadget::TableMetadata, PublicInputs, + }; + + use super::*; + + #[tokio::test] + async fn test_receipt_update_tree() -> Result<()> { + // First get the info we will feed in to our function + let epoch: u64 = 21362445; + let (receipts_root, event_info, _) = build_test_data(epoch).await?; + + let url = get_mainnet_url(); + // get some tx and receipt + let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); + + let extraction_plan = event_info.create_update_plan(epoch, &provider).await?; + + assert_eq!(*extraction_plan.update_tree.root(), receipts_root); + Ok(()) + } + + #[tokio::test] + async fn test_receipt_local_proving() -> Result<()> { + let pp = build_circuits_params::<512, 5>(); + // Test proving on a block with some relevant events + test_receipt_proving(21362445, &pp).await?; + // Test proving on a block with no relevant events + test_receipt_proving(21767312, &pp).await + } + + async fn test_receipt_proving(epoch: u64, pp: &PublicParameters<512, 5>) -> Result<()> { + // First get the info we will feed in to our function + let (receipts_root, event_info, proof_info) = build_test_data(epoch).await?; + + let url = get_mainnet_url(); + // get some tx and receipt + let provider = ProviderBuilder::new().on_http(url.parse().unwrap()); + + let final_proof_bytes = event_info + .prove_value_extraction(epoch, pp, &provider) + .await?; + + let final_proof = ProofWithVK::deserialize(&final_proof_bytes)?; + + let metadata = TableMetadata::from(event_info); + + let metadata_digest = metadata.digest(); + + let value_digest = proof_info.iter().try_fold(Digest::NEUTRAL, |acc, info| { + let node = info + .mpt_proof + .last() + .ok_or(MP2PlannerError::UpdateTreeError( + "MPT proof had no nodes".to_string(), + ))?; + Result::::Ok( + acc + metadata.receipt_value_digest(info.tx_index, node, &event_info), + ) + })?; + + let pi = PublicInputs::new(&final_proof.proof.public_inputs); + + // Check the output hash + { + assert_eq!( + pi.root_hash(), + receipts_root.0.to_vec().pack(Endianness::Little) + ); + } + + // Check value digest + { + assert_eq!(pi.values_digest(), value_digest.to_weierstrass()); + } + + // Check metadata digest + { + assert_eq!(pi.metadata_digest(), metadata_digest.to_weierstrass()); + } + + // Check that the number of rows is equal to the length of + { + assert_eq!(pi.n(), GFp::from_canonical_usize(proof_info.len())); + } + Ok(()) + } + + type TestData = (B256, EventLogInfo<2, 1>, Vec); + /// Function that fetches a block together with its transaction trie and receipt trie for testing purposes. + async fn build_test_data(block_number: u64) -> Result { + let url = get_mainnet_url(); + // get some tx and receipt + let provider = ProviderBuilder::new().on_http(url.parse()?); + + let event_info = test_receipt_trie_helper().await?; + let mut proof_info = vec![]; + let mut root = B256::default(); + let mut success = false; + for _ in 0..10 { + match event_info + .query_receipt_proofs(&provider, block_number.into()) + .await + { + // For each of the logs return the transacion its included in, then sort and remove duplicates. + Ok((response, fetched_root)) => { + proof_info = response; + root = fetched_root; + success = true; + break; + } + Err(_) => { + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + continue; + } + } + } + + if !success { + return Err(anyhow!("Could not query mainnet successfully")); + } + + Ok((root, event_info, proof_info)) + } + + /// Function to build a list of [`ReceiptProofInfo`] for a set block. + async fn test_receipt_trie_helper() -> Result> { + // First we choose the contract and event we are going to monitor. + // We use the mainnet PudgyPenguins contract at address 0xbd3531da5cf5857e7cfaa92426877b022e612cf8 + // and monitor for the `Approval` event. + let address = Address::from_str("0xbd3531da5cf5857e7cfaa92426877b022e612cf8")?; + + // We have to create what the event abi looks like + sol! { + #[allow(missing_docs)] + #[sol(rpc, abi)] + contract EventTest { + #[derive(Debug)] + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + + } + }; + + let approval_event = EventTest::abi::events() + .get("ApprovalForAll") + .ok_or(anyhow!("No ApprovalForAll event exists"))?[0] + .clone(); + + Ok(EventLogInfo::<2, 1>::new( + address, + &approval_event.signature(), + )) + } +} diff --git a/mp2-v1/src/values_extraction/public_inputs.rs b/mp2-v1/src/values_extraction/public_inputs.rs index ee7e39118..dfbaccd27 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 @@ -139,12 +139,16 @@ impl<'a, T: Copy> PublicInputs<'a, T> { (key, ptr) } + pub fn metadata_digest_raw(&self) -> &[T] { + &self.proof_inputs[DM_RANGE] + } + pub fn values_digest_info(&self) -> ([T; 5], [T; 5], T) { convert_slice_to_curve_point(&self.proof_inputs[DV_RANGE]) } pub fn metadata_digest_info(&self) -> ([T; 5], [T; 5], T) { - convert_slice_to_curve_point(&self.proof_inputs[DM_RANGE]) + convert_slice_to_curve_point(self.metadata_digest_raw()) } /// Return the number of leaves extracted from this subtree. diff --git a/mp2-v1/test-contracts/src/Event.sol b/mp2-v1/test-contracts/src/Event.sol new file mode 100644 index 000000000..683030020 --- /dev/null +++ b/mp2-v1/test-contracts/src/Event.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract EventEmitter { + uint256 public number; + event noIndexed(); + event oneIndexed(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 + ); + event noIOneD(uint256 num); + event noITwoD(uint256 num, uint256 numTwo); + event oneIOneD(uint256 indexed num, uint256 numTwo); + event oneITwoD(uint256 indexed num, uint256 numTwo, uint256 numThree); + event twoIOneD( + uint256 indexed num, + uint256 indexed numTwo, + uint256 numThree + ); + event twoITwoD( + uint256 indexed num, + uint256 indexed numTwo, + uint256 numThree, + uint256 numFour + ); + + function testNoIndexed() public { + emit noIndexed(); + } + + function testOneIndexed() public { + emit oneIndexed(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 testNoIOneD() public { + emit noIOneD(number); + increment(); + } + + function testNoITwoD() public { + emit noITwoD(number, number + 1); + increment(); + } + + function testOneIOneD() public { + emit oneIOneD(number, number + 1); + increment(); + } + + function testOneITwoD() public { + emit oneITwoD(number, number + 1, number + 2); + increment(); + } + + function testTwoIOneD() public { + emit twoIOneD(number, number + 1, number + 2); + increment(); + } + + function testTwoITwoD() public { + emit twoITwoD(number, number + 1, number + 2, number + 3); + increment(); + } + + function increment() public { + number++; + } +} diff --git a/mp2-v1/test-contracts/src/Simple.sol b/mp2-v1/test-contracts/src/Simple.sol index af865ed65..8f941fdec 100644 --- a/mp2-v1/test-contracts/src/Simple.sol +++ b/mp2-v1/test-contracts/src/Simple.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.13; contract Simple { - enum MappingOperation { - Deletion, - Update, - Insertion + enum MappingOperation { + Deletion, + Update, + Insertion } struct MappingChange { @@ -14,6 +14,37 @@ contract Simple { MappingOperation operation; } + struct MappingStructChange { + uint256 key; + uint256 field1; + uint128 field2; + uint128 field3; + MappingOperation operation; + } + + struct MappingOfSingleValueMappingsChange { + uint256 outerKey; + uint256 innerKey; + uint256 value; + MappingOperation operation; + } + + struct MappingOfStructMappingsChange { + uint256 outerKey; + uint256 innerKey; + uint256 field1; + uint128 field2; + uint128 field3; + MappingOperation operation; + } + + struct LargeStruct { + // This field should live in one EVM word + uint256 field1; + // Both these fields should live in the same EVM word + uint128 field2; + uint128 field3; + } // Test simple slots (slot 0 - 3) bool public s1; @@ -27,7 +58,21 @@ contract Simple { // Test array (slot 5) uint256[] public arr1; - // Set the simple slots. + // Test simple struct (slot 6) + LargeStruct public simpleStruct; + + // Test mapping struct (slot 8) + mapping(uint256 => LargeStruct) public structMapping; + + // Test mapping of single value mappings (slot 9) + mapping(uint256 => mapping(uint256 => uint256)) + public mappingOfSingleValueMappings; + + // Test mapping of struct mappings (slot 10) + mapping(uint256 => mapping(uint256 => LargeStruct)) + public mappingOfStructMappings; + + // Set the simple slots. function setSimples( bool newS1, uint256 newS2, @@ -39,8 +84,9 @@ contract Simple { s3 = newS3; s4 = newS4; } + function setS2(uint256 newS2) public { - s2 = newS2; + s2 = newS2; } // Set a mapping slot by key and value. @@ -49,17 +95,121 @@ contract Simple { } function changeMapping(MappingChange[] memory changes) public { - for (uint256 i = 0; i < changes.length; i++) { - if (changes[i].operation == MappingOperation.Deletion) { - delete m1[changes[i].key]; - } else if (changes[i].operation == MappingOperation.Insertion || changes[i].operation == MappingOperation.Update) { - setMapping(changes[i].key,changes[i].value); - } - } + for (uint256 i = 0; i < changes.length; i++) { + if (changes[i].operation == MappingOperation.Deletion) { + delete m1[changes[i].key]; + } else if ( + changes[i].operation == MappingOperation.Insertion || + changes[i].operation == MappingOperation.Update + ) { + setMapping(changes[i].key, changes[i].value); + } + } } // Add a value to the array. function addToArray(uint256 value) public { arr1.push(value); } + + // Set simple struct. + function setSimpleStruct(LargeStruct calldata input) public { + simpleStruct = input; + } + + // Set mapping struct. + function setMappingStruct( + uint256 _key, + uint256 _field1, + uint128 _field2, + uint128 _field3 + ) public { + structMapping[_key] = LargeStruct(_field1, _field2, _field3); + } + + function changeMapping(MappingStructChange[] memory changes) public { + for (uint256 i = 0; i < changes.length; i++) { + if (changes[i].operation == MappingOperation.Deletion) { + delete structMapping[changes[i].key]; + } else if ( + changes[i].operation == MappingOperation.Insertion || + changes[i].operation == MappingOperation.Update + ) { + setMappingStruct( + changes[i].key, + changes[i].field1, + changes[i].field2, + changes[i].field3 + ); + } + } + } + + function changeMapping( + MappingOfSingleValueMappingsChange[] memory changes + ) public { + for (uint256 i = 0; i < changes.length; i++) { + if (changes[i].operation == MappingOperation.Deletion) { + delete mappingOfSingleValueMappings[changes[i].outerKey][ + changes[i].innerKey + ]; + } else if ( + changes[i].operation == MappingOperation.Insertion || + changes[i].operation == MappingOperation.Update + ) { + setMappingOfSingleValueMappings( + changes[i].outerKey, + changes[i].innerKey, + changes[i].value + ); + } + } + } + + function changeMapping( + MappingOfStructMappingsChange[] memory changes + ) public { + for (uint256 i = 0; i < changes.length; i++) { + if (changes[i].operation == MappingOperation.Deletion) { + delete mappingOfStructMappings[changes[i].outerKey][ + changes[i].innerKey + ]; + } else if ( + changes[i].operation == MappingOperation.Insertion || + changes[i].operation == MappingOperation.Update + ) { + setMappingOfStructMappings( + changes[i].outerKey, + changes[i].innerKey, + changes[i].field1, + changes[i].field2, + changes[i].field3 + ); + } + } + } + + // Set mapping of single value mappings. + function setMappingOfSingleValueMappings( + uint256 outerKey, + uint256 innerKey, + uint256 value + ) public { + mappingOfSingleValueMappings[outerKey][innerKey] = value; + } + + // Set mapping of struct mappings. + function setMappingOfStructMappings( + uint256 outerKey, + uint256 innerKey, + uint256 field1, + uint128 field2, + uint128 field3 + ) public { + mappingOfStructMappings[outerKey][innerKey] = LargeStruct( + field1, + field2, + field3 + ); + } } diff --git a/mp2-v1/tests/common/bindings/eventemitter.rs b/mp2-v1/tests/common/bindings/eventemitter.rs new file mode 100644 index 000000000..7843a3356 --- /dev/null +++ b/mp2-v1/tests/common/bindings/eventemitter.rs @@ -0,0 +1,4197 @@ +/** + +Generated by the following Solidity interface... +```solidity +interface EventEmitter { + event noIOneD(uint256 num); + event noITwoD(uint256 num, uint256 numTwo); + event noIndexed(); + event oneData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour); + event oneIOneD(uint256 indexed num, uint256 numTwo); + event oneITwoD(uint256 indexed num, uint256 numTwo, uint256 numThree); + event oneIndexed(uint256 indexed num); + event threeIndexed(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree); + event twoData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour, uint256 numFive); + event twoIOneD(uint256 indexed num, uint256 indexed numTwo, uint256 numThree); + event twoITwoD(uint256 indexed num, uint256 indexed numTwo, uint256 numThree, uint256 numFour); + event twoIndexed(uint256 indexed num, uint256 indexed numTwo); + + function increment() external; + function number() external view returns (uint256); + function testNoIOneD() external; + function testNoITwoD() external; + function testNoIndexed() external; + function testOneData() external; + function testOneIOneD() external; + function testOneITwoD() external; + function testOneIndexed() external; + function testThreeIndexed() external; + function testTwoData() external; + function testTwoIOneD() external; + function testTwoITwoD() external; + function testTwoIndexed() external; +} +``` + +...which was generated by the following JSON ABI: +```json +[ + { + "type": "function", + "name": "increment", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "number", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "testNoIOneD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testNoITwoD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testNoIndexed", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testOneData", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testOneIOneD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testOneITwoD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testOneIndexed", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testThreeIndexed", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testTwoData", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testTwoIOneD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testTwoITwoD", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "testTwoIndexed", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "noIOneD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "noITwoD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "noIndexed", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "oneData", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numFour", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "oneIOneD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "oneITwoD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "oneIndexed", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "threeIndexed", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "twoData", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numFour", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "numFive", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "twoIOneD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "twoITwoD", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numThree", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "numFour", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "twoIndexed", + "inputs": [ + { + "name": "num", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + }, + { + "name": "numTwo", + "type": "uint256", + "indexed": true, + "internalType": "uint256" + } + ], + "anonymous": false + } +] +```*/ +#[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style, + clippy::empty_structs_with_brackets +)] +pub mod EventEmitter { + use super::*; + use alloy::sol_types as alloy_sol_types; + /// The creation / init bytecode of the contract. + /// + /// ```text + ///0x608060405234801561000f575f80fd5b5061057e8061001d5f395ff3fe608060405234801561000f575f80fd5b50600436106100e3575f3560e01c806346d6a7b5116100885780638381f58a116100635780638381f58a14610139578063b1e057a914610153578063c02420001461015b578063d09de08a14610163575f80fd5b806346d6a7b51461012157806363eb70f014610129578063729d452014610131575f80fd5b806331c1c63b116100c357806331c1c63b14610101578063338b538a146101095780634282ed58146101115780634369f72814610119575f80fd5b80623c7e56146100e7578062d83b55146100f15780632dc34764146100f9575b5f80fd5b6100ef61016b565b005b6100ef6101cf565b6100ef61022b565b6100ef610295565b6100ef6102e2565b6100ef610322565b6100ef61034b565b6100ef610387565b6100ef6103d7565b6100ef61042c565b6101415f5481565b60405190815260200160405180910390f35b6100ef61045f565b6100ef6104bc565b6100ef6104ee565b5f54610178906002610517565b5f54610185906001610517565b5f547ff57f433eb9493cf4d9cb5763c12221d9b095804644d4ee006a78c72076cff9476101b3826003610517565b6040519081526020015b60405180910390a46101cd6104ee565b565b5f547fef4c88193498df237f039055d1212ac2a3b93ed8aea88c814312e50f6a32592d6101fd826001610517565b5f5461020a906002610517565b604080519283526020830191909152015b60405180910390a26101cd6104ee565b5f54610238906002610517565b5f54610245906001610517565b5f547ff03d29753fbd5ac209bab88a99b396bcc25c3e72530d02c81aea4d324ab3d742610273826003610517565b5f54610280906004610517565b604080519283526020830191909152016101bd565b5f546102a2906002610517565b5f546102af906001610517565b5f805460405190917f1d18de2cd8798a1c29b9255930c807eb6c84ae0acb2219acbb11e0f65cf813e991a46101cd6104ee565b5f546102ef906001610517565b5f805460405190917fa6baf14d8f11d7a4497089bb3fca0adfc34837cfb1f4aa370634d36ef0305b4691a36101cd6104ee565b6040517ef7c74f0533aa15e5ac7cafa9f9261d14da1e78830deba7110fbc79001ed15e905f90a1565b5f547f168718c0b1eb6bfd7b0edecea5c6fc6502737ad73a4c9f52ffa7e553c8eb9f53610379826001610517565b60405190815260200161021b565b5f547f2fa61517ddf9dc7f2f3d5ca72414a01c834d9c5bb7c336c977423c85094bba61906103b6816001610517565b604080519283526020830191909152015b60405180910390a16101cd6104ee565b5f546103e4906001610517565b5f547f3bb2d6337882faa5526cf806c9763904a90f3363590dd4386913e3fcd8a2e1d1610412826002610517565b6040519081526020015b60405180910390a36101cd6104ee565b5f805460405190917fc2809a1a2fb95d84cfdc488cdb320a144c158f8d44836c9c2d4badba082bfdfa91a26101cd6104ee565b5f5461046c906001610517565b5f547f4b92229abe204a30d7b088d8110291760934d65b3c960680ad94e05f52a8860561049a826002610517565b5f546104a7906003610517565b6040805192835260208301919091520161041c565b7f04f7fb289e51ea9996ec98e62ff4b651becfa6e53f3b850be209b69741c66f245f546040516103c791815260200190565b5f805490806104fc83610530565b9190505550565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561052a5761052a610503565b92915050565b5f6001820161054157610541610503565b506001019056fea2646970667358221220b4cc2df5eed06f538a31157edfaeeee591a5719d35fb47f3b6ce5d31c1ffe2f964736f6c63430008180033 + /// ``` + #[rustfmt::skip] + #[allow(clippy::all)] + pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[Pa\x05~\x80a\0\x1D_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xE3W_5`\xE0\x1C\x80cF\xD6\xA7\xB5\x11a\0\x88W\x80c\x83\x81\xF5\x8A\x11a\0cW\x80c\x83\x81\xF5\x8A\x14a\x019W\x80c\xB1\xE0W\xA9\x14a\x01SW\x80c\xC0$ \0\x14a\x01[W\x80c\xD0\x9D\xE0\x8A\x14a\x01cW_\x80\xFD[\x80cF\xD6\xA7\xB5\x14a\x01!W\x80cc\xEBp\xF0\x14a\x01)W\x80cr\x9DE \x14a\x011W_\x80\xFD[\x80c1\xC1\xC6;\x11a\0\xC3W\x80c1\xC1\xC6;\x14a\x01\x01W\x80c3\x8BS\x8A\x14a\x01\tW\x80cB\x82\xEDX\x14a\x01\x11W\x80cCi\xF7(\x14a\x01\x19W_\x80\xFD[\x80b<~V\x14a\0\xE7W\x80b\xD8;U\x14a\0\xF1W\x80c-\xC3Gd\x14a\0\xF9W[_\x80\xFD[a\0\xEFa\x01kV[\0[a\0\xEFa\x01\xCFV[a\0\xEFa\x02+V[a\0\xEFa\x02\x95V[a\0\xEFa\x02\xE2V[a\0\xEFa\x03\"V[a\0\xEFa\x03KV[a\0\xEFa\x03\x87V[a\0\xEFa\x03\xD7V[a\0\xEFa\x04,V[a\x01A_T\x81V[`@Q\x90\x81R` \x01`@Q\x80\x91\x03\x90\xF3[a\0\xEFa\x04_V[a\0\xEFa\x04\xBCV[a\0\xEFa\x04\xEEV[_Ta\x01x\x90`\x02a\x05\x17V[_Ta\x01\x85\x90`\x01a\x05\x17V[_T\x7F\xF5\x7FC>\xB9I<\xF4\xD9\xCBWc\xC1\"!\xD9\xB0\x95\x80FD\xD4\xEE\0jx\xC7 v\xCF\xF9Ga\x01\xB3\x82`\x03a\x05\x17V[`@Q\x90\x81R` \x01[`@Q\x80\x91\x03\x90\xA4a\x01\xCDa\x04\xEEV[V[_T\x7F\xEFL\x88\x194\x98\xDF#\x7F\x03\x90U\xD1!*\xC2\xA3\xB9>\xD8\xAE\xA8\x8C\x81C\x12\xE5\x0Fj2Y-a\x01\xFD\x82`\x01a\x05\x17V[_Ta\x02\n\x90`\x02a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01[`@Q\x80\x91\x03\x90\xA2a\x01\xCDa\x04\xEEV[_Ta\x028\x90`\x02a\x05\x17V[_Ta\x02E\x90`\x01a\x05\x17V[_T\x7F\xF0=)u?\xBDZ\xC2\t\xBA\xB8\x8A\x99\xB3\x96\xBC\xC2\\>rS\r\x02\xC8\x1A\xEAM2J\xB3\xD7Ba\x02s\x82`\x03a\x05\x17V[_Ta\x02\x80\x90`\x04a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01a\x01\xBDV[_Ta\x02\xA2\x90`\x02a\x05\x17V[_Ta\x02\xAF\x90`\x01a\x05\x17V[_\x80T`@Q\x90\x91\x7F\x1D\x18\xDE,\xD8y\x8A\x1C)\xB9%Y0\xC8\x07\xEBl\x84\xAE\n\xCB\"\x19\xAC\xBB\x11\xE0\xF6\\\xF8\x13\xE9\x91\xA4a\x01\xCDa\x04\xEEV[_Ta\x02\xEF\x90`\x01a\x05\x17V[_\x80T`@Q\x90\x91\x7F\xA6\xBA\xF1M\x8F\x11\xD7\xA4Ip\x89\xBB?\xCA\n\xDF\xC3H7\xCF\xB1\xF4\xAA7\x064\xD3n\xF00[F\x91\xA3a\x01\xCDa\x04\xEEV[`@Q~\xF7\xC7O\x053\xAA\x15\xE5\xAC|\xAF\xA9\xF9&\x1D\x14\xDA\x1Ex\x83\r\xEB\xA7\x11\x0F\xBCy\0\x1E\xD1^\x90_\x90\xA1V[_T\x7F\x16\x87\x18\xC0\xB1\xEBk\xFD{\x0E\xDE\xCE\xA5\xC6\xFCe\x02sz\xD7:L\x9FR\xFF\xA7\xE5S\xC8\xEB\x9FSa\x03y\x82`\x01a\x05\x17V[`@Q\x90\x81R` \x01a\x02\x1BV[_T\x7F/\xA6\x15\x17\xDD\xF9\xDC\x7F/=\\\xA7$\x14\xA0\x1C\x83M\x9C[\xB7\xC36\xC9wB<\x85\tK\xBAa\x90a\x03\xB6\x81`\x01a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01[`@Q\x80\x91\x03\x90\xA1a\x01\xCDa\x04\xEEV[_Ta\x03\xE4\x90`\x01a\x05\x17V[_T\x7F;\xB2\xD63x\x82\xFA\xA5Rl\xF8\x06\xC9v9\x04\xA9\x0F3cY\r\xD48i\x13\xE3\xFC\xD8\xA2\xE1\xD1a\x04\x12\x82`\x02a\x05\x17V[`@Q\x90\x81R` \x01[`@Q\x80\x91\x03\x90\xA3a\x01\xCDa\x04\xEEV[_\x80T`@Q\x90\x91\x7F\xC2\x80\x9A\x1A/\xB9]\x84\xCF\xDCH\x8C\xDB2\n\x14L\x15\x8F\x8DD\x83l\x9C-K\xAD\xBA\x08+\xFD\xFA\x91\xA2a\x01\xCDa\x04\xEEV[_Ta\x04l\x90`\x01a\x05\x17V[_T\x7FK\x92\"\x9A\xBE J0\xD7\xB0\x88\xD8\x11\x02\x91v\t4\xD6[<\x96\x06\x80\xAD\x94\xE0_R\xA8\x86\x05a\x04\x9A\x82`\x02a\x05\x17V[_Ta\x04\xA7\x90`\x03a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01a\x04\x1CV[\x7F\x04\xF7\xFB(\x9EQ\xEA\x99\x96\xEC\x98\xE6/\xF4\xB6Q\xBE\xCF\xA6\xE5?;\x85\x0B\xE2\t\xB6\x97A\xC6o$_T`@Qa\x03\xC7\x91\x81R` \x01\x90V[_\x80T\x90\x80a\x04\xFC\x83a\x050V[\x91\x90PUPV[cNH{q`\xE0\x1B_R`\x11`\x04R`$_\xFD[\x80\x82\x01\x80\x82\x11\x15a\x05*Wa\x05*a\x05\x03V[\x92\x91PPV[_`\x01\x82\x01a\x05AWa\x05Aa\x05\x03V[P`\x01\x01\x90V\xFE\xA2dipfsX\"\x12 \xB4\xCC-\xF5\xEE\xD0oS\x8A1\x15~\xDF\xAE\xEE\xE5\x91\xA5q\x9D5\xFBG\xF3\xB6\xCE]1\xC1\xFF\xE2\xF9dsolcC\0\x08\x18\x003", + ); + /// The runtime bytecode of the contract, as deployed on the network. + /// + /// ```text + ///0x608060405234801561000f575f80fd5b50600436106100e3575f3560e01c806346d6a7b5116100885780638381f58a116100635780638381f58a14610139578063b1e057a914610153578063c02420001461015b578063d09de08a14610163575f80fd5b806346d6a7b51461012157806363eb70f014610129578063729d452014610131575f80fd5b806331c1c63b116100c357806331c1c63b14610101578063338b538a146101095780634282ed58146101115780634369f72814610119575f80fd5b80623c7e56146100e7578062d83b55146100f15780632dc34764146100f9575b5f80fd5b6100ef61016b565b005b6100ef6101cf565b6100ef61022b565b6100ef610295565b6100ef6102e2565b6100ef610322565b6100ef61034b565b6100ef610387565b6100ef6103d7565b6100ef61042c565b6101415f5481565b60405190815260200160405180910390f35b6100ef61045f565b6100ef6104bc565b6100ef6104ee565b5f54610178906002610517565b5f54610185906001610517565b5f547ff57f433eb9493cf4d9cb5763c12221d9b095804644d4ee006a78c72076cff9476101b3826003610517565b6040519081526020015b60405180910390a46101cd6104ee565b565b5f547fef4c88193498df237f039055d1212ac2a3b93ed8aea88c814312e50f6a32592d6101fd826001610517565b5f5461020a906002610517565b604080519283526020830191909152015b60405180910390a26101cd6104ee565b5f54610238906002610517565b5f54610245906001610517565b5f547ff03d29753fbd5ac209bab88a99b396bcc25c3e72530d02c81aea4d324ab3d742610273826003610517565b5f54610280906004610517565b604080519283526020830191909152016101bd565b5f546102a2906002610517565b5f546102af906001610517565b5f805460405190917f1d18de2cd8798a1c29b9255930c807eb6c84ae0acb2219acbb11e0f65cf813e991a46101cd6104ee565b5f546102ef906001610517565b5f805460405190917fa6baf14d8f11d7a4497089bb3fca0adfc34837cfb1f4aa370634d36ef0305b4691a36101cd6104ee565b6040517ef7c74f0533aa15e5ac7cafa9f9261d14da1e78830deba7110fbc79001ed15e905f90a1565b5f547f168718c0b1eb6bfd7b0edecea5c6fc6502737ad73a4c9f52ffa7e553c8eb9f53610379826001610517565b60405190815260200161021b565b5f547f2fa61517ddf9dc7f2f3d5ca72414a01c834d9c5bb7c336c977423c85094bba61906103b6816001610517565b604080519283526020830191909152015b60405180910390a16101cd6104ee565b5f546103e4906001610517565b5f547f3bb2d6337882faa5526cf806c9763904a90f3363590dd4386913e3fcd8a2e1d1610412826002610517565b6040519081526020015b60405180910390a36101cd6104ee565b5f805460405190917fc2809a1a2fb95d84cfdc488cdb320a144c158f8d44836c9c2d4badba082bfdfa91a26101cd6104ee565b5f5461046c906001610517565b5f547f4b92229abe204a30d7b088d8110291760934d65b3c960680ad94e05f52a8860561049a826002610517565b5f546104a7906003610517565b6040805192835260208301919091520161041c565b7f04f7fb289e51ea9996ec98e62ff4b651becfa6e53f3b850be209b69741c66f245f546040516103c791815260200190565b5f805490806104fc83610530565b9190505550565b634e487b7160e01b5f52601160045260245ffd5b8082018082111561052a5761052a610503565b92915050565b5f6001820161054157610541610503565b506001019056fea2646970667358221220b4cc2df5eed06f538a31157edfaeeee591a5719d35fb47f3b6ce5d31c1ffe2f964736f6c63430008180033 + /// ``` + #[rustfmt::skip] + #[allow(clippy::all)] + pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xE3W_5`\xE0\x1C\x80cF\xD6\xA7\xB5\x11a\0\x88W\x80c\x83\x81\xF5\x8A\x11a\0cW\x80c\x83\x81\xF5\x8A\x14a\x019W\x80c\xB1\xE0W\xA9\x14a\x01SW\x80c\xC0$ \0\x14a\x01[W\x80c\xD0\x9D\xE0\x8A\x14a\x01cW_\x80\xFD[\x80cF\xD6\xA7\xB5\x14a\x01!W\x80cc\xEBp\xF0\x14a\x01)W\x80cr\x9DE \x14a\x011W_\x80\xFD[\x80c1\xC1\xC6;\x11a\0\xC3W\x80c1\xC1\xC6;\x14a\x01\x01W\x80c3\x8BS\x8A\x14a\x01\tW\x80cB\x82\xEDX\x14a\x01\x11W\x80cCi\xF7(\x14a\x01\x19W_\x80\xFD[\x80b<~V\x14a\0\xE7W\x80b\xD8;U\x14a\0\xF1W\x80c-\xC3Gd\x14a\0\xF9W[_\x80\xFD[a\0\xEFa\x01kV[\0[a\0\xEFa\x01\xCFV[a\0\xEFa\x02+V[a\0\xEFa\x02\x95V[a\0\xEFa\x02\xE2V[a\0\xEFa\x03\"V[a\0\xEFa\x03KV[a\0\xEFa\x03\x87V[a\0\xEFa\x03\xD7V[a\0\xEFa\x04,V[a\x01A_T\x81V[`@Q\x90\x81R` \x01`@Q\x80\x91\x03\x90\xF3[a\0\xEFa\x04_V[a\0\xEFa\x04\xBCV[a\0\xEFa\x04\xEEV[_Ta\x01x\x90`\x02a\x05\x17V[_Ta\x01\x85\x90`\x01a\x05\x17V[_T\x7F\xF5\x7FC>\xB9I<\xF4\xD9\xCBWc\xC1\"!\xD9\xB0\x95\x80FD\xD4\xEE\0jx\xC7 v\xCF\xF9Ga\x01\xB3\x82`\x03a\x05\x17V[`@Q\x90\x81R` \x01[`@Q\x80\x91\x03\x90\xA4a\x01\xCDa\x04\xEEV[V[_T\x7F\xEFL\x88\x194\x98\xDF#\x7F\x03\x90U\xD1!*\xC2\xA3\xB9>\xD8\xAE\xA8\x8C\x81C\x12\xE5\x0Fj2Y-a\x01\xFD\x82`\x01a\x05\x17V[_Ta\x02\n\x90`\x02a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01[`@Q\x80\x91\x03\x90\xA2a\x01\xCDa\x04\xEEV[_Ta\x028\x90`\x02a\x05\x17V[_Ta\x02E\x90`\x01a\x05\x17V[_T\x7F\xF0=)u?\xBDZ\xC2\t\xBA\xB8\x8A\x99\xB3\x96\xBC\xC2\\>rS\r\x02\xC8\x1A\xEAM2J\xB3\xD7Ba\x02s\x82`\x03a\x05\x17V[_Ta\x02\x80\x90`\x04a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01a\x01\xBDV[_Ta\x02\xA2\x90`\x02a\x05\x17V[_Ta\x02\xAF\x90`\x01a\x05\x17V[_\x80T`@Q\x90\x91\x7F\x1D\x18\xDE,\xD8y\x8A\x1C)\xB9%Y0\xC8\x07\xEBl\x84\xAE\n\xCB\"\x19\xAC\xBB\x11\xE0\xF6\\\xF8\x13\xE9\x91\xA4a\x01\xCDa\x04\xEEV[_Ta\x02\xEF\x90`\x01a\x05\x17V[_\x80T`@Q\x90\x91\x7F\xA6\xBA\xF1M\x8F\x11\xD7\xA4Ip\x89\xBB?\xCA\n\xDF\xC3H7\xCF\xB1\xF4\xAA7\x064\xD3n\xF00[F\x91\xA3a\x01\xCDa\x04\xEEV[`@Q~\xF7\xC7O\x053\xAA\x15\xE5\xAC|\xAF\xA9\xF9&\x1D\x14\xDA\x1Ex\x83\r\xEB\xA7\x11\x0F\xBCy\0\x1E\xD1^\x90_\x90\xA1V[_T\x7F\x16\x87\x18\xC0\xB1\xEBk\xFD{\x0E\xDE\xCE\xA5\xC6\xFCe\x02sz\xD7:L\x9FR\xFF\xA7\xE5S\xC8\xEB\x9FSa\x03y\x82`\x01a\x05\x17V[`@Q\x90\x81R` \x01a\x02\x1BV[_T\x7F/\xA6\x15\x17\xDD\xF9\xDC\x7F/=\\\xA7$\x14\xA0\x1C\x83M\x9C[\xB7\xC36\xC9wB<\x85\tK\xBAa\x90a\x03\xB6\x81`\x01a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01[`@Q\x80\x91\x03\x90\xA1a\x01\xCDa\x04\xEEV[_Ta\x03\xE4\x90`\x01a\x05\x17V[_T\x7F;\xB2\xD63x\x82\xFA\xA5Rl\xF8\x06\xC9v9\x04\xA9\x0F3cY\r\xD48i\x13\xE3\xFC\xD8\xA2\xE1\xD1a\x04\x12\x82`\x02a\x05\x17V[`@Q\x90\x81R` \x01[`@Q\x80\x91\x03\x90\xA3a\x01\xCDa\x04\xEEV[_\x80T`@Q\x90\x91\x7F\xC2\x80\x9A\x1A/\xB9]\x84\xCF\xDCH\x8C\xDB2\n\x14L\x15\x8F\x8DD\x83l\x9C-K\xAD\xBA\x08+\xFD\xFA\x91\xA2a\x01\xCDa\x04\xEEV[_Ta\x04l\x90`\x01a\x05\x17V[_T\x7FK\x92\"\x9A\xBE J0\xD7\xB0\x88\xD8\x11\x02\x91v\t4\xD6[<\x96\x06\x80\xAD\x94\xE0_R\xA8\x86\x05a\x04\x9A\x82`\x02a\x05\x17V[_Ta\x04\xA7\x90`\x03a\x05\x17V[`@\x80Q\x92\x83R` \x83\x01\x91\x90\x91R\x01a\x04\x1CV[\x7F\x04\xF7\xFB(\x9EQ\xEA\x99\x96\xEC\x98\xE6/\xF4\xB6Q\xBE\xCF\xA6\xE5?;\x85\x0B\xE2\t\xB6\x97A\xC6o$_T`@Qa\x03\xC7\x91\x81R` \x01\x90V[_\x80T\x90\x80a\x04\xFC\x83a\x050V[\x91\x90PUPV[cNH{q`\xE0\x1B_R`\x11`\x04R`$_\xFD[\x80\x82\x01\x80\x82\x11\x15a\x05*Wa\x05*a\x05\x03V[\x92\x91PPV[_`\x01\x82\x01a\x05AWa\x05Aa\x05\x03V[P`\x01\x01\x90V\xFE\xA2dipfsX\"\x12 \xB4\xCC-\xF5\xEE\xD0oS\x8A1\x15~\xDF\xAE\xEE\xE5\x91\xA5q\x9D5\xFBG\xF3\xB6\xCE]1\xC1\xFF\xE2\xF9dsolcC\0\x08\x18\x003", + ); + /**Event with signature `noIOneD(uint256)` and selector `0x04f7fb289e51ea9996ec98e62ff4b651becfa6e53f3b850be209b69741c66f24`. + ```solidity + event noIOneD(uint256 num); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct noIOneD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for noIOneD { + type DataTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = (alloy_sol_types::sol_data::FixedBytes<32>,); + const SIGNATURE: &'static str = "noIOneD(uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 4u8, 247u8, 251u8, 40u8, 158u8, 81u8, 234u8, 153u8, 150u8, 236u8, 152u8, 230u8, + 47u8, 244u8, 182u8, 81u8, 190u8, 207u8, 166u8, 229u8, 63u8, 59u8, 133u8, 11u8, + 226u8, 9u8, 182u8, 151u8, 65u8, 198u8, 111u8, 36u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { num: data.0 } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.num, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(),) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for noIOneD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&noIOneD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &noIOneD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `noITwoD(uint256,uint256)` and selector `0x2fa61517ddf9dc7f2f3d5ca72414a01c834d9c5bb7c336c977423c85094bba61`. + ```solidity + event noITwoD(uint256 num, uint256 numTwo); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct noITwoD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for noITwoD { + type DataTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = (alloy_sol_types::sol_data::FixedBytes<32>,); + const SIGNATURE: &'static str = "noITwoD(uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 47u8, 166u8, 21u8, 23u8, 221u8, 249u8, 220u8, 127u8, 47u8, 61u8, 92u8, 167u8, + 36u8, 20u8, 160u8, 28u8, 131u8, 77u8, 156u8, 91u8, 183u8, 195u8, 54u8, 201u8, + 119u8, 66u8, 60u8, 133u8, 9u8, 75u8, 186u8, 97u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: data.0, + numTwo: data.1, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.num, + ), + as alloy_sol_types::SolType>::tokenize( + &self.numTwo, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(),) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for noITwoD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&noITwoD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &noITwoD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `noIndexed()` and selector `0x00f7c74f0533aa15e5ac7cafa9f9261d14da1e78830deba7110fbc79001ed15e`. + ```solidity + event noIndexed(); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct noIndexed {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for noIndexed { + type DataTuple<'a> = (); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = (alloy_sol_types::sol_data::FixedBytes<32>,); + const SIGNATURE: &'static str = "noIndexed()"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 0u8, 247u8, 199u8, 79u8, 5u8, 51u8, 170u8, 21u8, 229u8, 172u8, 124u8, 175u8, + 169u8, 249u8, 38u8, 29u8, 20u8, 218u8, 30u8, 120u8, 131u8, 13u8, 235u8, 167u8, + 17u8, 15u8, 188u8, 121u8, 0u8, 30u8, 209u8, 94u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self {} + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + () + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(),) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for noIndexed { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&noIndexed> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &noIndexed) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `oneData(uint256,uint256,uint256,uint256)` and selector `0xf57f433eb9493cf4d9cb5763c12221d9b095804644d4ee006a78c72076cff947`. + ```solidity + event oneData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct oneData { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numFour: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for oneData { + type DataTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "oneData(uint256,uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 245u8, 127u8, 67u8, 62u8, 185u8, 73u8, 60u8, 244u8, 217u8, 203u8, 87u8, 99u8, + 193u8, 34u8, 33u8, 217u8, 176u8, 149u8, 128u8, 70u8, 68u8, 212u8, 238u8, 0u8, + 106u8, 120u8, 199u8, 32u8, 118u8, 207u8, 249u8, 71u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + numThree: topics.3, + numFour: data.0, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numFour, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + self.numThree.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + out[3usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numThree); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for oneData { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&oneData> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &oneData) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `oneIOneD(uint256,uint256)` and selector `0x168718c0b1eb6bfd7b0edecea5c6fc6502737ad73a4c9f52ffa7e553c8eb9f53`. + ```solidity + event oneIOneD(uint256 indexed num, uint256 numTwo); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct oneIOneD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for oneIOneD { + type DataTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "oneIOneD(uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 22u8, 135u8, 24u8, 192u8, 177u8, 235u8, 107u8, 253u8, 123u8, 14u8, 222u8, + 206u8, 165u8, 198u8, 252u8, 101u8, 2u8, 115u8, 122u8, 215u8, 58u8, 76u8, 159u8, + 82u8, 255u8, 167u8, 229u8, 83u8, 200u8, 235u8, 159u8, 83u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: data.0, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numTwo, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(), self.num.clone()) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for oneIOneD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&oneIOneD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &oneIOneD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `oneITwoD(uint256,uint256,uint256)` and selector `0xef4c88193498df237f039055d1212ac2a3b93ed8aea88c814312e50f6a32592d`. + ```solidity + event oneITwoD(uint256 indexed num, uint256 numTwo, uint256 numThree); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct oneITwoD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for oneITwoD { + type DataTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "oneITwoD(uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 239u8, 76u8, 136u8, 25u8, 52u8, 152u8, 223u8, 35u8, 127u8, 3u8, 144u8, 85u8, + 209u8, 33u8, 42u8, 194u8, 163u8, 185u8, 62u8, 216u8, 174u8, 168u8, 140u8, + 129u8, 67u8, 18u8, 229u8, 15u8, 106u8, 50u8, 89u8, 45u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: data.0, + numThree: data.1, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numTwo, + ), + as alloy_sol_types::SolType>::tokenize( + &self.numThree, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(), self.num.clone()) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for oneITwoD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&oneITwoD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &oneITwoD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `oneIndexed(uint256)` and selector `0xc2809a1a2fb95d84cfdc488cdb320a144c158f8d44836c9c2d4badba082bfdfa`. + ```solidity + event oneIndexed(uint256 indexed num); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct oneIndexed { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for oneIndexed { + type DataTuple<'a> = (); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "oneIndexed(uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 194u8, 128u8, 154u8, 26u8, 47u8, 185u8, 93u8, 132u8, 207u8, 220u8, 72u8, 140u8, + 219u8, 50u8, 10u8, 20u8, 76u8, 21u8, 143u8, 141u8, 68u8, 131u8, 108u8, 156u8, + 45u8, 75u8, 173u8, 186u8, 8u8, 43u8, 253u8, 250u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { num: topics.1 } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + () + } + #[inline] + fn topics(&self) -> ::RustType { + (Self::SIGNATURE_HASH.into(), self.num.clone()) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for oneIndexed { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&oneIndexed> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &oneIndexed) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `threeIndexed(uint256,uint256,uint256)` and selector `0x1d18de2cd8798a1c29b9255930c807eb6c84ae0acb2219acbb11e0f65cf813e9`. + ```solidity + event threeIndexed(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct threeIndexed { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for threeIndexed { + type DataTuple<'a> = (); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "threeIndexed(uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 29u8, 24u8, 222u8, 44u8, 216u8, 121u8, 138u8, 28u8, 41u8, 185u8, 37u8, 89u8, + 48u8, 200u8, 7u8, 235u8, 108u8, 132u8, 174u8, 10u8, 203u8, 34u8, 25u8, 172u8, + 187u8, 17u8, 224u8, 246u8, 92u8, 248u8, 19u8, 233u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + numThree: topics.3, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + () + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + self.numThree.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + out[3usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numThree); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for threeIndexed { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&threeIndexed> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &threeIndexed) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `twoData(uint256,uint256,uint256,uint256,uint256)` and selector `0xf03d29753fbd5ac209bab88a99b396bcc25c3e72530d02c81aea4d324ab3d742`. + ```solidity + event twoData(uint256 indexed num, uint256 indexed numTwo, uint256 indexed numThree, uint256 numFour, uint256 numFive); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct twoData { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numFour: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numFive: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for twoData { + type DataTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "twoData(uint256,uint256,uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 240u8, 61u8, 41u8, 117u8, 63u8, 189u8, 90u8, 194u8, 9u8, 186u8, 184u8, 138u8, + 153u8, 179u8, 150u8, 188u8, 194u8, 92u8, 62u8, 114u8, 83u8, 13u8, 2u8, 200u8, + 26u8, 234u8, 77u8, 50u8, 74u8, 179u8, 215u8, 66u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + numThree: topics.3, + numFour: data.0, + numFive: data.1, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numFour, + ), + as alloy_sol_types::SolType>::tokenize( + &self.numFive, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + self.numThree.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + out[3usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numThree); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for twoData { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&twoData> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &twoData) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `twoIOneD(uint256,uint256,uint256)` and selector `0x3bb2d6337882faa5526cf806c9763904a90f3363590dd4386913e3fcd8a2e1d1`. + ```solidity + event twoIOneD(uint256 indexed num, uint256 indexed numTwo, uint256 numThree); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct twoIOneD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for twoIOneD { + type DataTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "twoIOneD(uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 59u8, 178u8, 214u8, 51u8, 120u8, 130u8, 250u8, 165u8, 82u8, 108u8, 248u8, 6u8, + 201u8, 118u8, 57u8, 4u8, 169u8, 15u8, 51u8, 99u8, 89u8, 13u8, 212u8, 56u8, + 105u8, 19u8, 227u8, 252u8, 216u8, 162u8, 225u8, 209u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + numThree: data.0, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numThree, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for twoIOneD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&twoIOneD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &twoIOneD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `twoITwoD(uint256,uint256,uint256,uint256)` and selector `0x4b92229abe204a30d7b088d8110291760934d65b3c960680ad94e05f52a88605`. + ```solidity + event twoITwoD(uint256 indexed num, uint256 indexed numTwo, uint256 numThree, uint256 numFour); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct twoITwoD { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numThree: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numFour: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for twoITwoD { + type DataTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "twoITwoD(uint256,uint256,uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 75u8, 146u8, 34u8, 154u8, 190u8, 32u8, 74u8, 48u8, 215u8, 176u8, 136u8, 216u8, + 17u8, 2u8, 145u8, 118u8, 9u8, 52u8, 214u8, 91u8, 60u8, 150u8, 6u8, 128u8, + 173u8, 148u8, 224u8, 95u8, 82u8, 168u8, 134u8, 5u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + numThree: data.0, + numFour: data.1, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.numThree, + ), + as alloy_sol_types::SolType>::tokenize( + &self.numFour, + ), + ) + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for twoITwoD { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&twoITwoD> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &twoITwoD) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Event with signature `twoIndexed(uint256,uint256)` and selector `0xa6baf14d8f11d7a4497089bb3fca0adfc34837cfb1f4aa370634d36ef0305b46`. + ```solidity + event twoIndexed(uint256 indexed num, uint256 indexed numTwo); + ```*/ + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + #[derive(Clone)] + pub struct twoIndexed { + #[allow(missing_docs)] + pub num: alloy::sol_types::private::primitives::aliases::U256, + #[allow(missing_docs)] + pub numTwo: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[automatically_derived] + impl alloy_sol_types::SolEvent for twoIndexed { + type DataTuple<'a> = (); + type DataToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + type TopicList = ( + alloy_sol_types::sol_data::FixedBytes<32>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + const SIGNATURE: &'static str = "twoIndexed(uint256,uint256)"; + const SIGNATURE_HASH: alloy_sol_types::private::B256 = + alloy_sol_types::private::B256::new([ + 166u8, 186u8, 241u8, 77u8, 143u8, 17u8, 215u8, 164u8, 73u8, 112u8, 137u8, + 187u8, 63u8, 202u8, 10u8, 223u8, 195u8, 72u8, 55u8, 207u8, 177u8, 244u8, 170u8, + 55u8, 6u8, 52u8, 211u8, 110u8, 240u8, 48u8, 91u8, 70u8, + ]); + const ANONYMOUS: bool = false; + #[allow(unused_variables)] + #[inline] + fn new( + topics: ::RustType, + data: as alloy_sol_types::SolType>::RustType, + ) -> Self { + Self { + num: topics.1, + numTwo: topics.2, + } + } + #[inline] + fn check_signature( + topics: &::RustType, + ) -> alloy_sol_types::Result<()> { + if topics.0 != Self::SIGNATURE_HASH { + return Err(alloy_sol_types::Error::invalid_event_signature_hash( + Self::SIGNATURE, + topics.0, + Self::SIGNATURE_HASH, + )); + } + Ok(()) + } + #[inline] + fn tokenize_body(&self) -> Self::DataToken<'_> { + () + } + #[inline] + fn topics(&self) -> ::RustType { + ( + Self::SIGNATURE_HASH.into(), + self.num.clone(), + self.numTwo.clone(), + ) + } + #[inline] + fn encode_topics_raw( + &self, + out: &mut [alloy_sol_types::abi::token::WordToken], + ) -> alloy_sol_types::Result<()> { + if out.len() < ::COUNT { + return Err(alloy_sol_types::Error::Overrun); + } + out[0usize] = alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH); + out[1usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.num); + out[2usize] = as alloy_sol_types::EventTopic>::encode_topic(&self.numTwo); + Ok(()) + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for twoIndexed { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + From::from(self) + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + From::from(&self) + } + } + #[automatically_derived] + impl From<&twoIndexed> for alloy_sol_types::private::LogData { + #[inline] + fn from(this: &twoIndexed) -> alloy_sol_types::private::LogData { + alloy_sol_types::SolEvent::encode_log_data(this) + } + } + }; + /**Function with signature `increment()` and selector `0xd09de08a`. + ```solidity + function increment() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct incrementCall {} + ///Container type for the return parameters of the [`increment()`](incrementCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct incrementReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: incrementCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for incrementCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: incrementReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for incrementReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for incrementCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = incrementReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "increment()"; + const SELECTOR: [u8; 4] = [208u8, 157u8, 224u8, 138u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `number()` and selector `0x8381f58a`. + ```solidity + function number() external view returns (uint256); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct numberCall {} + ///Container type for the return parameters of the [`number()`](numberCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct numberReturn { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: numberCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for numberCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: numberReturn) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for numberReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for numberCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = numberReturn; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "number()"; + const SELECTOR: [u8; 4] = [131u8, 129u8, 245u8, 138u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testNoIOneD()` and selector `0xc0242000`. + ```solidity + function testNoIOneD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoIOneDCall {} + ///Container type for the return parameters of the [`testNoIOneD()`](testNoIOneDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoIOneDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoIOneDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoIOneDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoIOneDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoIOneDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testNoIOneDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testNoIOneDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testNoIOneD()"; + const SELECTOR: [u8; 4] = [192u8, 36u8, 32u8, 0u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testNoITwoD()` and selector `0x46d6a7b5`. + ```solidity + function testNoITwoD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoITwoDCall {} + ///Container type for the return parameters of the [`testNoITwoD()`](testNoITwoDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoITwoDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoITwoDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoITwoDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoITwoDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoITwoDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testNoITwoDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testNoITwoDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testNoITwoD()"; + const SELECTOR: [u8; 4] = [70u8, 214u8, 167u8, 181u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testNoIndexed()` and selector `0x4282ed58`. + ```solidity + function testNoIndexed() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoIndexedCall {} + ///Container type for the return parameters of the [`testNoIndexed()`](testNoIndexedCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testNoIndexedReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoIndexedCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoIndexedCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testNoIndexedReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testNoIndexedReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testNoIndexedCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testNoIndexedReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testNoIndexed()"; + const SELECTOR: [u8; 4] = [66u8, 130u8, 237u8, 88u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testOneData()` and selector `0x003c7e56`. + ```solidity + function testOneData() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneDataCall {} + ///Container type for the return parameters of the [`testOneData()`](testOneDataCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneDataReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneDataCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneDataCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneDataReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneDataReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testOneDataCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testOneDataReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testOneData()"; + const SELECTOR: [u8; 4] = [0u8, 60u8, 126u8, 86u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testOneIOneD()` and selector `0x4369f728`. + ```solidity + function testOneIOneD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneIOneDCall {} + ///Container type for the return parameters of the [`testOneIOneD()`](testOneIOneDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneIOneDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneIOneDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneIOneDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneIOneDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneIOneDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testOneIOneDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testOneIOneDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testOneIOneD()"; + const SELECTOR: [u8; 4] = [67u8, 105u8, 247u8, 40u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testOneITwoD()` and selector `0x00d83b55`. + ```solidity + function testOneITwoD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneITwoDCall {} + ///Container type for the return parameters of the [`testOneITwoD()`](testOneITwoDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneITwoDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneITwoDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneITwoDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneITwoDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneITwoDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testOneITwoDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testOneITwoDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testOneITwoD()"; + const SELECTOR: [u8; 4] = [0u8, 216u8, 59u8, 85u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testOneIndexed()` and selector `0x729d4520`. + ```solidity + function testOneIndexed() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneIndexedCall {} + ///Container type for the return parameters of the [`testOneIndexed()`](testOneIndexedCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testOneIndexedReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneIndexedCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneIndexedCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testOneIndexedReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testOneIndexedReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testOneIndexedCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testOneIndexedReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testOneIndexed()"; + const SELECTOR: [u8; 4] = [114u8, 157u8, 69u8, 32u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testThreeIndexed()` and selector `0x31c1c63b`. + ```solidity + function testThreeIndexed() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testThreeIndexedCall {} + ///Container type for the return parameters of the [`testThreeIndexed()`](testThreeIndexedCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testThreeIndexedReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testThreeIndexedCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testThreeIndexedCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testThreeIndexedReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testThreeIndexedReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testThreeIndexedCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testThreeIndexedReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testThreeIndexed()"; + const SELECTOR: [u8; 4] = [49u8, 193u8, 198u8, 59u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testTwoData()` and selector `0x2dc34764`. + ```solidity + function testTwoData() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoDataCall {} + ///Container type for the return parameters of the [`testTwoData()`](testTwoDataCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoDataReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoDataCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoDataCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoDataReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoDataReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testTwoDataCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testTwoDataReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testTwoData()"; + const SELECTOR: [u8; 4] = [45u8, 195u8, 71u8, 100u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testTwoIOneD()` and selector `0x63eb70f0`. + ```solidity + function testTwoIOneD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoIOneDCall {} + ///Container type for the return parameters of the [`testTwoIOneD()`](testTwoIOneDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoIOneDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoIOneDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoIOneDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoIOneDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoIOneDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testTwoIOneDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testTwoIOneDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testTwoIOneD()"; + const SELECTOR: [u8; 4] = [99u8, 235u8, 112u8, 240u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testTwoITwoD()` and selector `0xb1e057a9`. + ```solidity + function testTwoITwoD() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoITwoDCall {} + ///Container type for the return parameters of the [`testTwoITwoD()`](testTwoITwoDCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoITwoDReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoITwoDCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoITwoDCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoITwoDReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoITwoDReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testTwoITwoDCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testTwoITwoDReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testTwoITwoD()"; + const SELECTOR: [u8; 4] = [177u8, 224u8, 87u8, 169u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `testTwoIndexed()` and selector `0x338b538a`. + ```solidity + function testTwoIndexed() external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoIndexedCall {} + ///Container type for the return parameters of the [`testTwoIndexed()`](testTwoIndexedCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct testTwoIndexedReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoIndexedCall) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoIndexedCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: testTwoIndexedReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for testTwoIndexedReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for testTwoIndexedCall { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = testTwoIndexedReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "testTwoIndexed()"; + const SELECTOR: [u8; 4] = [51u8, 139u8, 83u8, 138u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + ///Container for all the [`EventEmitter`](self) function calls. + pub enum EventEmitterCalls { + increment(incrementCall), + number(numberCall), + testNoIOneD(testNoIOneDCall), + testNoITwoD(testNoITwoDCall), + testNoIndexed(testNoIndexedCall), + testOneData(testOneDataCall), + testOneIOneD(testOneIOneDCall), + testOneITwoD(testOneITwoDCall), + testOneIndexed(testOneIndexedCall), + testThreeIndexed(testThreeIndexedCall), + testTwoData(testTwoDataCall), + testTwoIOneD(testTwoIOneDCall), + testTwoITwoD(testTwoITwoDCall), + testTwoIndexed(testTwoIndexedCall), + } + #[automatically_derived] + impl EventEmitterCalls { + /// All the selectors of this enum. + /// + /// Note that the selectors might not be in the same order as the variants. + /// No guarantees are made about the order of the selectors. + /// + /// Prefer using `SolInterface` methods instead. + pub const SELECTORS: &'static [[u8; 4usize]] = &[ + [0u8, 60u8, 126u8, 86u8], + [0u8, 216u8, 59u8, 85u8], + [45u8, 195u8, 71u8, 100u8], + [49u8, 193u8, 198u8, 59u8], + [51u8, 139u8, 83u8, 138u8], + [66u8, 130u8, 237u8, 88u8], + [67u8, 105u8, 247u8, 40u8], + [70u8, 214u8, 167u8, 181u8], + [99u8, 235u8, 112u8, 240u8], + [114u8, 157u8, 69u8, 32u8], + [131u8, 129u8, 245u8, 138u8], + [177u8, 224u8, 87u8, 169u8], + [192u8, 36u8, 32u8, 0u8], + [208u8, 157u8, 224u8, 138u8], + ]; + } + #[automatically_derived] + impl alloy_sol_types::SolInterface for EventEmitterCalls { + const NAME: &'static str = "EventEmitterCalls"; + const MIN_DATA_LENGTH: usize = 0usize; + const COUNT: usize = 14usize; + #[inline] + fn selector(&self) -> [u8; 4] { + match self { + Self::increment(_) => ::SELECTOR, + Self::number(_) => ::SELECTOR, + Self::testNoIOneD(_) => ::SELECTOR, + Self::testNoITwoD(_) => ::SELECTOR, + Self::testNoIndexed(_) => ::SELECTOR, + Self::testOneData(_) => ::SELECTOR, + Self::testOneIOneD(_) => ::SELECTOR, + Self::testOneITwoD(_) => ::SELECTOR, + Self::testOneIndexed(_) => { + ::SELECTOR + } + Self::testThreeIndexed(_) => { + ::SELECTOR + } + Self::testTwoData(_) => ::SELECTOR, + Self::testTwoIOneD(_) => ::SELECTOR, + Self::testTwoITwoD(_) => ::SELECTOR, + Self::testTwoIndexed(_) => { + ::SELECTOR + } + } + } + #[inline] + fn selector_at(i: usize) -> ::core::option::Option<[u8; 4]> { + Self::SELECTORS.get(i).copied() + } + #[inline] + fn valid_selector(selector: [u8; 4]) -> bool { + Self::SELECTORS.binary_search(&selector).is_ok() + } + #[inline] + #[allow(non_snake_case)] + fn abi_decode_raw( + selector: [u8; 4], + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + static DECODE_SHIMS: &[fn( + &[u8], + bool, + ) + -> alloy_sol_types::Result] = &[ + { + fn testOneData( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testOneData) + } + testOneData + }, + { + fn testOneITwoD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testOneITwoD) + } + testOneITwoD + }, + { + fn testTwoData( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testTwoData) + } + testTwoData + }, + { + fn testThreeIndexed( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testThreeIndexed) + } + testThreeIndexed + }, + { + fn testTwoIndexed( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testTwoIndexed) + } + testTwoIndexed + }, + { + fn testNoIndexed( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testNoIndexed) + } + testNoIndexed + }, + { + fn testOneIOneD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testOneIOneD) + } + testOneIOneD + }, + { + fn testNoITwoD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testNoITwoD) + } + testNoITwoD + }, + { + fn testTwoIOneD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testTwoIOneD) + } + testTwoIOneD + }, + { + fn testOneIndexed( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testOneIndexed) + } + testOneIndexed + }, + { + fn number( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw(data, validate) + .map(EventEmitterCalls::number) + } + number + }, + { + fn testTwoITwoD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testTwoITwoD) + } + testTwoITwoD + }, + { + fn testNoIOneD( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(EventEmitterCalls::testNoIOneD) + } + testNoIOneD + }, + { + fn increment( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw(data, validate) + .map(EventEmitterCalls::increment) + } + increment + }, + ]; + let Ok(idx) = Self::SELECTORS.binary_search(&selector) else { + return Err(alloy_sol_types::Error::unknown_selector( + ::NAME, + selector, + )); + }; + DECODE_SHIMS[idx](data, validate) + } + #[inline] + fn abi_encoded_size(&self) -> usize { + match self { + Self::increment(inner) => { + ::abi_encoded_size(inner) + } + Self::number(inner) => { + ::abi_encoded_size(inner) + } + Self::testNoIOneD(inner) => { + ::abi_encoded_size(inner) + } + Self::testNoITwoD(inner) => { + ::abi_encoded_size(inner) + } + Self::testNoIndexed(inner) => { + ::abi_encoded_size(inner) + } + Self::testOneData(inner) => { + ::abi_encoded_size(inner) + } + Self::testOneIOneD(inner) => { + ::abi_encoded_size(inner) + } + Self::testOneITwoD(inner) => { + ::abi_encoded_size(inner) + } + Self::testOneIndexed(inner) => { + ::abi_encoded_size(inner) + } + Self::testThreeIndexed(inner) => { + ::abi_encoded_size(inner) + } + Self::testTwoData(inner) => { + ::abi_encoded_size(inner) + } + Self::testTwoIOneD(inner) => { + ::abi_encoded_size(inner) + } + Self::testTwoITwoD(inner) => { + ::abi_encoded_size(inner) + } + Self::testTwoIndexed(inner) => { + ::abi_encoded_size(inner) + } + } + } + #[inline] + fn abi_encode_raw(&self, out: &mut alloy_sol_types::private::Vec) { + match self { + Self::increment(inner) => { + ::abi_encode_raw(inner, out) + } + Self::number(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testNoIOneD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testNoITwoD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testNoIndexed(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testOneData(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testOneIOneD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testOneITwoD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testOneIndexed(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testThreeIndexed(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testTwoData(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testTwoIOneD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testTwoITwoD(inner) => { + ::abi_encode_raw(inner, out) + } + Self::testTwoIndexed(inner) => { + ::abi_encode_raw(inner, out) + } + } + } + } + ///Container for all the [`EventEmitter`](self) events. + pub enum EventEmitterEvents { + noIOneD(noIOneD), + noITwoD(noITwoD), + noIndexed(noIndexed), + oneData(oneData), + oneIOneD(oneIOneD), + oneITwoD(oneITwoD), + oneIndexed(oneIndexed), + threeIndexed(threeIndexed), + twoData(twoData), + twoIOneD(twoIOneD), + twoITwoD(twoITwoD), + twoIndexed(twoIndexed), + } + #[automatically_derived] + impl EventEmitterEvents { + /// All the selectors of this enum. + /// + /// Note that the selectors might not be in the same order as the variants. + /// No guarantees are made about the order of the selectors. + /// + /// Prefer using `SolInterface` methods instead. + pub const SELECTORS: &'static [[u8; 32usize]] = &[ + [ + 0u8, 247u8, 199u8, 79u8, 5u8, 51u8, 170u8, 21u8, 229u8, 172u8, 124u8, 175u8, 169u8, + 249u8, 38u8, 29u8, 20u8, 218u8, 30u8, 120u8, 131u8, 13u8, 235u8, 167u8, 17u8, 15u8, + 188u8, 121u8, 0u8, 30u8, 209u8, 94u8, + ], + [ + 4u8, 247u8, 251u8, 40u8, 158u8, 81u8, 234u8, 153u8, 150u8, 236u8, 152u8, 230u8, + 47u8, 244u8, 182u8, 81u8, 190u8, 207u8, 166u8, 229u8, 63u8, 59u8, 133u8, 11u8, + 226u8, 9u8, 182u8, 151u8, 65u8, 198u8, 111u8, 36u8, + ], + [ + 22u8, 135u8, 24u8, 192u8, 177u8, 235u8, 107u8, 253u8, 123u8, 14u8, 222u8, 206u8, + 165u8, 198u8, 252u8, 101u8, 2u8, 115u8, 122u8, 215u8, 58u8, 76u8, 159u8, 82u8, + 255u8, 167u8, 229u8, 83u8, 200u8, 235u8, 159u8, 83u8, + ], + [ + 29u8, 24u8, 222u8, 44u8, 216u8, 121u8, 138u8, 28u8, 41u8, 185u8, 37u8, 89u8, 48u8, + 200u8, 7u8, 235u8, 108u8, 132u8, 174u8, 10u8, 203u8, 34u8, 25u8, 172u8, 187u8, + 17u8, 224u8, 246u8, 92u8, 248u8, 19u8, 233u8, + ], + [ + 47u8, 166u8, 21u8, 23u8, 221u8, 249u8, 220u8, 127u8, 47u8, 61u8, 92u8, 167u8, 36u8, + 20u8, 160u8, 28u8, 131u8, 77u8, 156u8, 91u8, 183u8, 195u8, 54u8, 201u8, 119u8, + 66u8, 60u8, 133u8, 9u8, 75u8, 186u8, 97u8, + ], + [ + 59u8, 178u8, 214u8, 51u8, 120u8, 130u8, 250u8, 165u8, 82u8, 108u8, 248u8, 6u8, + 201u8, 118u8, 57u8, 4u8, 169u8, 15u8, 51u8, 99u8, 89u8, 13u8, 212u8, 56u8, 105u8, + 19u8, 227u8, 252u8, 216u8, 162u8, 225u8, 209u8, + ], + [ + 75u8, 146u8, 34u8, 154u8, 190u8, 32u8, 74u8, 48u8, 215u8, 176u8, 136u8, 216u8, + 17u8, 2u8, 145u8, 118u8, 9u8, 52u8, 214u8, 91u8, 60u8, 150u8, 6u8, 128u8, 173u8, + 148u8, 224u8, 95u8, 82u8, 168u8, 134u8, 5u8, + ], + [ + 166u8, 186u8, 241u8, 77u8, 143u8, 17u8, 215u8, 164u8, 73u8, 112u8, 137u8, 187u8, + 63u8, 202u8, 10u8, 223u8, 195u8, 72u8, 55u8, 207u8, 177u8, 244u8, 170u8, 55u8, 6u8, + 52u8, 211u8, 110u8, 240u8, 48u8, 91u8, 70u8, + ], + [ + 194u8, 128u8, 154u8, 26u8, 47u8, 185u8, 93u8, 132u8, 207u8, 220u8, 72u8, 140u8, + 219u8, 50u8, 10u8, 20u8, 76u8, 21u8, 143u8, 141u8, 68u8, 131u8, 108u8, 156u8, 45u8, + 75u8, 173u8, 186u8, 8u8, 43u8, 253u8, 250u8, + ], + [ + 239u8, 76u8, 136u8, 25u8, 52u8, 152u8, 223u8, 35u8, 127u8, 3u8, 144u8, 85u8, 209u8, + 33u8, 42u8, 194u8, 163u8, 185u8, 62u8, 216u8, 174u8, 168u8, 140u8, 129u8, 67u8, + 18u8, 229u8, 15u8, 106u8, 50u8, 89u8, 45u8, + ], + [ + 240u8, 61u8, 41u8, 117u8, 63u8, 189u8, 90u8, 194u8, 9u8, 186u8, 184u8, 138u8, + 153u8, 179u8, 150u8, 188u8, 194u8, 92u8, 62u8, 114u8, 83u8, 13u8, 2u8, 200u8, 26u8, + 234u8, 77u8, 50u8, 74u8, 179u8, 215u8, 66u8, + ], + [ + 245u8, 127u8, 67u8, 62u8, 185u8, 73u8, 60u8, 244u8, 217u8, 203u8, 87u8, 99u8, + 193u8, 34u8, 33u8, 217u8, 176u8, 149u8, 128u8, 70u8, 68u8, 212u8, 238u8, 0u8, + 106u8, 120u8, 199u8, 32u8, 118u8, 207u8, 249u8, 71u8, + ], + ]; + } + #[automatically_derived] + impl alloy_sol_types::SolEventInterface for EventEmitterEvents { + const NAME: &'static str = "EventEmitterEvents"; + const COUNT: usize = 12usize; + fn decode_raw_log( + topics: &[alloy_sol_types::Word], + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + match topics.first().copied() { + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::noIOneD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::noITwoD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::noIndexed) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::oneData) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::oneIOneD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::oneITwoD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log( + topics, data, validate, + ) + .map(Self::oneIndexed) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log( + topics, data, validate, + ) + .map(Self::threeIndexed) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::twoData) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::twoIOneD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log(topics, data, validate) + .map(Self::twoITwoD) + } + Some(::SIGNATURE_HASH) => { + ::decode_raw_log( + topics, data, validate, + ) + .map(Self::twoIndexed) + } + _ => alloy_sol_types::private::Err(alloy_sol_types::Error::InvalidLog { + name: ::NAME, + log: alloy_sol_types::private::Box::new( + alloy_sol_types::private::LogData::new_unchecked( + topics.to_vec(), + data.to_vec().into(), + ), + ), + }), + } + } + } + #[automatically_derived] + impl alloy_sol_types::private::IntoLogData for EventEmitterEvents { + fn to_log_data(&self) -> alloy_sol_types::private::LogData { + match self { + Self::noIOneD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::noITwoD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::noIndexed(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::oneData(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::oneIOneD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::oneITwoD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::oneIndexed(inner) => { + alloy_sol_types::private::IntoLogData::to_log_data(inner) + } + Self::threeIndexed(inner) => { + alloy_sol_types::private::IntoLogData::to_log_data(inner) + } + Self::twoData(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::twoIOneD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::twoITwoD(inner) => alloy_sol_types::private::IntoLogData::to_log_data(inner), + Self::twoIndexed(inner) => { + alloy_sol_types::private::IntoLogData::to_log_data(inner) + } + } + } + fn into_log_data(self) -> alloy_sol_types::private::LogData { + match self { + Self::noIOneD(inner) => alloy_sol_types::private::IntoLogData::into_log_data(inner), + Self::noITwoD(inner) => alloy_sol_types::private::IntoLogData::into_log_data(inner), + Self::noIndexed(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::oneData(inner) => alloy_sol_types::private::IntoLogData::into_log_data(inner), + Self::oneIOneD(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::oneITwoD(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::oneIndexed(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::threeIndexed(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::twoData(inner) => alloy_sol_types::private::IntoLogData::into_log_data(inner), + Self::twoIOneD(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::twoITwoD(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + Self::twoIndexed(inner) => { + alloy_sol_types::private::IntoLogData::into_log_data(inner) + } + } + } + } + use alloy::contract as alloy_contract; + /**Creates a new wrapper around an on-chain [`EventEmitter`](self) contract instance. + + See the [wrapper's documentation](`EventEmitterInstance`) for more details.*/ + #[inline] + pub const fn new< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + >( + address: alloy_sol_types::private::Address, + provider: P, + ) -> EventEmitterInstance { + EventEmitterInstance::::new(address, provider) + } + /**Deploys this contract using the given `provider` and constructor arguments, if any. + + Returns a new instance of the contract, if the deployment was successful. + + For more fine-grained control over the deployment process, use [`deploy_builder`] instead.*/ + #[inline] + pub fn deploy< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + >( + provider: P, + ) -> impl ::core::future::Future>> + { + EventEmitterInstance::::deploy(provider) + } + /**Creates a `RawCallBuilder` for deploying this contract using the given `provider` + and constructor arguments, if any. + + This is a simple wrapper around creating a `RawCallBuilder` with the data set to + the bytecode concatenated with the constructor's ABI-encoded arguments.*/ + #[inline] + pub fn deploy_builder< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + >( + provider: P, + ) -> alloy_contract::RawCallBuilder { + EventEmitterInstance::::deploy_builder(provider) + } + /**A [`EventEmitter`](self) instance. + + Contains type-safe methods for interacting with an on-chain instance of the + [`EventEmitter`](self) contract located at a given `address`, using a given + provider `P`. + + If the contract bytecode is available (see the [`sol!`](alloy_sol_types::sol!) + documentation on how to provide it), the `deploy` and `deploy_builder` methods can + be used to deploy a new instance of the contract. + + See the [module-level documentation](self) for all the available methods.*/ + #[derive(Clone)] + pub struct EventEmitterInstance { + address: alloy_sol_types::private::Address, + provider: P, + _network_transport: ::core::marker::PhantomData<(N, T)>, + } + #[automatically_derived] + impl ::core::fmt::Debug for EventEmitterInstance { + #[inline] + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + f.debug_tuple("EventEmitterInstance") + .field(&self.address) + .finish() + } + } + /// Instantiation and getters/setters. + #[automatically_derived] + impl< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + > EventEmitterInstance + { + /**Creates a new wrapper around an on-chain [`EventEmitter`](self) contract instance. + + See the [wrapper's documentation](`EventEmitterInstance`) for more details.*/ + #[inline] + pub const fn new(address: alloy_sol_types::private::Address, provider: P) -> Self { + Self { + address, + provider, + _network_transport: ::core::marker::PhantomData, + } + } + /**Deploys this contract using the given `provider` and constructor arguments, if any. + + Returns a new instance of the contract, if the deployment was successful. + + For more fine-grained control over the deployment process, use [`deploy_builder`] instead.*/ + #[inline] + pub async fn deploy(provider: P) -> alloy_contract::Result> { + let call_builder = Self::deploy_builder(provider); + let contract_address = call_builder.deploy().await?; + Ok(Self::new(contract_address, call_builder.provider)) + } + /**Creates a `RawCallBuilder` for deploying this contract using the given `provider` + and constructor arguments, if any. + + This is a simple wrapper around creating a `RawCallBuilder` with the data set to + the bytecode concatenated with the constructor's ABI-encoded arguments.*/ + #[inline] + pub fn deploy_builder(provider: P) -> alloy_contract::RawCallBuilder { + alloy_contract::RawCallBuilder::new_raw_deploy( + provider, + ::core::clone::Clone::clone(&BYTECODE), + ) + } + /// Returns a reference to the address. + #[inline] + pub const fn address(&self) -> &alloy_sol_types::private::Address { + &self.address + } + /// Sets the address. + #[inline] + pub fn set_address(&mut self, address: alloy_sol_types::private::Address) { + self.address = address; + } + /// Sets the address and returns `self`. + pub fn at(mut self, address: alloy_sol_types::private::Address) -> Self { + self.set_address(address); + self + } + /// Returns a reference to the provider. + #[inline] + pub const fn provider(&self) -> &P { + &self.provider + } + } + impl EventEmitterInstance { + /// Clones the provider and returns a new instance with the cloned provider. + #[inline] + pub fn with_cloned_provider(self) -> EventEmitterInstance { + EventEmitterInstance { + address: self.address, + provider: ::core::clone::Clone::clone(&self.provider), + _network_transport: ::core::marker::PhantomData, + } + } + } + /// Function calls. + #[automatically_derived] + impl< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + > EventEmitterInstance + { + /// Creates a new call builder using this contract instance's provider and address. + /// + /// Note that the call can be any function call, not just those defined in this + /// contract. Prefer using the other methods for building type-safe contract calls. + pub fn call_builder( + &self, + call: &C, + ) -> alloy_contract::SolCallBuilder { + alloy_contract::SolCallBuilder::new_sol(&self.provider, &self.address, call) + } + ///Creates a new call builder for the [`increment`] function. + pub fn increment(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&incrementCall {}) + } + ///Creates a new call builder for the [`number`] function. + pub fn number(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&numberCall {}) + } + ///Creates a new call builder for the [`testNoIOneD`] function. + pub fn testNoIOneD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testNoIOneDCall {}) + } + ///Creates a new call builder for the [`testNoITwoD`] function. + pub fn testNoITwoD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testNoITwoDCall {}) + } + ///Creates a new call builder for the [`testNoIndexed`] function. + pub fn testNoIndexed(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testNoIndexedCall {}) + } + ///Creates a new call builder for the [`testOneData`] function. + pub fn testOneData(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testOneDataCall {}) + } + ///Creates a new call builder for the [`testOneIOneD`] function. + pub fn testOneIOneD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testOneIOneDCall {}) + } + ///Creates a new call builder for the [`testOneITwoD`] function. + pub fn testOneITwoD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testOneITwoDCall {}) + } + ///Creates a new call builder for the [`testOneIndexed`] function. + pub fn testOneIndexed( + &self, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&testOneIndexedCall {}) + } + ///Creates a new call builder for the [`testThreeIndexed`] function. + pub fn testThreeIndexed( + &self, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&testThreeIndexedCall {}) + } + ///Creates a new call builder for the [`testTwoData`] function. + pub fn testTwoData(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testTwoDataCall {}) + } + ///Creates a new call builder for the [`testTwoIOneD`] function. + pub fn testTwoIOneD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testTwoIOneDCall {}) + } + ///Creates a new call builder for the [`testTwoITwoD`] function. + pub fn testTwoITwoD(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&testTwoITwoDCall {}) + } + ///Creates a new call builder for the [`testTwoIndexed`] function. + pub fn testTwoIndexed( + &self, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&testTwoIndexedCall {}) + } + } + /// Event filters. + #[automatically_derived] + impl< + T: alloy_contract::private::Transport + ::core::clone::Clone, + P: alloy_contract::private::Provider, + N: alloy_contract::private::Network, + > EventEmitterInstance + { + /// Creates a new event filter using this contract instance's provider and address. + /// + /// Note that the type can be any event, not just those defined in this contract. + /// Prefer using the other methods for building type-safe event filters. + pub fn event_filter( + &self, + ) -> alloy_contract::Event { + alloy_contract::Event::new_sol(&self.provider, &self.address) + } + ///Creates a new event filter for the [`noIOneD`] event. + pub fn noIOneD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`noITwoD`] event. + pub fn noITwoD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`noIndexed`] event. + pub fn noIndexed_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`oneData`] event. + pub fn oneData_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`oneIOneD`] event. + pub fn oneIOneD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`oneITwoD`] event. + pub fn oneITwoD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`oneIndexed`] event. + pub fn oneIndexed_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`threeIndexed`] event. + pub fn threeIndexed_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`twoData`] event. + pub fn twoData_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`twoIOneD`] event. + pub fn twoIOneD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`twoITwoD`] event. + pub fn twoITwoD_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + ///Creates a new event filter for the [`twoIndexed`] event. + pub fn twoIndexed_filter(&self) -> alloy_contract::Event { + self.event_filter::() + } + } +} diff --git a/mp2-v1/tests/common/bindings/mod.rs b/mp2-v1/tests/common/bindings/mod.rs index c8e26af41..6539eb0db 100644 --- a/mp2-v1/tests/common/bindings/mod.rs +++ b/mp2-v1/tests/common/bindings/mod.rs @@ -3,4 +3,5 @@ //! This is autogenerated code. //! Do not manually edit these files. //! These files may be overwritten by the codegen system at any time. +pub mod eventemitter; pub mod simple; diff --git a/mp2-v1/tests/common/bindings/simple.rs b/mp2-v1/tests/common/bindings/simple.rs index c642fe59d..2bd794ac9 100644 --- a/mp2-v1/tests/common/bindings/simple.rs +++ b/mp2-v1/tests/common/bindings/simple.rs @@ -4,23 +4,60 @@ Generated by the following Solidity interface... ```solidity interface Simple { type MappingOperation is uint8; + struct LargeStruct { + uint256 field1; + uint128 field2; + uint128 field3; + } struct MappingChange { uint256 key; address value; MappingOperation operation; } + struct MappingOfSingleValueMappingsChange { + uint256 outerKey; + uint256 innerKey; + uint256 value; + MappingOperation operation; + } + struct MappingOfStructMappingsChange { + uint256 outerKey; + uint256 innerKey; + uint256 field1; + uint128 field2; + uint128 field3; + MappingOperation operation; + } + struct MappingStructChange { + uint256 key; + uint256 field1; + uint128 field2; + uint128 field3; + MappingOperation operation; + } function addToArray(uint256 value) external; function arr1(uint256) external view returns (uint256); + function changeMapping(MappingOfStructMappingsChange[] memory changes) external; function changeMapping(MappingChange[] memory changes) external; + function changeMapping(MappingOfSingleValueMappingsChange[] memory changes) external; + function changeMapping(MappingStructChange[] memory changes) external; function m1(uint256) external view returns (address); + function mappingOfSingleValueMappings(uint256, uint256) external view returns (uint256); + function mappingOfStructMappings(uint256, uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); function s1() external view returns (bool); function s2() external view returns (uint256); function s3() external view returns (string memory); function s4() external view returns (address); function setMapping(uint256 key, address value) external; + function setMappingOfSingleValueMappings(uint256 outerKey, uint256 innerKey, uint256 value) external; + function setMappingOfStructMappings(uint256 outerKey, uint256 innerKey, uint256 field1, uint128 field2, uint128 field3) external; + function setMappingStruct(uint256 _key, uint256 _field1, uint128 _field2, uint128 _field3) external; function setS2(uint256 newS2) external; + function setSimpleStruct(LargeStruct memory input) external; function setSimples(bool newS1, uint256 newS2, string memory newS3, address newS4) external; + function simpleStruct() external view returns (uint256 field1, uint128 field2, uint128 field3); + function structMapping(uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); } ``` @@ -59,6 +96,51 @@ interface Simple { ], "stateMutability": "view" }, + { + "type": "function", + "name": "changeMapping", + "inputs": [ + { + "name": "changes", + "type": "tuple[]", + "internalType": "struct Simple.MappingOfStructMappingsChange[]", + "components": [ + { + "name": "outerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "innerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "operation", + "type": "uint8", + "internalType": "enum Simple.MappingOperation" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "changeMapping", @@ -89,6 +171,81 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "changeMapping", + "inputs": [ + { + "name": "changes", + "type": "tuple[]", + "internalType": "struct Simple.MappingOfSingleValueMappingsChange[]", + "components": [ + { + "name": "outerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "innerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "operation", + "type": "uint8", + "internalType": "enum Simple.MappingOperation" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "changeMapping", + "inputs": [ + { + "name": "changes", + "type": "tuple[]", + "internalType": "struct Simple.MappingStructChange[]", + "components": [ + { + "name": "key", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "operation", + "type": "uint8", + "internalType": "enum Simple.MappingOperation" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "m1", @@ -108,6 +265,64 @@ interface Simple { ], "stateMutability": "view" }, + { + "type": "function", + "name": "mappingOfSingleValueMappings", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mappingOfStructMappings", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "s1", @@ -178,6 +393,90 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setMappingOfSingleValueMappings", + "inputs": [ + { + "name": "outerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "innerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setMappingOfStructMappings", + "inputs": [ + { + "name": "outerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "innerKey", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setMappingStruct", + "inputs": [ + { + "name": "_key", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "_field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setS2", @@ -191,6 +490,36 @@ interface Simple { "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "setSimpleStruct", + "inputs": [ + { + "name": "input", + "type": "tuple", + "internalType": "struct Simple.LargeStruct", + "components": [ + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "setSimples", @@ -218,34 +547,92 @@ interface Simple { ], "outputs": [], "stateMutability": "nonpayable" - } -] -```*/ -#[allow(non_camel_case_types, non_snake_case, clippy::style)] -pub mod Simple { - use super::*; - use alloy::sol_types as alloy_sol_types; - /// The creation / init bytecode of the contract. - /// - /// ```text - ///0x608060405234801561000f575f80fd5b506108a18061001d5f395ff3fe608060405234801561000f575f80fd5b50600436106100a6575f3560e01c80636cc014de1161006e5780636cc014de1461014b578063a314150f14610167578063a5d666a914610170578063c8af3aa614610185578063d15ec85114610198578063f25d54f5146101da575f80fd5b80630200225c146100aa5780630c1616c9146100bf5780631c134315146100d25780632ae42686146100e55780636987b1fb1461012a575b5f80fd5b6100bd6100b83660046104d3565b6101ed565b005b6100bd6100cd366004610592565b610231565b6100bd6100e0366004610672565b610372565b61010d6100f336600461069c565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61013d61013836600461069c565b61039f565b604051908152602001610121565b5f546101579060ff1681565b6040519015158152602001610121565b61013d60015481565b6101786103be565b60405161012191906106b3565b60035461010d906001600160a01b031681565b6100bd6101a636600461069c565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b6100bd6101e836600461069c565b600155565b5f805460ff19168515151790556001839055600261020b8382610783565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b815181101561036e575f82828151811061024f5761024f610857565b602002602001015160400151600281111561026c5761026c610843565b036102b35760045f83838151811061028657610286610857565b6020908102919091018101515182528101919091526040015f2080546001600160a01b0319169055610366565b60028282815181106102c7576102c7610857565b60200260200101516040015160028111156102e4576102e4610843565b148061031e575060018282815181106102ff576102ff610857565b602002602001015160400151600281111561031c5761031c610843565b145b156103665761036682828151811061033857610338610857565b60200260200101515f015183838151811061035557610355610857565b602002602001015160200151610372565b600101610233565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b600581815481106103ae575f80fd5b5f91825260209091200154905081565b600280546103cb906106ff565b80601f01602080910402602001604051908101604052809291908181526020018280546103f7906106ff565b80156104425780601f1061041957610100808354040283529160200191610442565b820191905f5260205f20905b81548152906001019060200180831161042557829003601f168201915b505050505081565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156104815761048161044a565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156104b0576104b061044a565b604052919050565b80356001600160a01b03811681146104ce575f80fd5b919050565b5f805f80608085870312156104e6575f80fd5b843580151581146104f5575f80fd5b93506020858101359350604086013567ffffffffffffffff80821115610519575f80fd5b818801915088601f83011261052c575f80fd5b81358181111561053e5761053e61044a565b610550601f8201601f19168501610487565b91508082528984828501011115610565575f80fd5b80848401858401375f84828401015250809450505050610587606086016104b8565b905092959194509250565b5f60208083850312156105a3575f80fd5b823567ffffffffffffffff808211156105ba575f80fd5b818501915085601f8301126105cd575f80fd5b8135818111156105df576105df61044a565b6105ed848260051b01610487565b8181528481019250606091820284018501918883111561060b575f80fd5b938501935b828510156106665780858a031215610626575f80fd5b61062e61045e565b8535815261063d8787016104b8565b8782015260408087013560038110610653575f80fd5b9082015284529384019392850192610610565b50979650505050505050565b5f8060408385031215610683575f80fd5b82359150610693602084016104b8565b90509250929050565b5f602082840312156106ac575f80fd5b5035919050565b5f602080835283518060208501525f5b818110156106df578581018301518582016040015282016106c3565b505f604082860101526040601f19601f8301168501019250505092915050565b600181811c9082168061071357607f821691505b60208210810361073157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561077e57805f5260205f20601f840160051c8101602085101561075c5750805b601f840160051c820191505b8181101561077b575f8155600101610768565b50505b505050565b815167ffffffffffffffff81111561079d5761079d61044a565b6107b1816107ab84546106ff565b84610737565b602080601f8311600181146107e4575f84156107cd5750858301515b5f19600386901b1c1916600185901b17855561083b565b5f85815260208120601f198616915b82811015610812578886015182559484019460019091019084016107f3565b508582101561082f57878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220e095397ffdc7d1eb7b22ee492c14952a243031465f1cc6f690524fec46fcc63b64736f6c63430008180033 - /// ``` - #[rustfmt::skip] - #[allow(clippy::all)] - pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[Pa\x08\xA1\x80a\0\x1D_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xA6W_5`\xE0\x1C\x80cl\xC0\x14\xDE\x11a\0nW\x80cl\xC0\x14\xDE\x14a\x01KW\x80c\xA3\x14\x15\x0F\x14a\x01gW\x80c\xA5\xD6f\xA9\x14a\x01pW\x80c\xC8\xAF:\xA6\x14a\x01\x85W\x80c\xD1^\xC8Q\x14a\x01\x98W\x80c\xF2]T\xF5\x14a\x01\xDAW_\x80\xFD[\x80c\x02\0\"\\\x14a\0\xAAW\x80c\x0C\x16\x16\xC9\x14a\0\xBFW\x80c\x1C\x13C\x15\x14a\0\xD2W\x80c*\xE4&\x86\x14a\0\xE5W\x80ci\x87\xB1\xFB\x14a\x01*W[_\x80\xFD[a\0\xBDa\0\xB86`\x04a\x04\xD3V[a\x01\xEDV[\0[a\0\xBDa\0\xCD6`\x04a\x05\x92V[a\x021V[a\0\xBDa\0\xE06`\x04a\x06rV[a\x03rV[a\x01\ra\0\xF36`\x04a\x06\x9CV[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01[`@Q\x80\x91\x03\x90\xF3[a\x01=a\x0186`\x04a\x06\x9CV[a\x03\x9FV[`@Q\x90\x81R` \x01a\x01!V[_Ta\x01W\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01!V[a\x01=`\x01T\x81V[a\x01xa\x03\xBEV[`@Qa\x01!\x91\x90a\x06\xB3V[`\x03Ta\x01\r\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\0\xBDa\x01\xA66`\x04a\x06\x9CV[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[a\0\xBDa\x01\xE86`\x04a\x06\x9CV[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x02\x0B\x83\x82a\x07\x83V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x03nW_\x82\x82\x81Q\x81\x10a\x02OWa\x02Oa\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x02lWa\x02la\x08CV[\x03a\x02\xB3W`\x04_\x83\x83\x81Q\x81\x10a\x02\x86Wa\x02\x86a\x08WV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x03fV[`\x02\x82\x82\x81Q\x81\x10a\x02\xC7Wa\x02\xC7a\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x02\xE4Wa\x02\xE4a\x08CV[\x14\x80a\x03\x1EWP`\x01\x82\x82\x81Q\x81\x10a\x02\xFFWa\x02\xFFa\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\x1CWa\x03\x1Ca\x08CV[\x14[\x15a\x03fWa\x03f\x82\x82\x81Q\x81\x10a\x038Wa\x038a\x08WV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x03UWa\x03Ua\x08WV[` \x02` \x01\x01Q` \x01Qa\x03rV[`\x01\x01a\x023V[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x03\xAEW_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`\x02\x80Ta\x03\xCB\x90a\x06\xFFV[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x03\xF7\x90a\x06\xFFV[\x80\x15a\x04BW\x80`\x1F\x10a\x04\x19Wa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x04BV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x04%W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x04\x81Wa\x04\x81a\x04JV[`@R\x90V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x04\xB0Wa\x04\xB0a\x04JV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x04\xCEW_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x04\xE6W_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x04\xF5W_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x05\x19W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x05,W_\x80\xFD[\x815\x81\x81\x11\x15a\x05>Wa\x05>a\x04JV[a\x05P`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x04\x87V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x05eW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x05\x87``\x86\x01a\x04\xB8V[\x90P\x92\x95\x91\x94P\x92PV[_` \x80\x83\x85\x03\x12\x15a\x05\xA3W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x05\xBAW_\x80\xFD[\x81\x85\x01\x91P\x85`\x1F\x83\x01\x12a\x05\xCDW_\x80\xFD[\x815\x81\x81\x11\x15a\x05\xDFWa\x05\xDFa\x04JV[a\x05\xED\x84\x82`\x05\x1B\x01a\x04\x87V[\x81\x81R\x84\x81\x01\x92P``\x91\x82\x02\x84\x01\x85\x01\x91\x88\x83\x11\x15a\x06\x0BW_\x80\xFD[\x93\x85\x01\x93[\x82\x85\x10\x15a\x06fW\x80\x85\x8A\x03\x12\x15a\x06&W_\x80\xFD[a\x06.a\x04^V[\x855\x81Ra\x06=\x87\x87\x01a\x04\xB8V[\x87\x82\x01R`@\x80\x87\x015`\x03\x81\x10a\x06SW_\x80\xFD[\x90\x82\x01R\x84R\x93\x84\x01\x93\x92\x85\x01\x92a\x06\x10V[P\x97\x96PPPPPPPV[_\x80`@\x83\x85\x03\x12\x15a\x06\x83W_\x80\xFD[\x825\x91Pa\x06\x93` \x84\x01a\x04\xB8V[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x06\xACW_\x80\xFD[P5\x91\x90PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x06\xDFW\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x06\xC3V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x07\x13W`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x071WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x07~W\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x07\\WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x07{W_\x81U`\x01\x01a\x07hV[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9DWa\x07\x9Da\x04JV[a\x07\xB1\x81a\x07\xAB\x84Ta\x06\xFFV[\x84a\x077V[` \x80`\x1F\x83\x11`\x01\x81\x14a\x07\xE4W_\x84\x15a\x07\xCDWP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\x08;V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\x08\x12W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x07\xF3V[P\x85\x82\x10\x15a\x08/W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 \xE0\x959\x7F\xFD\xC7\xD1\xEB{\"\xEEI,\x14\x95*$01F_\x1C\xC6\xF6\x90RO\xECF\xFC\xC6;dsolcC\0\x08\x18\x003", - ); - /// The runtime bytecode of the contract, as deployed on the network. - /// - /// ```text - ///0x608060405234801561000f575f80fd5b50600436106100a6575f3560e01c80636cc014de1161006e5780636cc014de1461014b578063a314150f14610167578063a5d666a914610170578063c8af3aa614610185578063d15ec85114610198578063f25d54f5146101da575f80fd5b80630200225c146100aa5780630c1616c9146100bf5780631c134315146100d25780632ae42686146100e55780636987b1fb1461012a575b5f80fd5b6100bd6100b83660046104d3565b6101ed565b005b6100bd6100cd366004610592565b610231565b6100bd6100e0366004610672565b610372565b61010d6100f336600461069c565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b61013d61013836600461069c565b61039f565b604051908152602001610121565b5f546101579060ff1681565b6040519015158152602001610121565b61013d60015481565b6101786103be565b60405161012191906106b3565b60035461010d906001600160a01b031681565b6100bd6101a636600461069c565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b6100bd6101e836600461069c565b600155565b5f805460ff19168515151790556001839055600261020b8382610783565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b815181101561036e575f82828151811061024f5761024f610857565b602002602001015160400151600281111561026c5761026c610843565b036102b35760045f83838151811061028657610286610857565b6020908102919091018101515182528101919091526040015f2080546001600160a01b0319169055610366565b60028282815181106102c7576102c7610857565b60200260200101516040015160028111156102e4576102e4610843565b148061031e575060018282815181106102ff576102ff610857565b602002602001015160400151600281111561031c5761031c610843565b145b156103665761036682828151811061033857610338610857565b60200260200101515f015183838151811061035557610355610857565b602002602001015160200151610372565b600101610233565b5050565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b600581815481106103ae575f80fd5b5f91825260209091200154905081565b600280546103cb906106ff565b80601f01602080910402602001604051908101604052809291908181526020018280546103f7906106ff565b80156104425780601f1061041957610100808354040283529160200191610442565b820191905f5260205f20905b81548152906001019060200180831161042557829003601f168201915b505050505081565b634e487b7160e01b5f52604160045260245ffd5b6040516060810167ffffffffffffffff811182821017156104815761048161044a565b60405290565b604051601f8201601f1916810167ffffffffffffffff811182821017156104b0576104b061044a565b604052919050565b80356001600160a01b03811681146104ce575f80fd5b919050565b5f805f80608085870312156104e6575f80fd5b843580151581146104f5575f80fd5b93506020858101359350604086013567ffffffffffffffff80821115610519575f80fd5b818801915088601f83011261052c575f80fd5b81358181111561053e5761053e61044a565b610550601f8201601f19168501610487565b91508082528984828501011115610565575f80fd5b80848401858401375f84828401015250809450505050610587606086016104b8565b905092959194509250565b5f60208083850312156105a3575f80fd5b823567ffffffffffffffff808211156105ba575f80fd5b818501915085601f8301126105cd575f80fd5b8135818111156105df576105df61044a565b6105ed848260051b01610487565b8181528481019250606091820284018501918883111561060b575f80fd5b938501935b828510156106665780858a031215610626575f80fd5b61062e61045e565b8535815261063d8787016104b8565b8782015260408087013560038110610653575f80fd5b9082015284529384019392850192610610565b50979650505050505050565b5f8060408385031215610683575f80fd5b82359150610693602084016104b8565b90509250929050565b5f602082840312156106ac575f80fd5b5035919050565b5f602080835283518060208501525f5b818110156106df578581018301518582016040015282016106c3565b505f604082860101526040601f19601f8301168501019250505092915050565b600181811c9082168061071357607f821691505b60208210810361073157634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561077e57805f5260205f20601f840160051c8101602085101561075c5750805b601f840160051c820191505b8181101561077b575f8155600101610768565b50505b505050565b815167ffffffffffffffff81111561079d5761079d61044a565b6107b1816107ab84546106ff565b84610737565b602080601f8311600181146107e4575f84156107cd5750858301515b5f19600386901b1c1916600185901b17855561083b565b5f85815260208120601f198616915b82811015610812578886015182559484019460019091019084016107f3565b508582101561082f57878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffdfea2646970667358221220e095397ffdc7d1eb7b22ee492c14952a243031465f1cc6f690524fec46fcc63b64736f6c63430008180033 - /// ``` - #[rustfmt::skip] + }, + { + "type": "function", + "name": "simpleStruct", + "inputs": [], + "outputs": [ + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "structMapping", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "field1", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "field2", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "field3", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + } +] +```*/ +#[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style, + clippy::empty_structs_with_brackets +)] +pub mod Simple { + use super::*; + use alloy::sol_types as alloy_sol_types; + /// The creation / init bytecode of the contract. + /// + /// ```text + ///0x608060405234801561000f575f80fd5b5061149a8061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061013d575f3560e01c80638026de31116100b4578063a5d666a911610079578063a5d666a91461035d578063c6a7f0fe14610372578063c8af3aa614610385578063d15ec85114610398578063ead18400146103da578063f25d54f5146103fc575f80fd5b80638026de311461027357806385b6489f1461028657806388dfddc6146102f057806396dc9a411461032a578063a314150f14610354575f80fd5b80633e70166e116101055780633e70166e146101d45780633e9060c7146101e75780634cf5a94a146101fa57806351976fc8146102235780636987b1fb146102365780636cc014de14610257575f80fd5b80630200225c1461014157806302e3003a146101565780630c1616c9146101695780631c1343151461017c5780632ae426861461018f575b5f80fd5b61015461014f366004610ce0565b61040f565b005b610154610164366004610de7565b610453565b610154610177366004610eda565b610616565b61015461018a366004610f93565b610753565b6101b761019d366004610fbd565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101546101e2366004610fd4565b610780565b6101546101f5366004610fea565b610792565b6101546102083660046110b5565b5f928352600960209081526040808520938552929052912055565b6101546102313660046110de565b610922565b610249610244366004610fbd565b610a96565b6040519081526020016101cb565b5f546102639060ff1681565b60405190151581526020016101cb565b6101546102813660046111b6565b610ab5565b6102cb6102943660046111fd565b600a60209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060016101cb565b6102cb6102fe366004610fbd565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b6102496103383660046111fd565b600960209081525f928352604080842090915290825290205481565b61024960015481565b610365610b07565b6040516101cb919061121d565b610154610380366004611269565b610b93565b6003546101b7906001600160a01b031681565b6101546103a6366004610fbd565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b6006546007546102cb91906001600160801b0380821691600160801b90041683565b61015461040a366004610fbd565b600155565b5f805460ff19168515151790556001839055600261042d8382611338565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b8151811015610612575f8282815181106104715761047161140c565b602002602001015160a00151600281111561048e5761048e6113f8565b036104fd57600a5f8383815181106104a8576104a861140c565b60200260200101515f015181526020019081526020015f205f8383815181106104d3576104d361140c565b60209081029190910181015181015182528101919091526040015f9081208181556001015561060a565b60028282815181106105115761051161140c565b602002602001015160a00151600281111561052e5761052e6113f8565b1480610568575060018282815181106105495761054961140c565b602002602001015160a001516002811115610566576105666113f8565b145b1561060a5761060a8282815181106105825761058261140c565b60200260200101515f015183838151811061059f5761059f61140c565b6020026020010151602001518484815181106105bd576105bd61140c565b6020026020010151604001518585815181106105db576105db61140c565b6020026020010151606001518686815181106105f9576105f961140c565b602002602001015160800151610b93565b600101610455565b5050565b5f5b8151811015610612575f8282815181106106345761063461140c565b6020026020010151604001516002811115610651576106516113f8565b036106985760045f83838151811061066b5761066b61140c565b6020908102919091018101515182528101919091526040015f2080546001600160a01b031916905561074b565b60028282815181106106ac576106ac61140c565b60200260200101516040015160028111156106c9576106c96113f8565b1480610703575060018282815181106106e4576106e461140c565b6020026020010151604001516002811115610701576107016113f8565b145b1561074b5761074b82828151811061071d5761071d61140c565b60200260200101515f015183838151811061073a5761073a61140c565b602002602001015160200151610753565b600101610618565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b80600661078d8282611420565b505050565b5f5b8151811015610612575f8282815181106107b0576107b061140c565b60200260200101516060015160028111156107cd576107cd6113f8565b036108335760095f8383815181106107e7576107e761140c565b60200260200101515f015181526020019081526020015f205f8383815181106108125761081261140c565b60200260200101516020015181526020019081526020015f205f905561091a565b60028282815181106108475761084761140c565b6020026020010151606001516002811115610864576108646113f8565b148061089e5750600182828151811061087f5761087f61140c565b602002602001015160600151600281111561089c5761089c6113f8565b145b1561091a5761091a8282815181106108b8576108b861140c565b60200260200101515f01518383815181106108d5576108d561140c565b6020026020010151602001518484815181106108f3576108f361140c565b6020026020010151604001515f928352600960209081526040808520938552929052912055565b600101610794565b5f5b8151811015610612575f8282815181106109405761094061140c565b602002602001015160800151600281111561095d5761095d6113f8565b0361099f5760085f8383815181106109775761097761140c565b6020908102919091018101515182528101919091526040015f90812081815560010155610a8e565b60028282815181106109b3576109b361140c565b60200260200101516080015160028111156109d0576109d06113f8565b1480610a0a575060018282815181106109eb576109eb61140c565b6020026020010151608001516002811115610a0857610a086113f8565b145b15610a8e57610a8e828281518110610a2457610a2461140c565b60200260200101515f0151838381518110610a4157610a4161140c565b602002602001015160200151848481518110610a5f57610a5f61140c565b602002602001015160400151858581518110610a7d57610a7d61140c565b602002602001015160600151610ab5565b600101610924565b60058181548110610aa5575f80fd5b5f91825260209091200154905081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f9687526008909352942092518355925192518116600160801b02921691909117600190910155565b60028054610b14906112bb565b80601f0160208091040260200160405190810160405280929190818152602001828054610b40906112bb565b8015610b8b5780601f10610b6257610100808354040283529160200191610b8b565b820191905f5260205f20905b815481529060010190602001808311610b6e57829003601f168201915b505050505081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f978852600a84528288209688529590925290942091518255925191518316600160801b029190921617600190910155565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715610c2557610c25610bee565b60405290565b6040516060810167ffffffffffffffff81118282101715610c2557610c25610bee565b6040516080810167ffffffffffffffff81118282101715610c2557610c25610bee565b60405160a0810167ffffffffffffffff81118282101715610c2557610c25610bee565b604051601f8201601f1916810167ffffffffffffffff81118282101715610cbd57610cbd610bee565b604052919050565b80356001600160a01b0381168114610cdb575f80fd5b919050565b5f805f8060808587031215610cf3575f80fd5b84358015158114610d02575f80fd5b93506020858101359350604086013567ffffffffffffffff80821115610d26575f80fd5b818801915088601f830112610d39575f80fd5b813581811115610d4b57610d4b610bee565b610d5d601f8201601f19168501610c94565b91508082528984828501011115610d72575f80fd5b80848401858401375f84828401015250809450505050610d9460608601610cc5565b905092959194509250565b5f67ffffffffffffffff821115610db857610db8610bee565b5060051b60200190565b6001600160801b0381168114610dd6575f80fd5b50565b803560038110610cdb575f80fd5b5f6020808385031215610df8575f80fd5b823567ffffffffffffffff811115610e0e575f80fd5b8301601f81018513610e1e575f80fd5b8035610e31610e2c82610d9f565b610c94565b81815260c09182028301840191848201919088841115610e4f575f80fd5b938501935b83851015610ece5780858a031215610e6a575f80fd5b610e72610c02565b85358152868601358782015260408087013590820152606080870135610e9781610dc2565b90820152608086810135610eaa81610dc2565b9082015260a0610ebb878201610dd9565b9082015283529384019391850191610e54565b50979650505050505050565b5f6020808385031215610eeb575f80fd5b823567ffffffffffffffff811115610f01575f80fd5b8301601f81018513610f11575f80fd5b8035610f1f610e2c82610d9f565b81815260609182028301840191848201919088841115610f3d575f80fd5b938501935b83851015610ece5780858a031215610f58575f80fd5b610f60610c2b565b85358152610f6f878701610cc5565b878201526040610f80818801610dd9565b9082015283529384019391850191610f42565b5f8060408385031215610fa4575f80fd5b82359150610fb460208401610cc5565b90509250929050565b5f60208284031215610fcd575f80fd5b5035919050565b5f60608284031215610fe4575f80fd5b50919050565b5f6020808385031215610ffb575f80fd5b823567ffffffffffffffff811115611011575f80fd5b8301601f81018513611021575f80fd5b803561102f610e2c82610d9f565b81815260079190911b8201830190838101908783111561104d575f80fd5b928401925b828410156110aa5760808489031215611069575f80fd5b611071610c4e565b843581528585013586820152604080860135908201526060611094818701610dd9565b9082015282526080939093019290840190611052565b979650505050505050565b5f805f606084860312156110c7575f80fd5b505081359360208301359350604090920135919050565b5f60208083850312156110ef575f80fd5b823567ffffffffffffffff811115611105575f80fd5b8301601f81018513611115575f80fd5b8035611123610e2c82610d9f565b81815260a09182028301840191848201919088841115611141575f80fd5b938501935b83851015610ece5780858a03121561115c575f80fd5b611164610c71565b85358152868601358782015260408087013561117f81610dc2565b9082015260608681013561119281610dc2565b9082015260806111a3878201610dd9565b9082015283529384019391850191611146565b5f805f80608085870312156111c9575f80fd5b843593506020850135925060408501356111e281610dc2565b915060608501356111f281610dc2565b939692955090935050565b5f806040838503121561120e575f80fd5b50508035926020909101359150565b5f602080835283518060208501525f5b818110156112495785810183015185820160400152820161122d565b505f604082860101526040601f19601f8301168501019250505092915050565b5f805f805f60a0868803121561127d575f80fd5b853594506020860135935060408601359250606086013561129d81610dc2565b915060808601356112ad81610dc2565b809150509295509295909350565b600181811c908216806112cf57607f821691505b602082108103610fe457634e487b7160e01b5f52602260045260245ffd5b601f82111561078d57805f5260205f20601f840160051c810160208510156113125750805b601f840160051c820191505b81811015611331575f815560010161131e565b5050505050565b815167ffffffffffffffff81111561135257611352610bee565b6113668161136084546112bb565b846112ed565b602080601f831160018114611399575f84156113825750858301515b5f19600386901b1c1916600185901b1785556113f0565b5f85815260208120601f198616915b828110156113c7578886015182559484019460019091019084016113a8565b50858210156113e457878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b81358155602082013561143281610dc2565b604083013561144081610dc2565b6001600160801b03198160801b166001600160801b0383161760018401555050505056fea2646970667358221220687c6fe6e265aabe62404e89b39f883c05f4922b7a4a138f5273c712c742ef4f64736f6c63430008180033 + /// ``` + #[rustfmt::skip] + #[allow(clippy::all)] + pub static BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[Pa\x14\x9A\x80a\0\x1D_9_\xF3\xFE`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\x01=W_5`\xE0\x1C\x80c\x80&\xDE1\x11a\0\xB4W\x80c\xA5\xD6f\xA9\x11a\0yW\x80c\xA5\xD6f\xA9\x14a\x03]W\x80c\xC6\xA7\xF0\xFE\x14a\x03rW\x80c\xC8\xAF:\xA6\x14a\x03\x85W\x80c\xD1^\xC8Q\x14a\x03\x98W\x80c\xEA\xD1\x84\0\x14a\x03\xDAW\x80c\xF2]T\xF5\x14a\x03\xFCW_\x80\xFD[\x80c\x80&\xDE1\x14a\x02sW\x80c\x85\xB6H\x9F\x14a\x02\x86W\x80c\x88\xDF\xDD\xC6\x14a\x02\xF0W\x80c\x96\xDC\x9AA\x14a\x03*W\x80c\xA3\x14\x15\x0F\x14a\x03TW_\x80\xFD[\x80c>p\x16n\x11a\x01\x05W\x80c>p\x16n\x14a\x01\xD4W\x80c>\x90`\xC7\x14a\x01\xE7W\x80cL\xF5\xA9J\x14a\x01\xFAW\x80cQ\x97o\xC8\x14a\x02#W\x80ci\x87\xB1\xFB\x14a\x026W\x80cl\xC0\x14\xDE\x14a\x02WW_\x80\xFD[\x80c\x02\0\"\\\x14a\x01AW\x80c\x02\xE3\0:\x14a\x01VW\x80c\x0C\x16\x16\xC9\x14a\x01iW\x80c\x1C\x13C\x15\x14a\x01|W\x80c*\xE4&\x86\x14a\x01\x8FW[_\x80\xFD[a\x01Ta\x01O6`\x04a\x0C\xE0V[a\x04\x0FV[\0[a\x01Ta\x01d6`\x04a\r\xE7V[a\x04SV[a\x01Ta\x01w6`\x04a\x0E\xDAV[a\x06\x16V[a\x01Ta\x01\x8A6`\x04a\x0F\x93V[a\x07SV[a\x01\xB7a\x01\x9D6`\x04a\x0F\xBDV[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01[`@Q\x80\x91\x03\x90\xF3[a\x01Ta\x01\xE26`\x04a\x0F\xD4V[a\x07\x80V[a\x01Ta\x01\xF56`\x04a\x0F\xEAV[a\x07\x92V[a\x01Ta\x02\x086`\x04a\x10\xB5V[_\x92\x83R`\t` \x90\x81R`@\x80\x85 \x93\x85R\x92\x90R\x91 UV[a\x01Ta\x0216`\x04a\x10\xDEV[a\t\"V[a\x02Ia\x02D6`\x04a\x0F\xBDV[a\n\x96V[`@Q\x90\x81R` \x01a\x01\xCBV[_Ta\x02c\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01\xCBV[a\x01Ta\x02\x816`\x04a\x11\xB6V[a\n\xB5V[a\x02\xCBa\x02\x946`\x04a\x11\xFDV[`\n` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01a\x01\xCBV[a\x02\xCBa\x02\xFE6`\x04a\x0F\xBDV[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x02Ia\x0386`\x04a\x11\xFDV[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 T\x81V[a\x02I`\x01T\x81V[a\x03ea\x0B\x07V[`@Qa\x01\xCB\x91\x90a\x12\x1DV[a\x01Ta\x03\x806`\x04a\x12iV[a\x0B\x93V[`\x03Ta\x01\xB7\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\x01Ta\x03\xA66`\x04a\x0F\xBDV[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x02\xCB\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01Ta\x04\n6`\x04a\x0F\xBDV[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x04-\x83\x82a\x138V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x04qWa\x04qa\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x04\x8EWa\x04\x8Ea\x13\xF8V[\x03a\x04\xFDW`\n_\x83\x83\x81Q\x81\x10a\x04\xA8Wa\x04\xA8a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x81R` \x01\x90\x81R` \x01_ _\x83\x83\x81Q\x81\x10a\x04\xD3Wa\x04\xD3a\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01Q\x81\x01Q\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\x06\nV[`\x02\x82\x82\x81Q\x81\x10a\x05\x11Wa\x05\x11a\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x05.Wa\x05.a\x13\xF8V[\x14\x80a\x05hWP`\x01\x82\x82\x81Q\x81\x10a\x05IWa\x05Ia\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x05fWa\x05fa\x13\xF8V[\x14[\x15a\x06\nWa\x06\n\x82\x82\x81Q\x81\x10a\x05\x82Wa\x05\x82a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x05\x9FWa\x05\x9Fa\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x05\xBDWa\x05\xBDa\x14\x0CV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\x05\xDBWa\x05\xDBa\x14\x0CV[` \x02` \x01\x01Q``\x01Q\x86\x86\x81Q\x81\x10a\x05\xF9Wa\x05\xF9a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Qa\x0B\x93V[`\x01\x01a\x04UV[PPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x064Wa\x064a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x06QWa\x06Qa\x13\xF8V[\x03a\x06\x98W`\x04_\x83\x83\x81Q\x81\x10a\x06kWa\x06ka\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x07KV[`\x02\x82\x82\x81Q\x81\x10a\x06\xACWa\x06\xACa\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x06\xC9Wa\x06\xC9a\x13\xF8V[\x14\x80a\x07\x03WP`\x01\x82\x82\x81Q\x81\x10a\x06\xE4Wa\x06\xE4a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x07\x01Wa\x07\x01a\x13\xF8V[\x14[\x15a\x07KWa\x07K\x82\x82\x81Q\x81\x10a\x07\x1DWa\x07\x1Da\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x07:Wa\x07:a\x14\x0CV[` \x02` \x01\x01Q` \x01Qa\x07SV[`\x01\x01a\x06\x18V[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[\x80`\x06a\x07\x8D\x82\x82a\x14 V[PPPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x07\xB0Wa\x07\xB0a\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x07\xCDWa\x07\xCDa\x13\xF8V[\x03a\x083W`\t_\x83\x83\x81Q\x81\x10a\x07\xE7Wa\x07\xE7a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x81R` \x01\x90\x81R` \x01_ _\x83\x83\x81Q\x81\x10a\x08\x12Wa\x08\x12a\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x81R` \x01\x90\x81R` \x01_ _\x90Ua\t\x1AV[`\x02\x82\x82\x81Q\x81\x10a\x08GWa\x08Ga\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x08dWa\x08da\x13\xF8V[\x14\x80a\x08\x9EWP`\x01\x82\x82\x81Q\x81\x10a\x08\x7FWa\x08\x7Fa\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x08\x9CWa\x08\x9Ca\x13\xF8V[\x14[\x15a\t\x1AWa\t\x1A\x82\x82\x81Q\x81\x10a\x08\xB8Wa\x08\xB8a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x08\xD5Wa\x08\xD5a\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x08\xF3Wa\x08\xF3a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q_\x92\x83R`\t` \x90\x81R`@\x80\x85 \x93\x85R\x92\x90R\x91 UV[`\x01\x01a\x07\x94V[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\t@Wa\t@a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\t]Wa\t]a\x13\xF8V[\x03a\t\x9FW`\x08_\x83\x83\x81Q\x81\x10a\twWa\twa\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\n\x8EV[`\x02\x82\x82\x81Q\x81\x10a\t\xB3Wa\t\xB3a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\t\xD0Wa\t\xD0a\x13\xF8V[\x14\x80a\n\nWP`\x01\x82\x82\x81Q\x81\x10a\t\xEBWa\t\xEBa\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\n\x08Wa\n\x08a\x13\xF8V[\x14[\x15a\n\x8EWa\n\x8E\x82\x82\x81Q\x81\x10a\n$Wa\n$a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\nAWa\nAa\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\n_Wa\n_a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\n}Wa\n}a\x14\x0CV[` \x02` \x01\x01Q``\x01Qa\n\xB5V[`\x01\x01a\t$V[`\x05\x81\x81T\x81\x10a\n\xA5W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x96\x87R`\x08\x90\x93R\x94 \x92Q\x83U\x92Q\x92Q\x81\x16`\x01`\x80\x1B\x02\x92\x16\x91\x90\x91\x17`\x01\x90\x91\x01UV[`\x02\x80Ta\x0B\x14\x90a\x12\xBBV[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x0B@\x90a\x12\xBBV[\x80\x15a\x0B\x8BW\x80`\x1F\x10a\x0BbWa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x0B\x8BV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x0BnW\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x97\x88R`\n\x84R\x82\x88 \x96\x88R\x95\x90\x92R\x90\x94 \x91Q\x82U\x92Q\x91Q\x83\x16`\x01`\x80\x1B\x02\x91\x90\x92\x16\x17`\x01\x90\x91\x01UV[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q`\xC0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@R\x90V[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\x80\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\xA0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C\xBDWa\x0C\xBDa\x0B\xEEV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x0C\xDBW_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x0C\xF3W_\x80\xFD[\x845\x80\x15\x15\x81\x14a\r\x02W_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\r&W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\r9W_\x80\xFD[\x815\x81\x81\x11\x15a\rKWa\rKa\x0B\xEEV[a\r]`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x0C\x94V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\rrW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\r\x94``\x86\x01a\x0C\xC5V[\x90P\x92\x95\x91\x94P\x92PV[_g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x82\x11\x15a\r\xB8Wa\r\xB8a\x0B\xEEV[P`\x05\x1B` \x01\x90V[`\x01`\x01`\x80\x1B\x03\x81\x16\x81\x14a\r\xD6W_\x80\xFD[PV[\x805`\x03\x81\x10a\x0C\xDBW_\x80\xFD[_` \x80\x83\x85\x03\x12\x15a\r\xF8W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0E\x0EW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0E\x1EW_\x80\xFD[\x805a\x0E1a\x0E,\x82a\r\x9FV[a\x0C\x94V[\x81\x81R`\xC0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0EOW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x0EjW_\x80\xFD[a\x0Era\x0C\x02V[\x855\x81R\x86\x86\x015\x87\x82\x01R`@\x80\x87\x015\x90\x82\x01R``\x80\x87\x015a\x0E\x97\x81a\r\xC2V[\x90\x82\x01R`\x80\x86\x81\x015a\x0E\xAA\x81a\r\xC2V[\x90\x82\x01R`\xA0a\x0E\xBB\x87\x82\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0ETV[P\x97\x96PPPPPPPV[_` \x80\x83\x85\x03\x12\x15a\x0E\xEBW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0F\x01W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0F\x11W_\x80\xFD[\x805a\x0F\x1Fa\x0E,\x82a\r\x9FV[\x81\x81R``\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0F=W_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x0FXW_\x80\xFD[a\x0F`a\x0C+V[\x855\x81Ra\x0Fo\x87\x87\x01a\x0C\xC5V[\x87\x82\x01R`@a\x0F\x80\x81\x88\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0FBV[_\x80`@\x83\x85\x03\x12\x15a\x0F\xA4W_\x80\xFD[\x825\x91Pa\x0F\xB4` \x84\x01a\x0C\xC5V[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x0F\xCDW_\x80\xFD[P5\x91\x90PV[_``\x82\x84\x03\x12\x15a\x0F\xE4W_\x80\xFD[P\x91\x90PV[_` \x80\x83\x85\x03\x12\x15a\x0F\xFBW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x10\x11W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x10!W_\x80\xFD[\x805a\x10/a\x0E,\x82a\r\x9FV[\x81\x81R`\x07\x91\x90\x91\x1B\x82\x01\x83\x01\x90\x83\x81\x01\x90\x87\x83\x11\x15a\x10MW_\x80\xFD[\x92\x84\x01\x92[\x82\x84\x10\x15a\x10\xAAW`\x80\x84\x89\x03\x12\x15a\x10iW_\x80\xFD[a\x10qa\x0CNV[\x845\x81R\x85\x85\x015\x86\x82\x01R`@\x80\x86\x015\x90\x82\x01R``a\x10\x94\x81\x87\x01a\r\xD9V[\x90\x82\x01R\x82R`\x80\x93\x90\x93\x01\x92\x90\x84\x01\x90a\x10RV[\x97\x96PPPPPPPV[_\x80_``\x84\x86\x03\x12\x15a\x10\xC7W_\x80\xFD[PP\x815\x93` \x83\x015\x93P`@\x90\x92\x015\x91\x90PV[_` \x80\x83\x85\x03\x12\x15a\x10\xEFW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x11\x05W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x11\x15W_\x80\xFD[\x805a\x11#a\x0E,\x82a\r\x9FV[\x81\x81R`\xA0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x11AW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x11\\W_\x80\xFD[a\x11da\x0CqV[\x855\x81R\x86\x86\x015\x87\x82\x01R`@\x80\x87\x015a\x11\x7F\x81a\r\xC2V[\x90\x82\x01R``\x86\x81\x015a\x11\x92\x81a\r\xC2V[\x90\x82\x01R`\x80a\x11\xA3\x87\x82\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x11FV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x11\xC9W_\x80\xFD[\x845\x93P` \x85\x015\x92P`@\x85\x015a\x11\xE2\x81a\r\xC2V[\x91P``\x85\x015a\x11\xF2\x81a\r\xC2V[\x93\x96\x92\x95P\x90\x93PPV[_\x80`@\x83\x85\x03\x12\x15a\x12\x0EW_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x12IW\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x12-V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[_\x80_\x80_`\xA0\x86\x88\x03\x12\x15a\x12}W_\x80\xFD[\x855\x94P` \x86\x015\x93P`@\x86\x015\x92P``\x86\x015a\x12\x9D\x81a\r\xC2V[\x91P`\x80\x86\x015a\x12\xAD\x81a\r\xC2V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x12\xCFW`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x0F\xE4WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[`\x1F\x82\x11\x15a\x07\x8DW\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x13\x12WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x131W_\x81U`\x01\x01a\x13\x1EV[PPPPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x13RWa\x13Ra\x0B\xEEV[a\x13f\x81a\x13`\x84Ta\x12\xBBV[\x84a\x12\xEDV[` \x80`\x1F\x83\x11`\x01\x81\x14a\x13\x99W_\x84\x15a\x13\x82WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\x13\xF0V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\x13\xC7W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x13\xA8V[P\x85\x82\x10\x15a\x13\xE4W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD[\x815\x81U` \x82\x015a\x142\x81a\r\xC2V[`@\x83\x015a\x14@\x81a\r\xC2V[`\x01`\x01`\x80\x1B\x03\x19\x81`\x80\x1B\x16`\x01`\x01`\x80\x1B\x03\x83\x16\x17`\x01\x84\x01UPPPPV\xFE\xA2dipfsX\"\x12 h|o\xE6\xE2e\xAA\xBEb@N\x89\xB3\x9F\x88<\x05\xF4\x92+zJ\x13\x8FRs\xC7\x12\xC7B\xEFOdsolcC\0\x08\x18\x003", + ); + /// The runtime bytecode of the contract, as deployed on the network. + /// + /// ```text + ///0x608060405234801561000f575f80fd5b506004361061013d575f3560e01c80638026de31116100b4578063a5d666a911610079578063a5d666a91461035d578063c6a7f0fe14610372578063c8af3aa614610385578063d15ec85114610398578063ead18400146103da578063f25d54f5146103fc575f80fd5b80638026de311461027357806385b6489f1461028657806388dfddc6146102f057806396dc9a411461032a578063a314150f14610354575f80fd5b80633e70166e116101055780633e70166e146101d45780633e9060c7146101e75780634cf5a94a146101fa57806351976fc8146102235780636987b1fb146102365780636cc014de14610257575f80fd5b80630200225c1461014157806302e3003a146101565780630c1616c9146101695780631c1343151461017c5780632ae426861461018f575b5f80fd5b61015461014f366004610ce0565b61040f565b005b610154610164366004610de7565b610453565b610154610177366004610eda565b610616565b61015461018a366004610f93565b610753565b6101b761019d366004610fbd565b60046020525f90815260409020546001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b6101546101e2366004610fd4565b610780565b6101546101f5366004610fea565b610792565b6101546102083660046110b5565b5f928352600960209081526040808520938552929052912055565b6101546102313660046110de565b610922565b610249610244366004610fbd565b610a96565b6040519081526020016101cb565b5f546102639060ff1681565b60405190151581526020016101cb565b6101546102813660046111b6565b610ab5565b6102cb6102943660046111fd565b600a60209081525f9283526040808420909152908252902080546001909101546001600160801b0380821691600160801b90041683565b604080519384526001600160801b0392831660208501529116908201526060016101cb565b6102cb6102fe366004610fbd565b60086020525f9081526040902080546001909101546001600160801b0380821691600160801b90041683565b6102496103383660046111fd565b600960209081525f928352604080842090915290825290205481565b61024960015481565b610365610b07565b6040516101cb919061121d565b610154610380366004611269565b610b93565b6003546101b7906001600160a01b031681565b6101546103a6366004610fbd565b600580546001810182555f919091527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db00155565b6006546007546102cb91906001600160801b0380821691600160801b90041683565b61015461040a366004610fbd565b600155565b5f805460ff19168515151790556001839055600261042d8382611338565b50600380546001600160a01b0319166001600160a01b0392909216919091179055505050565b5f5b8151811015610612575f8282815181106104715761047161140c565b602002602001015160a00151600281111561048e5761048e6113f8565b036104fd57600a5f8383815181106104a8576104a861140c565b60200260200101515f015181526020019081526020015f205f8383815181106104d3576104d361140c565b60209081029190910181015181015182528101919091526040015f9081208181556001015561060a565b60028282815181106105115761051161140c565b602002602001015160a00151600281111561052e5761052e6113f8565b1480610568575060018282815181106105495761054961140c565b602002602001015160a001516002811115610566576105666113f8565b145b1561060a5761060a8282815181106105825761058261140c565b60200260200101515f015183838151811061059f5761059f61140c565b6020026020010151602001518484815181106105bd576105bd61140c565b6020026020010151604001518585815181106105db576105db61140c565b6020026020010151606001518686815181106105f9576105f961140c565b602002602001015160800151610b93565b600101610455565b5050565b5f5b8151811015610612575f8282815181106106345761063461140c565b6020026020010151604001516002811115610651576106516113f8565b036106985760045f83838151811061066b5761066b61140c565b6020908102919091018101515182528101919091526040015f2080546001600160a01b031916905561074b565b60028282815181106106ac576106ac61140c565b60200260200101516040015160028111156106c9576106c96113f8565b1480610703575060018282815181106106e4576106e461140c565b6020026020010151604001516002811115610701576107016113f8565b145b1561074b5761074b82828151811061071d5761071d61140c565b60200260200101515f015183838151811061073a5761073a61140c565b602002602001015160200151610753565b600101610618565b5f9182526004602052604090912080546001600160a01b0319166001600160a01b03909216919091179055565b80600661078d8282611420565b505050565b5f5b8151811015610612575f8282815181106107b0576107b061140c565b60200260200101516060015160028111156107cd576107cd6113f8565b036108335760095f8383815181106107e7576107e761140c565b60200260200101515f015181526020019081526020015f205f8383815181106108125761081261140c565b60200260200101516020015181526020019081526020015f205f905561091a565b60028282815181106108475761084761140c565b6020026020010151606001516002811115610864576108646113f8565b148061089e5750600182828151811061087f5761087f61140c565b602002602001015160600151600281111561089c5761089c6113f8565b145b1561091a5761091a8282815181106108b8576108b861140c565b60200260200101515f01518383815181106108d5576108d561140c565b6020026020010151602001518484815181106108f3576108f361140c565b6020026020010151604001515f928352600960209081526040808520938552929052912055565b600101610794565b5f5b8151811015610612575f8282815181106109405761094061140c565b602002602001015160800151600281111561095d5761095d6113f8565b0361099f5760085f8383815181106109775761097761140c565b6020908102919091018101515182528101919091526040015f90812081815560010155610a8e565b60028282815181106109b3576109b361140c565b60200260200101516080015160028111156109d0576109d06113f8565b1480610a0a575060018282815181106109eb576109eb61140c565b6020026020010151608001516002811115610a0857610a086113f8565b145b15610a8e57610a8e828281518110610a2457610a2461140c565b60200260200101515f0151838381518110610a4157610a4161140c565b602002602001015160200151848481518110610a5f57610a5f61140c565b602002602001015160400151858581518110610a7d57610a7d61140c565b602002602001015160600151610ab5565b600101610924565b60058181548110610aa5575f80fd5b5f91825260209091200154905081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f9687526008909352942092518355925192518116600160801b02921691909117600190910155565b60028054610b14906112bb565b80601f0160208091040260200160405190810160405280929190818152602001828054610b40906112bb565b8015610b8b5780601f10610b6257610100808354040283529160200191610b8b565b820191905f5260205f20905b815481529060010190602001808311610b6e57829003601f168201915b505050505081565b604080516060810182529384526001600160801b0392831660208086019182529284168583019081525f978852600a84528288209688529590925290942091518255925191518316600160801b029190921617600190910155565b634e487b7160e01b5f52604160045260245ffd5b60405160c0810167ffffffffffffffff81118282101715610c2557610c25610bee565b60405290565b6040516060810167ffffffffffffffff81118282101715610c2557610c25610bee565b6040516080810167ffffffffffffffff81118282101715610c2557610c25610bee565b60405160a0810167ffffffffffffffff81118282101715610c2557610c25610bee565b604051601f8201601f1916810167ffffffffffffffff81118282101715610cbd57610cbd610bee565b604052919050565b80356001600160a01b0381168114610cdb575f80fd5b919050565b5f805f8060808587031215610cf3575f80fd5b84358015158114610d02575f80fd5b93506020858101359350604086013567ffffffffffffffff80821115610d26575f80fd5b818801915088601f830112610d39575f80fd5b813581811115610d4b57610d4b610bee565b610d5d601f8201601f19168501610c94565b91508082528984828501011115610d72575f80fd5b80848401858401375f84828401015250809450505050610d9460608601610cc5565b905092959194509250565b5f67ffffffffffffffff821115610db857610db8610bee565b5060051b60200190565b6001600160801b0381168114610dd6575f80fd5b50565b803560038110610cdb575f80fd5b5f6020808385031215610df8575f80fd5b823567ffffffffffffffff811115610e0e575f80fd5b8301601f81018513610e1e575f80fd5b8035610e31610e2c82610d9f565b610c94565b81815260c09182028301840191848201919088841115610e4f575f80fd5b938501935b83851015610ece5780858a031215610e6a575f80fd5b610e72610c02565b85358152868601358782015260408087013590820152606080870135610e9781610dc2565b90820152608086810135610eaa81610dc2565b9082015260a0610ebb878201610dd9565b9082015283529384019391850191610e54565b50979650505050505050565b5f6020808385031215610eeb575f80fd5b823567ffffffffffffffff811115610f01575f80fd5b8301601f81018513610f11575f80fd5b8035610f1f610e2c82610d9f565b81815260609182028301840191848201919088841115610f3d575f80fd5b938501935b83851015610ece5780858a031215610f58575f80fd5b610f60610c2b565b85358152610f6f878701610cc5565b878201526040610f80818801610dd9565b9082015283529384019391850191610f42565b5f8060408385031215610fa4575f80fd5b82359150610fb460208401610cc5565b90509250929050565b5f60208284031215610fcd575f80fd5b5035919050565b5f60608284031215610fe4575f80fd5b50919050565b5f6020808385031215610ffb575f80fd5b823567ffffffffffffffff811115611011575f80fd5b8301601f81018513611021575f80fd5b803561102f610e2c82610d9f565b81815260079190911b8201830190838101908783111561104d575f80fd5b928401925b828410156110aa5760808489031215611069575f80fd5b611071610c4e565b843581528585013586820152604080860135908201526060611094818701610dd9565b9082015282526080939093019290840190611052565b979650505050505050565b5f805f606084860312156110c7575f80fd5b505081359360208301359350604090920135919050565b5f60208083850312156110ef575f80fd5b823567ffffffffffffffff811115611105575f80fd5b8301601f81018513611115575f80fd5b8035611123610e2c82610d9f565b81815260a09182028301840191848201919088841115611141575f80fd5b938501935b83851015610ece5780858a03121561115c575f80fd5b611164610c71565b85358152868601358782015260408087013561117f81610dc2565b9082015260608681013561119281610dc2565b9082015260806111a3878201610dd9565b9082015283529384019391850191611146565b5f805f80608085870312156111c9575f80fd5b843593506020850135925060408501356111e281610dc2565b915060608501356111f281610dc2565b939692955090935050565b5f806040838503121561120e575f80fd5b50508035926020909101359150565b5f602080835283518060208501525f5b818110156112495785810183015185820160400152820161122d565b505f604082860101526040601f19601f8301168501019250505092915050565b5f805f805f60a0868803121561127d575f80fd5b853594506020860135935060408601359250606086013561129d81610dc2565b915060808601356112ad81610dc2565b809150509295509295909350565b600181811c908216806112cf57607f821691505b602082108103610fe457634e487b7160e01b5f52602260045260245ffd5b601f82111561078d57805f5260205f20601f840160051c810160208510156113125750805b601f840160051c820191505b81811015611331575f815560010161131e565b5050505050565b815167ffffffffffffffff81111561135257611352610bee565b6113668161136084546112bb565b846112ed565b602080601f831160018114611399575f84156113825750858301515b5f19600386901b1c1916600185901b1785556113f0565b5f85815260208120601f198616915b828110156113c7578886015182559484019460019091019084016113a8565b50858210156113e457878501515f19600388901b60f8161c191681555b505060018460011b0185555b505050505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b81358155602082013561143281610dc2565b604083013561144081610dc2565b6001600160801b03198160801b166001600160801b0383161760018401555050505056fea2646970667358221220687c6fe6e265aabe62404e89b39f883c05f4922b7a4a138f5273c712c742ef4f64736f6c63430008180033 + /// ``` + #[rustfmt::skip] #[allow(clippy::all)] pub static DEPLOYED_BYTECODE: alloy_sol_types::private::Bytes = alloy_sol_types::private::Bytes::from_static( - b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\0\xA6W_5`\xE0\x1C\x80cl\xC0\x14\xDE\x11a\0nW\x80cl\xC0\x14\xDE\x14a\x01KW\x80c\xA3\x14\x15\x0F\x14a\x01gW\x80c\xA5\xD6f\xA9\x14a\x01pW\x80c\xC8\xAF:\xA6\x14a\x01\x85W\x80c\xD1^\xC8Q\x14a\x01\x98W\x80c\xF2]T\xF5\x14a\x01\xDAW_\x80\xFD[\x80c\x02\0\"\\\x14a\0\xAAW\x80c\x0C\x16\x16\xC9\x14a\0\xBFW\x80c\x1C\x13C\x15\x14a\0\xD2W\x80c*\xE4&\x86\x14a\0\xE5W\x80ci\x87\xB1\xFB\x14a\x01*W[_\x80\xFD[a\0\xBDa\0\xB86`\x04a\x04\xD3V[a\x01\xEDV[\0[a\0\xBDa\0\xCD6`\x04a\x05\x92V[a\x021V[a\0\xBDa\0\xE06`\x04a\x06rV[a\x03rV[a\x01\ra\0\xF36`\x04a\x06\x9CV[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01[`@Q\x80\x91\x03\x90\xF3[a\x01=a\x0186`\x04a\x06\x9CV[a\x03\x9FV[`@Q\x90\x81R` \x01a\x01!V[_Ta\x01W\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01!V[a\x01=`\x01T\x81V[a\x01xa\x03\xBEV[`@Qa\x01!\x91\x90a\x06\xB3V[`\x03Ta\x01\r\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\0\xBDa\x01\xA66`\x04a\x06\x9CV[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[a\0\xBDa\x01\xE86`\x04a\x06\x9CV[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x02\x0B\x83\x82a\x07\x83V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x03nW_\x82\x82\x81Q\x81\x10a\x02OWa\x02Oa\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x02lWa\x02la\x08CV[\x03a\x02\xB3W`\x04_\x83\x83\x81Q\x81\x10a\x02\x86Wa\x02\x86a\x08WV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x03fV[`\x02\x82\x82\x81Q\x81\x10a\x02\xC7Wa\x02\xC7a\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x02\xE4Wa\x02\xE4a\x08CV[\x14\x80a\x03\x1EWP`\x01\x82\x82\x81Q\x81\x10a\x02\xFFWa\x02\xFFa\x08WV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x03\x1CWa\x03\x1Ca\x08CV[\x14[\x15a\x03fWa\x03f\x82\x82\x81Q\x81\x10a\x038Wa\x038a\x08WV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x03UWa\x03Ua\x08WV[` \x02` \x01\x01Q` \x01Qa\x03rV[`\x01\x01a\x023V[PPV[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[`\x05\x81\x81T\x81\x10a\x03\xAEW_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`\x02\x80Ta\x03\xCB\x90a\x06\xFFV[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x03\xF7\x90a\x06\xFFV[\x80\x15a\x04BW\x80`\x1F\x10a\x04\x19Wa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x04BV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x04%W\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x04\x81Wa\x04\x81a\x04JV[`@R\x90V[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x04\xB0Wa\x04\xB0a\x04JV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x04\xCEW_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x04\xE6W_\x80\xFD[\x845\x80\x15\x15\x81\x14a\x04\xF5W_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x05\x19W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\x05,W_\x80\xFD[\x815\x81\x81\x11\x15a\x05>Wa\x05>a\x04JV[a\x05P`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x04\x87V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\x05eW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\x05\x87``\x86\x01a\x04\xB8V[\x90P\x92\x95\x91\x94P\x92PV[_` \x80\x83\x85\x03\x12\x15a\x05\xA3W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\x05\xBAW_\x80\xFD[\x81\x85\x01\x91P\x85`\x1F\x83\x01\x12a\x05\xCDW_\x80\xFD[\x815\x81\x81\x11\x15a\x05\xDFWa\x05\xDFa\x04JV[a\x05\xED\x84\x82`\x05\x1B\x01a\x04\x87V[\x81\x81R\x84\x81\x01\x92P``\x91\x82\x02\x84\x01\x85\x01\x91\x88\x83\x11\x15a\x06\x0BW_\x80\xFD[\x93\x85\x01\x93[\x82\x85\x10\x15a\x06fW\x80\x85\x8A\x03\x12\x15a\x06&W_\x80\xFD[a\x06.a\x04^V[\x855\x81Ra\x06=\x87\x87\x01a\x04\xB8V[\x87\x82\x01R`@\x80\x87\x015`\x03\x81\x10a\x06SW_\x80\xFD[\x90\x82\x01R\x84R\x93\x84\x01\x93\x92\x85\x01\x92a\x06\x10V[P\x97\x96PPPPPPPV[_\x80`@\x83\x85\x03\x12\x15a\x06\x83W_\x80\xFD[\x825\x91Pa\x06\x93` \x84\x01a\x04\xB8V[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x06\xACW_\x80\xFD[P5\x91\x90PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x06\xDFW\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x06\xC3V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x07\x13W`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x071WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[P\x91\x90PV[`\x1F\x82\x11\x15a\x07~W\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x07\\WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x07{W_\x81U`\x01\x01a\x07hV[PP[PPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x07\x9DWa\x07\x9Da\x04JV[a\x07\xB1\x81a\x07\xAB\x84Ta\x06\xFFV[\x84a\x077V[` \x80`\x1F\x83\x11`\x01\x81\x14a\x07\xE4W_\x84\x15a\x07\xCDWP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\x08;V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\x08\x12W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x07\xF3V[P\x85\x82\x10\x15a\x08/W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD\xFE\xA2dipfsX\"\x12 \xE0\x959\x7F\xFD\xC7\xD1\xEB{\"\xEEI,\x14\x95*$01F_\x1C\xC6\xF6\x90RO\xECF\xFC\xC6;dsolcC\0\x08\x18\x003", + b"`\x80`@R4\x80\x15a\0\x0FW_\x80\xFD[P`\x046\x10a\x01=W_5`\xE0\x1C\x80c\x80&\xDE1\x11a\0\xB4W\x80c\xA5\xD6f\xA9\x11a\0yW\x80c\xA5\xD6f\xA9\x14a\x03]W\x80c\xC6\xA7\xF0\xFE\x14a\x03rW\x80c\xC8\xAF:\xA6\x14a\x03\x85W\x80c\xD1^\xC8Q\x14a\x03\x98W\x80c\xEA\xD1\x84\0\x14a\x03\xDAW\x80c\xF2]T\xF5\x14a\x03\xFCW_\x80\xFD[\x80c\x80&\xDE1\x14a\x02sW\x80c\x85\xB6H\x9F\x14a\x02\x86W\x80c\x88\xDF\xDD\xC6\x14a\x02\xF0W\x80c\x96\xDC\x9AA\x14a\x03*W\x80c\xA3\x14\x15\x0F\x14a\x03TW_\x80\xFD[\x80c>p\x16n\x11a\x01\x05W\x80c>p\x16n\x14a\x01\xD4W\x80c>\x90`\xC7\x14a\x01\xE7W\x80cL\xF5\xA9J\x14a\x01\xFAW\x80cQ\x97o\xC8\x14a\x02#W\x80ci\x87\xB1\xFB\x14a\x026W\x80cl\xC0\x14\xDE\x14a\x02WW_\x80\xFD[\x80c\x02\0\"\\\x14a\x01AW\x80c\x02\xE3\0:\x14a\x01VW\x80c\x0C\x16\x16\xC9\x14a\x01iW\x80c\x1C\x13C\x15\x14a\x01|W\x80c*\xE4&\x86\x14a\x01\x8FW[_\x80\xFD[a\x01Ta\x01O6`\x04a\x0C\xE0V[a\x04\x0FV[\0[a\x01Ta\x01d6`\x04a\r\xE7V[a\x04SV[a\x01Ta\x01w6`\x04a\x0E\xDAV[a\x06\x16V[a\x01Ta\x01\x8A6`\x04a\x0F\x93V[a\x07SV[a\x01\xB7a\x01\x9D6`\x04a\x0F\xBDV[`\x04` R_\x90\x81R`@\x90 T`\x01`\x01`\xA0\x1B\x03\x16\x81V[`@Q`\x01`\x01`\xA0\x1B\x03\x90\x91\x16\x81R` \x01[`@Q\x80\x91\x03\x90\xF3[a\x01Ta\x01\xE26`\x04a\x0F\xD4V[a\x07\x80V[a\x01Ta\x01\xF56`\x04a\x0F\xEAV[a\x07\x92V[a\x01Ta\x02\x086`\x04a\x10\xB5V[_\x92\x83R`\t` \x90\x81R`@\x80\x85 \x93\x85R\x92\x90R\x91 UV[a\x01Ta\x0216`\x04a\x10\xDEV[a\t\"V[a\x02Ia\x02D6`\x04a\x0F\xBDV[a\n\x96V[`@Q\x90\x81R` \x01a\x01\xCBV[_Ta\x02c\x90`\xFF\x16\x81V[`@Q\x90\x15\x15\x81R` \x01a\x01\xCBV[a\x01Ta\x02\x816`\x04a\x11\xB6V[a\n\xB5V[a\x02\xCBa\x02\x946`\x04a\x11\xFDV[`\n` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[`@\x80Q\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x85\x01R\x91\x16\x90\x82\x01R``\x01a\x01\xCBV[a\x02\xCBa\x02\xFE6`\x04a\x0F\xBDV[`\x08` R_\x90\x81R`@\x90 \x80T`\x01\x90\x91\x01T`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x02Ia\x0386`\x04a\x11\xFDV[`\t` \x90\x81R_\x92\x83R`@\x80\x84 \x90\x91R\x90\x82R\x90 T\x81V[a\x02I`\x01T\x81V[a\x03ea\x0B\x07V[`@Qa\x01\xCB\x91\x90a\x12\x1DV[a\x01Ta\x03\x806`\x04a\x12iV[a\x0B\x93V[`\x03Ta\x01\xB7\x90`\x01`\x01`\xA0\x1B\x03\x16\x81V[a\x01Ta\x03\xA66`\x04a\x0F\xBDV[`\x05\x80T`\x01\x81\x01\x82U_\x91\x90\x91R\x7F\x03kc\x84\xB5\xEC\xA7\x91\xC6'a\x15-\x0Cy\xBB\x06\x04\xC1\x04\xA5\xFBoN\xB0p?1T\xBB=\xB0\x01UV[`\x06T`\x07Ta\x02\xCB\x91\x90`\x01`\x01`\x80\x1B\x03\x80\x82\x16\x91`\x01`\x80\x1B\x90\x04\x16\x83V[a\x01Ta\x04\n6`\x04a\x0F\xBDV[`\x01UV[_\x80T`\xFF\x19\x16\x85\x15\x15\x17\x90U`\x01\x83\x90U`\x02a\x04-\x83\x82a\x138V[P`\x03\x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x92\x90\x92\x16\x91\x90\x91\x17\x90UPPPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x04qWa\x04qa\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x04\x8EWa\x04\x8Ea\x13\xF8V[\x03a\x04\xFDW`\n_\x83\x83\x81Q\x81\x10a\x04\xA8Wa\x04\xA8a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x81R` \x01\x90\x81R` \x01_ _\x83\x83\x81Q\x81\x10a\x04\xD3Wa\x04\xD3a\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01Q\x81\x01Q\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\x06\nV[`\x02\x82\x82\x81Q\x81\x10a\x05\x11Wa\x05\x11a\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x05.Wa\x05.a\x13\xF8V[\x14\x80a\x05hWP`\x01\x82\x82\x81Q\x81\x10a\x05IWa\x05Ia\x14\x0CV[` \x02` \x01\x01Q`\xA0\x01Q`\x02\x81\x11\x15a\x05fWa\x05fa\x13\xF8V[\x14[\x15a\x06\nWa\x06\n\x82\x82\x81Q\x81\x10a\x05\x82Wa\x05\x82a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x05\x9FWa\x05\x9Fa\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x05\xBDWa\x05\xBDa\x14\x0CV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\x05\xDBWa\x05\xDBa\x14\x0CV[` \x02` \x01\x01Q``\x01Q\x86\x86\x81Q\x81\x10a\x05\xF9Wa\x05\xF9a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Qa\x0B\x93V[`\x01\x01a\x04UV[PPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x064Wa\x064a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x06QWa\x06Qa\x13\xF8V[\x03a\x06\x98W`\x04_\x83\x83\x81Q\x81\x10a\x06kWa\x06ka\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_ \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16\x90Ua\x07KV[`\x02\x82\x82\x81Q\x81\x10a\x06\xACWa\x06\xACa\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x06\xC9Wa\x06\xC9a\x13\xF8V[\x14\x80a\x07\x03WP`\x01\x82\x82\x81Q\x81\x10a\x06\xE4Wa\x06\xE4a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q`\x02\x81\x11\x15a\x07\x01Wa\x07\x01a\x13\xF8V[\x14[\x15a\x07KWa\x07K\x82\x82\x81Q\x81\x10a\x07\x1DWa\x07\x1Da\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x07:Wa\x07:a\x14\x0CV[` \x02` \x01\x01Q` \x01Qa\x07SV[`\x01\x01a\x06\x18V[_\x91\x82R`\x04` R`@\x90\x91 \x80T`\x01`\x01`\xA0\x1B\x03\x19\x16`\x01`\x01`\xA0\x1B\x03\x90\x92\x16\x91\x90\x91\x17\x90UV[\x80`\x06a\x07\x8D\x82\x82a\x14 V[PPPV[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\x07\xB0Wa\x07\xB0a\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x07\xCDWa\x07\xCDa\x13\xF8V[\x03a\x083W`\t_\x83\x83\x81Q\x81\x10a\x07\xE7Wa\x07\xE7a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x81R` \x01\x90\x81R` \x01_ _\x83\x83\x81Q\x81\x10a\x08\x12Wa\x08\x12a\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x81R` \x01\x90\x81R` \x01_ _\x90Ua\t\x1AV[`\x02\x82\x82\x81Q\x81\x10a\x08GWa\x08Ga\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x08dWa\x08da\x13\xF8V[\x14\x80a\x08\x9EWP`\x01\x82\x82\x81Q\x81\x10a\x08\x7FWa\x08\x7Fa\x14\x0CV[` \x02` \x01\x01Q``\x01Q`\x02\x81\x11\x15a\x08\x9CWa\x08\x9Ca\x13\xF8V[\x14[\x15a\t\x1AWa\t\x1A\x82\x82\x81Q\x81\x10a\x08\xB8Wa\x08\xB8a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\x08\xD5Wa\x08\xD5a\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\x08\xF3Wa\x08\xF3a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q_\x92\x83R`\t` \x90\x81R`@\x80\x85 \x93\x85R\x92\x90R\x91 UV[`\x01\x01a\x07\x94V[_[\x81Q\x81\x10\x15a\x06\x12W_\x82\x82\x81Q\x81\x10a\t@Wa\t@a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\t]Wa\t]a\x13\xF8V[\x03a\t\x9FW`\x08_\x83\x83\x81Q\x81\x10a\twWa\twa\x14\x0CV[` \x90\x81\x02\x91\x90\x91\x01\x81\x01QQ\x82R\x81\x01\x91\x90\x91R`@\x01_\x90\x81 \x81\x81U`\x01\x01Ua\n\x8EV[`\x02\x82\x82\x81Q\x81\x10a\t\xB3Wa\t\xB3a\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\t\xD0Wa\t\xD0a\x13\xF8V[\x14\x80a\n\nWP`\x01\x82\x82\x81Q\x81\x10a\t\xEBWa\t\xEBa\x14\x0CV[` \x02` \x01\x01Q`\x80\x01Q`\x02\x81\x11\x15a\n\x08Wa\n\x08a\x13\xF8V[\x14[\x15a\n\x8EWa\n\x8E\x82\x82\x81Q\x81\x10a\n$Wa\n$a\x14\x0CV[` \x02` \x01\x01Q_\x01Q\x83\x83\x81Q\x81\x10a\nAWa\nAa\x14\x0CV[` \x02` \x01\x01Q` \x01Q\x84\x84\x81Q\x81\x10a\n_Wa\n_a\x14\x0CV[` \x02` \x01\x01Q`@\x01Q\x85\x85\x81Q\x81\x10a\n}Wa\n}a\x14\x0CV[` \x02` \x01\x01Q``\x01Qa\n\xB5V[`\x01\x01a\t$V[`\x05\x81\x81T\x81\x10a\n\xA5W_\x80\xFD[_\x91\x82R` \x90\x91 \x01T\x90P\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x96\x87R`\x08\x90\x93R\x94 \x92Q\x83U\x92Q\x92Q\x81\x16`\x01`\x80\x1B\x02\x92\x16\x91\x90\x91\x17`\x01\x90\x91\x01UV[`\x02\x80Ta\x0B\x14\x90a\x12\xBBV[\x80`\x1F\x01` \x80\x91\x04\x02` \x01`@Q\x90\x81\x01`@R\x80\x92\x91\x90\x81\x81R` \x01\x82\x80Ta\x0B@\x90a\x12\xBBV[\x80\x15a\x0B\x8BW\x80`\x1F\x10a\x0BbWa\x01\0\x80\x83T\x04\x02\x83R\x91` \x01\x91a\x0B\x8BV[\x82\x01\x91\x90_R` _ \x90[\x81T\x81R\x90`\x01\x01\x90` \x01\x80\x83\x11a\x0BnW\x82\x90\x03`\x1F\x16\x82\x01\x91[PPPPP\x81V[`@\x80Q``\x81\x01\x82R\x93\x84R`\x01`\x01`\x80\x1B\x03\x92\x83\x16` \x80\x86\x01\x91\x82R\x92\x84\x16\x85\x83\x01\x90\x81R_\x97\x88R`\n\x84R\x82\x88 \x96\x88R\x95\x90\x92R\x90\x94 \x91Q\x82U\x92Q\x91Q\x83\x16`\x01`\x80\x1B\x02\x91\x90\x92\x16\x17`\x01\x90\x91\x01UV[cNH{q`\xE0\x1B_R`A`\x04R`$_\xFD[`@Q`\xC0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@R\x90V[`@Q``\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\x80\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\xA0\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C%Wa\x0C%a\x0B\xEEV[`@Q`\x1F\x82\x01`\x1F\x19\x16\x81\x01g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x82\x82\x10\x17\x15a\x0C\xBDWa\x0C\xBDa\x0B\xEEV[`@R\x91\x90PV[\x805`\x01`\x01`\xA0\x1B\x03\x81\x16\x81\x14a\x0C\xDBW_\x80\xFD[\x91\x90PV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x0C\xF3W_\x80\xFD[\x845\x80\x15\x15\x81\x14a\r\x02W_\x80\xFD[\x93P` \x85\x81\x015\x93P`@\x86\x015g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x80\x82\x11\x15a\r&W_\x80\xFD[\x81\x88\x01\x91P\x88`\x1F\x83\x01\x12a\r9W_\x80\xFD[\x815\x81\x81\x11\x15a\rKWa\rKa\x0B\xEEV[a\r]`\x1F\x82\x01`\x1F\x19\x16\x85\x01a\x0C\x94V[\x91P\x80\x82R\x89\x84\x82\x85\x01\x01\x11\x15a\rrW_\x80\xFD[\x80\x84\x84\x01\x85\x84\x017_\x84\x82\x84\x01\x01RP\x80\x94PPPPa\r\x94``\x86\x01a\x0C\xC5V[\x90P\x92\x95\x91\x94P\x92PV[_g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x82\x11\x15a\r\xB8Wa\r\xB8a\x0B\xEEV[P`\x05\x1B` \x01\x90V[`\x01`\x01`\x80\x1B\x03\x81\x16\x81\x14a\r\xD6W_\x80\xFD[PV[\x805`\x03\x81\x10a\x0C\xDBW_\x80\xFD[_` \x80\x83\x85\x03\x12\x15a\r\xF8W_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0E\x0EW_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0E\x1EW_\x80\xFD[\x805a\x0E1a\x0E,\x82a\r\x9FV[a\x0C\x94V[\x81\x81R`\xC0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0EOW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x0EjW_\x80\xFD[a\x0Era\x0C\x02V[\x855\x81R\x86\x86\x015\x87\x82\x01R`@\x80\x87\x015\x90\x82\x01R``\x80\x87\x015a\x0E\x97\x81a\r\xC2V[\x90\x82\x01R`\x80\x86\x81\x015a\x0E\xAA\x81a\r\xC2V[\x90\x82\x01R`\xA0a\x0E\xBB\x87\x82\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0ETV[P\x97\x96PPPPPPPV[_` \x80\x83\x85\x03\x12\x15a\x0E\xEBW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x0F\x01W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x0F\x11W_\x80\xFD[\x805a\x0F\x1Fa\x0E,\x82a\r\x9FV[\x81\x81R``\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x0F=W_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x0FXW_\x80\xFD[a\x0F`a\x0C+V[\x855\x81Ra\x0Fo\x87\x87\x01a\x0C\xC5V[\x87\x82\x01R`@a\x0F\x80\x81\x88\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x0FBV[_\x80`@\x83\x85\x03\x12\x15a\x0F\xA4W_\x80\xFD[\x825\x91Pa\x0F\xB4` \x84\x01a\x0C\xC5V[\x90P\x92P\x92\x90PV[_` \x82\x84\x03\x12\x15a\x0F\xCDW_\x80\xFD[P5\x91\x90PV[_``\x82\x84\x03\x12\x15a\x0F\xE4W_\x80\xFD[P\x91\x90PV[_` \x80\x83\x85\x03\x12\x15a\x0F\xFBW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x10\x11W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x10!W_\x80\xFD[\x805a\x10/a\x0E,\x82a\r\x9FV[\x81\x81R`\x07\x91\x90\x91\x1B\x82\x01\x83\x01\x90\x83\x81\x01\x90\x87\x83\x11\x15a\x10MW_\x80\xFD[\x92\x84\x01\x92[\x82\x84\x10\x15a\x10\xAAW`\x80\x84\x89\x03\x12\x15a\x10iW_\x80\xFD[a\x10qa\x0CNV[\x845\x81R\x85\x85\x015\x86\x82\x01R`@\x80\x86\x015\x90\x82\x01R``a\x10\x94\x81\x87\x01a\r\xD9V[\x90\x82\x01R\x82R`\x80\x93\x90\x93\x01\x92\x90\x84\x01\x90a\x10RV[\x97\x96PPPPPPPV[_\x80_``\x84\x86\x03\x12\x15a\x10\xC7W_\x80\xFD[PP\x815\x93` \x83\x015\x93P`@\x90\x92\x015\x91\x90PV[_` \x80\x83\x85\x03\x12\x15a\x10\xEFW_\x80\xFD[\x825g\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x11\x05W_\x80\xFD[\x83\x01`\x1F\x81\x01\x85\x13a\x11\x15W_\x80\xFD[\x805a\x11#a\x0E,\x82a\r\x9FV[\x81\x81R`\xA0\x91\x82\x02\x83\x01\x84\x01\x91\x84\x82\x01\x91\x90\x88\x84\x11\x15a\x11AW_\x80\xFD[\x93\x85\x01\x93[\x83\x85\x10\x15a\x0E\xCEW\x80\x85\x8A\x03\x12\x15a\x11\\W_\x80\xFD[a\x11da\x0CqV[\x855\x81R\x86\x86\x015\x87\x82\x01R`@\x80\x87\x015a\x11\x7F\x81a\r\xC2V[\x90\x82\x01R``\x86\x81\x015a\x11\x92\x81a\r\xC2V[\x90\x82\x01R`\x80a\x11\xA3\x87\x82\x01a\r\xD9V[\x90\x82\x01R\x83R\x93\x84\x01\x93\x91\x85\x01\x91a\x11FV[_\x80_\x80`\x80\x85\x87\x03\x12\x15a\x11\xC9W_\x80\xFD[\x845\x93P` \x85\x015\x92P`@\x85\x015a\x11\xE2\x81a\r\xC2V[\x91P``\x85\x015a\x11\xF2\x81a\r\xC2V[\x93\x96\x92\x95P\x90\x93PPV[_\x80`@\x83\x85\x03\x12\x15a\x12\x0EW_\x80\xFD[PP\x805\x92` \x90\x91\x015\x91PV[_` \x80\x83R\x83Q\x80` \x85\x01R_[\x81\x81\x10\x15a\x12IW\x85\x81\x01\x83\x01Q\x85\x82\x01`@\x01R\x82\x01a\x12-V[P_`@\x82\x86\x01\x01R`@`\x1F\x19`\x1F\x83\x01\x16\x85\x01\x01\x92PPP\x92\x91PPV[_\x80_\x80_`\xA0\x86\x88\x03\x12\x15a\x12}W_\x80\xFD[\x855\x94P` \x86\x015\x93P`@\x86\x015\x92P``\x86\x015a\x12\x9D\x81a\r\xC2V[\x91P`\x80\x86\x015a\x12\xAD\x81a\r\xC2V[\x80\x91PP\x92\x95P\x92\x95\x90\x93PV[`\x01\x81\x81\x1C\x90\x82\x16\x80a\x12\xCFW`\x7F\x82\x16\x91P[` \x82\x10\x81\x03a\x0F\xE4WcNH{q`\xE0\x1B_R`\"`\x04R`$_\xFD[`\x1F\x82\x11\x15a\x07\x8DW\x80_R` _ `\x1F\x84\x01`\x05\x1C\x81\x01` \x85\x10\x15a\x13\x12WP\x80[`\x1F\x84\x01`\x05\x1C\x82\x01\x91P[\x81\x81\x10\x15a\x131W_\x81U`\x01\x01a\x13\x1EV[PPPPPV[\x81Qg\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x81\x11\x15a\x13RWa\x13Ra\x0B\xEEV[a\x13f\x81a\x13`\x84Ta\x12\xBBV[\x84a\x12\xEDV[` \x80`\x1F\x83\x11`\x01\x81\x14a\x13\x99W_\x84\x15a\x13\x82WP\x85\x83\x01Q[_\x19`\x03\x86\x90\x1B\x1C\x19\x16`\x01\x85\x90\x1B\x17\x85Ua\x13\xF0V[_\x85\x81R` \x81 `\x1F\x19\x86\x16\x91[\x82\x81\x10\x15a\x13\xC7W\x88\x86\x01Q\x82U\x94\x84\x01\x94`\x01\x90\x91\x01\x90\x84\x01a\x13\xA8V[P\x85\x82\x10\x15a\x13\xE4W\x87\x85\x01Q_\x19`\x03\x88\x90\x1B`\xF8\x16\x1C\x19\x16\x81U[PP`\x01\x84`\x01\x1B\x01\x85U[PPPPPPV[cNH{q`\xE0\x1B_R`!`\x04R`$_\xFD[cNH{q`\xE0\x1B_R`2`\x04R`$_\xFD[\x815\x81U` \x82\x015a\x142\x81a\r\xC2V[`@\x83\x015a\x14@\x81a\r\xC2V[`\x01`\x01`\x80\x1B\x03\x19\x81`\x80\x1B\x16`\x01`\x01`\x80\x1B\x03\x83\x16\x17`\x01\x84\x01UPPPPV\xFE\xA2dipfsX\"\x12 h|o\xE6\xE2e\xAA\xBEb@N\x89\xB3\x9F\x88<\x05\xF4\x92+zJ\x13\x8FRs\xC7\x12\xC7B\xEFOdsolcC\0\x08\x18\x003", ); - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] pub struct MappingOperation(u8); const _: () = { @@ -355,16 +742,242 @@ pub mod Simple { } }; /**```solidity + struct LargeStruct { uint256 field1; uint128 field2; uint128 field3; } + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct LargeStruct { + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: LargeStruct) -> Self { + (value.field1, value.field2, value.field3) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for LargeStruct { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + field1: tuple.0, + field2: tuple.1, + field3: tuple.2, + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolValue for LargeStruct { + type SolType = Self; + } + #[automatically_derived] + impl alloy_sol_types::private::SolTypeValue for LargeStruct { + #[inline] + fn stv_to_tokens(&self) -> ::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field3, + ), + ) + } + #[inline] + fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encoded_size(&tuple) + } + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + ::eip712_hash_struct(self) + } + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encode_packed_to( + &tuple, out, + ) + } + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size( + &tuple, + ) + } + } + #[automatically_derived] + impl alloy_sol_types::SolType for LargeStruct { + type RustType = Self; + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SOL_NAME: &'static str = ::NAME; + const ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + as alloy_sol_types::SolType>::valid_token(token) + } + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let tuple = as alloy_sol_types::SolType>::detokenize(token); + >>::from(tuple) + } + } + #[automatically_derived] + impl alloy_sol_types::SolStruct for LargeStruct { + const NAME: &'static str = "LargeStruct"; + #[inline] + fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> { + alloy_sol_types::private::Cow::Borrowed( + "LargeStruct(uint256 field1,uint128 field2,uint128 field3)", + ) + } + #[inline] + fn eip712_components( + ) -> alloy_sol_types::private::Vec> + { + alloy_sol_types::private::Vec::new() + } + #[inline] + fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + #[inline] + fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec { + [ + as alloy_sol_types::SolType>::eip712_data_word(&self.field1) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field2) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field3) + .0, + ] + .concat() + } + } + #[automatically_derived] + impl alloy_sol_types::EventTopic for LargeStruct { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + 0usize + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field1, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field2, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field3, + ) + } + #[inline] + fn encode_topic_preimage( + rust: &Self::RustType, + out: &mut alloy_sol_types::private::Vec, + ) { + out.reserve(::topic_preimage_length(rust)); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field1, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field2, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field3, + out, + ); + } + #[inline] + fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken { + let mut out = alloy_sol_types::private::Vec::new(); + ::encode_topic_preimage(rust, &mut out); + alloy_sol_types::abi::token::WordToken(alloy_sol_types::private::keccak256(out)) + } + } + }; + /**```solidity struct MappingChange { uint256 key; address value; MappingOperation operation; } ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] pub struct MappingChange { - pub key: alloy::sol_types::private::U256, + pub key: alloy::sol_types::private::primitives::aliases::U256, pub value: alloy::sol_types::private::Address, pub operation: ::RustType, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; #[doc(hidden)] @@ -375,7 +988,7 @@ pub mod Simple { ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( - alloy::sol_types::private::U256, + alloy::sol_types::private::primitives::aliases::U256, alloy::sol_types::private::Address, ::RustType, ); @@ -555,27 +1168,2148 @@ pub mod Simple { } } }; - /**Function with signature `addToArray(uint256)` and selector `0xd15ec851`. - ```solidity - function addToArray(uint256 value) external; + /**```solidity + struct MappingOfSingleValueMappingsChange { uint256 outerKey; uint256 innerKey; uint256 value; MappingOperation operation; } ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct addToArrayCall { - pub value: alloy::sol_types::private::U256, + pub struct MappingOfSingleValueMappingsChange { + pub outerKey: alloy::sol_types::private::primitives::aliases::U256, + pub innerKey: alloy::sol_types::private::primitives::aliases::U256, + pub value: alloy::sol_types::private::primitives::aliases::U256, + pub operation: ::RustType, } - ///Container type for the return parameters of the [`addToArray(uint256)`](addToArrayCall) function. - #[allow(non_camel_case_types, non_snake_case)] - #[derive(Clone)] - pub struct addToArrayReturn {} - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + MappingOperation, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + ::RustType, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: MappingOfSingleValueMappingsChange) -> Self { + (value.outerKey, value.innerKey, value.value, value.operation) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for MappingOfSingleValueMappingsChange { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + outerKey: tuple.0, + innerKey: tuple.1, + value: tuple.2, + operation: tuple.3, + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolValue for MappingOfSingleValueMappingsChange { + type SolType = Self; + } + #[automatically_derived] + impl alloy_sol_types::private::SolTypeValue for MappingOfSingleValueMappingsChange { + #[inline] + fn stv_to_tokens(&self) -> ::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.outerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.innerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.value, + ), + ::tokenize(&self.operation), + ) + } + #[inline] + fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encoded_size(&tuple) + } + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + ::eip712_hash_struct(self) + } + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encode_packed_to( + &tuple, out, + ) + } + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size( + &tuple, + ) + } + } + #[automatically_derived] + impl alloy_sol_types::SolType for MappingOfSingleValueMappingsChange { + type RustType = Self; + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SOL_NAME: &'static str = ::NAME; + const ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + as alloy_sol_types::SolType>::valid_token(token) + } + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let tuple = as alloy_sol_types::SolType>::detokenize(token); + >>::from(tuple) + } + } + #[automatically_derived] + impl alloy_sol_types::SolStruct for MappingOfSingleValueMappingsChange { + const NAME: &'static str = "MappingOfSingleValueMappingsChange"; + #[inline] + fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> { + alloy_sol_types::private::Cow::Borrowed( + "MappingOfSingleValueMappingsChange(uint256 outerKey,uint256 innerKey,uint256 value,uint8 operation)", + ) + } + #[inline] + fn eip712_components( + ) -> alloy_sol_types::private::Vec> + { + alloy_sol_types::private::Vec::new() + } + #[inline] + fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + #[inline] + fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec { + [ + as alloy_sol_types::SolType>::eip712_data_word(&self.outerKey) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.innerKey) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.value) + .0, + ::eip712_data_word( + &self.operation, + ) + .0, + ] + .concat() + } + } + #[automatically_derived] + impl alloy_sol_types::EventTopic for MappingOfSingleValueMappingsChange { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + 0usize + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.outerKey, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.innerKey, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length(&rust.value) + + ::topic_preimage_length( + &rust.operation, + ) + } + #[inline] + fn encode_topic_preimage( + rust: &Self::RustType, + out: &mut alloy_sol_types::private::Vec, + ) { + out.reserve(::topic_preimage_length(rust)); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.outerKey, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.innerKey, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.value, + out, + ); + ::encode_topic_preimage( + &rust.operation, + out, + ); + } + #[inline] + fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken { + let mut out = alloy_sol_types::private::Vec::new(); + ::encode_topic_preimage(rust, &mut out); + alloy_sol_types::abi::token::WordToken(alloy_sol_types::private::keccak256(out)) + } + } + }; + /**```solidity + struct MappingOfStructMappingsChange { uint256 outerKey; uint256 innerKey; uint256 field1; uint128 field2; uint128 field3; MappingOperation operation; } + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct MappingOfStructMappingsChange { + pub outerKey: alloy::sol_types::private::primitives::aliases::U256, + pub innerKey: alloy::sol_types::private::primitives::aliases::U256, + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + pub operation: ::RustType, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + MappingOperation, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ::RustType, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: MappingOfStructMappingsChange) -> Self { + ( + value.outerKey, + value.innerKey, + value.field1, + value.field2, + value.field3, + value.operation, + ) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for MappingOfStructMappingsChange { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + outerKey: tuple.0, + innerKey: tuple.1, + field1: tuple.2, + field2: tuple.3, + field3: tuple.4, + operation: tuple.5, + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolValue for MappingOfStructMappingsChange { + type SolType = Self; + } + #[automatically_derived] + impl alloy_sol_types::private::SolTypeValue for MappingOfStructMappingsChange { + #[inline] + fn stv_to_tokens(&self) -> ::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.outerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.innerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field3, + ), + ::tokenize(&self.operation), + ) + } + #[inline] + fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encoded_size(&tuple) + } + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + ::eip712_hash_struct(self) + } + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encode_packed_to( + &tuple, out, + ) + } + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size( + &tuple, + ) + } + } + #[automatically_derived] + impl alloy_sol_types::SolType for MappingOfStructMappingsChange { + type RustType = Self; + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SOL_NAME: &'static str = ::NAME; + const ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + as alloy_sol_types::SolType>::valid_token(token) + } + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let tuple = as alloy_sol_types::SolType>::detokenize(token); + >>::from(tuple) + } + } + #[automatically_derived] + impl alloy_sol_types::SolStruct for MappingOfStructMappingsChange { + const NAME: &'static str = "MappingOfStructMappingsChange"; + #[inline] + fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> { + alloy_sol_types::private::Cow::Borrowed( + "MappingOfStructMappingsChange(uint256 outerKey,uint256 innerKey,uint256 field1,uint128 field2,uint128 field3,uint8 operation)", + ) + } + #[inline] + fn eip712_components( + ) -> alloy_sol_types::private::Vec> + { + alloy_sol_types::private::Vec::new() + } + #[inline] + fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + #[inline] + fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec { + [ + as alloy_sol_types::SolType>::eip712_data_word(&self.outerKey) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.innerKey) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field1) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field2) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field3) + .0, + ::eip712_data_word( + &self.operation, + ) + .0, + ] + .concat() + } + } + #[automatically_derived] + impl alloy_sol_types::EventTopic for MappingOfStructMappingsChange { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + 0usize + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.outerKey, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.innerKey, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field1, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field2, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field3, + ) + + ::topic_preimage_length( + &rust.operation, + ) + } + #[inline] + fn encode_topic_preimage( + rust: &Self::RustType, + out: &mut alloy_sol_types::private::Vec, + ) { + out.reserve(::topic_preimage_length(rust)); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.outerKey, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.innerKey, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field1, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field2, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field3, + out, + ); + ::encode_topic_preimage( + &rust.operation, + out, + ); + } + #[inline] + fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken { + let mut out = alloy_sol_types::private::Vec::new(); + ::encode_topic_preimage(rust, &mut out); + alloy_sol_types::abi::token::WordToken(alloy_sol_types::private::keccak256(out)) + } + } + }; + /**```solidity + struct MappingStructChange { uint256 key; uint256 field1; uint128 field2; uint128 field3; MappingOperation operation; } + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct MappingStructChange { + pub key: alloy::sol_types::private::primitives::aliases::U256, + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + pub operation: ::RustType, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + MappingOperation, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ::RustType, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: MappingStructChange) -> Self { + ( + value.key, + value.field1, + value.field2, + value.field3, + value.operation, + ) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for MappingStructChange { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + key: tuple.0, + field1: tuple.1, + field2: tuple.2, + field3: tuple.3, + operation: tuple.4, + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolValue for MappingStructChange { + type SolType = Self; + } + #[automatically_derived] + impl alloy_sol_types::private::SolTypeValue for MappingStructChange { + #[inline] + fn stv_to_tokens(&self) -> ::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.key, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field3, + ), + ::tokenize(&self.operation), + ) + } + #[inline] + fn stv_abi_encoded_size(&self) -> usize { + if let Some(size) = ::ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encoded_size(&tuple) + } + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + ::eip712_hash_struct(self) + } + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_encode_packed_to( + &tuple, out, + ) + } + #[inline] + fn stv_abi_packed_encoded_size(&self) -> usize { + if let Some(size) = ::PACKED_ENCODED_SIZE { + return size; + } + let tuple = + as ::core::convert::From>::from(self.clone()); + as alloy_sol_types::SolType>::abi_packed_encoded_size( + &tuple, + ) + } + } + #[automatically_derived] + impl alloy_sol_types::SolType for MappingStructChange { + type RustType = Self; + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SOL_NAME: &'static str = ::NAME; + const ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::ENCODED_SIZE; + const PACKED_ENCODED_SIZE: Option = + as alloy_sol_types::SolType>::PACKED_ENCODED_SIZE; + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + as alloy_sol_types::SolType>::valid_token(token) + } + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let tuple = as alloy_sol_types::SolType>::detokenize(token); + >>::from(tuple) + } + } + #[automatically_derived] + impl alloy_sol_types::SolStruct for MappingStructChange { + const NAME: &'static str = "MappingStructChange"; + #[inline] + fn eip712_root_type() -> alloy_sol_types::private::Cow<'static, str> { + alloy_sol_types::private::Cow::Borrowed( + "MappingStructChange(uint256 key,uint256 field1,uint128 field2,uint128 field3,uint8 operation)", + ) + } + #[inline] + fn eip712_components( + ) -> alloy_sol_types::private::Vec> + { + alloy_sol_types::private::Vec::new() + } + #[inline] + fn eip712_encode_type() -> alloy_sol_types::private::Cow<'static, str> { + ::eip712_root_type() + } + #[inline] + fn eip712_encode_data(&self) -> alloy_sol_types::private::Vec { + [ + as alloy_sol_types::SolType>::eip712_data_word(&self.key) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field1) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field2) + .0, + as alloy_sol_types::SolType>::eip712_data_word(&self.field3) + .0, + ::eip712_data_word( + &self.operation, + ) + .0, + ] + .concat() + } + } + #[automatically_derived] + impl alloy_sol_types::EventTopic for MappingStructChange { + #[inline] + fn topic_preimage_length(rust: &Self::RustType) -> usize { + 0usize + + as alloy_sol_types::EventTopic>::topic_preimage_length(&rust.key) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field1, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field2, + ) + + as alloy_sol_types::EventTopic>::topic_preimage_length( + &rust.field3, + ) + + ::topic_preimage_length( + &rust.operation, + ) + } + #[inline] + fn encode_topic_preimage( + rust: &Self::RustType, + out: &mut alloy_sol_types::private::Vec, + ) { + out.reserve(::topic_preimage_length(rust)); + as alloy_sol_types::EventTopic>::encode_topic_preimage(&rust.key, out); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field1, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field2, + out, + ); + as alloy_sol_types::EventTopic>::encode_topic_preimage( + &rust.field3, + out, + ); + ::encode_topic_preimage( + &rust.operation, + out, + ); + } + #[inline] + fn encode_topic(rust: &Self::RustType) -> alloy_sol_types::abi::token::WordToken { + let mut out = alloy_sol_types::private::Vec::new(); + ::encode_topic_preimage(rust, &mut out); + alloy_sol_types::abi::token::WordToken(alloy_sol_types::private::keccak256(out)) + } + } + }; + /**Function with signature `addToArray(uint256)` and selector `0xd15ec851`. + ```solidity + function addToArray(uint256 value) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct addToArrayCall { + pub value: alloy::sol_types::private::primitives::aliases::U256, + } + ///Container type for the return parameters of the [`addToArray(uint256)`](addToArrayCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct addToArrayReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: addToArrayCall) -> Self { + (value.value,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for addToArrayCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { value: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: addToArrayReturn) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for addToArrayReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for addToArrayCall { + type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = addToArrayReturn; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "addToArray(uint256)"; + const SELECTOR: [u8; 4] = [209u8, 94u8, 200u8, 81u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self.value, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `arr1(uint256)` and selector `0x6987b1fb`. + ```solidity + function arr1(uint256) external view returns (uint256); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct arr1Call { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + ///Container type for the return parameters of the [`arr1(uint256)`](arr1Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct arr1Return { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: arr1Call) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for arr1Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: arr1Return) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for arr1Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for arr1Call { + type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = arr1Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "arr1(uint256)"; + const SELECTOR: [u8; 4] = [105u8, 135u8, 177u8, 251u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._0, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `changeMapping((uint256,uint256,uint256,uint128,uint128,uint8)[])` and selector `0x02e3003a`. + ```solidity + function changeMapping(MappingOfStructMappingsChange[] memory changes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_0Call { + pub changes: alloy::sol_types::private::Vec< + ::RustType, + >, + } + ///Container type for the return parameters of the [`changeMapping((uint256,uint256,uint256,uint128,uint128,uint8)[])`](changeMapping_0Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_0Return {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = + (alloy::sol_types::sol_data::Array,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec< + ::RustType, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_0Call) -> Self { + (value.changes,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_0Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { changes: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_0Return) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_0Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for changeMapping_0Call { + type Parameters<'a> = + (alloy::sol_types::sol_data::Array,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = changeMapping_0Return; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = + "changeMapping((uint256,uint256,uint256,uint128,uint128,uint8)[])"; + const SELECTOR: [u8; 4] = [2u8, 227u8, 0u8, 58u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( as alloy_sol_types::SolType>::tokenize( + &self.changes + ),) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `changeMapping((uint256,address,uint8)[])` and selector `0x0c1616c9`. + ```solidity + function changeMapping(MappingChange[] memory changes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_1Call { + pub changes: + alloy::sol_types::private::Vec<::RustType>, + } + ///Container type for the return parameters of the [`changeMapping((uint256,address,uint8)[])`](changeMapping_1Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_1Return {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Array,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec< + ::RustType, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_1Call) -> Self { + (value.changes,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_1Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { changes: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_1Return) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_1Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for changeMapping_1Call { + type Parameters<'a> = (alloy::sol_types::sol_data::Array,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = changeMapping_1Return; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "changeMapping((uint256,address,uint8)[])"; + const SELECTOR: [u8; 4] = [12u8, 22u8, 22u8, 201u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize(&self.changes), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `changeMapping((uint256,uint256,uint256,uint8)[])` and selector `0x3e9060c7`. + ```solidity + function changeMapping(MappingOfSingleValueMappingsChange[] memory changes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_2Call { + pub changes: alloy::sol_types::private::Vec< + ::RustType, + >, + } + ///Container type for the return parameters of the [`changeMapping((uint256,uint256,uint256,uint8)[])`](changeMapping_2Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_2Return {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = + (alloy::sol_types::sol_data::Array,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec< + ::RustType, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_2Call) -> Self { + (value.changes,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_2Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { changes: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_2Return) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_2Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for changeMapping_2Call { + type Parameters<'a> = + (alloy::sol_types::sol_data::Array,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = changeMapping_2Return; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "changeMapping((uint256,uint256,uint256,uint8)[])"; + const SELECTOR: [u8; 4] = [62u8, 144u8, 96u8, 199u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( as alloy_sol_types::SolType>::tokenize( + &self.changes + ),) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `changeMapping((uint256,uint256,uint128,uint128,uint8)[])` and selector `0x51976fc8`. + ```solidity + function changeMapping(MappingStructChange[] memory changes) external; + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_3Call { + pub changes: alloy::sol_types::private::Vec< + ::RustType, + >, + } + ///Container type for the return parameters of the [`changeMapping((uint256,uint256,uint128,uint128,uint8)[])`](changeMapping_3Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct changeMapping_3Return {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Array,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::Vec< + ::RustType, + >, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_3Call) -> Self { + (value.changes,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_3Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { changes: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: changeMapping_3Return) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for changeMapping_3Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for changeMapping_3Call { + type Parameters<'a> = (alloy::sol_types::sol_data::Array,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = changeMapping_3Return; + type ReturnTuple<'a> = (); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = + "changeMapping((uint256,uint256,uint128,uint128,uint8)[])"; + const SELECTOR: [u8; 4] = [81u8, 151u8, 111u8, 200u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize(&self.changes), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `m1(uint256)` and selector `0x2ae42686`. + ```solidity + function m1(uint256) external view returns (address); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct m1Call { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + ///Container type for the return parameters of the [`m1(uint256)`](m1Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct m1Return { + pub _0: alloy::sol_types::private::Address, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: m1Call) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for m1Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: m1Return) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for m1Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for m1Call { + type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = m1Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Address,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "m1(uint256)"; + const SELECTOR: [u8; 4] = [42u8, 228u8, 38u8, 134u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._0, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `mappingOfSingleValueMappings(uint256,uint256)` and selector `0x96dc9a41`. + ```solidity + function mappingOfSingleValueMappings(uint256, uint256) external view returns (uint256); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct mappingOfSingleValueMappingsCall { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + pub _1: alloy::sol_types::private::primitives::aliases::U256, + } + ///Container type for the return parameters of the [`mappingOfSingleValueMappings(uint256,uint256)`](mappingOfSingleValueMappingsCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct mappingOfSingleValueMappingsReturn { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: mappingOfSingleValueMappingsCall) -> Self { + (value._0, value._1) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for mappingOfSingleValueMappingsCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + _0: tuple.0, + _1: tuple.1, + } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: mappingOfSingleValueMappingsReturn) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for mappingOfSingleValueMappingsReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for mappingOfSingleValueMappingsCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = mappingOfSingleValueMappingsReturn; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "mappingOfSingleValueMappings(uint256,uint256)"; + const SELECTOR: [u8; 4] = [150u8, 220u8, 154u8, 65u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._0, + ), + as alloy_sol_types::SolType>::tokenize( + &self._1, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `mappingOfStructMappings(uint256,uint256)` and selector `0x85b6489f`. + ```solidity + function mappingOfStructMappings(uint256, uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct mappingOfStructMappingsCall { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + pub _1: alloy::sol_types::private::primitives::aliases::U256, + } + ///Container type for the return parameters of the [`mappingOfStructMappings(uint256,uint256)`](mappingOfStructMappingsCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct mappingOfStructMappingsReturn { + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: mappingOfStructMappingsCall) -> Self { + (value._0, value._1) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for mappingOfStructMappingsCall { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + _0: tuple.0, + _1: tuple.1, + } + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: mappingOfStructMappingsReturn) -> Self { + (value.field1, value.field2, value.field3) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for mappingOfStructMappingsReturn { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { + field1: tuple.0, + field2: tuple.1, + field3: tuple.2, + } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for mappingOfStructMappingsCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = mappingOfStructMappingsReturn; + type ReturnTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "mappingOfStructMappings(uint256,uint256)"; + const SELECTOR: [u8; 4] = [133u8, 182u8, 72u8, 159u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + ( + as alloy_sol_types::SolType>::tokenize( + &self._0, + ), + as alloy_sol_types::SolType>::tokenize( + &self._1, + ), + ) + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `s1()` and selector `0x6cc014de`. + ```solidity + function s1() external view returns (bool); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s1Call {} + ///Container type for the return parameters of the [`s1()`](s1Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s1Return { + pub _0: bool, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s1Call) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for s1Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Bool,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (bool,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s1Return) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for s1Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for s1Call { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = s1Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Bool,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "s1()"; + const SELECTOR: [u8; 4] = [108u8, 192u8, 20u8, 222u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `s2()` and selector `0xa314150f`. + ```solidity + function s2() external view returns (uint256); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s2Call {} + ///Container type for the return parameters of the [`s2()`](s2Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s2Return { + pub _0: alloy::sol_types::private::primitives::aliases::U256, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] + const _: () = { + use alloy::sol_types as alloy_sol_types; + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s2Call) -> Self { + () + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for s2Call { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self {} + } + } + } + { + #[doc(hidden)] + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + #[doc(hidden)] + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); + #[cfg(test)] + #[allow(dead_code, unreachable_patterns)] + fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { + match _t { + alloy_sol_types::private::AssertTypeEq::< + ::RustType, + >(_) => {} + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s2Return) -> Self { + (value._0,) + } + } + #[automatically_derived] + #[doc(hidden)] + impl ::core::convert::From> for s2Return { + fn from(tuple: UnderlyingRustTuple<'_>) -> Self { + Self { _0: tuple.0 } + } + } + } + #[automatically_derived] + impl alloy_sol_types::SolCall for s2Call { + type Parameters<'a> = (); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = s2Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; + const SIGNATURE: &'static str = "s2()"; + const SELECTOR: [u8; 4] = [163u8, 20u8, 21u8, 15u8]; + #[inline] + fn new<'a>( + tuple: as alloy_sol_types::SolType>::RustType, + ) -> Self { + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () + } + #[inline] + fn abi_decode_returns( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + as alloy_sol_types::SolType>::abi_decode_sequence( + data, validate, + ) + .map(Into::into) + } + } + }; + /**Function with signature `s3()` and selector `0xa5d666a9`. + ```solidity + function s3() external view returns (string memory); + ```*/ + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s3Call {} + ///Container type for the return parameters of the [`s3()`](s3Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct s3Return { + pub _0: alloy::sol_types::private::String, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -587,24 +3321,24 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: addToArrayCall) -> Self { - (value.value,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s3Call) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for addToArrayCall { + impl ::core::convert::From> for s3Call { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { value: tuple.0 } + Self {} } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::String,); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::String,); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -616,28 +3350,28 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: addToArrayReturn) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s3Return) -> Self { + (value._0,) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for addToArrayReturn { + impl ::core::convert::From> for s3Return { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { _0: tuple.0 } } } } #[automatically_derived] - impl alloy_sol_types::SolCall for addToArrayCall { - type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + impl alloy_sol_types::SolCall for s3Call { + type Parameters<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = addToArrayReturn; - type ReturnTuple<'a> = (); + type Return = s3Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::String,); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "addToArray(uint256)"; - const SELECTOR: [u8; 4] = [209u8, 94u8, 200u8, 81u8]; + const SIGNATURE: &'static str = "s3()"; + const SELECTOR: [u8; 4] = [165u8, 214u8, 102u8, 169u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -646,11 +3380,7 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - ( - as alloy_sol_types::SolType>::tokenize( - &self.value, - ), - ) + () } #[inline] fn abi_decode_returns( @@ -664,29 +3394,32 @@ pub mod Simple { } } }; - /**Function with signature `arr1(uint256)` and selector `0x6987b1fb`. + /**Function with signature `s4()` and selector `0xc8af3aa6`. ```solidity - function arr1(uint256) external view returns (uint256); + function s4() external view returns (address); ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct arr1Call { - pub _0: alloy::sol_types::private::U256, - } - ///Container type for the return parameters of the [`arr1(uint256)`](arr1Call) function. - #[allow(non_camel_case_types, non_snake_case)] + pub struct s4Call {} + ///Container type for the return parameters of the [`s4()`](s4Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct arr1Return { - pub _0: alloy::sol_types::private::U256, + pub struct s4Return { + pub _0: alloy::sol_types::private::Address, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -698,24 +3431,24 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: arr1Call) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s4Call) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for arr1Call { + impl ::core::convert::From> for s4Call { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -727,28 +3460,28 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: arr1Return) -> Self { + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: s4Return) -> Self { (value._0,) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for arr1Return { + impl ::core::convert::From> for s4Return { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { Self { _0: tuple.0 } } } } #[automatically_derived] - impl alloy_sol_types::SolCall for arr1Call { - type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + impl alloy_sol_types::SolCall for s4Call { + type Parameters<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = arr1Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Return = s4Return; + type ReturnTuple<'a> = (alloy::sol_types::sol_data::Address,); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "arr1(uint256)"; - const SELECTOR: [u8; 4] = [105u8, 135u8, 177u8, 251u8]; + const SIGNATURE: &'static str = "s4()"; + const SELECTOR: [u8; 4] = [200u8, 175u8, 58u8, 166u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -757,11 +3490,7 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - ( - as alloy_sol_types::SolType>::tokenize( - &self._0, - ), - ) + () } #[inline] fn abi_decode_returns( @@ -775,31 +3504,38 @@ pub mod Simple { } } }; - /**Function with signature `changeMapping((uint256,address,uint8)[])` and selector `0x0c1616c9`. + /**Function with signature `setMapping(uint256,address)` and selector `0x1c134315`. ```solidity - function changeMapping(MappingChange[] memory changes) external; + function setMapping(uint256 key, address value) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct changeMappingCall { - pub changes: - alloy::sol_types::private::Vec<::RustType>, + pub struct setMappingCall { + pub key: alloy::sol_types::private::primitives::aliases::U256, + pub value: alloy::sol_types::private::Address, } - ///Container type for the return parameters of the [`changeMapping((uint256,address,uint8)[])`](changeMappingCall) function. - #[allow(non_camel_case_types, non_snake_case)] + ///Container type for the return parameters of the [`setMapping(uint256,address)`](setMappingCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct changeMappingReturn {} - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct setMappingReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Array,); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Address, + ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( - alloy::sol_types::private::Vec< - ::RustType, - >, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::Address, ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] @@ -812,16 +3548,19 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: changeMappingCall) -> Self { - (value.changes,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingCall) -> Self { + (value.key, value.value) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for changeMappingCall { + impl ::core::convert::From> for setMappingCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { changes: tuple.0 } + Self { + key: tuple.0, + value: tuple.1, + } } } } @@ -841,28 +3580,31 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: changeMappingReturn) -> Self { + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingReturn) -> Self { () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for changeMappingReturn { + impl ::core::convert::From> for setMappingReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for changeMappingCall { - type Parameters<'a> = (alloy::sol_types::sol_data::Array,); + impl alloy_sol_types::SolCall for setMappingCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Address, + ); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = changeMappingReturn; + type Return = setMappingReturn; type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "changeMapping((uint256,address,uint8)[])"; - const SELECTOR: [u8; 4] = [12u8, 22u8, 22u8, 201u8]; + const SIGNATURE: &'static str = "setMapping(uint256,address)"; + const SELECTOR: [u8; 4] = [28u8, 19u8, 67u8, 21u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -872,9 +3614,12 @@ pub mod Simple { #[inline] fn tokenize(&self) -> Self::Token<'_> { ( - as alloy_sol_types::SolType>::tokenize(&self.changes), + as alloy_sol_types::SolType>::tokenize( + &self.key, + ), + ::tokenize( + &self.value, + ), ) } #[inline] @@ -889,29 +3634,42 @@ pub mod Simple { } } }; - /**Function with signature `m1(uint256)` and selector `0x2ae42686`. + /**Function with signature `setMappingOfSingleValueMappings(uint256,uint256,uint256)` and selector `0x4cf5a94a`. ```solidity - function m1(uint256) external view returns (address); + function setMappingOfSingleValueMappings(uint256 outerKey, uint256 innerKey, uint256 value) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct m1Call { - pub _0: alloy::sol_types::private::U256, + pub struct setMappingOfSingleValueMappingsCall { + pub outerKey: alloy::sol_types::private::primitives::aliases::U256, + pub innerKey: alloy::sol_types::private::primitives::aliases::U256, + pub value: alloy::sol_types::private::primitives::aliases::U256, } - ///Container type for the return parameters of the [`m1(uint256)`](m1Call) function. - #[allow(non_camel_case_types, non_snake_case)] + ///Container type for the return parameters of the [`setMappingOfSingleValueMappings(uint256,uint256,uint256)`](setMappingOfSingleValueMappingsCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct m1Return { - pub _0: alloy::sol_types::private::Address, - } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct setMappingOfSingleValueMappingsReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -923,24 +3681,28 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: m1Call) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingOfSingleValueMappingsCall) -> Self { + (value.outerKey, value.innerKey, value.value) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for m1Call { + impl ::core::convert::From> for setMappingOfSingleValueMappingsCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self { + outerKey: tuple.0, + innerKey: tuple.1, + value: tuple.2, + } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -952,28 +3714,33 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: m1Return) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingOfSingleValueMappingsReturn) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for m1Return { + impl ::core::convert::From> for setMappingOfSingleValueMappingsReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for m1Call { - type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + impl alloy_sol_types::SolCall for setMappingOfSingleValueMappingsCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + ); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = m1Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::Address,); + type Return = setMappingOfSingleValueMappingsReturn; + type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "m1(uint256)"; - const SELECTOR: [u8; 4] = [42u8, 228u8, 38u8, 134u8]; + const SIGNATURE: &'static str = + "setMappingOfSingleValueMappings(uint256,uint256,uint256)"; + const SELECTOR: [u8; 4] = [76u8, 245u8, 169u8, 74u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -984,7 +3751,13 @@ pub mod Simple { fn tokenize(&self) -> Self::Token<'_> { ( as alloy_sol_types::SolType>::tokenize( - &self._0, + &self.outerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.innerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.value, ), ) } @@ -1000,27 +3773,48 @@ pub mod Simple { } } }; - /**Function with signature `s1()` and selector `0x6cc014de`. + /**Function with signature `setMappingOfStructMappings(uint256,uint256,uint256,uint128,uint128)` and selector `0xc6a7f0fe`. ```solidity - function s1() external view returns (bool); + function setMappingOfStructMappings(uint256 outerKey, uint256 innerKey, uint256 field1, uint128 field2, uint128 field3) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct s1Call {} - ///Container type for the return parameters of the [`s1()`](s1Call) function. - #[allow(non_camel_case_types, non_snake_case)] - #[derive(Clone)] - pub struct s1Return { - pub _0: bool, + pub struct setMappingOfStructMappingsCall { + pub outerKey: alloy::sol_types::private::primitives::aliases::U256, + pub innerKey: alloy::sol_types::private::primitives::aliases::U256, + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + ///Container type for the return parameters of the [`setMappingOfStructMappings(uint256,uint256,uint256,uint128,uint128)`](setMappingOfStructMappingsCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct setMappingOfStructMappingsReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1032,24 +3826,36 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s1Call) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingOfStructMappingsCall) -> Self { + ( + value.outerKey, + value.innerKey, + value.field1, + value.field2, + value.field3, + ) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s1Call { + impl ::core::convert::From> for setMappingOfStructMappingsCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { + outerKey: tuple.0, + innerKey: tuple.1, + field1: tuple.2, + field2: tuple.3, + field3: tuple.4, + } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Bool,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (bool,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1061,28 +3867,35 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s1Return) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingOfStructMappingsReturn) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s1Return { + impl ::core::convert::From> for setMappingOfStructMappingsReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for s1Call { - type Parameters<'a> = (); + impl alloy_sol_types::SolCall for setMappingOfStructMappingsCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = s1Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::Bool,); + type Return = setMappingOfStructMappingsReturn; + type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "s1()"; - const SELECTOR: [u8; 4] = [108u8, 192u8, 20u8, 222u8]; + const SIGNATURE: &'static str = + "setMappingOfStructMappings(uint256,uint256,uint256,uint128,uint128)"; + const SELECTOR: [u8; 4] = [198u8, 167u8, 240u8, 254u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1091,7 +3904,23 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - () + ( + as alloy_sol_types::SolType>::tokenize( + &self.outerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.innerKey, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self.field3, + ), + ) } #[inline] fn abi_decode_returns( @@ -1105,27 +3934,45 @@ pub mod Simple { } } }; - /**Function with signature `s2()` and selector `0xa314150f`. + /**Function with signature `setMappingStruct(uint256,uint256,uint128,uint128)` and selector `0x8026de31`. ```solidity - function s2() external view returns (uint256); + function setMappingStruct(uint256 _key, uint256 _field1, uint128 _field2, uint128 _field3) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] - #[derive(Clone)] - pub struct s2Call {} - ///Container type for the return parameters of the [`s2()`](s2Call) function. - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct s2Return { - pub _0: alloy::sol_types::private::U256, + pub struct setMappingStructCall { + pub _key: alloy::sol_types::private::primitives::aliases::U256, + pub _field1: alloy::sol_types::private::primitives::aliases::U256, + pub _field2: u128, + pub _field3: u128, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + ///Container type for the return parameters of the [`setMappingStruct(uint256,uint256,uint128,uint128)`](setMappingStructCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct setMappingStructReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1137,24 +3984,29 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s2Call) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingStructCall) -> Self { + (value._key, value._field1, value._field2, value._field3) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s2Call { + impl ::core::convert::From> for setMappingStructCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { + _key: tuple.0, + _field1: tuple.1, + _field2: tuple.2, + _field3: tuple.3, + } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1166,28 +4018,33 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s2Return) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setMappingStructReturn) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s2Return { + impl ::core::convert::From> for setMappingStructReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for s2Call { - type Parameters<'a> = (); + impl alloy_sol_types::SolCall for setMappingStructCall { + type Parameters<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = s2Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Return = setMappingStructReturn; + type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "s2()"; - const SELECTOR: [u8; 4] = [163u8, 20u8, 21u8, 15u8]; + const SIGNATURE: &'static str = "setMappingStruct(uint256,uint256,uint128,uint128)"; + const SELECTOR: [u8; 4] = [128u8, 38u8, 222u8, 49u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1196,7 +4053,20 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - () + ( + as alloy_sol_types::SolType>::tokenize( + &self._key, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field1, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field2, + ), + as alloy_sol_types::SolType>::tokenize( + &self._field3, + ), + ) } #[inline] fn abi_decode_returns( @@ -1210,27 +4080,32 @@ pub mod Simple { } } }; - /**Function with signature `s3()` and selector `0xa5d666a9`. + /**Function with signature `setS2(uint256)` and selector `0xf25d54f5`. ```solidity - function s3() external view returns (string memory); + function setS2(uint256 newS2) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] - #[derive(Clone)] - pub struct s3Call {} - ///Container type for the return parameters of the [`s3()`](s3Call) function. - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct s3Return { - pub _0: alloy::sol_types::private::String, + pub struct setS2Call { + pub newS2: alloy::sol_types::private::primitives::aliases::U256, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + ///Container type for the return parameters of the [`setS2(uint256)`](setS2Call) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct setS2Return {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1242,24 +4117,24 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s3Call) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setS2Call) -> Self { + (value.newS2,) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s3Call { + impl ::core::convert::From> for setS2Call { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { newS2: tuple.0 } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::String,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::String,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1271,28 +4146,28 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s3Return) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setS2Return) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s3Return { + impl ::core::convert::From> for setS2Return { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for s3Call { - type Parameters<'a> = (); + impl alloy_sol_types::SolCall for setS2Call { + type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = s3Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::String,); + type Return = setS2Return; + type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "s3()"; - const SELECTOR: [u8; 4] = [165u8, 214u8, 102u8, 169u8]; + const SIGNATURE: &'static str = "setS2(uint256)"; + const SELECTOR: [u8; 4] = [242u8, 93u8, 84u8, 245u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1301,7 +4176,11 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - () + ( + as alloy_sol_types::SolType>::tokenize( + &self.newS2, + ), + ) } #[inline] fn abi_decode_returns( @@ -1315,27 +4194,32 @@ pub mod Simple { } } }; - /**Function with signature `s4()` and selector `0xc8af3aa6`. + /**Function with signature `setSimpleStruct((uint256,uint128,uint128))` and selector `0x3e70166e`. ```solidity - function s4() external view returns (address); + function setSimpleStruct(LargeStruct memory input) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] - #[derive(Clone)] - pub struct s4Call {} - ///Container type for the return parameters of the [`s4()`](s4Call) function. - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct s4Return { - pub _0: alloy::sol_types::private::Address, + pub struct setSimpleStructCall { + pub input: ::RustType, } - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + ///Container type for the return parameters of the [`setSimpleStruct((uint256,uint128,uint128))`](setSimpleStructCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] + #[derive(Clone)] + pub struct setSimpleStructReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = (LargeStruct,); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = (::RustType,); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1347,24 +4231,24 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s4Call) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimpleStructCall) -> Self { + (value.input,) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s4Call { + impl ::core::convert::From> for setSimpleStructCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { input: tuple.0 } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Address,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::Address,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1376,28 +4260,28 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: s4Return) -> Self { - (value._0,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimpleStructReturn) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for s4Return { + impl ::core::convert::From> for setSimpleStructReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { _0: tuple.0 } + Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for s4Call { - type Parameters<'a> = (); + impl alloy_sol_types::SolCall for setSimpleStructCall { + type Parameters<'a> = (LargeStruct,); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = s4Return; - type ReturnTuple<'a> = (alloy::sol_types::sol_data::Address,); + type Return = setSimpleStructReturn; + type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "s4()"; - const SELECTOR: [u8; 4] = [200u8, 175u8, 58u8, 166u8]; + const SIGNATURE: &'static str = "setSimpleStruct((uint256,uint128,uint128))"; + const SELECTOR: [u8; 4] = [62u8, 112u8, 22u8, 110u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1406,7 +4290,9 @@ pub mod Simple { } #[inline] fn tokenize(&self) -> Self::Token<'_> { - () + (::tokenize( + &self.input, + ),) } #[inline] fn abi_decode_returns( @@ -1420,32 +4306,43 @@ pub mod Simple { } } }; - /**Function with signature `setMapping(uint256,address)` and selector `0x1c134315`. + /**Function with signature `setSimples(bool,uint256,string,address)` and selector `0x0200225c`. ```solidity - function setMapping(uint256 key, address value) external; + function setSimples(bool newS1, uint256 newS2, string memory newS3, address newS4) external; ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setMappingCall { - pub key: alloy::sol_types::private::U256, - pub value: alloy::sol_types::private::Address, + pub struct setSimplesCall { + pub newS1: bool, + pub newS2: alloy::sol_types::private::primitives::aliases::U256, + pub newS3: alloy::sol_types::private::String, + pub newS4: alloy::sol_types::private::Address, } - ///Container type for the return parameters of the [`setMapping(uint256,address)`](setMappingCall) function. - #[allow(non_camel_case_types, non_snake_case)] + ///Container type for the return parameters of the [`setSimples(bool,uint256,string,address)`](setSimplesCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setMappingReturn {} - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct setSimplesReturn {} + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Bool, alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::String, alloy::sol_types::sol_data::Address, ); #[doc(hidden)] type UnderlyingRustTuple<'a> = ( - alloy::sol_types::private::U256, + bool, + alloy::sol_types::private::primitives::aliases::U256, + alloy::sol_types::private::String, alloy::sol_types::private::Address, ); #[cfg(test)] @@ -1459,18 +4356,20 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setMappingCall) -> Self { - (value.key, value.value) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimplesCall) -> Self { + (value.newS1, value.newS2, value.newS3, value.newS4) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setMappingCall { + impl ::core::convert::From> for setSimplesCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { Self { - key: tuple.0, - value: tuple.1, + newS1: tuple.0, + newS2: tuple.1, + newS3: tuple.2, + newS4: tuple.3, } } } @@ -1491,31 +4390,33 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setMappingReturn) -> Self { + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: setSimplesReturn) -> Self { () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setMappingReturn { + impl ::core::convert::From> for setSimplesReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { Self {} } } } #[automatically_derived] - impl alloy_sol_types::SolCall for setMappingCall { + impl alloy_sol_types::SolCall for setSimplesCall { type Parameters<'a> = ( + alloy::sol_types::sol_data::Bool, alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::String, alloy::sol_types::sol_data::Address, ); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = setMappingReturn; + type Return = setSimplesReturn; type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "setMapping(uint256,address)"; - const SELECTOR: [u8; 4] = [28u8, 19u8, 67u8, 21u8]; + const SIGNATURE: &'static str = "setSimples(bool,uint256,string,address)"; + const SELECTOR: [u8; 4] = [2u8, 0u8, 34u8, 92u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1525,11 +4426,17 @@ pub mod Simple { #[inline] fn tokenize(&self) -> Self::Token<'_> { ( + ::tokenize( + &self.newS1, + ), as alloy_sol_types::SolType>::tokenize( - &self.key, + &self.newS2, + ), + ::tokenize( + &self.newS3, ), ::tokenize( - &self.value, + &self.newS4, ), ) } @@ -1545,27 +4452,34 @@ pub mod Simple { } } }; - /**Function with signature `setS2(uint256)` and selector `0xf25d54f5`. + /**Function with signature `simpleStruct()` and selector `0xead18400`. ```solidity - function setS2(uint256 newS2) external; + function simpleStruct() external view returns (uint256 field1, uint128 field2, uint128 field3); ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setS2Call { - pub newS2: alloy::sol_types::private::U256, - } - ///Container type for the return parameters of the [`setS2(uint256)`](setS2Call) function. - #[allow(non_camel_case_types, non_snake_case)] + pub struct simpleStructCall {} + ///Container type for the return parameters of the [`simpleStruct()`](simpleStructCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setS2Return {} - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct simpleStructReturn { + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type UnderlyingSolTuple<'a> = (); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (alloy::sol_types::private::U256,); + type UnderlyingRustTuple<'a> = (); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1577,24 +4491,32 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setS2Call) -> Self { - (value.newS2,) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: simpleStructCall) -> Self { + () } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setS2Call { + impl ::core::convert::From> for simpleStructCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { newS2: tuple.0 } + Self {} } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1606,41 +4528,45 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setS2Return) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: simpleStructReturn) -> Self { + (value.field1, value.field2, value.field3) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setS2Return { + impl ::core::convert::From> for simpleStructReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { + field1: tuple.0, + field2: tuple.1, + field3: tuple.2, + } } } } #[automatically_derived] - impl alloy_sol_types::SolCall for setS2Call { - type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + impl alloy_sol_types::SolCall for simpleStructCall { + type Parameters<'a> = (); type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = setS2Return; - type ReturnTuple<'a> = (); + type Return = simpleStructReturn; + type ReturnTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "setS2(uint256)"; - const SELECTOR: [u8; 4] = [242u8, 93u8, 84u8, 245u8]; + const SIGNATURE: &'static str = "simpleStruct()"; + const SELECTOR: [u8; 4] = [234u8, 209u8, 132u8, 0u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, ) -> Self { - tuple.into() - } - #[inline] - fn tokenize(&self) -> Self::Token<'_> { - ( - as alloy_sol_types::SolType>::tokenize( - &self.newS2, - ), - ) + tuple.into() + } + #[inline] + fn tokenize(&self) -> Self::Token<'_> { + () } #[inline] fn abi_decode_returns( @@ -1654,40 +4580,36 @@ pub mod Simple { } } }; - /**Function with signature `setSimples(bool,uint256,string,address)` and selector `0x0200225c`. + /**Function with signature `structMapping(uint256)` and selector `0x88dfddc6`. ```solidity - function setSimples(bool newS1, uint256 newS2, string memory newS3, address newS4) external; + function structMapping(uint256) external view returns (uint256 field1, uint128 field2, uint128 field3); ```*/ - #[allow(non_camel_case_types, non_snake_case)] + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setSimplesCall { - pub newS1: bool, - pub newS2: alloy::sol_types::private::U256, - pub newS3: alloy::sol_types::private::String, - pub newS4: alloy::sol_types::private::Address, + pub struct structMappingCall { + pub _0: alloy::sol_types::private::primitives::aliases::U256, } - ///Container type for the return parameters of the [`setSimples(bool,uint256,string,address)`](setSimplesCall) function. - #[allow(non_camel_case_types, non_snake_case)] + ///Container type for the return parameters of the [`structMapping(uint256)`](structMappingCall) function. + #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields)] #[derive(Clone)] - pub struct setSimplesReturn {} - #[allow(non_camel_case_types, non_snake_case, clippy::style)] + pub struct structMappingReturn { + pub field1: alloy::sol_types::private::primitives::aliases::U256, + pub field2: u128, + pub field3: u128, + } + #[allow( + non_camel_case_types, + non_snake_case, + clippy::pub_underscore_fields, + clippy::style + )] const _: () = { use alloy::sol_types as alloy_sol_types; { #[doc(hidden)] - type UnderlyingSolTuple<'a> = ( - alloy::sol_types::sol_data::Bool, - alloy::sol_types::sol_data::Uint<256>, - alloy::sol_types::sol_data::String, - alloy::sol_types::sol_data::Address, - ); + type UnderlyingSolTuple<'a> = (alloy::sol_types::sol_data::Uint<256>,); #[doc(hidden)] - type UnderlyingRustTuple<'a> = ( - bool, - alloy::sol_types::private::U256, - alloy::sol_types::private::String, - alloy::sol_types::private::Address, - ); + type UnderlyingRustTuple<'a> = (alloy::sol_types::private::primitives::aliases::U256,); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1699,29 +4621,32 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setSimplesCall) -> Self { - (value.newS1, value.newS2, value.newS3, value.newS4) + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: structMappingCall) -> Self { + (value._0,) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setSimplesCall { + impl ::core::convert::From> for structMappingCall { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self { - newS1: tuple.0, - newS2: tuple.1, - newS3: tuple.2, - newS4: tuple.3, - } + Self { _0: tuple.0 } } } } { #[doc(hidden)] - type UnderlyingSolTuple<'a> = (); + type UnderlyingSolTuple<'a> = ( + alloy::sol_types::sol_data::Uint<256>, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, + ); #[doc(hidden)] - type UnderlyingRustTuple<'a> = (); + type UnderlyingRustTuple<'a> = ( + alloy::sol_types::private::primitives::aliases::U256, + u128, + u128, + ); #[cfg(test)] #[allow(dead_code, unreachable_patterns)] fn _type_assertion(_t: alloy_sol_types::private::AssertTypeEq) { @@ -1733,33 +4658,36 @@ pub mod Simple { } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From for UnderlyingRustTuple<'_> { - fn from(value: setSimplesReturn) -> Self { - () + impl ::core::convert::From for UnderlyingRustTuple<'_> { + fn from(value: structMappingReturn) -> Self { + (value.field1, value.field2, value.field3) } } #[automatically_derived] #[doc(hidden)] - impl ::core::convert::From> for setSimplesReturn { + impl ::core::convert::From> for structMappingReturn { fn from(tuple: UnderlyingRustTuple<'_>) -> Self { - Self {} + Self { + field1: tuple.0, + field2: tuple.1, + field3: tuple.2, + } } } } #[automatically_derived] - impl alloy_sol_types::SolCall for setSimplesCall { - type Parameters<'a> = ( - alloy::sol_types::sol_data::Bool, + impl alloy_sol_types::SolCall for structMappingCall { + type Parameters<'a> = (alloy::sol_types::sol_data::Uint<256>,); + type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; + type Return = structMappingReturn; + type ReturnTuple<'a> = ( alloy::sol_types::sol_data::Uint<256>, - alloy::sol_types::sol_data::String, - alloy::sol_types::sol_data::Address, + alloy::sol_types::sol_data::Uint<128>, + alloy::sol_types::sol_data::Uint<128>, ); - type Token<'a> = as alloy_sol_types::SolType>::Token<'a>; - type Return = setSimplesReturn; - type ReturnTuple<'a> = (); type ReturnToken<'a> = as alloy_sol_types::SolType>::Token<'a>; - const SIGNATURE: &'static str = "setSimples(bool,uint256,string,address)"; - const SELECTOR: [u8; 4] = [2u8, 0u8, 34u8, 92u8]; + const SIGNATURE: &'static str = "structMapping(uint256)"; + const SELECTOR: [u8; 4] = [136u8, 223u8, 221u8, 198u8]; #[inline] fn new<'a>( tuple: as alloy_sol_types::SolType>::RustType, @@ -1769,17 +4697,8 @@ pub mod Simple { #[inline] fn tokenize(&self) -> Self::Token<'_> { ( - ::tokenize( - &self.newS1, - ), as alloy_sol_types::SolType>::tokenize( - &self.newS2, - ), - ::tokenize( - &self.newS3, - ), - ::tokenize( - &self.newS4, + &self._0, ), ) } @@ -1799,15 +4718,26 @@ pub mod Simple { pub enum SimpleCalls { addToArray(addToArrayCall), arr1(arr1Call), - changeMapping(changeMappingCall), + changeMapping_0(changeMapping_0Call), + changeMapping_1(changeMapping_1Call), + changeMapping_2(changeMapping_2Call), + changeMapping_3(changeMapping_3Call), m1(m1Call), + mappingOfSingleValueMappings(mappingOfSingleValueMappingsCall), + mappingOfStructMappings(mappingOfStructMappingsCall), s1(s1Call), s2(s2Call), s3(s3Call), s4(s4Call), setMapping(setMappingCall), + setMappingOfSingleValueMappings(setMappingOfSingleValueMappingsCall), + setMappingOfStructMappings(setMappingOfStructMappingsCall), + setMappingStruct(setMappingStructCall), setS2(setS2Call), + setSimpleStruct(setSimpleStructCall), setSimples(setSimplesCall), + simpleStruct(simpleStructCall), + structMapping(structMappingCall), } #[automatically_derived] impl SimpleCalls { @@ -1819,15 +4749,26 @@ pub mod Simple { /// Prefer using `SolInterface` methods instead. pub const SELECTORS: &'static [[u8; 4usize]] = &[ [2u8, 0u8, 34u8, 92u8], + [2u8, 227u8, 0u8, 58u8], [12u8, 22u8, 22u8, 201u8], [28u8, 19u8, 67u8, 21u8], [42u8, 228u8, 38u8, 134u8], + [62u8, 112u8, 22u8, 110u8], + [62u8, 144u8, 96u8, 199u8], + [76u8, 245u8, 169u8, 74u8], + [81u8, 151u8, 111u8, 200u8], [105u8, 135u8, 177u8, 251u8], [108u8, 192u8, 20u8, 222u8], + [128u8, 38u8, 222u8, 49u8], + [133u8, 182u8, 72u8, 159u8], + [136u8, 223u8, 221u8, 198u8], + [150u8, 220u8, 154u8, 65u8], [163u8, 20u8, 21u8, 15u8], [165u8, 214u8, 102u8, 169u8], + [198u8, 167u8, 240u8, 254u8], [200u8, 175u8, 58u8, 166u8], [209u8, 94u8, 200u8, 81u8], + [234u8, 209u8, 132u8, 0u8], [242u8, 93u8, 84u8, 245u8], ]; } @@ -1835,21 +4776,52 @@ pub mod Simple { impl alloy_sol_types::SolInterface for SimpleCalls { const NAME: &'static str = "SimpleCalls"; const MIN_DATA_LENGTH: usize = 0usize; - const COUNT: usize = 11usize; + const COUNT: usize = 22usize; #[inline] fn selector(&self) -> [u8; 4] { match self { Self::addToArray(_) => ::SELECTOR, Self::arr1(_) => ::SELECTOR, - Self::changeMapping(_) => ::SELECTOR, + Self::changeMapping_0(_) => { + ::SELECTOR + } + Self::changeMapping_1(_) => { + ::SELECTOR + } + Self::changeMapping_2(_) => { + ::SELECTOR + } + Self::changeMapping_3(_) => { + ::SELECTOR + } Self::m1(_) => ::SELECTOR, + Self::mappingOfSingleValueMappings(_) => { + ::SELECTOR + } + Self::mappingOfStructMappings(_) => { + ::SELECTOR + } Self::s1(_) => ::SELECTOR, Self::s2(_) => ::SELECTOR, Self::s3(_) => ::SELECTOR, Self::s4(_) => ::SELECTOR, Self::setMapping(_) => ::SELECTOR, + Self::setMappingOfSingleValueMappings(_) => { + ::SELECTOR + } + Self::setMappingOfStructMappings(_) => { + ::SELECTOR + } + Self::setMappingStruct(_) => { + ::SELECTOR + } Self::setS2(_) => ::SELECTOR, + Self::setSimpleStruct(_) => { + ::SELECTOR + } Self::setSimples(_) => ::SELECTOR, + Self::simpleStruct(_) => ::SELECTOR, + Self::structMapping(_) => ::SELECTOR, } } #[inline] @@ -1861,7 +4833,7 @@ pub mod Simple { Self::SELECTORS.binary_search(&selector).is_ok() } #[inline] - #[allow(unsafe_code, non_snake_case)] + #[allow(non_snake_case)] fn abi_decode_raw( selector: [u8; 4], data: &[u8], @@ -1879,16 +4851,28 @@ pub mod Simple { setSimples }, { - fn changeMapping( + fn changeMapping_0( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::changeMapping_0) + } + changeMapping_0 + }, + { + fn changeMapping_1( data: &[u8], validate: bool, ) -> alloy_sol_types::Result { - ::abi_decode_raw( + ::abi_decode_raw( data, validate, ) - .map(SimpleCalls::changeMapping) + .map(SimpleCalls::changeMapping_1) } - changeMapping + changeMapping_1 }, { fn setMapping( @@ -1907,6 +4891,55 @@ pub mod Simple { } m1 }, + { + fn setSimpleStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::setSimpleStruct) + } + setSimpleStruct + }, + { + fn changeMapping_2( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::changeMapping_2) + } + changeMapping_2 + }, + { + fn setMappingOfSingleValueMappings( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, + validate, + ) + .map(SimpleCalls::setMappingOfSingleValueMappings) + } + setMappingOfSingleValueMappings + }, + { + fn changeMapping_3( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::changeMapping_3) + } + changeMapping_3 + }, { fn arr1(data: &[u8], validate: bool) -> alloy_sol_types::Result { ::abi_decode_raw(data, validate) @@ -1921,6 +4954,55 @@ pub mod Simple { } s1 }, + { + fn setMappingStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::setMappingStruct) + } + setMappingStruct + }, + { + fn mappingOfStructMappings( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::mappingOfStructMappings) + } + mappingOfStructMappings + }, + { + fn structMapping( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::structMapping) + } + structMapping + }, + { + fn mappingOfSingleValueMappings( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, + validate, + ) + .map(SimpleCalls::mappingOfSingleValueMappings) + } + mappingOfSingleValueMappings + }, { fn s2(data: &[u8], validate: bool) -> alloy_sol_types::Result { ::abi_decode_raw(data, validate) @@ -1935,6 +5017,19 @@ pub mod Simple { } s3 }, + { + fn setMappingOfStructMappings( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, + validate, + ) + .map(SimpleCalls::setMappingOfStructMappings) + } + setMappingOfStructMappings + }, { fn s4(data: &[u8], validate: bool) -> alloy_sol_types::Result { ::abi_decode_raw(data, validate) @@ -1952,6 +5047,18 @@ pub mod Simple { } addToArray }, + { + fn simpleStruct( + data: &[u8], + validate: bool, + ) -> alloy_sol_types::Result { + ::abi_decode_raw( + data, validate, + ) + .map(SimpleCalls::simpleStruct) + } + simpleStruct + }, { fn setS2(data: &[u8], validate: bool) -> alloy_sol_types::Result { ::abi_decode_raw(data, validate) @@ -1966,7 +5073,7 @@ pub mod Simple { selector, )); }; - (unsafe { DECODE_SHIMS.get_unchecked(idx) })(data, validate) + DECODE_SHIMS[idx](data, validate) } #[inline] fn abi_encoded_size(&self) -> usize { @@ -1977,50 +5084,205 @@ pub mod Simple { Self::arr1(inner) => { ::abi_encoded_size(inner) } - Self::changeMapping(inner) => { - ::abi_encoded_size(inner) + Self::changeMapping_0(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::changeMapping_1(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::changeMapping_2(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::changeMapping_3(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::m1(inner) => { + ::abi_encoded_size(inner) + } + Self::mappingOfSingleValueMappings(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::mappingOfStructMappings(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::s1(inner) => { + ::abi_encoded_size(inner) + } + Self::s2(inner) => { + ::abi_encoded_size(inner) + } + Self::s3(inner) => { + ::abi_encoded_size(inner) + } + Self::s4(inner) => { + ::abi_encoded_size(inner) } - Self::m1(inner) => ::abi_encoded_size(inner), - Self::s1(inner) => ::abi_encoded_size(inner), - Self::s2(inner) => ::abi_encoded_size(inner), - Self::s3(inner) => ::abi_encoded_size(inner), - Self::s4(inner) => ::abi_encoded_size(inner), Self::setMapping(inner) => { ::abi_encoded_size(inner) } + Self::setMappingOfSingleValueMappings(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::setMappingOfStructMappings(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::setMappingStruct(inner) => { + ::abi_encoded_size( + inner, + ) + } Self::setS2(inner) => { ::abi_encoded_size(inner) } + Self::setSimpleStruct(inner) => { + ::abi_encoded_size( + inner, + ) + } Self::setSimples(inner) => { ::abi_encoded_size(inner) } + Self::simpleStruct(inner) => { + ::abi_encoded_size( + inner, + ) + } + Self::structMapping(inner) => { + ::abi_encoded_size( + inner, + ) + } } } #[inline] fn abi_encode_raw(&self, out: &mut alloy_sol_types::private::Vec) { match self { Self::addToArray(inner) => { - ::abi_encode_raw(inner, out) + ::abi_encode_raw( + inner, + out, + ) } Self::arr1(inner) => { ::abi_encode_raw(inner, out) } - Self::changeMapping(inner) => { - ::abi_encode_raw(inner, out) + Self::changeMapping_0(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::changeMapping_1(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::changeMapping_2(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::changeMapping_3(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::m1(inner) => { + ::abi_encode_raw(inner, out) + } + Self::mappingOfSingleValueMappings(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::mappingOfStructMappings(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::s1(inner) => { + ::abi_encode_raw(inner, out) + } + Self::s2(inner) => { + ::abi_encode_raw(inner, out) + } + Self::s3(inner) => { + ::abi_encode_raw(inner, out) + } + Self::s4(inner) => { + ::abi_encode_raw(inner, out) } - Self::m1(inner) => ::abi_encode_raw(inner, out), - Self::s1(inner) => ::abi_encode_raw(inner, out), - Self::s2(inner) => ::abi_encode_raw(inner, out), - Self::s3(inner) => ::abi_encode_raw(inner, out), - Self::s4(inner) => ::abi_encode_raw(inner, out), Self::setMapping(inner) => { - ::abi_encode_raw(inner, out) + ::abi_encode_raw( + inner, + out, + ) + } + Self::setMappingOfSingleValueMappings(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::setMappingOfStructMappings(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::setMappingStruct(inner) => { + ::abi_encode_raw( + inner, + out, + ) } Self::setS2(inner) => { ::abi_encode_raw(inner, out) } + Self::setSimpleStruct(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } Self::setSimples(inner) => { - ::abi_encode_raw(inner, out) + ::abi_encode_raw( + inner, + out, + ) + } + Self::simpleStruct(inner) => { + ::abi_encode_raw( + inner, + out, + ) + } + Self::structMapping(inner) => { + ::abi_encode_raw( + inner, + out, + ) } } } @@ -2191,33 +5453,76 @@ pub mod Simple { ///Creates a new call builder for the [`addToArray`] function. pub fn addToArray( &self, - value: alloy::sol_types::private::U256, + value: alloy::sol_types::private::primitives::aliases::U256, ) -> alloy_contract::SolCallBuilder { self.call_builder(&addToArrayCall { value }) } ///Creates a new call builder for the [`arr1`] function. pub fn arr1( &self, - _0: alloy::sol_types::private::U256, + _0: alloy::sol_types::private::primitives::aliases::U256, ) -> alloy_contract::SolCallBuilder { self.call_builder(&arr1Call { _0 }) } - ///Creates a new call builder for the [`changeMapping`] function. - pub fn changeMapping( + ///Creates a new call builder for the [`changeMapping_0`] function. + pub fn changeMapping_0( + &self, + changes: alloy::sol_types::private::Vec< + ::RustType, + >, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&changeMapping_0Call { changes }) + } + ///Creates a new call builder for the [`changeMapping_1`] function. + pub fn changeMapping_1( &self, changes: alloy::sol_types::private::Vec< ::RustType, >, - ) -> alloy_contract::SolCallBuilder { - self.call_builder(&changeMappingCall { changes }) + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&changeMapping_1Call { changes }) + } + ///Creates a new call builder for the [`changeMapping_2`] function. + pub fn changeMapping_2( + &self, + changes: alloy::sol_types::private::Vec< + ::RustType, + >, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&changeMapping_2Call { changes }) + } + ///Creates a new call builder for the [`changeMapping_3`] function. + pub fn changeMapping_3( + &self, + changes: alloy::sol_types::private::Vec< + ::RustType, + >, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&changeMapping_3Call { changes }) } ///Creates a new call builder for the [`m1`] function. pub fn m1( &self, - _0: alloy::sol_types::private::U256, + _0: alloy::sol_types::private::primitives::aliases::U256, ) -> alloy_contract::SolCallBuilder { self.call_builder(&m1Call { _0 }) } + ///Creates a new call builder for the [`mappingOfSingleValueMappings`] function. + pub fn mappingOfSingleValueMappings( + &self, + _0: alloy::sol_types::private::primitives::aliases::U256, + _1: alloy::sol_types::private::primitives::aliases::U256, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&mappingOfSingleValueMappingsCall { _0, _1 }) + } + ///Creates a new call builder for the [`mappingOfStructMappings`] function. + pub fn mappingOfStructMappings( + &self, + _0: alloy::sol_types::private::primitives::aliases::U256, + _1: alloy::sol_types::private::primitives::aliases::U256, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&mappingOfStructMappingsCall { _0, _1 }) + } ///Creates a new call builder for the [`s1`] function. pub fn s1(&self) -> alloy_contract::SolCallBuilder { self.call_builder(&s1Call {}) @@ -2237,23 +5542,75 @@ pub mod Simple { ///Creates a new call builder for the [`setMapping`] function. pub fn setMapping( &self, - key: alloy::sol_types::private::U256, + key: alloy::sol_types::private::primitives::aliases::U256, value: alloy::sol_types::private::Address, ) -> alloy_contract::SolCallBuilder { self.call_builder(&setMappingCall { key, value }) } + ///Creates a new call builder for the [`setMappingOfSingleValueMappings`] function. + pub fn setMappingOfSingleValueMappings( + &self, + outerKey: alloy::sol_types::private::primitives::aliases::U256, + innerKey: alloy::sol_types::private::primitives::aliases::U256, + value: alloy::sol_types::private::primitives::aliases::U256, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setMappingOfSingleValueMappingsCall { + outerKey, + innerKey, + value, + }) + } + ///Creates a new call builder for the [`setMappingOfStructMappings`] function. + pub fn setMappingOfStructMappings( + &self, + outerKey: alloy::sol_types::private::primitives::aliases::U256, + innerKey: alloy::sol_types::private::primitives::aliases::U256, + field1: alloy::sol_types::private::primitives::aliases::U256, + field2: u128, + field3: u128, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setMappingOfStructMappingsCall { + outerKey, + innerKey, + field1, + field2, + field3, + }) + } + ///Creates a new call builder for the [`setMappingStruct`] function. + pub fn setMappingStruct( + &self, + _key: alloy::sol_types::private::primitives::aliases::U256, + _field1: alloy::sol_types::private::primitives::aliases::U256, + _field2: u128, + _field3: u128, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setMappingStructCall { + _key, + _field1, + _field2, + _field3, + }) + } ///Creates a new call builder for the [`setS2`] function. pub fn setS2( &self, - newS2: alloy::sol_types::private::U256, + newS2: alloy::sol_types::private::primitives::aliases::U256, ) -> alloy_contract::SolCallBuilder { self.call_builder(&setS2Call { newS2 }) } + ///Creates a new call builder for the [`setSimpleStruct`] function. + pub fn setSimpleStruct( + &self, + input: ::RustType, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&setSimpleStructCall { input }) + } ///Creates a new call builder for the [`setSimples`] function. pub fn setSimples( &self, newS1: bool, - newS2: alloy::sol_types::private::U256, + newS2: alloy::sol_types::private::primitives::aliases::U256, newS3: alloy::sol_types::private::String, newS4: alloy::sol_types::private::Address, ) -> alloy_contract::SolCallBuilder { @@ -2264,6 +5621,17 @@ pub mod Simple { newS4, }) } + ///Creates a new call builder for the [`simpleStruct`] function. + pub fn simpleStruct(&self) -> alloy_contract::SolCallBuilder { + self.call_builder(&simpleStructCall {}) + } + ///Creates a new call builder for the [`structMapping`] function. + pub fn structMapping( + &self, + _0: alloy::sol_types::private::primitives::aliases::U256, + ) -> alloy_contract::SolCallBuilder { + self.call_builder(&structMappingCall { _0 }) + } } /// Event filters. #[automatically_derived] 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/common/cases/contract.rs b/mp2-v1/tests/common/cases/contract.rs index c25b3aa44..029b11d2c 100644 --- a/mp2-v1/tests/common/cases/contract.rs +++ b/mp2-v1/tests/common/cases/contract.rs @@ -1,10 +1,26 @@ -use alloy::{primitives::Address, providers::ProviderBuilder}; +use std::future::Future; + +use super::slot_info::{LargeStruct, MappingInfo, StorageSlotMappingKey, StorageSlotValue}; +use crate::common::{ + bindings::{ + eventemitter::EventEmitter::{self, EventEmitterInstance}, + simple::{Simple, Simple::MappingOperation}, + }, + cases::indexing::ReceiptUpdate, + TestContext, +}; +use alloy::{ + contract::private::Provider, + network::Ethereum, + primitives::{Address, U256}, + providers::{ProviderBuilder, RootProvider}, + transports::Transport, +}; use anyhow::Result; +use itertools::Itertools; use log::info; -use crate::common::{bindings::simple::Simple, TestContext}; - -use super::indexing::{SimpleSingleValue, UpdateSimpleStorage}; +use super::indexing::ContractUpdate; pub struct Contract { pub address: Address, @@ -12,35 +28,208 @@ pub struct Contract { } impl Contract { - pub async fn current_single_values(&self, ctx: &TestContext) -> Result { + /// Deploy the simple contract. + pub(crate) async fn deploy_simple_contract(ctx: &TestContext) -> Self { + // Create a provider with the wallet for contract deployment and interaction. let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(ctx.wallet()) .on_http(ctx.rpc_url.parse().unwrap()); - let contract = Simple::new(self.address, &provider); + let contract = Simple::deploy(&provider).await.unwrap(); + let address = *contract.address(); + info!("Deployed Simple contract at address: {address}"); + let chain_id = ctx.rpc.get_chain_id().await.unwrap(); + Self { address, chain_id } + } + + /// Creates a new [`Contract`] from an [`Address`] and `chain_id` + #[allow(dead_code)] + pub fn new(address: Address, chain_id: u64) -> Contract { + Contract { address, chain_id } + } + /// Getter for `chain_id` + pub fn chain_id(&self) -> u64 { + self.chain_id + } + /// Getter for [`Address`] + pub fn address(&self) -> Address { + self.address + } +} + +/// Trait implemented by any test contract. +pub trait TestContract +where + T: Transport + Clone, +{ + /// How this implementor ingests updates. + type Update: ContractUpdate; + /// The actual contract instance. + type Contract; + /// Function that generates a new instance of self given a [`Provider`] and a `chain_id` + fn new(address: Address, provider: &RootProvider) -> Self; + /// Get an instance of the contract. + fn get_instance(&self) -> &Self::Contract; + /// Apply an update to the contract. + async fn apply_update(&self, ctx: &TestContext, update: &Self::Update) -> Result<()> { + let contract = self.get_instance(); + update.apply_to(ctx, contract).await; + info!("Updated contract with new values {:?}", update); + Ok(()) + } +} + +/// Common functions for a specific type to interact with the test contract +pub trait ContractController { + /// Get the current values from the contract. + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self; + + /// Update the values to the contract. + fn update_contract( + &self, + ctx: &TestContext, + contract: &Contract, + ) -> impl Future + Send; +} - Ok(SimpleSingleValue { +/// Single values collection +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct SimpleSingleValues { + pub(crate) s1: bool, + pub(crate) s2: U256, + pub(crate) s3: String, + pub(crate) s4: Address, +} + +impl ContractController for SimpleSingleValues { + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); + + SimpleSingleValues { s1: contract.s1().call().await.unwrap()._0, s2: contract.s2().call().await.unwrap()._0, s3: contract.s3().call().await.unwrap()._0, s4: contract.s4().call().await.unwrap()._0, + } + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let simple_contract = Simple::new(contract.address, &provider); + + let call = simple_contract.setSimples(self.s1, self.s2, self.s3.clone(), self.s4); + call.send().await.unwrap().watch().await.unwrap(); + log::info!("Updated simple contract single values"); + // Sanity check + { + let updated = Self::current_values(ctx, contract).await; + assert_eq!(self, &updated); + } + } +} + +impl ContractController for LargeStruct { + async fn current_values(ctx: &TestContext, contract: &Contract) -> Self { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); + + contract.simpleStruct().call().await.unwrap().into() + } + + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + let simple_contract = Simple::new(contract.address, &provider); + + let call = simple_contract.setSimpleStruct((*self).into()); + call.send().await.unwrap().watch().await.unwrap(); + // Sanity check + { + let updated = Self::current_values(ctx, contract).await; + assert_eq!(self, &updated); + } + log::info!("Updated simple contract for LargeStruct"); + } +} + +#[derive(Clone, Debug)] +pub enum MappingUpdate { + // key and value + Insertion(K, V), + // key and value + Deletion(K, V), + // key, previous value and new value + Update(K, V, V), +} + +impl MappingUpdate +where + K: StorageSlotMappingKey, + V: StorageSlotValue, +{ + pub fn to_tuple(&self) -> (K, V) { + match self { + MappingUpdate::Insertion(key, value) + | MappingUpdate::Deletion(key, value) + | MappingUpdate::Update(key, _, value) => (key.clone(), value.clone()), + } + } +} + +impl From<&MappingUpdate> for MappingOperation { + fn from(update: &MappingUpdate) -> Self { + Self::from(match update { + MappingUpdate::Deletion(_, _) => 0, + MappingUpdate::Update(_, _, _) => 1, + MappingUpdate::Insertion(_, _) => 2, }) } - // Returns the table updated - pub async fn apply_update( - &self, - ctx: &TestContext, - update: &UpdateSimpleStorage, - ) -> Result<()> { +} + +impl ContractController for Vec> { + async fn current_values(_ctx: &TestContext, _contract: &Contract) -> Self { + unimplemented!("Unimplemented for fetching the all mapping values") + } + async fn update_contract(&self, ctx: &TestContext, contract: &Contract) { let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(ctx.wallet()) .on_http(ctx.rpc_url.parse().unwrap()); + let contract = Simple::new(contract.address, &provider); - let contract = Simple::new(self.address, &provider); - update.apply_to(&contract).await; - info!("Updated contract with new values {:?}", update); - Ok(()) + let changes = self.iter().map(T::to_call).collect_vec(); + + T::call_contract(&contract, changes).await + } +} +pub struct EventContract { + pub instance: EventEmitterInstance, Ethereum>, +} + +impl TestContract for EventContract { + type Update = ReceiptUpdate; + type Contract = EventEmitterInstance>; + + fn new(address: Address, provider: &RootProvider) -> Self { + Self { + instance: EventEmitter::new(address, provider.clone()), + } + } + + fn get_instance(&self) -> &Self::Contract { + &self.instance } } diff --git a/mp2-v1/tests/common/cases/indexing.rs b/mp2-v1/tests/common/cases/indexing.rs index c164bec5d..a948ef693 100644 --- a/mp2-v1/tests/common/cases/indexing.rs +++ b/mp2-v1/tests/common/cases/indexing.rs @@ -1,8 +1,13 @@ //! Test case for local Simple contract //! Reference `test-contracts/src/Simple.sol` for the details of Simple contract. +use std::future::Future; + +use anyhow::Result; +use itertools::Itertools; use log::{debug, info}; use mp2_v1::{ + api::SlotInput, contract_extraction, indexing::{ block::BlockPrimaryIndex, @@ -10,321 +15,644 @@ use mp2_v1::{ row::{CellCollection, CellInfo, Row, RowTreeKey}, ColumnID, }, - values_extraction::identifier_block_column, + values_extraction::{ + identifier_block_column, identifier_for_data_column, identifier_for_gas_used_column, + identifier_for_inner_mapping_key_column, identifier_for_outer_mapping_key_column, + identifier_for_topic_column, identifier_for_value_column, DATA_NAME, GAS_USED_NAME, + TOPIC_NAME, + }, }; + +use rand::{thread_rng, Rng}; use ryhope::storage::RoEpochKvStorage; use crate::common::{ - bindings::simple::Simple::{self, MappingChange, MappingOperation}, + bindings::eventemitter::EventEmitter::{self, EventEmitterInstance}, cases::{ contract::Contract, - identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, + identifier_for_mapping_key_column, + slot_info::{ + LargeStruct, SimpleMapping, SimpleNestedMapping, StructMapping, StructNestedMapping, + }, table_source::{ - LengthExtractionArgs, MappingIndex, MappingValuesExtractionArgs, MergeSource, - SingleValuesExtractionArgs, DEFAULT_ADDRESS, + ContractExtractionArgs, MappingExtractionArgs, MappingIndex, MergeSource, + ReceiptExtractionArgs, SingleExtractionArgs, TableSource, }, + TableIndexing, }, proof_storage::{ProofKey, ProofStorage}, rowtree::SecondaryIndexCell, table::{ - CellsUpdate, IndexType, IndexUpdate, Table, TableColumn, TableColumns, TreeRowUpdate, - TreeUpdateType, + CellsUpdate, IndexType, IndexUpdate, Table, TableColumn, TableColumns, TableRowUniqueID, + TreeRowUpdate, TreeUpdateType, }, TableInfo, TestContext, }; -use super::{ - super::bindings::simple::Simple::SimpleInstance, ContractExtractionArgs, TableIndexing, - TableSource, -}; use alloy::{ contract::private::{Network, Provider, Transport}, - primitives::{Address, U256}, - providers::ProviderBuilder, + network::{Ethereum, TransactionBuilder}, + primitives::U256, + providers::{ext::AnvilApi, ProviderBuilder, RootProvider}, + sol_types::SolEvent, +}; +use mp2_common::{ + eth::{EventLogInfo, StorageSlot}, + proof::ProofWithVK, + types::HashOutput, }; -use mp2_common::{eth::StorageSlot, proof::ProofWithVK, types::HashOutput}; /// Test slots for single values extraction -const SINGLE_SLOTS: [u8; 4] = [0, 1, 2, 3]; -/// Define which slots is the secondary index. In this case, it's the U256 -const INDEX_SLOT: u8 = 1; +pub(crate) const SINGLE_SLOTS: [u8; 4] = [0, 1, 2, 3]; /// Test slot for mapping values extraction const MAPPING_SLOT: u8 = 4; /// Test slot for length extraction +#[allow(dead_code)] const LENGTH_SLOT: u8 = 1; /// Test length value for length extraction +#[allow(dead_code)] const LENGTH_VALUE: u8 = 2; /// Test slot for contract extraction const CONTRACT_SLOT: usize = 1; +/// Test slot for single Struct extraction +pub(crate) const SINGLE_STRUCT_SLOT: usize = 6; + +/// Test slot for mapping Struct extraction +const MAPPING_STRUCT_SLOT: usize = 8; + +/// Test slot for mapping of single value mappings extraction +pub(crate) const MAPPING_OF_SINGLE_VALUE_MAPPINGS_SLOT: u8 = 9; + +/// Test slot for mapping of struct mappings extraction +pub(crate) const MAPPING_OF_STRUCT_MAPPINGS_SLOT: u8 = 10; + /// human friendly name about the column containing the block number pub(crate) const BLOCK_COLUMN_NAME: &str = "block_number"; -pub(crate) const MAPPING_VALUE_COLUMN: &str = "map_value"; -pub(crate) const MAPPING_KEY_COLUMN: &str = "map_key"; +pub(crate) const SINGLE_SECONDARY_COLUMN: &str = "single_secondary_column"; +pub(crate) const MAPPING_KEY_COLUMN: &str = "mapping_key_column"; +pub(crate) const MAPPING_VALUE_COLUMN: &str = "mapping_value_column"; +pub(crate) const MAPPING_OF_MAPPINGS_OUTER_KEY_COLUMN: &str = + "mapping_of_mappings_outer_key_column"; +pub(crate) const MAPPING_OF_MAPPINGS_INNER_KEY_COLUMN: &str = + "mapping_of_mappings_inner_key_column"; +pub(crate) const MAPPING_OF_MAPPINGS_VALUE_COLUMN: &str = "mapping_of_mappings_value_column"; + +/// Construct the all slot inputs for single value testing. +fn single_value_slot_inputs() -> Vec { + let mut slot_inputs = SINGLE_SLOTS + .map(|slot| SlotInput::new(slot, 0, 32, 0)) + .to_vec(); + + // Add the Struct single slots. + let struct_slots = LargeStruct::slot_inputs(SINGLE_STRUCT_SLOT as u8); + slot_inputs.extend(struct_slots); -impl TableIndexing { + slot_inputs +} + +pub(crate) const TX_INDEX_COLUMN: &str = "tx_index"; + +impl TableIndexing { pub(crate) async fn merge_table_test_case( ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { - // Create a provider with the wallet for contract deployment and interaction. - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(ctx.wallet()) - .on_http(ctx.rpc_url.parse().unwrap()); + ) -> Result<( + TableIndexing, + Vec>, + )> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; - let contract = Simple::deploy(&provider).await.unwrap(); - info!( - "Deployed Simple contract at address: {}", - contract.address() - ); - let contract_address = contract.address(); - let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - let contract = Contract { - address: *contract_address, - chain_id, - }; - let single_source = SingleValuesExtractionArgs { - // this test puts the mapping value as secondary index so there is no index for the - // single variable slots. - index_slot: None, - slots: SINGLE_SLOTS.to_vec(), - }; - // to toggle off and on - let value_as_index = true; - let value_id = - identifier_for_mapping_value_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let key_id = - identifier_for_mapping_key_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let (mapping_index_id, mapping_index, mapping_cell_id) = match value_as_index { - true => (value_id, MappingIndex::Value(value_id), key_id), - false => (key_id, MappingIndex::Key(key_id), value_id), - }; - - let mapping_source = MappingValuesExtractionArgs { - slot: MAPPING_SLOT, - index: mapping_index, - // at the beginning there is no mapping key inserted - // NOTE: This array is a convenience to handle smart contract updates - // manually, but does not need to be stored explicitely by dist system. - mapping_keys: vec![], + // This test puts the mapping value as secondary index so there is no index for the + // single variable slots. + let single_source = { + let slot_inputs = single_value_slot_inputs(); + SingleExtractionArgs::new(None, slot_inputs) }; - let mut source = TableSource::Merge(MergeSource::new(single_source, mapping_source)); - let genesis_change = source.init_contract_data(ctx, &contract).await; - let single_columns = SINGLE_SLOTS + let single_columns = single_source + .slot_inputs .iter() .enumerate() - .map(|(i, slot)| { + .map(|(i, slot_input)| { let identifier = - identifier_single_var_column(*slot, contract_address, chain_id, vec![]); + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]); + TableColumn { - name: format!("column_{}", i), - identifier, + name: format!("single_column_{i}"), index: IndexType::None, // ALL single columns are "multiplier" since we do tableA * D(tableB), i.e. all // entries of table A are repeated for each entry of table B. multiplier: true, + identifier, } }) - .collect::>(); - let mapping_column = vec![TableColumn { - name: if value_as_index { - MAPPING_KEY_COLUMN - } else { - MAPPING_VALUE_COLUMN - } - .to_string(), - identifier: mapping_cell_id, - index: IndexType::None, - // here is it important to specify false to mean that the entries of table B are - // not repeated. - multiplier: false, - }]; - let value_column = mapping_column[0].name.clone(); - let all_columns = [single_columns.as_slice(), mapping_column.as_slice()].concat(); + .collect_vec(); + let (mapping_secondary_column, mapping_rest_columns, row_unique_id, mapping_source) = { + let slot_inputs = LargeStruct::slot_inputs(MAPPING_STRUCT_SLOT as u8); + let key_id = identifier_for_mapping_key_column( + MAPPING_STRUCT_SLOT as u8, + &contract_address, + chain_id, + vec![], + ); + let mut value_ids = slot_inputs + .iter() + .map(|slot_input| { + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]) + }) + .collect_vec(); + // Switch the test index. + // let mapping_index = MappingIndex::Value(value_ids(1)); + let mapping_index = MappingIndex::OuterKey(key_id); + let source = MappingExtractionArgs::new( + MAPPING_STRUCT_SLOT as u8, + mapping_index, + slot_inputs.clone(), + None, + ); + // Construct the table columns. + let (secondary_column, rest_columns) = match mapping_index { + MappingIndex::OuterKey(_) => { + let secondary_column = TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: key_id, + }; + let rest_columns = value_ids + .into_iter() + .enumerate() + .map(|(i, id)| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + identifier: id, + }) + .collect_vec(); + + (secondary_column, rest_columns) + } + MappingIndex::Value(secondary_value_id) => { + let pos = value_ids + .iter() + .position(|id| id == &secondary_value_id) + .unwrap(); + let secondary_id = value_ids.remove(pos); + + let secondary_column = TableColumn { + name: MAPPING_VALUE_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: secondary_id, + }; + let mut rest_columns = value_ids + .into_iter() + .enumerate() + .map(|(i, id)| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + identifier: id, + }) + .collect_vec(); + rest_columns.push(TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + // The slot input is useless for the key column. + identifier: key_id, + }); + + (secondary_column, rest_columns) + } + _ => unreachable!(), + }; + let row_unique_id = TableRowUniqueID::Mapping(key_id); + + (secondary_column, rest_columns, row_unique_id, source) + }; + let mut source = MergeSource::new(single_source, mapping_source); + let genesis_change = source.init_contract_data(ctx, &contract).await; + let value_column = mapping_rest_columns[0].name.clone(); + let all_columns = [single_columns.as_slice(), &mapping_rest_columns].concat(); let columns = TableColumns { primary: TableColumn { name: BLOCK_COLUMN_NAME.to_string(), - identifier: identifier_block_column(), index: IndexType::Primary, // it doesn't matter for this one since block is "outside" of the table definition // really, it is a special column we add multiplier: true, + // Only valid for the identifier of block column, others are dummy. + identifier: identifier_block_column(), }, - secondary: TableColumn { - name: if value_as_index { - MAPPING_VALUE_COLUMN - } else { - MAPPING_KEY_COLUMN - } - .to_string(), - identifier: mapping_index_id, - index: IndexType::Secondary, - // here is it important to specify false to mean that the entries of table B are - // not repeated. - multiplier: false, - }, + secondary: mapping_secondary_column, rest: all_columns, }; - println!( + info!( "Table information:\n{}\n", serde_json::to_string_pretty(&columns)? ); let indexing_genesis_block = ctx.block_number().await; - let table = Table::new(indexing_genesis_block, "merged_table".to_string(), columns).await; + let table = Table::new( + indexing_genesis_block, + "merged_table".to_string(), + columns, + row_unique_id, + ) + .await?; Ok(( - Self { + TableIndexing:: { value_column, source: source.clone(), table, contract, - contract_extraction: ContractExtractionArgs { + contract_extraction: Some(ContractExtractionArgs { slot: StorageSlot::Simple(CONTRACT_SLOT), - }, + }), }, genesis_change, )) } + /// The single value test case includes the all single value slots and one single Struct slot. pub(crate) async fn single_value_test_case( ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { - // Create a provider with the wallet for contract deployment and interaction. - let provider = ProviderBuilder::new() - .with_recommended_fillers() - .wallet(ctx.wallet()) - .on_http(ctx.rpc_url.parse().unwrap()); + ) -> Result<( + TableIndexing, + Vec>, + )> { + let rng = &mut thread_rng(); - let contract = Simple::deploy(&provider).await.unwrap(); - info!( - "Deployed Simple contract at address: {}", - contract.address() - ); - let contract_address = contract.address(); - let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - let contract = Contract { - address: *contract_address, - chain_id, - }; + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; - let mut source = TableSource::SingleValues(SingleValuesExtractionArgs { - index_slot: Some(INDEX_SLOT), - slots: SINGLE_SLOTS.to_vec(), - }); + let mut source = { + let slot_inputs = single_value_slot_inputs(); + let secondary_index = rng.gen_range(0..slot_inputs.len()); + SingleExtractionArgs::new(Some(secondary_index), slot_inputs) + }; let genesis_updates = source.init_contract_data(ctx, &contract).await; - let indexing_genesis_block = ctx.block_number().await; + let secondary_index_slot_input = source.secondary_index_slot_input().unwrap(); + let rest_column_slot_inputs = source.rest_column_slot_inputs(); + // Defining the columns structure of the table from the source slots - // This is depending on what is our data source, mappings and CSV both have their o + // This is depending on what is our data source, mappings and CSV both have their // own way of defining their table. let columns = TableColumns { primary: TableColumn { name: BLOCK_COLUMN_NAME.to_string(), - identifier: identifier_block_column(), index: IndexType::Primary, multiplier: false, + // Only valid for the identifier of block column, others are dummy. + identifier: identifier_block_column(), }, secondary: TableColumn { - name: "column_value".to_string(), - identifier: identifier_single_var_column( - INDEX_SLOT, - contract_address, - chain_id, - vec![], - ), + name: SINGLE_SECONDARY_COLUMN.to_string(), index: IndexType::Secondary, // here we put false always since these are not coming from a "merged" table multiplier: false, + identifier: identifier_for_value_column( + &secondary_index_slot_input, + &contract_address, + chain_id, + vec![], + ), }, - rest: SINGLE_SLOTS + rest: rest_column_slot_inputs .iter() .enumerate() - .filter_map(|(i, slot)| match i { - _ if *slot == INDEX_SLOT => None, - _ => { - let identifier = - identifier_single_var_column(*slot, contract_address, chain_id, vec![]); - Some(TableColumn { - name: format!("column_{}", i), - identifier, - index: IndexType::None, - // here we put false always since these are not coming from a "merged" table - multiplier: false, - }) + .map(|(i, slot_input)| { + let identifier = identifier_for_value_column( + slot_input, + &contract_address, + chain_id, + vec![], + ); + + TableColumn { + name: format!("rest_column_{i}"), + index: IndexType::None, + multiplier: false, + identifier, } }) - .collect::>(), + .collect_vec(), }; - let table = Table::new(indexing_genesis_block, "single_table".to_string(), columns).await; + let row_unique_id = TableRowUniqueID::Single; + let table = Table::new( + indexing_genesis_block, + "single_table".to_string(), + columns, + row_unique_id, + ) + .await?; Ok(( - Self { + TableIndexing:: { value_column: "".to_string(), - source: source.clone(), + source, table, contract, - contract_extraction: ContractExtractionArgs { + contract_extraction: Some(ContractExtractionArgs { slot: StorageSlot::Simple(CONTRACT_SLOT), - }, + }), }, genesis_updates, )) } - pub(crate) async fn mapping_test_case( + /// The test case for mapping of single values + pub(crate) async fn mapping_value_test_case( + ctx: &mut TestContext, + ) -> Result<( + TableIndexing>, + Vec>, + )> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_input = SlotInput::new(MAPPING_SLOT, 0, 32, 0); + let key_id = + identifier_for_mapping_key_column(MAPPING_SLOT, &contract_address, chain_id, vec![]); + let value_id = + identifier_for_value_column(&slot_input, &contract_address, chain_id, vec![]); + // Switch the test index. + // let mapping_index = MappingIndex::Value(value_id); + let mapping_index = MappingIndex::OuterKey(key_id); + let mut source = MappingExtractionArgs::::new( + MAPPING_SLOT, + mapping_index, + vec![slot_input], + None, + ); + + let contract = Contract { + address: contract_address, + chain_id, + }; + + let table_row_updates = source.init_contract_data(ctx, &contract).await; + + let table = build_mapping_table(ctx, &mapping_index, key_id, vec![value_id]).await; + let value_column = table.columns.rest[0].name.clone(); + + Ok(( + TableIndexing::> { + value_column, + contract_extraction: Some(ContractExtractionArgs { + slot: StorageSlot::Simple(CONTRACT_SLOT), + }), + contract, + source, + table, + }, + table_row_updates, + )) + } + + /// The test case for mapping of Struct values + pub(crate) async fn mapping_struct_test_case( + ctx: &mut TestContext, + ) -> Result<( + TableIndexing>, + Vec>, + )> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_inputs = LargeStruct::slot_inputs(MAPPING_STRUCT_SLOT as u8); + let key_id = identifier_for_mapping_key_column( + MAPPING_STRUCT_SLOT as u8, + &contract_address, + chain_id, + vec![], + ); + let value_ids = slot_inputs + .iter() + .map(|slot_input| { + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]) + }) + .collect_vec(); + // Switch the test index. + // let mapping_index = MappingIndex::OuterKey(key_id); + let mapping_index = MappingIndex::Value(value_ids[1]); + let mut source = MappingExtractionArgs::::new( + MAPPING_STRUCT_SLOT as u8, + mapping_index, + slot_inputs.clone(), + None, + ); + + let table_row_updates = source.init_contract_data(ctx, &contract).await; + + let table = build_mapping_table(ctx, &mapping_index, key_id, value_ids).await; + let value_column = table.columns.rest[0].name.clone(); + + Ok(( + TableIndexing::> { + value_column, + contract_extraction: Some(ContractExtractionArgs { + slot: StorageSlot::Simple(CONTRACT_SLOT), + }), + contract, + source, + table, + }, + table_row_updates, + )) + } + + pub(crate) async fn mapping_of_single_value_mappings_test_case( + ctx: &mut TestContext, + ) -> Result<( + TableIndexing>, + Vec>, + )> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_input = SlotInput::new(MAPPING_OF_SINGLE_VALUE_MAPPINGS_SLOT, 0, 32, 0); + let outer_key_id = identifier_for_outer_mapping_key_column( + MAPPING_OF_SINGLE_VALUE_MAPPINGS_SLOT, + &contract_address, + chain_id, + vec![], + ); + let inner_key_id = identifier_for_inner_mapping_key_column( + MAPPING_OF_SINGLE_VALUE_MAPPINGS_SLOT, + &contract_address, + chain_id, + vec![], + ); + let value_id = + identifier_for_value_column(&slot_input, &contract_address, chain_id, vec![]); + // Enable to test different indexes. + // let index = MappingIndex::Value(value_id); + // let index = MappingIndex::OuterKey(outer_key_id); + let index = MappingIndex::InnerKey(inner_key_id); + let mut source = MappingExtractionArgs::::new( + MAPPING_OF_SINGLE_VALUE_MAPPINGS_SLOT, + index, + vec![slot_input], + None, + ); + + let table_row_updates = source.init_contract_data(ctx, &contract).await; + + let table = build_mapping_of_mappings_table( + ctx, + &index, + outer_key_id, + inner_key_id, + vec![value_id], + ) + .await; + let value_column = table.columns.rest[0].name.clone(); + + Ok(( + TableIndexing::> { + value_column, + contract_extraction: Some(ContractExtractionArgs { + slot: StorageSlot::Simple(CONTRACT_SLOT), + }), + contract, + source, + table, + }, + table_row_updates, + )) + } + + pub(crate) async fn mapping_of_struct_mappings_test_case( + ctx: &mut TestContext, + ) -> Result<( + TableIndexing>, + Vec>, + )> { + // Deploy the simple contract. + let contract = Contract::deploy_simple_contract(ctx).await; + let contract_address = contract.address; + let chain_id = contract.chain_id; + + let slot_inputs = LargeStruct::slot_inputs(MAPPING_OF_STRUCT_MAPPINGS_SLOT); + let outer_key_id = identifier_for_outer_mapping_key_column( + MAPPING_OF_STRUCT_MAPPINGS_SLOT, + &contract_address, + chain_id, + vec![], + ); + let inner_key_id = identifier_for_inner_mapping_key_column( + MAPPING_OF_STRUCT_MAPPINGS_SLOT, + &contract_address, + chain_id, + vec![], + ); + let value_ids = slot_inputs + .iter() + .map(|slot_input| { + identifier_for_value_column(slot_input, &contract_address, chain_id, vec![]) + }) + .collect_vec(); + // Enable to test different indexes. + // let index = MappingIndex::OuterKey(outer_key_id); + // let index = MappingIndex::InnerKey(inner_key_id); + let index = MappingIndex::Value(value_ids[1]); + let mut source = MappingExtractionArgs::::new( + MAPPING_OF_STRUCT_MAPPINGS_SLOT, + index, + slot_inputs.clone(), + None, + ); + + let table_row_updates = source.init_contract_data(ctx, &contract).await; + + let table = + build_mapping_of_mappings_table(ctx, &index, outer_key_id, inner_key_id, value_ids) + .await; + let value_column = table.columns.rest[0].name.clone(); + + Ok(( + TableIndexing::> { + value_column, + contract_extraction: Some(ContractExtractionArgs { + slot: StorageSlot::Simple(CONTRACT_SLOT), + }), + contract, + source, + table, + }, + table_row_updates, + )) + } + + pub(crate) async fn receipt_test_case( + no_topics: usize, + no_data: usize, ctx: &mut TestContext, - ) -> anyhow::Result<(Self, Vec>)> { + ) -> Result<(TableIndexing, Vec>)> + where + T: ReceiptExtractionArgs, + [(); ::NO_TOPICS]:, + [(); ::MAX_DATA_WORDS]:, + { // Create a provider with the wallet for contract deployment and interaction. let provider = ProviderBuilder::new() .with_recommended_fillers() .wallet(ctx.wallet()) .on_http(ctx.rpc_url.parse().unwrap()); - let contract = Simple::deploy(&provider).await.unwrap(); + let contract = EventEmitter::deploy(&provider).await.unwrap(); info!( - "Deployed MAPPING Simple contract at address: {}", + "Deployed EventEmitter contract at address: {}", contract.address() ); let contract_address = contract.address(); let chain_id = ctx.rpc.get_chain_id().await.unwrap(); - // to toggle off and on - let value_as_index = true; - let value_id = - identifier_for_mapping_value_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let key_id = - identifier_for_mapping_key_column(MAPPING_SLOT, contract_address, chain_id, vec![]); - let (index_identifier, mapping_index, cell_identifier) = match value_as_index { - true => (value_id, MappingIndex::Value(value_id), key_id), - false => (key_id, MappingIndex::Key(key_id), value_id), - }; - - let mapping_args = MappingValuesExtractionArgs { - slot: MAPPING_SLOT, - index: mapping_index, - // at the beginning there is no mapping key inserted - // NOTE: This array is a convenience to handle smart contract updates - // manually, but does not need to be stored explicitely by dist system. - mapping_keys: vec![], - }; - - let mut source = TableSource::Mapping(( - mapping_args, - Some(LengthExtractionArgs { - slot: LENGTH_SLOT, - value: LENGTH_VALUE, - }), - )); let contract = Contract { address: *contract_address, chain_id, }; - let table_row_updates = source.init_contract_data(ctx, &contract).await; - // Defining the columns structure of the table from the source slots + // Retrieve the event signature `str` based on `no_topics` and `no_data` + let event_signature = match (no_topics, no_data) { + (0, 0) => EventEmitter::noIndexed::SIGNATURE, + (0, 1) => EventEmitter::noIOneD::SIGNATURE, + (0, 2) => EventEmitter::noITwoD::SIGNATURE, + (1, 0) => EventEmitter::oneIndexed::SIGNATURE, + (1, 1) => EventEmitter::oneIOneD::SIGNATURE, + (1, 2) => EventEmitter::oneITwoD::SIGNATURE, + (2, 0) => EventEmitter::twoIndexed::SIGNATURE, + (2, 1) => EventEmitter::twoIOneD::SIGNATURE, + (2, 2) => EventEmitter::twoITwoD::SIGNATURE, + (3, 0) => EventEmitter::threeIndexed::SIGNATURE, + (3, 1) => EventEmitter::oneData::SIGNATURE, + (3, 2) => EventEmitter::twoData::SIGNATURE, + _ => panic!( + "Events with {} topics and {} additional pieces of data not supported", + no_topics, no_data + ), + }; + + let mut source = T::new(contract.address(), event_signature); + let genesis_updates = source.init_contract_data(ctx, &contract).await; + + let indexing_genesis_block = ctx.block_number().await; + // Defining the columns structure of the table from the source event // This is depending on what is our data source, mappings and CSV both have their o // own way of defining their table. let columns = TableColumns { @@ -335,46 +663,43 @@ impl TableIndexing { multiplier: false, }, secondary: TableColumn { - name: if value_as_index { - MAPPING_VALUE_COLUMN - } else { - MAPPING_KEY_COLUMN - } - .to_string(), - identifier: index_identifier, + name: TX_INDEX_COLUMN.to_string(), + identifier: ::get_index(&source), + index: IndexType::Secondary, - // here important to put false since these are not coming from any "merged" table + // here we put false always since these are not coming from a "merged" table multiplier: false, }, - rest: vec![TableColumn { - name: if value_as_index { - MAPPING_KEY_COLUMN - } else { - MAPPING_VALUE_COLUMN - } - .to_string(), - identifier: cell_identifier, - index: IndexType::None, - // here important to put false since these are not coming from any "merged" table - multiplier: false, - }], + rest: compute_non_indexed_receipt_column_ids(&source.get_event()) + .into_iter() + .map(|(name, identifier)| TableColumn { + name, + identifier, + index: IndexType::None, + multiplier: false, + }) + .collect::>(), }; - let value_column = columns.rest[0].name.clone(); - debug!("MAPPING ZK COLUMNS -> {:?}", columns); - let index_genesis_block = ctx.block_number().await; - let table = Table::new(index_genesis_block, "mapping_table".to_string(), columns).await; + let tx_index_id = columns.secondary_column().identifier(); + let gas_used_id = columns.rest[0].identifier(); + let row_unique_id = TableRowUniqueID::Receipt(tx_index_id, gas_used_id); + let table = Table::new( + indexing_genesis_block, + "receipt_table".to_string(), + columns, + row_unique_id, + ) + .await?; Ok(( - Self { - value_column, - contract_extraction: ContractExtractionArgs { - slot: StorageSlot::Simple(CONTRACT_SLOT), - }, - contract, + TableIndexing:: { + value_column: table.columns.rest[0].name.clone(), source, table, + contract, + contract_extraction: None, }, - table_row_updates, + genesis_updates, )) } @@ -401,7 +726,28 @@ impl TableIndexing { .source .random_contract_update(ctx, &self.contract, ut) .await; + if table_row_updates.is_empty() { + continue; + } + + // If we are dealing with receipts we need to remove everything already in the row tree let bn = ctx.block_number().await as BlockPrimaryIndex; + + let table_row_updates = if let ChangeType::Receipt(..) = ut { + let current_row_epoch = self.table.row.current_epoch().await?; + let current_row_keys = self + .table + .row + .keys_at(current_row_epoch) + .await + .into_iter() + .map(TableRowUpdate::::Deletion) + .collect::>(); + [current_row_keys, table_row_updates].concat() + } else { + table_row_updates + }; + log::info!("Applying follow up updates to contract done - now at block {bn}",); // we first run the initial preprocessing and db creation. // NOTE: we don't show copy on write here - the fact of only reproving what has been @@ -455,7 +801,7 @@ impl TableIndexing { false => Row::default(), }; let new_cell_collection = row_update.updated_cells_collection( - self.table.columns.secondary_column().identifier, + self.table.columns.secondary_column().identifier(), bn, &previous_row.payload.cells, ); @@ -488,7 +834,7 @@ impl TableIndexing { .await? .expect("unable to find previous row"); let new_cell_collection = row_update.updated_cells_collection( - self.table.columns.secondary_column().identifier, + self.table.columns.secondary_column().identifier(), bn, &old_row.cells, ); @@ -554,7 +900,7 @@ impl TableIndexing { expected_metadata_hash, ) .await; - info!("Generated final IVC proof for block {}", current_block,); + info!("Generated final IVC proof for block {}", current_block); Ok(()) } @@ -575,34 +921,39 @@ impl TableIndexing { proof } Err(_) => { - let contract_proof = ctx - .prove_contract_extraction( - &self.contract.address, - self.contract_extraction.slot.clone(), - bn, - ) - .await; - ctx.storage - .store_proof(contract_proof_key, contract_proof.clone())?; - info!( - "Generated Contract Extraction (C.3) proof for block number {}", - bn - ); - { - let pvk = ProofWithVK::deserialize(&contract_proof)?; - let pis = - contract_extraction::PublicInputs::from_slice(&pvk.proof().public_inputs); - debug!( - " CONTRACT storage root pis.storage_root() {:?}", - hex::encode( - pis.root_hash_field() - .into_iter() - .flat_map(|u| u.to_be_bytes()) - .collect::>() + if let Some(contract_extraction) = &self.contract_extraction { + let contract_proof = ctx + .prove_contract_extraction( + &self.contract.address, + contract_extraction.slot.clone(), + bn, ) + .await; + ctx.storage + .store_proof(contract_proof_key, contract_proof.clone())?; + info!( + "Generated Contract Extraction (C.3) proof for block number {}", + bn ); + { + let pvk = ProofWithVK::deserialize(&contract_proof)?; + let pis = contract_extraction::PublicInputs::from_slice( + &pvk.proof().public_inputs, + ); + debug!( + " CONTRACT storage root pis.storage_root() {:?}", + hex::encode( + pis.root_hash_field() + .into_iter() + .flat_map(|u| u.to_be_bytes()) + .collect::>() + ) + ); + } + contract_proof + } else { + vec![] } - contract_proof } }; @@ -631,7 +982,7 @@ impl TableIndexing { } }; - let table_id = &self.table.public_name.clone(); + let table_id = &self.table.public_name; // we construct the proof key for both mappings and single variable in the same way since // it is derived from the table id which should be different for any tables we create. let value_key = ProofKey::ValueExtraction((table_id.clone(), bn as BlockPrimaryIndex)); @@ -641,6 +992,7 @@ impl TableIndexing { .source .generate_extraction_proof_inputs(ctx, &self.contract, value_key) .await?; + // no need to generate it if it's already present if ctx.storage.get_proof_exact(&final_key).is_err() { let proof = ctx @@ -657,130 +1009,389 @@ impl TableIndexing { } } -#[derive(Clone, Debug)] -pub enum UpdateSimpleStorage { - Single(SimpleSingleValue), - Mapping(Vec), -} +/// Function that computes the column identifiers for the non-indexed columns together with their names as [`String`]s. +pub fn compute_non_indexed_receipt_column_ids< + const NO_TOPICS: usize, + const MAX_DATA_WORDS: usize, +>( + event: &EventLogInfo, +) -> Vec<(String, ColumnID)> { + let gas_used_column_id = + identifier_for_gas_used_column(&event.event_signature, &event.address, &[]); -/// Represents the update that can come from the chain -#[derive(Clone, Debug)] -pub enum MappingUpdate { - // key, value - Deletion(U256, U256), - // key, previous_value, new_value - Update(U256, U256, U256), - // key, value - Insertion(U256, U256), -} + let topic_ids = event + .topics + .iter() + .enumerate() + .map(|(j, _)| { + ( + format!("{}_{}", TOPIC_NAME, j + 1), + identifier_for_topic_column(&event.event_signature, &event.address, &[j as u8 + 1]), + ) + }) + .collect::>(); -/// passing form the rust type to the solidity type -impl From<&MappingUpdate> for MappingOperation { - fn from(value: &MappingUpdate) -> Self { - Self::from(match value { - MappingUpdate::Deletion(_, _) => 0, - MappingUpdate::Update(_, _, _) => 1, - MappingUpdate::Insertion(_, _) => 2, + let data_ids = event + .data + .iter() + .enumerate() + .map(|(j, _)| { + ( + format!("{}_{}", DATA_NAME, j + 1), + identifier_for_data_column(&event.event_signature, &event.address, &[j as u8 + 1]), + ) }) - } + .collect::>(); + + [ + vec![(GAS_USED_NAME.to_string(), gas_used_column_id)], + topic_ids, + data_ids, + ] + .concat() } -#[derive(Clone, Debug)] -pub struct SimpleSingleValue { - pub(crate) s1: bool, - pub(crate) s2: U256, - pub(crate) s3: String, - pub(crate) s4: Address, +/// Build the mapping table. +async fn build_mapping_table( + ctx: &TestContext, + mapping_index: &MappingIndex, + key_id: u64, + mut value_ids: Vec, +) -> Table { + // Construct the table columns. + let (secondary_column, rest_columns) = match mapping_index { + MappingIndex::OuterKey(_) => { + let secondary_column = TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: key_id, + }; + let rest_columns = value_ids + .into_iter() + .enumerate() + .map(|(i, id)| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + identifier: id, + }) + .collect_vec(); + + (secondary_column, rest_columns) + } + MappingIndex::Value(secondary_value_id) => { + let pos = value_ids + .iter() + .position(|id| id == secondary_value_id) + .unwrap(); + let secondary_id = value_ids.remove(pos); + + let secondary_column = TableColumn { + name: MAPPING_VALUE_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: secondary_id, + }; + let mut rest_columns = value_ids + .into_iter() + .enumerate() + .map(|(i, id)| TableColumn { + name: format!("{MAPPING_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + identifier: id, + }) + .collect_vec(); + rest_columns.push(TableColumn { + name: MAPPING_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + // The slot input is useless for the key column. + identifier: key_id, + }); + + (secondary_column, rest_columns) + } + _ => unreachable!(), + }; + // Defining the columns structure of the table from the source slots + // This is depending on what is our data source, mappings and CSV both have their o + // own way of defining their table. + let columns = TableColumns { + primary: TableColumn { + name: BLOCK_COLUMN_NAME.to_string(), + index: IndexType::Primary, + multiplier: false, + // Only valid for the identifier of block column, others are dummy. + identifier: identifier_block_column(), + }, + secondary: secondary_column, + rest: rest_columns, + }; + debug!("MAPPING ZK COLUMNS -> {:?}", columns); + let index_genesis_block = ctx.block_number().await; + let row_unique_id = TableRowUniqueID::Mapping(key_id); + Table::new( + index_genesis_block, + "mapping_table".to_string(), + columns, + row_unique_id, + ) + .await + .unwrap() } -impl UpdateSimpleStorage { - // This function applies the update in _one_ transaction so that Anvil only moves by one block - // so we can test the "subsequent block" - pub async fn apply_to, N: Network>( - &self, - contract: &SimpleInstance, - ) { - match self { - UpdateSimpleStorage::Single(ref single) => { - Self::update_single_values(contract, single).await +/// Build the mapping of mappings table. +async fn build_mapping_of_mappings_table( + ctx: &TestContext, + index: &MappingIndex, + outer_key_id: u64, + inner_key_id: u64, + value_ids: Vec, +) -> Table { + let mut rest_columns = value_ids + .into_iter() + .enumerate() + .map(|(i, id)| TableColumn { + name: format!("{MAPPING_OF_MAPPINGS_VALUE_COLUMN}_{i}"), + index: IndexType::None, + multiplier: false, + identifier: id, + }) + .collect_vec(); + + let secondary_column = match index { + MappingIndex::OuterKey(_) => { + rest_columns.push(TableColumn { + name: MAPPING_OF_MAPPINGS_INNER_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + identifier: inner_key_id, + }); + + TableColumn { + name: MAPPING_OF_MAPPINGS_OUTER_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: outer_key_id, } - UpdateSimpleStorage::Mapping(ref updates) => { - Self::update_mapping_values(contract, updates).await + } + MappingIndex::InnerKey(_) => { + rest_columns.push(TableColumn { + name: MAPPING_OF_MAPPINGS_OUTER_KEY_COLUMN.to_string(), + index: IndexType::None, + multiplier: false, + identifier: outer_key_id, + }); + + TableColumn { + name: MAPPING_OF_MAPPINGS_INNER_KEY_COLUMN.to_string(), + index: IndexType::Secondary, + multiplier: false, + identifier: inner_key_id, } } - } + MappingIndex::Value(secondary_value_id) => { + let pos = rest_columns + .iter() + .position(|col| &col.identifier() == secondary_value_id) + .unwrap(); + let mut secondary_column = rest_columns.remove(pos); + secondary_column.index = IndexType::Secondary; + let key_columns = [ + (outer_key_id, MAPPING_OF_MAPPINGS_OUTER_KEY_COLUMN), + (inner_key_id, MAPPING_OF_MAPPINGS_INNER_KEY_COLUMN), + ] + .map(|(id, name)| TableColumn { + name: name.to_string(), + index: IndexType::None, + multiplier: false, + identifier: id, + }); + rest_columns.extend(key_columns); - async fn update_single_values, N: Network>( - contract: &SimpleInstance, - values: &SimpleSingleValue, - ) { - let b = contract.setSimples(values.s1, values.s2, values.s3.clone(), values.s4); - b.send().await.unwrap().watch().await.unwrap(); - log::info!("Updated simple contract single values"); + secondary_column + } + _ => unreachable!(), + }; + + let columns = TableColumns { + primary: TableColumn { + name: BLOCK_COLUMN_NAME.to_string(), + index: IndexType::Primary, + multiplier: false, + identifier: identifier_block_column(), + }, + secondary: secondary_column, + rest: rest_columns, + }; + debug!("MAPPING OF MAPPINGS ZK COLUMNS -> {:?}", columns); + let index_genesis_block = ctx.block_number().await; + let row_unique_id = TableRowUniqueID::MappingOfMappings(outer_key_id, inner_key_id); + Table::new( + index_genesis_block, + "mapping_of_mappings_table".to_string(), + columns, + row_unique_id, + ) + .await + .unwrap() +} + +#[derive(Debug, Clone, Copy)] +pub struct ReceiptUpdate { + pub event_type: (u8, u8), + /// The number of events to emit related to the event defined by `event_type` + pub no_relevant: usize, + /// The number of other random events to emit. + pub no_others: usize, +} + +impl ReceiptUpdate { + /// Create a new [`ReceiptUpdate`] + pub fn new(event_type: (u8, u8), no_relevant: usize, no_others: usize) -> ReceiptUpdate { + ReceiptUpdate { + event_type, + no_relevant, + no_others, + } } - async fn update_mapping_values, N: Network>( - contract: &SimpleInstance, - values: &[MappingUpdate], + /// Apply an update to an [`EventEmitterInstance`]. + pub async fn apply_update>( + &self, + ctx: &TestContext, + contract: &EventEmitterInstance, ) { - let contract_changes = values - .iter() - .map(|tuple| { - let op: MappingOperation = tuple.into(); - let (k, v) = match tuple { - MappingUpdate::Deletion(k, _) => (*k, *DEFAULT_ADDRESS), - MappingUpdate::Update(k, _, v) | MappingUpdate::Insertion(k, v) => { - (*k, Address::from_slice(&v.to_be_bytes_trimmed_vec())) + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let addresses = ctx.local_node.as_ref().unwrap().addresses(); + + provider.anvil_set_auto_mine(false).await.unwrap(); + + provider.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![]; + + for j in 0..(self.no_relevant + self.no_others) { + let (tx_req, address_index) = { + let first_random = rand::random::() % 5; + let second_random = rand::random::() % 5; + let tx_req = if j < self.no_relevant { + self.select_event(contract) + } else { + let random = match first_random { + 0 => contract.testNoIndexed().into_transaction_request(), + 1 => contract.testTwoIndexed().into_transaction_request(), + 2 => contract.testThreeIndexed().into_transaction_request(), + 3 => contract.testOneData().into_transaction_request(), + 4 => contract.testTwoData().into_transaction_request(), + _ => unreachable!(), + }; + + let random_two = match second_random { + 0 => contract.testOneIOneD().into_transaction_request(), + 1 => contract.testTwoIOneD().into_transaction_request(), + 2 => contract.testTwoITwoD().into_transaction_request(), + 3 => contract.testNoIOneD().into_transaction_request(), + 4 => contract.testNoITwoD().into_transaction_request(), + _ => unreachable!(), + }; + match j % 2 { + 0 => random, + 1 => random_two, + _ => unreachable!(), } }; - MappingChange { - key: k, - value: v, - operation: op.into(), - } - }) - .collect::>(); + let address_index = rand::random::() % addresses.len(); + (tx_req, address_index) + }; + let sender_address = addresses[address_index]; - let b = contract.changeMapping(contract_changes); - b.send().await.unwrap().watch().await.unwrap(); - { - // sanity check - for op in values { - match op { - MappingUpdate::Deletion(k, _) => { - let res = contract.m1(*k).call().await.unwrap(); - let vu: U256 = res._0.into_word().into(); - let is_correct = vu == U256::from(0); - assert!(is_correct, "key deletion not correct on contract"); - } - MappingUpdate::Insertion(k, v) => { - let res = contract.m1(*k).call().await.unwrap(); - let newv: U256 = res._0.into_word().into(); - let is_correct = newv == *v; - assert!(is_correct, "key insertion not correct on contract"); - } - MappingUpdate::Update(k, _, v) => { - let res = contract.m1(*k).call().await.unwrap(); - let newv: U256 = res._0.into_word().into(); - let is_correct = newv == *v; - assert!(is_correct, "KEY Updated, new value valid ? {is_correct}"); - } - } - } + let funding = U256::from(1e18 as u64); + + provider + .anvil_set_balance(sender_address, funding) + .await + .unwrap(); + + let new_req = tx_req.with_from(sender_address); + let tx_req_final = provider + .fill(new_req) + .await + .unwrap() + .as_envelope() + .cloned() + .unwrap(); + pending_tx_builders.push(provider.send_tx_envelope(tx_req_final).await.unwrap()); + } + + provider + .anvil_auto_impersonate_account(false) + .await + .unwrap(); + provider.anvil_set_auto_mine(true).await.unwrap(); + + for pending_tx in pending_tx_builders { + pending_tx.watch().await.unwrap(); + } + } + + fn select_event, N: Network>( + &self, + contract: &EventEmitterInstance, + ) -> N::TransactionRequest { + match self.event_type { + (0, 0) => contract.testNoIndexed().into_transaction_request(), + (1, 0) => contract.testOneIndexed().into_transaction_request(), + (2, 0) => contract.testTwoIndexed().into_transaction_request(), + (3, 0) => contract.testThreeIndexed().into_transaction_request(), + (0, 1) => contract.testNoIOneD().into_transaction_request(), + (0, 2) => contract.testNoITwoD().into_transaction_request(), + (1, 1) => contract.testOneIOneD().into_transaction_request(), + (1, 2) => contract.testOneITwoD().into_transaction_request(), + (2, 1) => contract.testTwoIOneD().into_transaction_request(), + (2, 2) => contract.testTwoITwoD().into_transaction_request(), + (3, 1) => contract.testOneData().into_transaction_request(), + (3, 2) => contract.testTwoData().into_transaction_request(), + _ => contract.testNoIndexed().into_transaction_request(), } - log::info!("Updated simple contract single values"); } } -#[derive(Clone, Debug)] +pub trait ContractUpdate: std::fmt::Debug +where + T: Transport + Clone, +{ + type Contract; + + fn apply_to(&self, ctx: &TestContext, contract: &Self::Contract) -> impl Future; +} + +impl ContractUpdate for ReceiptUpdate +where + T: Transport + Clone, +{ + type Contract = EventEmitterInstance>; + + async fn apply_to(&self, ctx: &TestContext, contract: &Self::Contract) { + self.apply_update(ctx, contract).await + } +} +#[derive(Clone, Debug, Copy)] pub enum ChangeType { Deletion, Insertion, Update(UpdateType), Silent, + Receipt(usize, usize), } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Copy)] pub enum UpdateType { SecondaryIndex, Rest, @@ -832,7 +1443,7 @@ impl TableRowValues TableInfo { +impl TableIndexing { + pub fn table_info(&self) -> TableInfo { TableInfo { public_name: self.table.public_name.clone(), value_column: self.value_column.clone(), - chain_id: self.contract.chain_id, + chain_id: self.contract.chain_id(), columns: self.table.columns.clone(), contract_address: self.contract.address, source: self.source.clone(), + row_unique_id: self.table.row_unique_id.clone(), } } } diff --git a/mp2-v1/tests/common/cases/mod.rs b/mp2-v1/tests/common/cases/mod.rs index 991c2eef8..05dff5a8e 100644 --- a/mp2-v1/tests/common/cases/mod.rs +++ b/mp2-v1/tests/common/cases/mod.rs @@ -1,10 +1,7 @@ //! Define test cases use contract::Contract; -use mp2_v1::values_extraction::{ - identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, -}; +use mp2_v1::values_extraction::identifier_for_mapping_key_column; use table_source::{ContractExtractionArgs, TableSource}; use super::table::Table; @@ -12,14 +9,15 @@ use super::table::Table; pub mod contract; pub mod indexing; pub mod query; +pub mod slot_info; pub mod table_source; /// Test case definition -pub(crate) struct TableIndexing { +pub(crate) struct TableIndexing { pub(crate) table: Table, pub(crate) contract: Contract, - pub(crate) contract_extraction: ContractExtractionArgs, - pub(crate) source: TableSource, + pub(crate) contract_extraction: Option, + pub(crate) source: T, // the column over which we can do queries like ` y > 64`. It is not the address column that we // assume it the secondary index always. pub(crate) value_column: String, diff --git a/mp2-v1/tests/common/cases/query/aggregated_queries.rs b/mp2-v1/tests/common/cases/query/aggregated_queries.rs index 0d4194f84..688b4ec81 100644 --- a/mp2-v1/tests/common/cases/query/aggregated_queries.rs +++ b/mp2-v1/tests/common/cases/query/aggregated_queries.rs @@ -7,17 +7,16 @@ use crate::common::{ cases::{ indexing::BLOCK_COLUMN_NAME, query::{QueryCooking, SqlReturn, SqlType, NUM_CHUNKS, NUM_ROWS}, - table_source::BASE_VALUE, + table_source::{TableSource, BASE_VALUE}, }, proof_storage::{ProofKey, ProofStorage}, - rowtree::MerkleRowTree, table::Table, TableInfo, }; use crate::context::TestContext; use alloy::primitives::U256; -use anyhow::{bail, Result}; +use anyhow::Result; use futures::{stream, FutureExt, StreamExt}; use itertools::Itertools; @@ -34,11 +33,11 @@ use mp2_v1::{ self, block::BlockPrimaryIndex, cell::MerkleCell, - row::{Row, RowPayload, RowTreeKey}, + row::{MerkleRowTree, Row, RowPayload, RowTreeKey}, }, query::{ batching_planner::{generate_chunks_and_update_tree, UTForChunkProofs, UTKey}, - planner::{execute_row_query, NonExistenceInput, TreeFetcher}, + planner::{execute_row_query, NonExistenceInputIndex, NonExistenceInputRow, TreeFetcher}, }, }; use parsil::{ @@ -51,7 +50,7 @@ use ryhope::{ updatetree::{Next, WorkplanItem}, EpochKvStorage, RoEpochKvStorage, TreeTransactionalStorage, }, - Epoch, + UserEpoch, }; use sqlparser::ast::Query; use tokio_postgres::Row as PsqlRow; @@ -79,55 +78,56 @@ pub(crate) async fn prove_query( metadata: MetadataHash, planner: &mut QueryPlanner<'_>, ) -> Result<()> { - let row_cache = planner + let current_epoch = planner.table.index.current_epoch().await? as BlockPrimaryIndex; + let index_query = core_keys_for_index_tree( + current_epoch as UserEpoch, + (planner.query.min_block, planner.query.max_block), + &planner.table.index_table_name(), + )?; + let big_index_cache = planner .table - .row + .index + // The bounds here means between which versions of the tree should we look. For index tree, + // we only look at _one_ version of the tree. .wide_lineage_between( - planner.table.row.current_epoch(), - &core_keys_for_row_tree( - &planner.query.query, - planner.settings, - &planner.pis.bounds, - &planner.query.placeholders, - )?, - ( - planner.query.min_block as Epoch, - planner.query.max_block as Epoch, - ), + current_epoch as UserEpoch, + &index_query, + (current_epoch as UserEpoch, current_epoch as UserEpoch), ) .await?; // prove the index tree, on a single version. Both path can be taken depending if we do have // some nodes or not - let initial_epoch = planner.table.index.initial_epoch() as BlockPrimaryIndex; - let current_epoch = planner.table.index.current_epoch() as BlockPrimaryIndex; + let initial_epoch = planner.table.genesis_block; let block_range = - planner.query.min_block.max(initial_epoch + 1)..=planner.query.max_block.min(current_epoch); + planner.query.min_block.max(initial_epoch)..=planner.query.max_block.min(current_epoch); + let num_blocks_in_range = big_index_cache.num_touched_rows(); info!( "found {} blocks in range: {:?}", - block_range.clone().count(), - block_range + num_blocks_in_range, block_range ); let column_ids = ColumnIDs::from(&planner.table.columns); - let query_proof_id = if block_range.is_empty() { + let query_proof_id = if num_blocks_in_range == 0 { info!("Running INDEX TREE proving for EMPTY query"); - // no valid blocks in the query range, so we need to choose a block to prove - // non-existence. Either the one after genesis or the last one - let to_be_proven_node = if planner.query.max_block < initial_epoch { - initial_epoch + 1 - } else if planner.query.min_block > current_epoch { - current_epoch - } else { - bail!( + let to_be_proven_node = NonExistenceInputIndex::new( + &planner.table.index, + planner.table.index_table_name().to_string(), + &planner.table.db_pool, + planner.settings, + &planner.pis.bounds, + ) + .find_node_for_non_existence(current_epoch as BlockPrimaryIndex) + .await + .unwrap_or_else(|_| { + panic!( "Empty block range to be proven for query bounds {}, {}, but no node to be proven with non-existence circuit was found. Something is wrong", - planner.query.min_block, - planner.query.max_block - ); - } as BlockPrimaryIndex; + planner.query.min_block, planner.query.max_block + ) + }); let index_path = planner .table .index - .compute_path(&to_be_proven_node, current_epoch as Epoch) + .compute_path(&to_be_proven_node, current_epoch as UserEpoch) .await .unwrap_or_else(|| { panic!("Compute path for index node with key {to_be_proven_node} failed") @@ -154,36 +154,46 @@ pub(crate) async fn prove_query( .store_proof(proof_key.clone(), query_proof)?; proof_key } else { - info!("Running INDEX tree proving from cache"); - // Only here we can run the SQL query for index so it doesn't crash - let index_query = core_keys_for_index_tree( - current_epoch as Epoch, - (planner.query.min_block, planner.query.max_block), - )?; - let big_index_cache = planner + info!( + "Row cache query: {}", + &core_keys_for_row_tree( + &planner.query.query, + planner.settings, + &planner.pis.bounds, + &planner.query.placeholders, + )? + ); + let row_cache = planner .table - .index - // The bounds here means between which versions of the tree should we look. For index tree, - // we only look at _one_ version of the tree. + .row .wide_lineage_between( - current_epoch as Epoch, - &index_query, - (current_epoch as Epoch, current_epoch as Epoch), + planner.table.row.current_epoch().await?, + &core_keys_for_row_tree( + &planner.query.query, + planner.settings, + &planner.pis.bounds, + &planner.query.placeholders, + )?, + ( + planner.query.min_block as UserEpoch, + planner.query.max_block as UserEpoch, + ), ) .await?; + info!("Running INDEX tree proving from cache"); let (proven_chunks, update_tree) = generate_chunks_and_update_tree::( row_cache, big_index_cache, &column_ids, - NonExistenceInput::new( + NonExistenceInputRow::new( &planner.table.row, planner.table.public_name.clone(), &planner.table.db_pool, planner.settings, &planner.pis.bounds, ), - current_epoch as Epoch, + current_epoch as UserEpoch, ) .await?; info!("Root of update tree is {:?}", update_tree.root()); @@ -252,7 +262,7 @@ pub(crate) async fn prove_query( planner.ctx, &planner.query, planner.pis, - planner.table.index.current_epoch(), + planner.table.index.current_epoch().await?, &query_proof_id, ) .await?; @@ -280,7 +290,7 @@ pub(crate) async fn prove_query( planner.table, &planner.query, &pis, - planner.table.index.current_epoch(), + planner.table.index.current_epoch().await?, num_touched_rows, res, metadata, @@ -293,7 +303,7 @@ async fn prove_revelation( ctx: &TestContext, query: &QueryCooking, pis: &DynamicCircuitPis, - tree_epoch: Epoch, + tree_epoch: UserEpoch, query_proof_id: &ProofKey, ) -> Result> { // load the query proof, which is at the root of the tree @@ -325,7 +335,7 @@ pub(crate) fn check_final_outputs( table: &Table, query: &QueryCooking, pis: &StaticCircuitPis, - tree_epoch: Epoch, + tree_epoch: UserEpoch, num_touched_rows: usize, res: Vec, offcircuit_md: MetadataHash, @@ -424,11 +434,11 @@ pub(crate) fn check_final_outputs( type BlockRange = (BlockPrimaryIndex, BlockPrimaryIndex); -pub(crate) async fn cook_query_between_blocks( +pub(crate) async fn cook_query_between_blocks( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { - let max = table.row.current_epoch(); + let max = table.row.current_epoch().await?; let min = max - 1; let value_column = &info.value_column; @@ -451,12 +461,12 @@ pub(crate) async fn cook_query_between_blocks( }) } -pub(crate) async fn cook_query_secondary_index_nonexisting_placeholder( +pub(crate) async fn cook_query_secondary_index_nonexisting_placeholder( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; - let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); + let key_value = encode_hex(longest_key.value); info!( "Longest sequence is for key {longest_key:?} -> from block {:?} to {:?}, hex -> {}", min_block, max_block, key_value @@ -499,12 +509,12 @@ pub(crate) async fn cook_query_secondary_index_nonexisting_placeholder( // cook up a SQL query on the secondary index and with a predicate on the non-indexed column. // we just iterate on mapping keys and take the one that exist for most blocks. We also choose // a value to filter over the non-indexed column -pub(crate) async fn cook_query_secondary_index_placeholder( +pub(crate) async fn cook_query_secondary_index_placeholder( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; - let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); + let key_value = encode_hex(longest_key.value); info!( "Longest sequence is for key {longest_key:?} -> from block {:?} to {:?}, hex -> {}", min_block, max_block, key_value @@ -544,12 +554,12 @@ pub(crate) async fn cook_query_secondary_index_placeholder( // cook up a SQL query on the secondary index. For that we just iterate on mapping keys and // take the one that exist for most blocks -pub(crate) async fn cook_query_unique_secondary_index( +pub(crate) async fn cook_query_unique_secondary_index( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; - let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); + let key_value = encode_hex(longest_key.value); info!( "Longest sequence is for key {longest_key:?} -> from block {:?} to {:?}, hex -> {}", min_block, max_block, key_value @@ -620,12 +630,12 @@ pub(crate) async fn cook_query_unique_secondary_index( }) } -pub(crate) async fn cook_query_partial_block_range( +pub(crate) async fn cook_query_partial_block_range( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; - let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); + let key_value = encode_hex(longest_key.value); info!( "Longest sequence is for key {longest_key:?} -> from block {:?} to {:?}, hex -> {}", min_block, max_block, key_value @@ -634,7 +644,7 @@ pub(crate) async fn cook_query_partial_block_range( let key_column = table.columns.secondary.name.clone(); let value_column = info.value_column.clone(); let table_name = &table.public_name; - let initial_epoch = table.row.initial_epoch(); + let initial_epoch = table.row.initial_epoch().await; // choose a min query bound smaller than initial epoch let min_block = initial_epoch - 1; let placeholders = Placeholders::new_empty(U256::from(min_block), U256::from(max_block)); @@ -656,11 +666,11 @@ pub(crate) async fn cook_query_partial_block_range( }) } -pub(crate) async fn cook_query_no_matching_entries( +pub(crate) async fn cook_query_no_matching_entries( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { - let initial_epoch = table.row.initial_epoch(); + let initial_epoch = table.row.initial_epoch().await; // choose query bounds outside of the range [initial_epoch, last_epoch] let min_block = 0; let max_block = initial_epoch - 1; @@ -688,12 +698,12 @@ pub(crate) async fn cook_query_no_matching_entries( /// Cook a query where there are no entries satisying the secondary query bounds only for some /// blocks of the primary index bounds (not for all the blocks) -pub(crate) async fn cook_query_non_matching_entries_some_blocks( +pub(crate) async fn cook_query_non_matching_entries_some_blocks( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, true).await?; - let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); + let key_value = encode_hex(longest_key.value); info!( "Longest sequence is for key {longest_key:?} -> from block {:?} to {:?}, hex -> {}", min_block, max_block, key_value @@ -704,8 +714,8 @@ pub(crate) async fn cook_query_non_matching_entries_some_blocks( let table_name = &table.public_name; // in this query we set query bounds on block numbers to the widest range, so that we // are sure that there are blocks where the chosen key is not alive - let min_block = table.row.initial_epoch() + 1; - let max_block = table.row.current_epoch(); + let min_block = table.genesis_block; + let max_block = table.row.current_epoch().await?; let placeholders = Placeholders::new_empty(U256::from(min_block), U256::from(max_block)); let query_str = format!( @@ -727,13 +737,13 @@ pub(crate) async fn cook_query_non_matching_entries_some_blocks( /// Utility function to associated to each row in the tree, the blocks where the row /// was valid -async fn extract_row_liveness(table: &Table) -> Result>> { +async fn extract_row_liveness(table: &Table) -> Result>> { let mut all_table = HashMap::new(); - let max = table.row.current_epoch(); - let min = table.row.initial_epoch() + 1; - for block in (min..=max).rev() { + let current_epoch = table.index.current_epoch().await?; + let epochs = table.index.keys_at(current_epoch).await; + for block in epochs { println!("Querying for block {block}"); - let rows = collect_all_at(&table.row, block).await?; + let rows = collect_all_at(&table.row, block as UserEpoch).await?; debug!( "Collecting {} rows at epoch {} (rows_keys {:?})", rows.len(), @@ -742,7 +752,7 @@ async fn extract_row_liveness(table: &Table) -> Result Result<(RowTreeKey, BlockRange)> { - let initial_epoch = table.row.initial_epoch() + 1; - let last_epoch = table.row.current_epoch(); + let initial_epoch = table.genesis_block as UserEpoch; + let last_epoch = table.row.current_epoch().await?; let all_table = extract_row_liveness(table).await?; + let consecutive_epochs = { + let mut epochs = table.index.keys_at(last_epoch).await; + epochs.sort_unstable(); + epochs + .windows(2) + .map(|w| (w[0] as i64, w[1] as i64)) + .collect::>() + }; // find the longest running row - let (longest_key, longest_sequence, starting) = all_table + let (longest_key, _, starting, ending) = all_table .iter() .filter_map(|(k, epochs)| { // simplification here to start at first epoch where this row was. Otherwise need to do // longest consecutive sequence etc... - let (l, start) = find_longest_consecutive_sequence(epochs.to_vec()); + let (l, start, end) = find_longest_consecutive_sequence(epochs, &consecutive_epochs); debug!("finding sequence of {l} blocks for key {k:?} (epochs {epochs:?}"); if must_not_be_alive_in_some_blocks { - if start > initial_epoch || (start + l as i64) < last_epoch { - Some((k, l, start)) + if start > initial_epoch || end < last_epoch { + Some((k, l, start, end)) } else { None // it's live for all blocks, so we drop this row } } else { - Some((k, l, start)) + Some((k, l, start, end)) } }) - .max_by_key(|(_k, l, _start)| *l) + .max_by_key(|(_k, l, _start, _end)| *l) .unwrap_or_else(|| { panic!( "unable to find longest row? -> length all _table {}, max {}", @@ -790,11 +808,14 @@ pub(crate) async fn find_longest_lived_key( }); // we set the block bounds let min_block = starting as BlockPrimaryIndex; - let max_block = min_block + longest_sequence; + let max_block = ending as BlockPrimaryIndex; Ok((longest_key.clone(), (min_block, max_block))) } -async fn collect_all_at(tree: &MerkleRowTree, at: Epoch) -> Result>> { +async fn collect_all_at( + tree: &MerkleRowTree, + at: UserEpoch, +) -> Result>> { let root_key = tree.root_at(at).await?.unwrap(); let (ctx, payload) = tree .try_fetch_with_context_at(&root_key, at) @@ -835,18 +856,29 @@ async fn collect_all_at(tree: &MerkleRowTree, at: Epoch) -> Result) -> (usize, i64) { - let mut longest = 0; +fn find_longest_consecutive_sequence( + v: &[i64], + consecutive_epochs: &HashMap, +) -> (usize, i64, i64) { + let mut current = 0; let mut starting_idx = 0; + let mut longest = (0, 0); + let mut update_longest = |current, idx| { + if current > (longest.1 - longest.0) { + longest = (starting_idx, idx) + } + starting_idx = idx + 1; + }; for i in 0..v.len() - 1 { - if v[i] + 1 == v[i + 1] { - longest += 1; + if *consecutive_epochs.get(&v[i]).unwrap() == v[i + 1] { + current += 1; } else { - longest = 0; - starting_idx = i + 1; + update_longest(current, i); + current = 0; } } - (longest, v[starting_idx]) + update_longest(current, v.len() - 1); + (longest.1 - longest.0, v[longest.0], v[longest.1]) } #[allow(dead_code)] @@ -882,3 +914,12 @@ async fn check_correct_cells_tree( ); Ok(()) } + +/// Function for encoding [`U256`] values as hex strings which accounts for the value being zero +fn encode_hex(value: U256) -> String { + if value != U256::ZERO { + hex::encode(value.to_be_bytes_trimmed_vec()) + } else { + hex::encode([0]) + } +} diff --git a/mp2-v1/tests/common/cases/query/mod.rs b/mp2-v1/tests/common/cases/query/mod.rs index 7887e635d..03c6fddf1 100644 --- a/mp2-v1/tests/common/cases/query/mod.rs +++ b/mp2-v1/tests/common/cases/query/mod.rs @@ -5,7 +5,7 @@ use aggregated_queries::{ cook_query_unique_secondary_index, prove_query as prove_aggregation_query, }; use alloy::primitives::U256; -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use itertools::Itertools; use log::info; use mp2_v1::{ @@ -31,7 +31,7 @@ use crate::common::{ TableInfo, TestContext, }; -use super::table_source::TableSource; +use super::TableSource; pub mod aggregated_queries; pub mod simple_select_queries; @@ -103,15 +103,25 @@ pub(crate) struct QueryPlanner<'a> { pub(crate) columns: TableColumns, } -pub async fn test_query(ctx: &mut TestContext, table: Table, t: TableInfo) -> Result<()> { - match &t.source { - TableSource::Mapping(_) | TableSource::Merge(_) => query_mapping(ctx, &table, &t).await?, - _ => unimplemented!("yet"), +pub async fn test_query( + ctx: &mut TestContext, + table: Table, + t: TableInfo, +) -> Result<()> { + if t.source.can_query() { + query_mapping(ctx, &table, &t).await?; + } else { + return Err(anyhow!("Can't query this type of table source yet")); } + Ok(()) } -async fn query_mapping(ctx: &mut TestContext, table: &Table, info: &TableInfo) -> Result<()> { +async fn query_mapping( + ctx: &mut TestContext, + table: &Table, + info: &TableInfo, +) -> Result<()> { let table_hash = info.metadata_hash(); let query_info = cook_query_between_blocks(table, info).await?; test_query_mapping(ctx, table, query_info, &table_hash).await?; diff --git a/mp2-v1/tests/common/cases/query/simple_select_queries.rs b/mp2-v1/tests/common/cases/query/simple_select_queries.rs index 6531a2117..70b7611e5 100644 --- a/mp2-v1/tests/common/cases/query/simple_select_queries.rs +++ b/mp2-v1/tests/common/cases/query/simple_select_queries.rs @@ -19,7 +19,7 @@ use parsil::{ }; use ryhope::{ storage::{pgsql::ToFromBytea, RoEpochKvStorage}, - Epoch, NodePayload, + NodePayload, UserEpoch, }; use sqlparser::ast::Query; use std::{fmt::Debug, hash::Hash}; @@ -34,17 +34,20 @@ use verifiable_db::{ test_utils::MAX_NUM_OUTPUTS, }; -use crate::common::{ - cases::{ - indexing::BLOCK_COLUMN_NAME, - query::{ - aggregated_queries::{check_final_outputs, find_longest_lived_key}, - GlobalCircuitInput, QueryPlanner, RevelationCircuitInput, SqlReturn, SqlType, +use crate::{ + common::{ + cases::{ + indexing::BLOCK_COLUMN_NAME, + query::{ + aggregated_queries::{check_final_outputs, find_longest_lived_key}, + GlobalCircuitInput, QueryPlanner, RevelationCircuitInput, SqlReturn, SqlType, + }, }, + proof_storage::{ProofKey, ProofStorage}, + table::{Table, TableColumns}, + TableInfo, }, - proof_storage::{ProofKey, ProofStorage}, - table::{Table, TableColumns}, - TableInfo, + TableSource, }; use super::{QueryCircuitInput, QueryCooking, TestContext}; @@ -69,7 +72,7 @@ pub(crate) async fn prove_query( .iter() .map(|row| { let key = RowTreeKey::from_bytea(row.try_get::<_, &[u8]>(0)?.to_vec()); - let epoch = row.try_get::<_, Epoch>(1)?; + let epoch = row.try_get::<_, UserEpoch>(1)?; // all the other items are query results let result = (2..row.len()) .filter_map(|i| { @@ -82,7 +85,7 @@ pub(crate) async fn prove_query( }) .collect::>>()?; // compute input for each matching row - let current_epoch = planner.table.index.current_epoch(); + let current_epoch = planner.table.index.current_epoch().await?; let mut matching_rows_input = vec![]; for (key, epoch, result) in matching_rows.into_iter() { let row_proof = prove_single_row( @@ -157,7 +160,7 @@ pub(crate) async fn prove_query( async fn get_path_info>( key: &K, tree_info: &T, - epoch: Epoch, + epoch: UserEpoch, ) -> Result<(Vec<(NodeInfo, ChildPosition)>, Vec>)> where K: Debug + Hash + Clone + Send + Sync + Eq, @@ -253,7 +256,7 @@ pub(crate) async fn prove_single_row( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); @@ -351,9 +354,9 @@ pub(crate) async fn cook_query_with_max_num_matching_rows( }) } -pub(crate) async fn cook_query_with_matching_rows( +pub(crate) async fn cook_query_with_matching_rows( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); @@ -397,9 +400,9 @@ pub(crate) async fn cook_query_with_matching_rows( } /// Cook a query where the offset is big enough to have no matching rows -pub(crate) async fn cook_query_too_big_offset( +pub(crate) async fn cook_query_too_big_offset( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); @@ -440,12 +443,12 @@ pub(crate) async fn cook_query_too_big_offset( }) } -pub(crate) async fn cook_query_no_matching_rows( +pub(crate) async fn cook_query_no_matching_rows( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { - let initial_epoch = table.index.initial_epoch(); - let current_epoch = table.index.current_epoch(); + let initial_epoch = table.index.initial_epoch().await; + let current_epoch = table.index.current_epoch().await?; let min_block = initial_epoch as BlockPrimaryIndex; let max_block = current_epoch as BlockPrimaryIndex; @@ -486,9 +489,9 @@ pub(crate) async fn cook_query_no_matching_rows( }) } -pub(crate) async fn cook_query_with_distinct( +pub(crate) async fn cook_query_with_distinct( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); @@ -529,10 +532,10 @@ pub(crate) async fn cook_query_with_distinct( }) } -pub(crate) async fn cook_query_with_wildcard( +pub(crate) async fn cook_query_with_wildcard( table: &Table, distinct: bool, - info: &TableInfo, + info: &TableInfo, ) -> Result { let (longest_key, (min_block, max_block)) = find_longest_lived_key(table, false).await?; let key_value = hex::encode(longest_key.value.to_be_bytes_trimmed_vec()); @@ -583,16 +586,16 @@ pub(crate) async fn cook_query_with_wildcard( }) } -pub(crate) async fn cook_query_with_wildcard_no_distinct( +pub(crate) async fn cook_query_with_wildcard_no_distinct( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { cook_query_with_wildcard(table, false, info).await } -pub(crate) async fn cook_query_with_wildcard_and_distinct( +pub(crate) async fn cook_query_with_wildcard_and_distinct( table: &Table, - info: &TableInfo, + info: &TableInfo, ) -> Result { cook_query_with_wildcard(table, true, info).await } diff --git a/mp2-v1/tests/common/cases/slot_info.rs b/mp2-v1/tests/common/cases/slot_info.rs new file mode 100644 index 000000000..1e099df05 --- /dev/null +++ b/mp2-v1/tests/common/cases/slot_info.rs @@ -0,0 +1,643 @@ +//! Mapping key, storage value types and related functions for the storage slot + +use crate::common::{ + bindings::simple::{ + self, + Simple::{ + mappingOfStructMappingsReturn, simpleStructReturn, structMappingReturn, MappingChange, + MappingOfSingleValueMappingsChange, MappingOfStructMappingsChange, MappingOperation, + MappingStructChange, SimpleInstance, + }, + }, + Deserialize, Serialize, +}; +use alloy::{ + network::Network, + primitives::{Address, U256}, + providers::Provider, + transports::Transport, +}; +use derive_more::Constructor; +use itertools::Itertools; +use log::warn; +use mp2_common::{ + eth::{StorageSlot, StorageSlotNode}, + types::MAPPING_LEAF_VALUE_LEN, +}; +use mp2_v1::api::{SlotInput, SlotInputs}; +use rand::{thread_rng, Rng}; + +use std::{array, fmt::Debug, future::Future, hash::Hash}; + +use super::contract::MappingUpdate; + +pub(crate) trait MappingInfo: StorageSlotMappingKey { + type Value: StorageSlotValue; + type Call; + fn to_call(update: &MappingUpdate) -> Self::Call; + + fn call_contract, N: Network>( + contract: &SimpleInstance, + changes: Vec, + ) -> impl Future + Send; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct SimpleMapping { + inner: U256, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct StructMapping { + inner: U256, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct SimpleNestedMapping { + outer: U256, + inner: U256, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Hash)] +pub struct StructNestedMapping { + outer: U256, + inner: U256, +} + +impl StorageSlotMappingKey for StructNestedMapping { + type Key = U256; + + const NO_KEYS: usize = 2; + + fn sample_key() -> Self { + let rng = &mut thread_rng(); + StructNestedMapping { + outer: U256::from_limbs(rng.gen()), + inner: U256::from_limbs(rng.gen()), + } + } + + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::MappingOfMappings(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![self.outer, self.inner] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = { + let parent_slot = StorageSlot::Mapping(self.outer.to_be_bytes_vec(), slot as usize); + StorageSlot::Node( + StorageSlotNode::new_mapping(parent_slot, self.inner.to_be_bytes_vec()).unwrap(), + ) + }; + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +impl MappingInfo for StructNestedMapping { + type Value = LargeStruct; + type Call = MappingOfStructMappingsChange; + fn to_call(update: &MappingUpdate) -> MappingOfStructMappingsChange { + let op: MappingOperation = update.into(); + + let (key, value) = update.to_tuple(); + + MappingOfStructMappingsChange { + outerKey: key.outer, + innerKey: key.inner, + field1: value.field1, + field2: value.field2, + field3: value.field3, + operation: op.into(), + } + } + + async fn call_contract, N: Network>( + contract: &SimpleInstance, + changes: Vec, + ) { + let call = contract.changeMapping_0(changes); + call.send().await.unwrap().watch().await.unwrap(); + } +} + +impl StorageSlotMappingKey for SimpleNestedMapping { + type Key = U256; + + const NO_KEYS: usize = 2; + + fn sample_key() -> Self { + let rng = &mut thread_rng(); + SimpleNestedMapping { + outer: U256::from_limbs(rng.gen()), + inner: U256::from_limbs(rng.gen()), + } + } + + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::MappingOfMappings(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![self.outer, self.inner] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = { + let parent_slot = StorageSlot::Mapping(self.outer.to_be_bytes_vec(), slot as usize); + StorageSlot::Node( + StorageSlotNode::new_mapping(parent_slot, self.inner.to_be_bytes_vec()).unwrap(), + ) + }; + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +impl MappingInfo for SimpleNestedMapping { + type Value = U256; + type Call = MappingOfSingleValueMappingsChange; + fn to_call(update: &MappingUpdate) -> MappingOfSingleValueMappingsChange { + let op: MappingOperation = update.into(); + + let (key, value) = update.to_tuple(); + + MappingOfSingleValueMappingsChange { + outerKey: key.outer, + innerKey: key.inner, + value, + operation: op.into(), + } + } + + async fn call_contract, N: Network>( + contract: &SimpleInstance, + changes: Vec, + ) { + let call = contract.changeMapping_2(changes); + call.send().await.unwrap().watch().await.unwrap(); + } +} + +impl StorageSlotMappingKey for SimpleMapping { + type Key = U256; + + const NO_KEYS: usize = 1; + + fn sample_key() -> Self { + SimpleMapping { + inner: sample_u256(), + } + } + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::Mapping(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![self.inner] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = StorageSlot::Mapping(self.inner.to_be_bytes_vec(), slot as usize); + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +impl MappingInfo for SimpleMapping { + type Value = Address; + type Call = MappingChange; + + fn to_call(update: &MappingUpdate) -> Self::Call { + let op: MappingOperation = update.into(); + + let (key, value) = update.to_tuple(); + + MappingChange { + key: key.inner, + value, + operation: op.into(), + } + } + + async fn call_contract, N: Network>( + contract: &SimpleInstance, + changes: Vec, + ) { + let call = contract.changeMapping_1(changes); + call.send().await.unwrap().watch().await.unwrap(); + } +} + +impl StorageSlotMappingKey for StructMapping { + type Key = U256; + + const NO_KEYS: usize = 1; + + fn sample_key() -> Self { + StructMapping { + inner: sample_u256(), + } + } + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::Mapping(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![self.inner] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = StorageSlot::Mapping(self.inner.to_be_bytes_vec(), slot as usize); + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +impl MappingInfo for StructMapping { + type Value = LargeStruct; + type Call = MappingStructChange; + + fn to_call(update: &MappingUpdate) -> MappingStructChange { + let op: MappingOperation = update.into(); + + let (key, value) = update.to_tuple(); + + MappingStructChange { + key: key.inner, + field1: value.field1, + field2: value.field2, + field3: value.field3, + operation: op.into(), + } + } + + async fn call_contract, N: Network>( + contract: &SimpleInstance, + changes: Vec, + ) { + let call = contract.changeMapping_3(changes); + call.send().await.unwrap().watch().await.unwrap(); + } +} + +/// Abstract for the mapping key of the storage slot. +/// It could be a normal mapping key, or a pair of keys which identifies the +/// mapping of mapppings key. +pub trait StorageSlotMappingKey: Clone + Debug + PartialOrd + Ord + Send + Sync { + /// This is what the keys actually look like. + type Key; + + /// How many keys there are + const NO_KEYS: usize; + + /// Generate a random key for testing. + fn sample_key() -> Self; + + /// Construct an SlotInputs enum. + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs; + + /// Convert into an Uint256 vector. + fn to_u256_vec(&self) -> Vec; + + /// Construct a storage slot for a mapping entry. + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot; +} + +pub(crate) type MappingKey = U256; + +impl StorageSlotMappingKey for MappingKey { + type Key = U256; + + const NO_KEYS: usize = 1; + + fn sample_key() -> Self { + sample_u256() + } + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::Mapping(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![*self] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = StorageSlot::Mapping(self.to_be_bytes_vec(), slot as usize); + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +#[derive( + Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Constructor, +)] +pub(crate) struct MappingOfMappingsKey { + pub(crate) outer_key: U256, + pub(crate) inner_key: U256, +} + +impl StorageSlotMappingKey for MappingOfMappingsKey { + type Key = U256; + + const NO_KEYS: usize = 2; + + fn sample_key() -> Self { + let [outer_key, inner_key] = array::from_fn(|_| MappingKey::sample_key()); + Self::new(outer_key, inner_key) + } + fn slot_inputs(slot_inputs: Vec, length: Option) -> SlotInputs { + if let Some(length_slot) = length { + SlotInputs::MappingWithLength(slot_inputs, length_slot) + } else { + SlotInputs::MappingOfMappings(slot_inputs) + } + } + fn to_u256_vec(&self) -> Vec { + vec![self.outer_key, self.inner_key] + } + fn storage_slot(&self, slot: u8, evm_word: u32) -> StorageSlot { + let storage_slot = { + let parent_slot = StorageSlot::Mapping(self.outer_key.to_be_bytes_vec(), slot as usize); + StorageSlot::Node( + StorageSlotNode::new_mapping(parent_slot, self.inner_key.to_be_bytes_vec()) + .unwrap(), + ) + }; + if evm_word == 0 { + // We could construct the mapping slot for the EVM word of 0 directly even if the + // mapping value is a Struct, since the returned storage slot is only used to compute + // the slot location, and it's same with the Struct mapping and the EVM word of 0. + return storage_slot; + } + + // It's definitely a Struct if the EVM word is non zero. + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } +} + +/// Abstract for the value saved in the storage slot. +/// It could be a single value as Uint256 or a Struct. +pub trait StorageSlotValue: Clone + Send + Sync { + /// The number of fields this value has. + const NUM_FIELDS: usize; + + /// Generate a random value for testing. + fn sample_value() -> Self; + + /// Update the slot input specified field to a random value. + fn random_update(&mut self, slot_input_to_update: &SlotInput); + + /// Convert from an Uint256 vector. + fn from_u256_slice(u: &[U256]) -> Self; + + /// Convert into an Uint256 vector. + fn to_u256_vec(&self) -> Vec; +} + +impl StorageSlotValue for Address { + const NUM_FIELDS: usize = 1; + + fn sample_value() -> Self { + Address::random() + } + fn random_update(&mut self, _: &SlotInput) { + loop { + let new_addr = Self::sample_value(); + if &new_addr != self { + *self = new_addr; + break; + } + warn!("Generated the same address"); + } + } + fn from_u256_slice(u: &[U256]) -> Self { + assert_eq!(u.len(), 1, "Must convert from one U256"); + + Address::from_slice(&u[0].to_be_bytes_trimmed_vec()) + } + fn to_u256_vec(&self) -> Vec { + vec![U256::from_be_slice(self.as_ref())] + } +} + +impl StorageSlotValue for U256 { + const NUM_FIELDS: usize = 1; + + fn sample_value() -> Self { + U256::from(sample_u128()) // sample as u128 to be safe for overflow in queries + } + fn random_update(&mut self, _: &SlotInput) { + loop { + let new_value = Self::sample_value(); + if &new_value != self { + *self = new_value; + break; + } + warn!("Generated the same Uint256"); + } + } + fn from_u256_slice(u: &[U256]) -> Self { + assert_eq!(u.len(), 1, "Should be one U256"); + + u[0] + } + fn to_u256_vec(&self) -> Vec { + vec![*self] + } +} + +fn sample_u256() -> U256 { + let rng = &mut thread_rng(); + let sampled: u64 = rng.gen(); + U256::from(sampled) +} + +fn sample_u128() -> u128 { + let rng = &mut thread_rng(); + rng.gen() +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize, Copy)] +pub struct LargeStruct { + pub(crate) field1: U256, + pub(crate) field2: u128, + pub(crate) field3: u128, +} + +impl StorageSlotValue for LargeStruct { + const NUM_FIELDS: usize = 3; + + fn sample_value() -> Self { + let field1 = U256::from(sample_u128()); // sample as u128 to be safe for overflow in queries + let [field2, field3] = array::from_fn(|_| sample_u128()); + + Self { + field1, + field2, + field3, + } + } + fn random_update(&mut self, slot_input_to_update: &SlotInput) { + let field_index = LargeStruct::slot_inputs(slot_input_to_update.slot()) + .iter() + .position(|slot_input| slot_input == slot_input_to_update) + .unwrap(); + let rng = &mut thread_rng(); + let diff = rng.gen_range(1..100); + if field_index == 0 { + self.field1 += U256::from(diff); + } else if field_index == 1 { + self.field2 += diff; + } else if field_index == 2 { + self.field3 += diff; + } else { + panic!("Wrong Struct field index"); + } + } + fn from_u256_slice(u: &[U256]) -> Self { + assert_eq!(u.len(), 3, "Must convert from three U256 for LargeStruct"); + + let field1 = u[0]; + let field2 = u[1].to(); + let field3 = u[2].to(); + + Self { + field1, + field2, + field3, + } + } + fn to_u256_vec(&self) -> Vec { + let [field2, field3] = [self.field2, self.field3].map(U256::from); + vec![self.field1, field2, field3] + } +} + +impl LargeStruct { + pub fn new(field1: U256, field2: u128, field3: u128) -> Self { + Self { + field1, + field2, + field3, + } + } + + pub fn slot_inputs(slot: u8) -> Vec { + vec![ + SlotInput::new(slot, 0, 32, 0), + // Big-endian layout + SlotInput::new(slot, 16, 16, 1), + SlotInput::new(slot, 0, 16, 1), + ] + } +} + +impl From for LargeStruct { + fn from(res: simpleStructReturn) -> Self { + Self { + field1: res.field1, + field2: res.field2, + field3: res.field3, + } + } +} + +impl From for LargeStruct { + fn from(res: structMappingReturn) -> Self { + Self { + field1: res.field1, + field2: res.field2, + field3: res.field3, + } + } +} + +impl From for LargeStruct { + fn from(res: mappingOfStructMappingsReturn) -> Self { + Self { + field1: res.field1, + field2: res.field2, + field3: res.field3, + } + } +} + +impl From<&[[u8; MAPPING_LEAF_VALUE_LEN]]> for LargeStruct { + fn from(fields: &[[u8; MAPPING_LEAF_VALUE_LEN]]) -> Self { + assert_eq!(fields.len(), Self::NUM_FIELDS); + + let fields = fields + .iter() + .cloned() + .map(U256::from_be_bytes) + .collect_vec(); + + let field1 = fields[0]; + let field2 = fields[1].to(); + let field3 = fields[2].to(); + Self { + field1, + field2, + field3, + } + } +} + +impl From for simple::Simple::LargeStruct { + fn from(value: LargeStruct) -> Self { + Self { + field1: value.field1, + field2: value.field2, + field3: value.field3, + } + } +} diff --git a/mp2-v1/tests/common/cases/table_source.rs b/mp2-v1/tests/common/cases/table_source.rs index 01f0497d6..c80f198ed 100644 --- a/mp2-v1/tests/common/cases/table_source.rs +++ b/mp2-v1/tests/common/cases/table_source.rs @@ -1,98 +1,159 @@ use std::{ + array, assert_matches::assert_matches, + collections::{BTreeSet, HashMap}, + fmt::Debug, + future::Future, + hash::Hash, str::FromStr, sync::atomic::{AtomicU64, AtomicUsize}, }; use alloy::{ + consensus::TxReceipt, eips::BlockNumberOrTag, primitives::{Address, U256}, - providers::Provider, + providers::{Provider, ProviderBuilder}, }; use anyhow::{bail, Result}; use futures::{future::BoxFuture, FutureExt}; +use itertools::Itertools; use log::{debug, info}; use mp2_common::{ - digest::TableDimension, - eth::{ProofQuery, StorageSlot}, + eth::{EventLogInfo, ProofQuery, ReceiptProofInfo, StorageSlot, StorageSlotNode}, + poseidon::H, proof::ProofWithVK, - types::HashOutput, + types::{GFp, HashOutput}, }; use mp2_v1::{ - api::{merge_metadata_hash, metadata_hash, SlotInputs}, + api::{ + combine_digest_and_block, compute_table_info, merge_metadata_hash, + metadata_hash as metadata_hash_function, SlotInput, SlotInputs, + }, indexing::{ block::BlockPrimaryIndex, cell::Cell, row::{RowTreeKey, ToNonce}, }, values_extraction::{ - identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, + gadgets::{column_info::ExtractedColumnInfo, metadata_gadget::TableMetadata}, + identifier_for_inner_mapping_key_column, identifier_for_mapping_key_column, + identifier_for_outer_mapping_key_column, identifier_for_value_column, + planner::Extractable, + StorageSlotInfo, }, }; -use rand::{Rng, SeedableRng}; -use serde::{Deserialize, Serialize}; +use plonky2::field::types::PrimeField64; +use rand::{ + distributions::{Alphanumeric, DistString}, + rngs::StdRng, + Rng, SeedableRng, +}; use crate::common::{ - cases::indexing::{MappingUpdate, SimpleSingleValue, TableRowValues}, + cases::{ + contract::EventContract, + indexing::{ReceiptUpdate, TableRowValues, TX_INDEX_COLUMN}, + }, final_extraction::{ExtractionProofInput, ExtractionTableProof, MergeExtractionProof}, proof_storage::{ProofKey, ProofStorage}, rowtree::SecondaryIndexCell, table::CellsUpdate, - TestContext, + Deserialize, MetadataHash, Serialize, TestContext, }; use super::{ - contract::Contract, - indexing::{ChangeType, TableRowUpdate, UpdateSimpleStorage, UpdateType}, + contract::{Contract, ContractController, MappingUpdate, SimpleSingleValues, TestContract}, + indexing::{ChangeType, TableRowUpdate, UpdateType, SINGLE_SLOTS, SINGLE_STRUCT_SLOT}, + slot_info::{LargeStruct, MappingInfo, StorageSlotMappingKey, StorageSlotValue, StructMapping}, }; -/// The key,value such that the combination is unique. This can be turned into a RowTreeKey. -/// to store in the row tree. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct UniqueMappingEntry { - key: U256, - value: U256, +fn metadata_hash( + slot_input: SlotInputs, + contract_address: &Address, + chain_id: u64, + extra: Vec, +) -> MetadataHash { + metadata_hash_function(slot_input, contract_address, chain_id, extra) } -impl From<(U256, U256)> for UniqueMappingEntry { - fn from(pair: (U256, U256)) -> Self { - Self { - key: pair.0, - value: pair.1, - } +/// Save the columns information of same slot and EVM word. +#[derive(Debug, Clone)] +struct SlotEvmWordColumns(Vec); + +impl SlotEvmWordColumns { + fn new(column_info: Vec) -> Self { + // Ensure the column information should have the same slot and EVM word. + + let slot = column_info[0].extraction_id()[7].0 as u8; + let evm_word = column_info[0].location_offset().0 as u32; + column_info[1..].iter().for_each(|col| { + let col_slot = col.extraction_id()[7].0 as u8; + let col_word = col.location_offset().0 as u32; + assert_eq!(col_slot, slot); + assert_eq!(col_word, evm_word); + }); + + Self(column_info) + } + fn slot(&self) -> u8 { + // The columns should have the same slot. + u8::try_from(self.0[0].extraction_id()[7].to_canonical_u64()).unwrap() + } + fn evm_word(&self) -> u32 { + // The columns should have the same EVM word. + u32::try_from(self.0[0].location_offset().to_canonical_u64()).unwrap() + } + fn column_info(&self) -> &[ExtractedColumnInfo] { + &self.0 } } /// What is the secondary index chosen for the table in the mapping. /// Each entry contains the identifier of the column expected to store in our tree -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Copy)] pub enum MappingIndex { - Key(u64), + OuterKey(u64), + InnerKey(u64), Value(u64), // This can happen if it is being part of a merge table and the secondary index is from the // other table None, } -impl UniqueMappingEntry { - pub fn new(k: &U256, v: &U256) -> Self { - Self { key: *k, value: *v } +/// The key,value such that the combination is unique. This can be turned into a RowTreeKey. +/// to store in the row tree. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UniqueMappingEntry { + key: K, + value: V, +} + +impl From<(K, V)> for UniqueMappingEntry { + fn from(pair: (K, V)) -> Self { + Self { + key: pair.0, + value: pair.1, + } + } +} + +impl UniqueMappingEntry { + pub fn new(key: K, value: V) -> Self { + Self { key, value } } pub fn to_update( &self, block_number: BlockPrimaryIndex, + contract: &Contract, mapping_index: &MappingIndex, - slot: u8, - contract: &Address, - chain_id: u64, + slot_inputs: &[SlotInput], previous_row_key: Option, ) -> (CellsUpdate, SecondaryIndexCell) { - let row_value = - self.to_table_row_value(block_number, mapping_index, slot, contract, chain_id); + let row_value = self.to_table_row_value(block_number, contract, mapping_index, slot_inputs); let cells_update = CellsUpdate { previous_row_key: previous_row_key.unwrap_or_default(), - new_row_key: self.to_row_key(mapping_index), + new_row_key: self.to_row_key(contract, mapping_index, slot_inputs), updated_cells: row_value.current_cells, primary: block_number, }; @@ -103,705 +164,306 @@ impl UniqueMappingEntry { /// Return a row given this mapping entry, depending on the chosen index pub fn to_table_row_value( &self, - block_number: BlockPrimaryIndex, + primary: BlockPrimaryIndex, + contract: &Contract, index: &MappingIndex, - slot: u8, - contract: &Address, - chain_id: u64, + slot_inputs: &[SlotInput], ) -> TableRowValues { - // we construct the two associated cells in the table. One of them will become - // a SecondaryIndexCell depending on the secondary index type we have chosen - // for this mapping. - let extract_key = MappingIndex::Key(identifier_for_mapping_key_column( - slot, - contract, - chain_id, - vec![], - )); - let key_cell = self.to_cell(extract_key); - let extract_key = MappingIndex::Value(identifier_for_mapping_value_column( - slot, - contract, - chain_id, - vec![], - )); - let value_cell = self.to_cell(extract_key); - // then we look at which one is must be the secondary cell, if any - let (secondary, rest_cells) = match index { - MappingIndex::Key(_) => ( - // by definition, mapping key is unique, so there is no need for a specific - // nonce for the tree in that case - SecondaryIndexCell::new_from(key_cell, U256::from(0)), - vec![value_cell], - ), - MappingIndex::Value(_) => { - // Here we take the tuple (value,key) as uniquely identifying a row in the - // table - ( - SecondaryIndexCell::new_from(value_cell, self.key), - vec![key_cell], - ) + let slot = slot_inputs[0].slot(); + // Ensure it's the same mapping slot. + slot_inputs[1..] + .iter() + .for_each(|slot_input| assert_eq!(slot_input.slot(), slot)); + let [outer_key_cell, inner_key_cell] = match self.key.to_u256_vec().as_slice() { + [mapping_key] => { + let key_id = identifier_for_mapping_key_column( + slot, + &contract.address, + contract.chain_id, + vec![], + ); + + [Some(Cell::new(key_id, *mapping_key)), None] + } + [outer_key, inner_key] => { + let outer_key_cell = { + let id = identifier_for_outer_mapping_key_column( + slot, + &contract.address, + contract.chain_id, + vec![], + ); + + Cell::new(id, *outer_key) + }; + let inner_key_cell = { + let id = identifier_for_inner_mapping_key_column( + slot, + &contract.address, + contract.chain_id, + vec![], + ); + + Cell::new(id, *inner_key) + }; + + [Some(outer_key_cell), Some(inner_key_cell)] + } + _ => unreachable!(), + }; + let mut current_cells = slot_inputs + .iter() + .zip_eq(self.value.to_u256_vec()) + .map(|(slot_input, field)| { + let values_id = identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ); + + Cell::new(values_id, field) + }) + .collect_vec(); + + let secondary_cell = match index { + MappingIndex::OuterKey(_) => { + if let Some(cell) = inner_key_cell { + current_cells.push(cell); + } + + outer_key_cell.unwrap() + } + MappingIndex::InnerKey(_) => { + if let Some(cell) = outer_key_cell { + current_cells.push(cell); + } + + inner_key_cell.unwrap() + } + MappingIndex::Value(secondary_value_id) => { + let pos = current_cells + .iter() + .position(|c| &c.identifier() == secondary_value_id) + .unwrap(); + let secondary_cell = current_cells.remove(pos); + + [outer_key_cell, inner_key_cell] + .into_iter() + .for_each(|cell| { + if let Some(cell) = cell { + current_cells.push(cell); + } + }); + + secondary_cell } - MappingIndex::None => (Default::default(), vec![value_cell, key_cell]), + MappingIndex::None => unreachable!(), }; debug!( - " --- MAPPING: to row: secondary index {:?} -- cell {:?}", - secondary, rest_cells + " --- MAPPING to row: secondary index {:?} -- cells {:?}", + secondary_cell, current_cells, ); + let current_secondary = Some(SecondaryIndexCell::new_from(secondary_cell, U256::from(0))); TableRowValues { - current_cells: rest_cells, - current_secondary: Some(secondary), - primary: block_number, + current_cells, + current_secondary, + primary, } } - // using MappingIndex is a misleading name but it allows us to choose which part of the mapping - // we want to extract - pub fn to_cell(&self, index: MappingIndex) -> Cell { - match index { - MappingIndex::Key(id) => Cell::new(id, self.key), - MappingIndex::Value(id) => Cell::new(id, self.value), - MappingIndex::None => panic!("this should never happen"), - } - } + pub fn to_row_key( + &self, + contract: &Contract, + index: &MappingIndex, + slot_inputs: &[SlotInput], + ) -> RowTreeKey { + let (row_key, rest) = match index { + MappingIndex::OuterKey(_) => { + // The mapping keys are unique for rows. + let mapping_keys = self.key.to_u256_vec(); + let key = mapping_keys[0]; + let rest = mapping_keys.get(1).unwrap_or(&U256::ZERO).to_be_bytes_vec(); + + (key, rest) + } + MappingIndex::InnerKey(_) => { + // The mapping keys are unique for rows. + let mapping_keys = self.key.to_u256_vec(); + let key = mapping_keys[1]; + let rest = mapping_keys[0].to_be_bytes_vec(); + + (key, rest) + } + MappingIndex::Value(secondary_value_id) => { + let pos = slot_inputs + .iter() + .position(|slot_input| { + &identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ) == secondary_value_id + }) + .unwrap(); + let secondary_value = self.value.to_u256_vec().remove(pos); + + // The mapping key is unique for rows. + let rest = self + .key + .to_u256_vec() + .into_iter() + .flat_map(|u| u.to_be_bytes_vec()) + .collect_vec(); + + (secondary_value, rest) + } + MappingIndex::None => unreachable!(), + }; - pub fn to_row_key(&self, index: &MappingIndex) -> RowTreeKey { - match index { - MappingIndex::Key(_) => RowTreeKey { - // tree key indexed by mapping key - value: self.key, - rest: self.value.to_nonce(), - }, - MappingIndex::Value(_) => RowTreeKey { - // tree key indexed by mapping value - value: self.value, - rest: self.key.to_nonce(), - }, - MappingIndex::None => RowTreeKey::default(), + RowTreeKey { + value: row_key, + rest: rest.to_nonce(), } } } -#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] -pub(crate) enum TableSource { - /// Test arguments for single values extraction (C.1) - SingleValues(SingleValuesExtractionArgs), - /// Test arguments for mapping values extraction (C.1) - /// We can test with and without the length - Mapping((MappingValuesExtractionArgs, Option)), - Merge(MergeSource), +pub(crate) trait TableSource { + type Metadata; + + fn get_data(&self) -> Self::Metadata; + + fn init_contract_data<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + ) -> BoxFuture<'a, Vec>>; + + fn generate_extraction_proof_inputs( + &self, + ctx: &mut TestContext, + contract: &Contract, + value_key: ProofKey, + ) -> impl Future>; + + fn random_contract_update<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + c: ChangeType, + ) -> BoxFuture<'a, Vec>>; + + fn metadata_hash(&self, contract_address: Address, chain_id: u64) -> MetadataHash; + + fn can_query(&self) -> bool; } -impl TableSource { - pub fn slot_input(&self) -> SlotInputs { - match self { - TableSource::SingleValues(single) => SlotInputs::Simple(single.slots.clone()), - TableSource::Mapping((m, _)) => SlotInputs::Mapping(m.slot), - TableSource::Merge(_) => panic!("can't call slot inputs on merge table"), - } +impl TableSource for SingleExtractionArgs { + type Metadata = SlotInputs; + + fn get_data(&self) -> SlotInputs { + SlotInputs::Simple(self.slot_inputs.clone()) } - #[allow(elided_named_lifetimes)] - pub fn init_contract_data<'a>( + fn init_contract_data<'a>( &'a mut self, ctx: &'a mut TestContext, contract: &'a Contract, - ) -> BoxFuture>> { - async move { - match self { - TableSource::SingleValues(ref mut s) => s.init_contract_data(ctx, contract).await, - TableSource::Mapping((ref mut m, _)) => m.init_contract_data(ctx, contract).await, - TableSource::Merge(ref mut merge) => merge.init_contract_data(ctx, contract).await, - } - } - .boxed() + ) -> BoxFuture<'a, Vec>> { + async move { SingleExtractionArgs::init_contract_data(self, ctx, contract).await }.boxed() } - pub async fn generate_extraction_proof_inputs( + async fn generate_extraction_proof_inputs( &self, ctx: &mut TestContext, contract: &Contract, value_key: ProofKey, ) -> Result<(ExtractionProofInput, HashOutput)> { - match self { - // first lets do without length - TableSource::Mapping((ref mapping, _)) => { - mapping - .generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - TableSource::SingleValues(ref args) => { - args.generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - TableSource::Merge(ref merge) => { - merge - .generate_extraction_proof_inputs(ctx, contract, value_key) - .await - } - } + SingleExtractionArgs::generate_extraction_proof_inputs(self, ctx, contract, value_key).await } - #[allow(elided_named_lifetimes)] - pub fn random_contract_update<'a>( + fn random_contract_update<'a>( &'a mut self, ctx: &'a mut TestContext, contract: &'a Contract, c: ChangeType, - ) -> BoxFuture>> { - async move { - match self { - TableSource::Mapping((ref mut mapping, _)) => { - mapping.random_contract_update(ctx, contract, c).await - } - TableSource::SingleValues(ref v) => { - v.random_contract_update(ctx, contract, c).await - } - TableSource::Merge(ref mut merge) => { - merge.random_contract_update(ctx, contract, c).await - } - } - } - .boxed() + ) -> BoxFuture<'a, Vec>> { + async move { SingleExtractionArgs::random_contract_update(self, ctx, contract, c).await } + .boxed() } -} -/// Single values extraction arguments (C.1) -#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct SingleValuesExtractionArgs { - /// Simple slots - pub(crate) slots: Vec, - // in case of merge table, there might not be any index slot for single table - pub(crate) index_slot: Option, -} + fn metadata_hash(&self, contract_address: Address, chain_id: u64) -> MetadataHash { + let slot = self.get_data(); + metadata_hash(slot, &contract_address, chain_id, vec![]) + } -impl SingleValuesExtractionArgs { - async fn init_contract_data( - &mut self, - ctx: &mut TestContext, - contract: &Contract, - ) -> Vec> { - let contract_update = SimpleSingleValue { - s1: true, - s2: U256::from(123), - s3: "test".to_string(), - s4: next_address(), - }; - // since the table is not created yet, we are giving an empty table row. When making the - // diff with the new updated contract storage, the logic will detect it's an initialization - // phase - let old_table_values = TableRowValues::default(); - contract - .apply_update(ctx, &UpdateSimpleStorage::Single(contract_update)) - .await - .unwrap(); - let new_table_values = self.current_table_row_values(ctx, contract).await; - assert!( - new_table_values.len() == 1, - "single variable case should only have one row" - ); - let update = old_table_values.compute_update(&new_table_values[0]); - assert!(update.len() == 1, "one row at a time"); - assert_matches!( - update[0], - TableRowUpdate::Insertion(_, _), - "initialization of the contract's table should be init" - ); - update + fn can_query(&self) -> bool { + false } +} - pub async fn random_contract_update( - &self, - ctx: &mut TestContext, - contract: &Contract, - c: ChangeType, - ) -> Vec> { - let old_table_values = self.current_table_row_values(ctx, contract).await; - // we can take the first one since we're asking for single value and there is only - // one row - let old_table_values = &old_table_values[0]; - let mut current_values = contract - .current_single_values(ctx) - .await - .expect("can't get current values"); - match c { - ChangeType::Silent => {} - ChangeType::Deletion => { - panic!("can't remove a single row from blockchain data over single values") - } - ChangeType::Insertion => { - panic!("can't add a new row for blockchain data over single values") - } - ChangeType::Update(u) => match u { - UpdateType::Rest => current_values.s4 = next_address(), - UpdateType::SecondaryIndex => { - current_values.s2 = next_value(); - } - }, - }; +impl TableSource for MergeSource { + type Metadata = (SlotInputs, SlotInputs); - let contract_update = UpdateSimpleStorage::Single(current_values); - contract.apply_update(ctx, &contract_update).await.unwrap(); - let new_table_values = self.current_table_row_values(ctx, contract).await; - assert!( - new_table_values.len() == 1, - "there should be only a single row for single case" - ); - old_table_values.compute_update(&new_table_values[0]) + fn get_data(&self) -> Self::Metadata { + (self.single.get_data(), self.mapping.get_data()) } - // construct a row of the table from the actual value in the contract by fetching from MPT - async fn current_table_row_values( - &self, - ctx: &mut TestContext, - contract: &Contract, - ) -> Vec> { - let mut secondary_cell = None; - let mut rest_cells = Vec::new(); - for slot in self.slots.iter() { - let query = ProofQuery::new_simple_slot(contract.address, *slot as usize); - let id = identifier_single_var_column( - *slot, - &contract.address, - ctx.rpc.get_chain_id().await.unwrap(), - vec![], - ); - // Instead of manually setting the value to U256, we really extract from the - // MPT proof to mimick the way to "see" update. Also, it ensures we are getting - // the formatting and endianness right. - let value = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await - .storage_proof[0] - .value; - let cell = Cell::new(id, value); - // make sure we separate the secondary cells and rest of the cells separately. - if let Some(index) = self.index_slot - && index == *slot - { - // we put 0 since we know there are no other rows with that secondary value since we are dealing - // we single values, so only 1 row. - secondary_cell = Some(SecondaryIndexCell::new_from(cell, 0)); - } else { - // This is triggered for every cells that are not secondary index. If there is no - // secondary index, then all the values will end up there. - rest_cells.push(cell); - } - } - vec![TableRowValues { - current_cells: rest_cells, - current_secondary: secondary_cell, - primary: ctx.block_number().await as BlockPrimaryIndex, - }] + + fn init_contract_data<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + ) -> BoxFuture<'a, Vec>> { + async move { MergeSource::init_contract_data(self, ctx, contract).await }.boxed() } - pub async fn generate_extraction_proof_inputs( + async fn generate_extraction_proof_inputs( &self, ctx: &mut TestContext, contract: &Contract, - proof_key: ProofKey, + value_key: ProofKey, ) -> Result<(ExtractionProofInput, HashOutput)> { - let chain_id = ctx.rpc.get_chain_id().await?; - let ProofKey::ValueExtraction((_id, bn)) = proof_key.clone() else { - bail!("invalid proof key"); - }; - let single_value_proof = match ctx.storage.get_proof_exact(&proof_key) { - Ok(p) => p, - Err(_) => { - let single_values_proof = ctx - .prove_single_values_extraction( - &contract.address, - BlockNumberOrTag::Number(bn as u64), - &self.slots, - ) - .await; - ctx.storage - .store_proof(proof_key, single_values_proof.clone())?; - info!("Generated Values Extraction (C.1) proof for single variables"); - { - let pproof = ProofWithVK::deserialize(&single_values_proof).unwrap(); - let pi = - mp2_v1::values_extraction::PublicInputs::new(&pproof.proof().public_inputs); - debug!( - "[--] SINGLE FINAL MPT DIGEST VALUE --> {:?} ", - pi.values_digest() - ); - debug!( - "[--] SINGLE FINAL ROOT HASH --> {:?} ", - hex::encode( - pi.root_hash() - .into_iter() - .flat_map(|u| u.to_be_bytes()) - .collect::>() - ) - ); - } - single_values_proof - } - }; - let slot_input = SlotInputs::Simple(self.slots.clone()); - let metadata_hash = metadata_hash(slot_input, &contract.address, chain_id, vec![]); - // we're just proving a single set of a value - let input = ExtractionProofInput::Single(ExtractionTableProof { - dimension: TableDimension::Single, - value_proof: single_value_proof, - length_proof: None, - }); - Ok((input, metadata_hash)) + MergeSource::generate_extraction_proof_inputs(self, ctx, contract, value_key).await } -} -/// Mapping values extraction arguments (C.1) -#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] -pub(crate) struct MappingValuesExtractionArgs { - /// Mapping slot number - pub(crate) slot: u8, - pub(crate) index: MappingIndex, - /// Mapping keys: they are useful for two things: - /// * doing some controlled changes on the smart contract, since if we want to do an update we - /// need to know an existing key - /// * doing the MPT proofs over, since this test doesn't implement the copy on write for MPT - /// (yet), we're just recomputing all the proofs at every block and we need the keys for that. - pub(crate) mapping_keys: Vec>, + fn random_contract_update<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + c: ChangeType, + ) -> BoxFuture<'a, Vec>> { + async move { MergeSource::random_contract_update(self, ctx, contract, c).await }.boxed() + } + + fn metadata_hash(&self, contract_address: Address, chain_id: u64) -> MetadataHash { + let (single, mapping) = self.get_data(); + merge_metadata_hash(contract_address, chain_id, vec![], single, mapping) + } + + fn can_query(&self) -> bool { + true + } } -impl MappingValuesExtractionArgs { - pub async fn init_contract_data( - &mut self, - ctx: &mut TestContext, - contract: &Contract, - ) -> Vec> { - let index = self.index.clone(); - let slot = self.slot; - let init_pair = (next_value(), next_address()); - // NOTE: here is the same address but for different mapping key (10,11) - let pair2 = (next_value(), init_pair.1); - let init_state = [init_pair, pair2, (next_value(), next_address())]; - // NOTE: uncomment this for simpler testing - //let init_state = [init_pair]; - // saving the keys we are tracking in the mapping - self.mapping_keys.extend( - init_state - .iter() - .map(|u| u.0.to_be_bytes_trimmed_vec()) - .collect::>(), - ); - let mapping_updates = init_state - .iter() - .map(|u| MappingUpdate::Insertion(u.0, u.1.into_word().into())) - .collect::>(); +#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] +pub struct MergeSource { + // NOTE: this is a hardcore assumption currently that table_a is single and table_b is mapping for now + // Extending to full merge between any table is not far - it requires some quick changes in + // circuit but quite a lot of changes in integrated test. + pub(crate) single: SingleExtractionArgs, + pub(crate) mapping: MappingExtractionArgs, +} - contract - .apply_update(ctx, &UpdateSimpleStorage::Mapping(mapping_updates.clone())) - .await - .unwrap(); - let new_block_number = ctx.block_number().await as BlockPrimaryIndex; - self.mapping_to_table_update(new_block_number, mapping_updates, index, slot, contract) +impl MergeSource { + pub fn new( + single: SingleExtractionArgs, + mapping: MappingExtractionArgs, + ) -> Self { + Self { single, mapping } } - async fn random_contract_update( - &mut self, - ctx: &mut TestContext, - contract: &Contract, - c: ChangeType, - ) -> Vec> { - // NOTE 1: The first part is just trying to construct the right input to simulate any - // changes on a mapping. This is mostly irrelevant for dist system but needs to - // manually construct our test cases here. The second part is more interesting as it looks at "what to do - // when receiving an update from scrapper". The core of the function is in - // `from_mapping_to_table_update` - // - // NOTE 2: Thhis implementation tries to emulate as much as possible what happens in dist - // system. TO compute the set of updates, it first simulate an update on the contract - // and creates the signal "MappingUpdate" corresponding to the update. From that point - // onwards, the table row updates are manually created. - // Note this can actually lead to more work than necessary in some cases. - // Take an example where the mapping is storing (10->A), (11->A), and where the - // secondary index value is the value, i.e. A. - // Our table initially looks like `A | 10`, `A | 11`. - // Imagine an update where we want to change the first row to `A | 12`. In the "table" - // world, this is only a simple update of a simple cell, no index even involved. But - // from the perspective of mapping, the "scrapper" can only tells us : - // * Key 10 has been deleted - // * Key 12 has been added with value A - // In the backend, we translate that in the "table world" to a deletion and an insertion. - // Having such optimization could be done later on, need to properly evaluate the cost - // of it. - let idx = 0; - let mkey = &self.mapping_keys[idx].clone(); - let slot = self.slot as usize; - let index_type = self.index.clone(); - let address = &contract.address.clone(); - let query = ProofQuery::new_mapping_slot(*address, slot, mkey.to_owned()); - let response = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await; - let current_value = response.storage_proof[0].value; - let current_key = U256::from_be_slice(mkey); - let new_key = next_mapping_key(); - let new_value: U256 = next_address().into_word().into(); - let mapping_updates = match c { - ChangeType::Silent => vec![], - ChangeType::Insertion => { - vec![MappingUpdate::Insertion(new_key, new_value)] - } - ChangeType::Deletion => { - // NOTE: We care about the value here since that allows _us_ to pinpoint the - // correct row in the table and delete it since for a mpping, we uniquely - // identify row per (mapping_key,mapping_value) (in the order dictated by - // the secondary index) - vec![MappingUpdate::Deletion(current_key, current_value)] - } - ChangeType::Update(u) => { - match u { - // update the non-indexed column - UpdateType::Rest => { - // check which one it is and change accordingly - match index_type { - MappingIndex::Key(_) => { - // we simply change the mapping value since the key is the secondary index - vec![MappingUpdate::Update(current_key, current_value, new_value)] - } - MappingIndex::Value(_) => { - // TRICKY: in this case, the mapping key must change. But from the - // onchain perspective, it means a transfer - // mapping(old_key -> new_key,value) - vec![ - MappingUpdate::Deletion(current_key, current_value), - MappingUpdate::Insertion(new_key, current_value), - ] - } - MappingIndex::None => { - // a random update of the mapping, we don't care which since it is - // not impacting the secondary index of the table since the mapping - // doesn't contain the column which is the secondary index, in case - // of the merge table case. - vec![MappingUpdate::Update(current_key, current_value, new_value)] - } - } - } - UpdateType::SecondaryIndex => { - match index_type { - MappingIndex::Key(_) => { - // TRICKY: if the mapping key changes, it's a deletion then - // insertion from onchain perspective - vec![ - MappingUpdate::Deletion(current_key, current_value), - // we insert the same value but with a new mapping key - MappingUpdate::Insertion(new_key, current_value), - ] - } - MappingIndex::Value(_) => { - // if the value changes, it's a simple update in mapping - vec![MappingUpdate::Update(current_key, current_value, new_value)] - } - MappingIndex::None => { - // empty vec since this table has no secondary index so it should - // give no updates - vec![] - } - } - } - } - } - }; - // small iteration to always have a good updated list of mapping keys - for update in mapping_updates.iter() { - match update { - MappingUpdate::Deletion(mkey, _) => { - info!("Removing key {} from mappping keys tracking", mkey); - let key_stored = mkey.to_be_bytes_trimmed_vec(); - self.mapping_keys.retain(|u| u != &key_stored); - } - MappingUpdate::Insertion(mkey, _) => { - info!("Inserting key {} to mappping keys tracking", mkey); - self.mapping_keys.push(mkey.to_be_bytes_trimmed_vec()); - } - // the mapping key doesn't change here so no need to update the list - MappingUpdate::Update(_, _, _) => {} - } - } - - contract - .apply_update(ctx, &UpdateSimpleStorage::Mapping(mapping_updates.clone())) - .await - .unwrap(); - let new_block_number = ctx.block_number().await as BlockPrimaryIndex; - // NOTE HERE is the interesting bit for dist system as this is the logic to execute - // on receiving updates from scapper. This only needs to have the relevant - // information from update and it will translate that to changes in the tree. - self.mapping_to_table_update( - new_block_number, - mapping_updates, - index_type, - slot as u8, - contract, - ) - } - - pub async fn generate_extraction_proof_inputs( - &self, - ctx: &mut TestContext, - contract: &Contract, - proof_key: ProofKey, - ) -> Result<(ExtractionProofInput, HashOutput)> { - let chain_id = ctx.rpc.get_chain_id().await?; - let mapping_root_proof = match ctx.storage.get_proof_exact(&proof_key) { - Ok(p) => p, - Err(_) => { - let mapping_values_proof = ctx - .prove_mapping_values_extraction( - &contract.address, - self.slot, - self.mapping_keys.clone(), - ) - .await; - - ctx.storage - .store_proof(proof_key, mapping_values_proof.clone())?; - info!("Generated Values Extraction (C.1) proof for mapping slots"); - { - let pproof = ProofWithVK::deserialize(&mapping_values_proof).unwrap(); - let pi = - mp2_v1::values_extraction::PublicInputs::new(&pproof.proof().public_inputs); - debug!( - "[--] MAPPING FINAL MPT DIGEST VALUE --> {:?} ", - pi.values_digest() - ); - debug!( - "[--] MAPPING FINAL ROOT HASH --> {:?} ", - hex::encode( - pi.root_hash() - .into_iter() - .flat_map(|u| u.to_be_bytes()) - .collect::>() - ) - ); - } - mapping_values_proof - } - }; - let slot_input = SlotInputs::Mapping(self.slot); - let metadata_hash = metadata_hash(slot_input, &contract.address, chain_id, vec![]); - // it's a compoound value type of proof since we're not using the length - let input = ExtractionProofInput::Single(ExtractionTableProof { - dimension: TableDimension::Compound, - value_proof: mapping_root_proof, - length_proof: None, - }); - Ok((input, metadata_hash)) - } - - pub fn mapping_to_table_update( - &self, - block_number: BlockPrimaryIndex, - updates: Vec, - index: MappingIndex, - slot: u8, - contract: &Contract, - ) -> Vec> { - updates - .iter() - .flat_map(|mapping_change| { - match mapping_change { - MappingUpdate::Deletion(mkey, mvalue) => { - // find the associated row key tree to that value - // HERE: there are multiple possibilities: - // * search for the entry at the previous block instead - // * passing inside the deletion the value deleted as well, so we can - // reconstruct the row key - // * or have this extra list of mapping keys - let entry = UniqueMappingEntry::new(mkey, mvalue); - vec![TableRowUpdate::Deletion(entry.to_row_key(&index))] - } - MappingUpdate::Insertion(mkey, mvalue) => { - // we transform the mapping entry into the "table notion" of row - let entry = UniqueMappingEntry::new(mkey, mvalue); - let (cells, index) = entry.to_update( - block_number, - &index, - slot, - &contract.address, - contract.chain_id, - None, - ); - vec![TableRowUpdate::Insertion(cells, index)] - } - MappingUpdate::Update(mkey, old_value, mvalue) => { - // NOTE: we need here to (a) delete current row and (b) insert new row - // Regardless of the change if it's on the mapping key or value, since a - // row is uniquely identified by its pair (key,value) then if one of those - // change, that means the row tree key needs to change as well, i.e. it's a - // deletion and addition. - let previous_entry = UniqueMappingEntry::new(mkey, old_value); - let previous_row_key = previous_entry.to_row_key(&index); - let new_entry = UniqueMappingEntry::new(mkey, mvalue); - - let (mut cells, mut secondary_index) = new_entry.to_update( - block_number, - &index, - slot, - &contract.address, - contract.chain_id, - // NOTE: here we provide the previous key such that we can - // reconstruct the cells tree as it was before and then apply - // the update and put it in a new row. Otherwise we don't know - // the update plan since we don't have a base tree to deal - // with. - // In the case the key is the cell, that's good, we don't need to do - // anything to the tree then since the doesn't change. - // In the case it's the value, then we'll have to reprove the cell. - Some(previous_row_key.clone()), - ); - match index { - MappingIndex::Key(_) => { - // in this case, the mapping value changed, so the cells changed so - // we need to start from scratch. Telling there was no previous row - // key means it's treated as a full new cells tree. - cells.previous_row_key = Default::default(); - } - MappingIndex::Value(_) => { - // This is a bit hacky way but essentially it means that there is - // no update in the cells tree to apply, even tho it's still a new - // insertion of a new row, since we pick up the cells tree form the - // previous location, and that cells tree didn't change (since it's - // based on the mapping key), then no need to update anything. - // TODO: maybe make a better API to express the different - // possibilities: - // * insertion with new cells tree - // * insertion without modification to cells tree - // * update with modification to cells tree (default) - cells.updated_cells = vec![]; - } - MappingIndex::None => { - secondary_index = Default::default(); - } - }; - vec![ - TableRowUpdate::Deletion(previous_row_key), - TableRowUpdate::Insertion(cells, secondary_index), - ] - } - } - }) - .collect::>() - } -} - -#[derive(Serialize, Deserialize, Debug, Hash, Clone, PartialEq, Eq)] -pub struct MergeSource { - // NOTE: this is a hardcore assumption currently that table_a is single and table_b is mapping for now - // Extending to full merge between any table is not far - it requires some quick changes in - // circuit but quite a lot of changes in integrated test. - pub(crate) single: SingleValuesExtractionArgs, - pub(crate) mapping: MappingValuesExtractionArgs, -} - -impl MergeSource { - pub fn new(single: SingleValuesExtractionArgs, mapping: MappingValuesExtractionArgs) -> Self { - Self { single, mapping } - } - - pub async fn init_contract_data( + pub async fn init_contract_data( &mut self, ctx: &mut TestContext, contract: &Contract, @@ -847,13 +509,14 @@ impl MergeSource { TableRowUpdate::Insertion(ref cella, seca), TableRowUpdate::Insertion(cellb, secb), ) => { - assert_eq!(*secb,SecondaryIndexCell::default(),"no secondary index on single supported at the moment in integrated test"); + assert_eq!(*secb, SecondaryIndexCell::default(), "no secondary index on single supported at the moment in integrated test"); let mut cella = cella.clone(); cella.updated_cells.extend(cellb.updated_cells.iter().cloned()); TableRowUpdate::Insertion(cella,seca.clone()) } - }).collect::>() - }).collect() + }).collect_vec() + }) + .collect() } pub async fn random_contract_update( @@ -874,18 +537,12 @@ impl MergeSource { let single_updates = self.single.random_contract_update(ctx, contract, c).await; let rsu = &single_updates; let bn = ctx.block_number().await; - let mslot = self.mapping.slot as usize; - let address = &contract.address.clone(); // we fetch the value of all mapping entries, and let mut all_updates = Vec::new(); for mk in &self.mapping.mapping_keys { - let query = ProofQuery::new_mapping_slot(*address, mslot, mk.to_owned()); - let response = ctx - .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) - .await; - let current_value = response.storage_proof[0].value; - let current_key = U256::from_be_slice(mk); - let entry = UniqueMappingEntry::new(¤t_key, ¤t_value); + let current_value = self.mapping.query_value(ctx, contract, mk).await; + let current_key = *mk; + let entry = UniqueMappingEntry::new(current_key, current_value); // create one update for each update of the first table (note again there // should be only one update since it's single var) all_updates.extend(rsu.iter().map(|s| { @@ -894,8 +551,16 @@ impl MergeSource { }; TableRowUpdate::Update(CellsUpdate { // the row key doesn't change since the mapping value doesn't change - previous_row_key: entry.to_row_key(&self.mapping.index), - new_row_key: entry.to_row_key(&self.mapping.index), + previous_row_key: entry.to_row_key( + contract, + &self.mapping.index, + &self.mapping.slot_inputs, + ), + new_row_key: entry.to_row_key( + contract, + &self.mapping.index, + &self.mapping.slot_inputs, + ), // only insert the new cells from the single update updated_cells: su.updated_cells.clone(), primary: bn as BlockPrimaryIndex, @@ -934,7 +599,7 @@ impl MergeSource { } } }) - .collect::>() + .collect_vec() } } } @@ -977,13 +642,9 @@ impl MergeSource { }; // add the metadata hashes together - this is mostly for debugging - let md = merge_metadata_hash( - contract.address, - contract.chain_id, - vec![], - TableSource::SingleValues(self.single.clone()).slot_input(), - TableSource::Mapping((self.mapping.clone(), None)).slot_input(), - ); + let (simple, mapping) = self.get_data(); + let md = + merge_metadata_hash(contract.address, contract.chain_id, vec![], simple, mapping); assert!(extract_a != extract_b); Ok(( ExtractionProofInput::Merge(MergeExtractionProof { @@ -996,8 +657,9 @@ impl MergeSource { .boxed() } } + /// Length extraction arguments (C.2) -#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone, Copy)] pub(crate) struct LengthExtractionArgs { /// Length slot pub(crate) slot: u8, @@ -1005,6 +667,290 @@ pub(crate) struct LengthExtractionArgs { pub(crate) value: u8, } +pub trait ReceiptExtractionArgs: + Serialize + for<'de> Deserialize<'de> + Debug + Hash + Eq + PartialEq + Clone + Copy +{ + const NO_TOPICS: usize; + const MAX_DATA_WORDS: usize; + + fn new(address: Address, event_signature: &str) -> Self + where + Self: Sized; + + fn get_event(&self) -> EventLogInfo<{ Self::NO_TOPICS }, { Self::MAX_DATA_WORDS }>; + + fn get_index(&self) -> u64; + + fn to_table_rows( + proof_infos: &[ReceiptProofInfo], + event: &EventLogInfo<{ Self::NO_TOPICS }, { Self::MAX_DATA_WORDS }>, + block: PrimaryIndex, + ) -> Vec> { + let metadata = TableMetadata::from(*event); + + let (_, row_id) = metadata.input_value_digest(&[&[0u8; 32]; 2]); + let input_columns_ids = metadata + .input_columns() + .iter() + .map(|col| col.identifier().0) + .collect::>(); + let extracted_column_ids = metadata + .extracted_columns() + .iter() + .map(|col| col.identifier().0) + .collect::>(); + + proof_infos + .iter() + .flat_map(|info| { + let receipt_with_bloom = info.to_receipt().unwrap(); + + let tx_index_cell = Cell::new(input_columns_ids[0], U256::from(info.tx_index)); + + let gas_used_cell = Cell::new( + input_columns_ids[1], + U256::from(receipt_with_bloom.receipt.cumulative_gas_used), + ); + + receipt_with_bloom + .logs() + .iter() + .filter_map(|log| { + if log.address == event.address + && log.topics()[0].0 == event.event_signature + { + Some(log.clone()) + } else { + None + } + }) + .map(|log| { + let log = log.clone(); + let (topics, data) = log.data.split(); + let topics_cells = topics + .into_iter() + .skip(1) + .enumerate() + .map(|(j, topic)| Cell::new(extracted_column_ids[j], topic.into())) + .collect::>(); + + let data_start = topics_cells.len(); + let data_cells = data + .chunks(32) + .enumerate() + .map(|(j, data_slice)| { + Cell::new( + extracted_column_ids[data_start + j], + U256::from_be_slice(data_slice), + ) + }) + .collect::>(); + + let secondary = + SecondaryIndexCell::new_from(tx_index_cell, row_id.0.to_vec()); + + let collection = CellsUpdate:: { + previous_row_key: RowTreeKey::default(), + new_row_key: RowTreeKey::from(&secondary), + updated_cells: [vec![gas_used_cell], topics_cells, data_cells].concat(), + primary: block.clone(), + }; + + TableRowUpdate::::Insertion(collection, secondary) + }) + .collect::>>() + }) + .collect::>>() + } +} + +impl ReceiptExtractionArgs + for EventLogInfo +{ + const MAX_DATA_WORDS: usize = MAX_DATA_WORDS; + const NO_TOPICS: usize = NO_TOPICS; + + fn new(address: Address, event_signature: &str) -> Self + where + Self: Sized, + { + EventLogInfo::::new(address, event_signature) + } + + fn get_event(&self) -> EventLogInfo<{ Self::NO_TOPICS }, { Self::MAX_DATA_WORDS }> + where + [(); Self::NO_TOPICS]:, + [(); Self::MAX_DATA_WORDS]:, + { + let topics: [usize; Self::NO_TOPICS] = self + .topics + .into_iter() + .collect::>() + .try_into() + .unwrap(); + let data: [usize; Self::MAX_DATA_WORDS] = self + .data + .into_iter() + .collect::>() + .try_into() + .unwrap(); + EventLogInfo::<{ Self::NO_TOPICS }, { Self::MAX_DATA_WORDS }> { + size: self.size, + address: self.address, + add_rel_offset: self.add_rel_offset, + event_signature: self.event_signature, + sig_rel_offset: self.sig_rel_offset, + topics, + data, + } + } + + fn get_index(&self) -> u64 { + use plonky2::{ + field::types::{Field, PrimeField64}, + plonk::config::Hasher, + }; + + let tx_index_input = [ + self.address.as_slice(), + self.event_signature.as_slice(), + TX_INDEX_COLUMN.as_bytes(), + ] + .concat() + .into_iter() + .map(GFp::from_canonical_u8) + .collect::>(); + H::hash_no_pad(&tx_index_input).elements[0].to_canonical_u64() + } +} + +impl TableSource for R +where + [(); ::NO_TOPICS]:, + [(); ::MAX_DATA_WORDS]:, +{ + type Metadata = EventLogInfo<{ R::NO_TOPICS }, { R::MAX_DATA_WORDS }>; + + fn can_query(&self) -> bool { + true + } + + fn get_data(&self) -> Self::Metadata { + self.get_event() + } + + fn init_contract_data<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + ) -> BoxFuture<'a, Vec>> { + let event = self.get_event(); + async move { + let contract_update = + ReceiptUpdate::new((R::NO_TOPICS as u8, R::MAX_DATA_WORDS as u8), 5, 15); + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let event_emitter = EventContract::new(contract.address(), provider.root()); + event_emitter + .apply_update(ctx, &contract_update) + .await + .unwrap(); + + let block_number = ctx.block_number().await; + let new_block_number = block_number as BlockPrimaryIndex; + + let (proof_infos, _) = event + .query_receipt_proofs(provider.root(), block_number.into()) + .await + .unwrap(); + + R::to_table_rows(&proof_infos, &event, new_block_number) + } + .boxed() + } + + async fn generate_extraction_proof_inputs( + &self, + ctx: &mut TestContext, + contract: &Contract, + value_key: ProofKey, + ) -> Result<(ExtractionProofInput, HashOutput)> { + let event = self.get_event(); + + let ProofKey::ValueExtraction((_, bn)) = value_key else { + bail!("key wrong"); + }; + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let value_proof = event + .prove_value_extraction::<32, 512, _>( + bn as u64, + ctx.params().get_value_extraction_params(), + provider.root(), + ) + .await?; + Ok(( + ExtractionProofInput::Receipt(value_proof), + self.metadata_hash(contract.address(), contract.chain_id()), + )) + } + + fn random_contract_update<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + c: ChangeType, + ) -> BoxFuture<'a, Vec>> { + let event = self.get_event(); + async move { + let ChangeType::Receipt(relevant, others) = c else { + panic!("Need ChangeType::Receipt, got: {:?}", c); + }; + let contract_update = ReceiptUpdate::new( + (R::NO_TOPICS as u8, R::MAX_DATA_WORDS as u8), + relevant, + others, + ); + + let provider = ProviderBuilder::new() + .with_recommended_fillers() + .wallet(ctx.wallet()) + .on_http(ctx.rpc_url.parse().unwrap()); + + let event_emitter = EventContract::new(contract.address(), provider.root()); + event_emitter + .apply_update(ctx, &contract_update) + .await + .unwrap(); + + let block_number = ctx.block_number().await; + let new_block_number = block_number as BlockPrimaryIndex; + + let (proof_infos, _) = event + .query_receipt_proofs(provider.root(), block_number.into()) + .await + .unwrap(); + + R::to_table_rows(&proof_infos, &event, new_block_number) + } + .boxed() + } + + fn metadata_hash(&self, _contract_address: Address, _chain_id: u64) -> MetadataHash { + let table_metadata = TableMetadata::from(self.get_event()); + let digest = table_metadata.digest(); + combine_digest_and_block(digest) + } +} + /// Contract extraction arguments (C.3) #[derive(Debug)] pub(crate) struct ContractExtractionArgs { @@ -1017,7 +963,7 @@ static ROTATOR: AtomicUsize = AtomicUsize::new(0); use lazy_static::lazy_static; lazy_static! { - pub(crate) static ref BASE_VALUE: U256 = U256::from(10); + pub(crate) static ref BASE_VALUE: U256 = U256::from(10u8); pub static ref DEFAULT_ADDRESS: Address = Address::from_str("0xBA401cdAc1A3B6AEede21c9C4A483bE6c29F88C4").unwrap(); } @@ -1026,17 +972,794 @@ lazy_static! { pub fn rotate() -> usize { ROTATOR.fetch_add(1, std::sync::atomic::Ordering::Relaxed) % 2 } -pub fn next_mapping_key() -> U256 { - next_value() -} pub fn next_address() -> Address { let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(shift); let slice = rng.gen::<[u8; 20]>(); Address::from_slice(&slice) } -pub fn next_value() -> U256 { - let shift = SHIFT.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let bv: U256 = *BASE_VALUE; - bv + U256::from(shift) + +/// Extraction arguments for simple slots which stores both single values (Address or U256) and +/// Struct values (LargeStruct for testing) +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] +pub(crate) struct SingleExtractionArgs { + /// The index of below slot input vector to identify which is the secondardy index column + pub(crate) secondary_index: Option, + /// Slot inputs for this table + pub(crate) slot_inputs: Vec, +} + +// This implementation includes the common function for extraction arguments of simple slots. +impl SingleExtractionArgs { + pub(crate) fn new(secondary_index: Option, slot_inputs: Vec) -> Self { + Self { + secondary_index, + slot_inputs, + } + } + + pub(crate) fn secondary_index_slot_input(&self) -> Option { + self.secondary_index.map(|idx| self.slot_inputs[idx]) + } + + pub(crate) fn rest_column_slot_inputs(&self) -> Vec { + let mut slot_inputs = self.slot_inputs.clone(); + if let Some(idx) = self.secondary_index { + slot_inputs.remove(idx); + } + + slot_inputs + } + + async fn generate_extraction_proof_inputs( + &self, + ctx: &mut TestContext, + contract: &Contract, + proof_key: ProofKey, + ) -> Result<(ExtractionProofInput, HashOutput)> { + let ProofKey::ValueExtraction((_, bn)) = proof_key.clone() else { + bail!("Invalid proof key"); + }; + let value_proof = match ctx.storage.get_proof_exact(&proof_key) { + Ok(p) => p, + Err(_) => { + let storage_slot_info = self.storage_slot_info(contract); + let root_proof = ctx + .prove_values_extraction( + &contract.address, + BlockNumberOrTag::Number(bn as u64), + &storage_slot_info, + ) + .await; + ctx.storage.store_proof(proof_key, root_proof.clone())?; + info!("Generated extraction proof for simple slots"); + { + let pproof = ProofWithVK::deserialize(&root_proof).unwrap(); + let pi = + mp2_v1::values_extraction::PublicInputs::new(&pproof.proof().public_inputs); + debug!( + "[--] SINGLE FINAL MPT DIGEST VALUE --> {:?} ", + pi.values_digest() + ); + debug!( + "[--] SINGLE FINAL ROOT HASH --> {:?} ", + hex::encode( + pi.root_hash() + .into_iter() + .flat_map(|u| u.to_be_bytes()) + .collect_vec(), + ) + ); + } + + root_proof + } + }; + let slot_inputs = SlotInputs::Simple(self.slot_inputs.clone()); + let metadata_hash = + metadata_hash(slot_inputs, &contract.address, contract.chain_id, vec![]); + let input = ExtractionProofInput::Single(ExtractionTableProof { + value_proof, + length_proof: None, + }); + Ok((input, metadata_hash)) + } + + async fn current_table_row_values( + &self, + ctx: &mut TestContext, + contract: &Contract, + ) -> Vec> { + let mut secondary_cell = None; + let mut rest_cells = Vec::new(); + let secondary_id = self.secondary_index_identifier(contract); + let evm_word_cols = self.evm_word_column_info(contract); + let storage_slots = self.storage_slots(&evm_word_cols); + for (evm_word_col, storage_slot) in evm_word_cols.into_iter().zip(storage_slots) { + let query = ProofQuery::new(contract.address, storage_slot); + let value = ctx + .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) + .await + .storage_proof[0] + .value; + let value_bytes: [u8; 32] = value.to_be_bytes(); + evm_word_col.column_info().iter().for_each(|col_info| { + let extracted_value = col_info.extract_value(value_bytes.as_slice()); + let extracted_value = U256::from_be_bytes(extracted_value); + let id = col_info.identifier().to_canonical_u64(); + let cell = Cell::new(col_info.identifier().to_canonical_u64(), extracted_value); + if Some(id) == secondary_id { + assert!(secondary_cell.is_none()); + secondary_cell = Some(SecondaryIndexCell::new_from(cell, 0)); + } else { + rest_cells.push(cell); + } + }); + } + vec![TableRowValues { + current_cells: rest_cells, + current_secondary: secondary_cell, + primary: ctx.block_number().await as BlockPrimaryIndex, + }] + } + + fn secondary_index_identifier(&self, contract: &Contract) -> Option { + self.secondary_index_slot_input().map(|slot_input| { + identifier_for_value_column(&slot_input, &contract.address, contract.chain_id, vec![]) + }) + } + + fn table_info(&self, contract: &Contract) -> Vec { + table_info(contract, self.slot_inputs.clone()) + } + + fn evm_word_column_info(&self, contract: &Contract) -> Vec { + let table_info = table_info(contract, self.slot_inputs.clone()); + evm_word_column_info(&table_info) + } + + fn storage_slots(&self, evm_word_cols: &[SlotEvmWordColumns]) -> Vec { + evm_word_cols + .iter() + .map(|evm_word_col| { + // The slot number and EVM word of extracted columns are same in the metadata. + let slot = evm_word_col.slot(); + let evm_word = evm_word_col.evm_word(); + // We could assume it's a single value slot if the EVM word is 0, even if it's the + // first field of a Struct. Since the computed slot location is same either it's + // considered as a single value slot or the first field of a Struct slot. + let storage_slot = StorageSlot::Simple(slot as usize); + if evm_word == 0 { + storage_slot + } else { + StorageSlot::Node(StorageSlotNode::new_struct(storage_slot, evm_word)) + } + }) + .collect() + } + + fn storage_slot_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + self.storage_slots(&self.evm_word_column_info(contract)) + .into_iter() + .map(|storage_slot| StorageSlotInfo::new(storage_slot, table_info.clone())) + .collect() + } +} + +// This implementation includes the functions only used for testing. Since we need to +// generate random data and interact with a specific contract. +impl SingleExtractionArgs { + pub async fn init_contract_data( + &mut self, + ctx: &mut TestContext, + contract: &Contract, + ) -> Vec> { + // Generate a Rng with Send. + let rng = &mut StdRng::from_entropy(); + let single_values = SimpleSingleValues { + s1: rng.gen(), + s2: U256::from_limbs(rng.gen()), + s3: Alphanumeric.sample_string(rng, 10), + s4: next_address(), + }; + single_values.update_contract(ctx, contract).await; + let single_struct = LargeStruct { + field1: U256::from_limbs(rng.gen()), + field2: rng.gen(), + field3: rng.gen(), + }; + single_struct.update_contract(ctx, contract).await; + + // Since the table is not created yet, we are giving an empty table row. When making the + // diff with the new updated contract storage, the logic will detect it's an initialization + // phase. + let old_table_values = TableRowValues::default(); + let new_table_values = self.current_table_row_values(ctx, contract).await; + assert_eq!( + new_table_values.len(), + 1, + "Single variable case should only have one row", + ); + let updates = old_table_values.compute_update(&new_table_values[0]); + assert_eq!(updates.len(), 1); + assert_matches!( + updates[0], + TableRowUpdate::Insertion(_, _), + "Initialization of the contract's table should be init" + ); + + updates + } + + pub async fn random_contract_update( + &self, + ctx: &mut TestContext, + contract: &Contract, + change_type: ChangeType, + ) -> Vec> { + let old_table_values = self.current_table_row_values(ctx, contract).await; + // We can take the first one since we're asking for single value and there is only one row. + let old_table_values = &old_table_values[0]; + match change_type { + ChangeType::Receipt(..) => { + panic!("Can't add a new receipt change for storage variable") + } + ChangeType::Silent => {} + ChangeType::Insertion => { + panic!("Can't add a new row for blockchain data over single values") + } + ChangeType::Deletion => { + panic!("Can't remove a single row from blockchain data over single values") + } + ChangeType::Update(update) => { + let index_slot_input = self.secondary_index_slot_input(); + match update { + UpdateType::Rest => { + let index_slot = index_slot_input.map(|slot_input| slot_input.slot()); + if index_slot == Some(SINGLE_STRUCT_SLOT as u8) { + // Update the single value slots as `Rest` if single Struct slot is the index. + let mut current_values = + SimpleSingleValues::current_values(ctx, contract).await; + current_values.s4 = next_address(); + current_values.update_contract(ctx, contract).await; + } else { + // Update the single Struct slot as `Rest` if one of single value slots is the index. + let mut current_struct = + LargeStruct::current_values(ctx, contract).await; + current_struct.field2 += 1; + current_struct.update_contract(ctx, contract).await; + } + } + UpdateType::SecondaryIndex => { + if let Some(index_slot_input) = index_slot_input { + let slot = index_slot_input.slot(); + let rng = &mut StdRng::from_entropy(); + if slot == SINGLE_STRUCT_SLOT as u8 { + let mut current_struct = + LargeStruct::current_values(ctx, contract).await; + // We only update the secondary index value here. + current_struct.random_update(&index_slot_input); + current_struct.update_contract(ctx, contract).await; + } else { + let mut current_values = + SimpleSingleValues::current_values(ctx, contract).await; + if slot == SINGLE_SLOTS[0] { + current_values.s1 = !current_values.s1; + } else if slot == SINGLE_SLOTS[1] { + current_values.s2 += U256::from(1); + } else if slot == SINGLE_SLOTS[2] { + current_values.s3 = Alphanumeric.sample_string(rng, 10); + } else if slot == SINGLE_SLOTS[3] { + current_values.s4 = next_address(); + } else { + panic!("Wrong slot number"); + } + current_values.update_contract(ctx, contract).await; + } + } + } + } + } + }; + + let new_table_values = self.current_table_row_values(ctx, contract).await; + assert_eq!( + new_table_values.len(), + 1, + "Single variable case should only have one row", + ); + old_table_values.compute_update(&new_table_values[0]) + } +} + +#[derive(Serialize, Deserialize, Debug, Hash, Eq, PartialEq, Clone)] +pub(crate) struct MappingExtractionArgs { + /// Mapping slot number + slot: u8, + /// Mapping index type + index: MappingIndex, + /// Slot input information + slot_inputs: Vec, + /// Mapping keys: they are useful for two things: + /// * doing some controlled changes on the smart contract, since if we want to do an update we + /// need to know an existing key + /// * doing the MPT proofs over, since this test doesn't implement the copy on write for MPT + /// (yet), we're just recomputing all the proofs at every block and we need the keys for that. + mapping_keys: BTreeSet, + /// The optional length extraction parameters + length_args: Option, +} + +impl TableSource for MappingExtractionArgs +where + T: MappingInfo, + Vec>: ContractController, +{ + type Metadata = SlotInputs; + + fn get_data(&self) -> Self::Metadata { + if let Some(l_args) = self.length_args.as_ref() { + T::slot_inputs(self.slot_inputs.clone(), Some(l_args.slot)) + } else { + T::slot_inputs(self.slot_inputs.clone(), None) + } + } + + fn init_contract_data<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + ) -> BoxFuture<'a, Vec>> { + async { + let init_key_and_value: [_; 3] = + array::from_fn(|_| (T::sample_key(), ::Value::sample_value())); + // Save the mapping keys. + self.mapping_keys + .extend(init_key_and_value.iter().map(|u| u.0.clone()).collect_vec()); + let updates = init_key_and_value + .into_iter() + .map(|(key, value)| MappingUpdate::Insertion(key, value)) + .collect_vec(); + + updates.update_contract(ctx, contract).await; + + let new_block_number = ctx.block_number().await as BlockPrimaryIndex; + self.mapping_to_table_update(new_block_number, contract, &updates) + } + .boxed() + } + + async fn generate_extraction_proof_inputs( + &self, + ctx: &mut TestContext, + contract: &Contract, + value_key: ProofKey, + ) -> Result<(ExtractionProofInput, HashOutput)> { + let ProofKey::ValueExtraction((_, bn)) = value_key.clone() else { + bail!("invalid proof key"); + }; + let mapping_root_proof = match ctx.storage.get_proof_exact(&value_key) { + Ok(p) => p, + Err(_) => { + let storage_slot_info = self.all_storage_slot_info(contract); + let mapping_values_proof = ctx + .prove_values_extraction( + &contract.address, + BlockNumberOrTag::Number(bn as u64), + &storage_slot_info, + ) + .await; + ctx.storage + .store_proof(value_key, mapping_values_proof.clone())?; + info!("Generated Values Extraction proof for mapping slot"); + { + let pproof = ProofWithVK::deserialize(&mapping_values_proof).unwrap(); + let pi = + mp2_v1::values_extraction::PublicInputs::new(&pproof.proof().public_inputs); + debug!( + "[--] MAPPING FINAL MPT DIGEST VALUE --> {:?} ", + pi.values_digest() + ); + debug!( + "[--] MAPPING FINAL ROOT HASH --> {:?} ", + hex::encode( + pi.root_hash() + .into_iter() + .flat_map(|u| u.to_be_bytes()) + .collect_vec() + ) + ); + } + mapping_values_proof + } + }; + let metadata_hash = self.metadata_hash(contract.address(), contract.chain_id()); + // it's a compoound value type of proof since we're not using the length + let input = ExtractionProofInput::Single(ExtractionTableProof { + value_proof: mapping_root_proof, + length_proof: None, + }); + Ok((input, metadata_hash)) + } + + fn random_contract_update<'a>( + &'a mut self, + ctx: &'a mut TestContext, + contract: &'a Contract, + c: ChangeType, + ) -> BoxFuture<'a, Vec>> { + async move { + // NOTE 1: The first part is just trying to construct the right input to simulate any + // changes on a mapping. This is mostly irrelevant for dist system but needs to manually + // construct our test cases here. The second part is more interesting as it looks at + // "what to do when receiving an update from scrapper". The core of the function is in + // `mapping_to_table_update` + // + // NOTE 2: This implementation tries to emulate as much as possible what happens in dist + // system. To compute the set of updates, it first simulate an update on the contract + // and creates the signal "MappingUpdate" corresponding to the update. From that point + // onwards, the table row updates are manually created. + // Note this can actually lead to more work than necessary in some cases. + // Take an example where the mapping is storing (10->A), (11->A), and where the + // secondary index value is the value, i.e. A. + // Our table initially looks like `A | 10`, `A | 11`. + // Imagine an update where we want to change the first row to `A | 12`. In the "table" + // world, this is only a simple update of a simple cell, no index even involved. But + // from the perspective of mapping, the "scrapper" can only tells us : + // * Key 10 has been deleted + // * Key 12 has been added with value A + // In the backend, we translate that in the "table world" to a deletion and an insertion. + // Having such optimization could be done later on, need to properly evaluate the cost + // of it. + let current_key = self.mapping_keys.first().unwrap(); + let current_value = self.query_value(ctx, contract, current_key).await; + let new_key = T::sample_key(); + let updates = match c { + ChangeType::Receipt(..) => { + panic!("Can't add a new receipt change for storage variable") + } + ChangeType::Silent => vec![], + ChangeType::Insertion => { + vec![MappingUpdate::Insertion( + new_key, + ::Value::sample_value(), + )] + } + ChangeType::Deletion => { + vec![MappingUpdate::Deletion(current_key.clone(), current_value)] + } + ChangeType::Update(u) => { + match u { + UpdateType::Rest => { + let new_value = ::Value::sample_value(); + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { + // we simply change the mapping value since the key is the secondary index + vec![MappingUpdate::Update( + current_key.clone(), + current_value, + new_value, + )] + } + MappingIndex::Value(_) => { + // TRICKY: in this case, the mapping key must change. But from the + // onchain perspective, it means a transfer mapping(old_key -> new_key,value) + vec![ + MappingUpdate::Deletion( + current_key.clone(), + current_value.clone(), + ), + MappingUpdate::Insertion(new_key, current_value), + ] + } + MappingIndex::None => { + // a random update of the mapping, we don't care which since it is + // not impacting the secondary index of the table since the mapping + // doesn't contain the column which is the secondary index, in case + // of the merge table case. + vec![MappingUpdate::Update( + current_key.clone(), + current_value, + new_value, + )] + } + } + } + UpdateType::SecondaryIndex => { + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { + // TRICKY: if the mapping key changes, it's a deletion then + // insertion from onchain perspective + vec![ + MappingUpdate::Deletion( + current_key.clone(), + current_value.clone(), + ), + // we insert the same value but with a new mapping key + MappingUpdate::Insertion(new_key, current_value), + ] + } + MappingIndex::Value(secondary_value_id) => { + // We only update the second index value here. + let slot_input_to_update = self + .slot_inputs + .iter() + .find(|slot_input| { + identifier_for_value_column( + slot_input, + &contract.address, + contract.chain_id, + vec![], + ) == secondary_value_id + }) + .unwrap(); + let mut new_value = current_value.clone(); + new_value.random_update(slot_input_to_update); + // if the value changes, it's a simple update in mapping + vec![MappingUpdate::Update( + current_key.clone(), + current_value, + new_value, + )] + } + MappingIndex::None => { + // empty vec since this table has no secondary index so it should + // give no updates + vec![] + } + } + } + } + } + }; + // small iteration to always have a good updated list of mapping keys + for update in &updates { + match update { + MappingUpdate::Deletion(key_to_delete, _) => { + info!("Removing key {key_to_delete:?} from tracking mapping keys"); + self.mapping_keys.retain(|u| u != key_to_delete); + } + MappingUpdate::Insertion(key_to_insert, _) => { + info!("Inserting key {key_to_insert:?} to tracking mapping keys"); + self.mapping_keys.insert(key_to_insert.clone()); + } + // the mapping key doesn't change here so no need to update the list + MappingUpdate::Update(_, _, _) => {} + } + } + updates.update_contract(ctx, contract).await; + + let new_block_number = ctx.block_number().await as BlockPrimaryIndex; + // NOTE HERE is the interesting bit for dist system as this is the logic to execute + // on receiving updates from scapper. This only needs to have the relevant + // information from update and it will translate that to changes in the tree. + self.mapping_to_table_update(new_block_number, contract, &updates) + } + .boxed() + } + + fn metadata_hash(&self, contract_address: Address, chain_id: u64) -> MetadataHash { + metadata_hash(self.get_data(), &contract_address, chain_id, vec![]) + } + + fn can_query(&self) -> bool { + true + } +} + +impl MappingExtractionArgs { + pub fn new( + slot: u8, + index: MappingIndex, + slot_inputs: Vec, + length_args: Option, + ) -> Self { + Self { + slot, + index, + slot_inputs, + mapping_keys: BTreeSet::new(), + length_args, + } + } + /// The generic parameter `V` could be set to an Uint256 as single value or a Struct. + pub fn mapping_to_table_update( + &self, + block_number: BlockPrimaryIndex, + contract: &Contract, + updates: &[MappingUpdate], + ) -> Vec> { + updates + .iter() + .flat_map(|update| { + match update { + MappingUpdate::Insertion(key, value) => { + // we transform the mapping entry into the "table notion" of row + let entry = UniqueMappingEntry::new(key.clone(), value.clone()); + let (cells, index) = entry.to_update( + block_number, + contract, + &self.index, + &self.slot_inputs, + None, + ); + debug!( + "Insert mapping cells: secondary_index = {:?}, update_cell_len = {}", + index, + cells.updated_cells.len() + ); + vec![TableRowUpdate::Insertion(cells, index)] + } + MappingUpdate::Deletion(key, value) => { + // find the associated row key tree to that value + // HERE: there are multiple possibilities: + // * search for the entry at the previous block instead + // * passing inside the deletion the value deleted as well, so we can + // reconstruct the row key + // * or have this extra list of mapping keys + let entry = UniqueMappingEntry::new(key.clone(), value.clone()); + vec![TableRowUpdate::Deletion(entry.to_row_key( + contract, + &self.index, + &self.slot_inputs, + ))] + } + MappingUpdate::Update(key, old_value, new_value) => { + // NOTE: we need here to (a) delete current row and (b) insert new row + // Regardless of the change if it's on the mapping key or value, since a + // row is uniquely identified by its pair (key,value) then if one of those + // change, that means the row tree key needs to change as well, i.e. it's a + // deletion and addition. + let previous_entry = + UniqueMappingEntry::new(key.clone(), old_value.clone()); + let previous_row_key = + previous_entry.to_row_key(contract, &self.index, &self.slot_inputs); + let new_entry = UniqueMappingEntry::new(key.clone(), new_value.clone()); + + let (mut cells, mut secondary_index) = new_entry.to_update( + block_number, + contract, + &self.index, + &self.slot_inputs, + // NOTE: here we provide the previous key such that we can + // reconstruct the cells tree as it was before and then apply + // the update and put it in a new row. Otherwise we don't know + // the update plan since we don't have a base tree to deal + // with. + // In the case the key is the cell, that's good, we don't need to do + // anything to the tree then since the doesn't change. + // In the case it's the value, then we'll have to reprove the cell. + Some(previous_row_key.clone()), + ); + match self.index { + MappingIndex::OuterKey(_) | MappingIndex::InnerKey(_) => { + // in this case, the mapping value changed, so the cells changed so + // we need to start from scratch. Telling there was no previous row + // key means it's treated as a full new cells tree. + cells.previous_row_key = Default::default(); + } + MappingIndex::Value(_) => { + // This is a bit hacky way but essentially it means that there is + // no update in the cells tree to apply, even tho it's still a new + // insertion of a new row, since we pick up the cells tree form the + // previous location, and that cells tree didn't change (since it's + // based on the mapping key), then no need to update anything. + // TODO: maybe make a better API to express the different + // possibilities: + // * insertion with new cells tree + // * insertion without modification to cells tree + // * update with modification to cells tree (default) + cells.updated_cells = vec![]; + } + MappingIndex::None => { + secondary_index = Default::default(); + } + }; + vec![ + TableRowUpdate::Deletion(previous_row_key), + TableRowUpdate::Insertion(cells, secondary_index), + ] + } + } + }) + .collect_vec() + } + + /// Construct a storage slot info by metadata and a mapping key. + fn storage_slot_info( + &self, + evm_word: u32, + table_info: Vec, + mapping_key: &T, + ) -> StorageSlotInfo { + let storage_slot = mapping_key.storage_slot(self.slot, evm_word); + + StorageSlotInfo::new(storage_slot, table_info) + } + + /// Construct the storage slot info by the all mapping keys. + fn all_storage_slot_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + let evm_word_cols = self.evm_word_column_info(contract); + evm_word_cols + .iter() + .cartesian_product(self.mapping_keys.iter()) + .map(|(evm_word_col, mapping_key)| { + self.storage_slot_info(evm_word_col.evm_word(), table_info.clone(), mapping_key) + }) + .collect() + } + + /// Query a storage slot value by a mapping key. + async fn query_value( + &self, + ctx: &mut TestContext, + contract: &Contract, + mapping_key: &T, + ) -> T::Value { + let mut extracted_values = vec![]; + let evm_word_cols = self.evm_word_column_info(contract); + for evm_word_col in evm_word_cols { + let storage_slot = mapping_key.storage_slot(self.slot, evm_word_col.evm_word()); + let query = ProofQuery::new(contract.address, storage_slot); + let value = ctx + .query_mpt_proof(&query, BlockNumberOrTag::Number(ctx.block_number().await)) + .await + .storage_proof[0] + .value; + + let value_bytes: [u8; 32] = value.to_be_bytes(); + evm_word_col.column_info().iter().for_each(|col_info| { + let bytes = col_info.extract_value(&value_bytes); + let value = U256::from_be_bytes(bytes); + debug!( + "Mapping extract value: column: {:?}, value = {}", + col_info, value, + ); + + extracted_values.push(value); + }); + } + + ::Value::from_u256_slice(&extracted_values) + } + + fn table_info(&self, contract: &Contract) -> Vec { + table_info(contract, self.slot_inputs.clone()) + } + + fn evm_word_column_info(&self, contract: &Contract) -> Vec { + let table_info = self.table_info(contract); + evm_word_column_info(&table_info) + } +} + +/// Contruct the table information by the contract and slot inputs. +fn table_info(contract: &Contract, slot_inputs: Vec) -> Vec { + compute_table_info(slot_inputs, &contract.address, contract.chain_id, vec![]) +} + +/// Construct the column information for each slot and EVM word. +fn evm_word_column_info(table_info: &[ExtractedColumnInfo]) -> Vec { + // Initialize a mapping of `(slot, evm_word) -> column_Identifier`. + let mut column_info_map = HashMap::new(); + table_info.iter().for_each(|col| { + column_info_map + .entry(( + col.extraction_id()[7].0 as u8, + col.location_offset().0 as u32, + )) + .and_modify(|cols: &mut Vec<_>| cols.push(*col)) + .or_insert(vec![*col]); + }); + + column_info_map + .values() + .cloned() + .map(SlotEvmWordColumns::new) + // This sort is used for the storage slot Struct extraction (in generic), + // since we need to collect the Struct field in the right order. + .sorted_by_key(|info| info.evm_word()) + .collect() } diff --git a/mp2-v1/tests/common/celltree.rs b/mp2-v1/tests/common/celltree.rs index 3b229ef95..073f65e44 100644 --- a/mp2-v1/tests/common/celltree.rs +++ b/mp2-v1/tests/common/celltree.rs @@ -169,7 +169,7 @@ impl TestContext { "[+] [+] Merkle SLOT identifier {:?} -> value {} value.digest() = {:?}", cell.identifier(), cell.value(), - pi.individual_digest_point() + pi.individual_values_digest_point() ); self.storage @@ -256,7 +256,7 @@ impl TestContext { // only move the cells tree proof of the actual cells, not the secondary index ! // CellsCollection is a bit weird because it has to contain as well the secondary // index to be able to search in it in JSON - if *id == table.columns.secondary_column().identifier { + if *id == table.columns.secondary_column().identifier() { return (*id, new_cell); } @@ -265,7 +265,7 @@ impl TestContext { " --- CELL TREE key {} index of {id} vs secondary id {} vs table.secondary_id {}", tree_key, previous_row.payload.secondary_index_column, - table.columns.secondary.identifier + table.columns.secondary.identifier() ); // we need to update the primary on the impacted cells at least, OR on all the cells if // we are moving all the proofs to a new row key which happens when doing an DELETE + @@ -309,7 +309,7 @@ impl TestContext { ); Ok(RowPayload { - secondary_index_column: table.columns.secondary_column().identifier, + secondary_index_column: table.columns.secondary_column().identifier(), cell_root_key: Some(root_key), cell_root_hash: Some(tree_hash), cell_root_column: Some( diff --git a/mp2-v1/tests/common/context.rs b/mp2-v1/tests/common/context.rs index 83ea8fe1a..383943759 100644 --- a/mp2-v1/tests/common/context.rs +++ b/mp2-v1/tests/common/context.rs @@ -12,7 +12,7 @@ use anyhow::{Context, Result}; use envconfig::Envconfig; use log::info; use mp2_common::eth::ProofQuery; -use mp2_v1::api::{build_circuits_params, PublicParameters}; +use mp2_v1::api::build_circuits_params; use std::{ fs::File, io::{BufReader, BufWriter}, @@ -33,6 +33,7 @@ use super::{ }, }, proof_storage::ProofKV, + PublicParameters, }; #[derive(Envconfig)] @@ -199,8 +200,13 @@ const INDEX_INFO_FILE: &str = "index.info"; impl TestContext { pub(crate) fn wallet(&self) -> EthereumWallet { - let signer: PrivateKeySigner = self.local_node.as_ref().unwrap().keys()[0].clone().into(); - EthereumWallet::from(signer) + let keys = self.local_node.as_ref().unwrap().keys(); + let signer: PrivateKeySigner = keys[0].clone().into(); + let mut wallet = EthereumWallet::from(signer); + keys.iter().skip(1).for_each(|key| { + wallet.register_signer::(key.clone().into()); + }); + wallet } /// Build the parameters. /// diff --git a/mp2-v1/tests/common/contract_extraction.rs b/mp2-v1/tests/common/contract_extraction.rs index 0da18216d..532b0d1c2 100644 --- a/mp2-v1/tests/common/contract_extraction.rs +++ b/mp2-v1/tests/common/contract_extraction.rs @@ -40,6 +40,7 @@ impl TestContext { StorageSlot::Mapping(mapping_key, slot) => { ProofQuery::new_mapping_slot(*contract_address, slot, mapping_key) } + _ => unimplemented!(), }; let res = self .query_mpt_proof(&query, BlockNumberOrTag::Number(block_number as u64)) diff --git a/mp2-v1/tests/common/final_extraction.rs b/mp2-v1/tests/common/final_extraction.rs index 0ae8db58a..4616810bf 100644 --- a/mp2-v1/tests/common/final_extraction.rs +++ b/mp2-v1/tests/common/final_extraction.rs @@ -1,8 +1,11 @@ use log::debug; -use mp2_common::{digest::TableDimension, proof::ProofWithVK, types::HashOutput, utils::ToFields}; +use mp2_common::{ + group_hashing::weierstrass_to_point, proof::ProofWithVK, types::HashOutput, utils::ToFields, F, +}; use mp2_v1::{ - api, + api, contract_extraction, final_extraction::{CircuitInput, PublicInputs}, + values_extraction, }; use super::TestContext; @@ -11,7 +14,6 @@ use anyhow::Result; #[derive(Clone, Debug, Eq, PartialEq)] pub struct ExtractionTableProof { pub value_proof: Vec, - pub dimension: TableDimension, pub length_proof: Option>, } @@ -22,10 +24,13 @@ pub struct MergeExtractionProof { pub mapping: ExtractionTableProof, } +type ReceiptExtractionProof = Vec; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum ExtractionProofInput { Single(ExtractionTableProof), Merge(MergeExtractionProof), + Receipt(ReceiptExtractionProof), } impl TestContext { @@ -44,12 +49,25 @@ impl TestContext { inputs.length_proof.unwrap(), ) } - ExtractionProofInput::Single(inputs) => CircuitInput::new_simple_input( - block_proof, - contract_proof, - inputs.value_proof, - inputs.dimension, - ), + ExtractionProofInput::Single(inputs) => { + { + let value_proof = ProofWithVK::deserialize(&inputs.value_proof).unwrap(); + let value_pi = values_extraction::PublicInputs::::new( + &value_proof.proof().public_inputs, + ); + let contract_proof = ProofWithVK::deserialize(&contract_proof).unwrap(); + let contract_pi = contract_extraction::PublicInputs::from_slice( + &contract_proof.proof().public_inputs, + ); + debug!( + "BEFORE proving final extraction:\n\tvalues_ex_md = {:?}\n\tcontract_md = {:?}\n\texpected_final_md = {:?}", + value_pi.metadata_digest(), + contract_pi.metadata_point(), + (weierstrass_to_point(&value_pi.metadata_digest()) + weierstrass_to_point(&contract_pi.metadata_point())).to_weierstrass(), + ); + } + CircuitInput::new_simple_input(block_proof, contract_proof, inputs.value_proof) + } // NOTE hardcoded for single and mapping right now ExtractionProofInput::Merge(inputs) => CircuitInput::new_merge_single_and_mapping( block_proof, @@ -57,6 +75,9 @@ impl TestContext { inputs.single.value_proof, inputs.mapping.value_proof, ), + ExtractionProofInput::Receipt(input) => { + CircuitInput::new_receipt_input(block_proof, input) + } }?; let params = self.params(); let proof = self @@ -76,7 +97,11 @@ impl TestContext { assert_eq!(pis.block_number(), block.header.number); assert_eq!(pis.block_hash_raw(), block_hash.to_fields()); assert_eq!(pis.prev_block_hash_raw(), prev_block_hash.to_fields()); - debug!(" FINAL EXTRACTION MPT - digest: {:?}", pis.value_point()); + debug!( + " FINAL EXTRACTION MPT -\n\tvalues digest: {:?}\n\tmetadata digest: {:?}", + pis.value_point(), + pis.metadata_point(), + ); Ok(proof) } diff --git a/mp2-v1/tests/common/index_tree.rs b/mp2-v1/tests/common/index_tree.rs index 556d35e90..df5eb08e3 100644 --- a/mp2-v1/tests/common/index_tree.rs +++ b/mp2-v1/tests/common/index_tree.rs @@ -1,11 +1,10 @@ use alloy::primitives::U256; - use log::{debug, info}; use mp2_common::{poseidon::empty_poseidon_hash, proof::ProofWithVK}; use mp2_v1::{ api, indexing::{ - block::{BlockPrimaryIndex, BlockTree, BlockTreeKey}, + block::{get_previous_epoch, BlockPrimaryIndex, BlockTreeKey, MerkleIndexTree}, index::IndexNode, }, values_extraction::identifier_block_column, @@ -13,12 +12,12 @@ use mp2_v1::{ use plonky2::plonk::config::GenericHashOut; use ryhope::{ storage::{ - pgsql::PgsqlStorage, updatetree::{Next, UpdateTree}, RoEpochKvStorage, }, - MerkleTreeKvDb, + UserEpoch, }; +use verifiable_db::block_tree::compute_final_digest; use crate::common::proof_storage::{IndexProofIdentifier, ProofKey}; @@ -28,9 +27,6 @@ use super::{ TestContext, }; -pub type IndexStorage = PgsqlStorage>; -pub type MerkleIndexTree = MerkleTreeKvDb, IndexStorage>; - impl TestContext { /// NOTE: we require the added_index information because we need to distinguish if a new node /// added has a leaf or a as parent. The rest of the nodes in the update tree are to be proven @@ -83,12 +79,19 @@ impl TestContext { let ext_pi = mp2_v1::final_extraction::PublicInputs::from_slice( &ext_proof.proof().public_inputs, ); + let is_merge = ext_pi.merge_flag(); + let final_db_digest = compute_final_digest(is_merge, &row_pi).to_weierstrass(); assert_eq!( - row_pi.rows_digest_field(), + final_db_digest, ext_pi.value_point(), - "values extracted vs value in db don't match (left row, right mpt (block {})", + "Block (DB) values digest and values extraction don't match (left DB, right MPT, is_merge {} block {})", + is_merge, node.value.0.to::() ); + debug!( + "NodeIndex Proving - multiplier digest: {:?}", + row_pi.multiplier_digest_point(), + ); } let proof = if context.is_leaf() { info!( @@ -163,7 +166,14 @@ impl TestContext { // here we are simply proving the new updated nodes from the new node to // the root. We fetch the same node but at the previous version of the // tree to prove the update. - let previous_node = t.try_fetch_at(k, t.current_epoch() - 1).await?.unwrap(); + let previous_epoch = + get_previous_epoch(t, t.current_epoch().await? as BlockPrimaryIndex) + .await? + .expect("No previous epoch found, we shouldn't be in this case"); + let previous_node = t + .try_fetch_at(k, previous_epoch as UserEpoch) + .await? + .unwrap(); let left_key = context.left.expect("should always be a left child"); let left_node = t.try_fetch(&left_key).await?.unwrap(); // this should be one of the nodes we just proved in this loop before diff --git a/mp2-v1/tests/common/ivc.rs b/mp2-v1/tests/common/ivc.rs index 1203ea1ff..467a9efe7 100644 --- a/mp2-v1/tests/common/ivc.rs +++ b/mp2-v1/tests/common/ivc.rs @@ -1,11 +1,13 @@ use super::{ context::TestContext, - index_tree::MerkleIndexTree, proof_storage::{IndexProofIdentifier, ProofKey, ProofStorage}, table::TableID, }; use mp2_common::{proof::ProofWithVK, types::HashOutput, F}; -use mp2_v1::{api, indexing::block::BlockPrimaryIndex}; +use mp2_v1::{ + api, + indexing::block::{get_previous_epoch, BlockPrimaryIndex, MerkleIndexTree}, +}; use plonky2::{hash::hash_types::HashOut, plonk::config::GenericHashOut}; use verifiable_db::ivc::PublicInputs; @@ -30,12 +32,13 @@ impl TestContext { // load the previous IVC proof if there is one // we simply can try to load from the storage at block -1 // TODO: generalize that to a better more generic method for any index tree - let previous_ivc_key = ProofKey::IVC(bn - 1); - let input = match self.storage.get_proof_exact(&previous_ivc_key) { - Ok(previous_proof) => { - verifiable_db::ivc::CircuitInput::new_subsequent_input(root_proof, previous_proof) - } - Err(_) => verifiable_db::ivc::CircuitInput::new_first_input(root_proof), + let previous_block = get_previous_epoch(index_tree, bn).await?; + let input = if let Some(prev_bn) = previous_block { + let previous_ivc_key = ProofKey::IVC(prev_bn); + let previous_proof = self.storage.get_proof_exact(&previous_ivc_key)?; + verifiable_db::ivc::CircuitInput::new_subsequent_input(root_proof, previous_proof) + } else { + verifiable_db::ivc::CircuitInput::new_first_input(root_proof) } .expect("unable to create ivc circuit inputs"); let ivc_proof = self diff --git a/mp2-v1/tests/common/length_extraction.rs b/mp2-v1/tests/common/length_extraction.rs index f036afa9d..ec3608f3b 100644 --- a/mp2-v1/tests/common/length_extraction.rs +++ b/mp2-v1/tests/common/length_extraction.rs @@ -1,9 +1,9 @@ -use alloy::{eips::BlockNumberOrTag, primitives::Address}; +use alloy::{eips::BlockNumberOrTag, primitives::Address, providers::Provider}; use log::info; use mp2_common::{ eth::StorageSlot, mpt_sequential::utils::bytes_to_nibbles, proof::ProofWithVK, types::GFp, }; -use mp2_v1::length_extraction::PublicInputs; +use mp2_v1::{length_extraction::PublicInputs, values_extraction::StorageSlotInfo}; use plonky2::field::types::Field; use crate::common::storage_trie::TestStorageTrie; @@ -17,17 +17,19 @@ impl TestContext { &self, contract_address: &Address, bn: BlockNumberOrTag, - chain_id: u64, - slot: u8, + slot_info: StorageSlotInfo, value: u8, ) -> ProofWithVK { // Initialize the test trie. let mut trie = TestStorageTrie::new(); info!("Initialized the test storage trie"); + let slot = slot_info.slot().slot(); + // Query the slot and add the node path to the trie. - trie.query_proof_and_add_slot(self, contract_address, bn, slot as usize) + trie.query_proof_and_add_slot(self, contract_address, bn, slot_info) .await; + let chain_id = self.rpc.get_chain_id().await.unwrap(); let proof = trie.prove_length(contract_address, chain_id, value, self.params(), &self.b); // Check the public inputs. diff --git a/mp2-v1/tests/common/mod.rs b/mp2-v1/tests/common/mod.rs index c604a1424..7f7dd971f 100644 --- a/mp2-v1/tests/common/mod.rs +++ b/mp2-v1/tests/common/mod.rs @@ -2,9 +2,9 @@ use alloy::primitives::Address; use anyhow::Result; use cases::table_source::TableSource; -use mp2_v1::api::{merge_metadata_hash, metadata_hash, MetadataHash, SlotInputs}; -use serde::{Deserialize, Serialize}; -use table::TableColumns; +use mp2_v1::api::MetadataHash; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use table::{TableColumns, TableRowUniqueID}; pub mod benchmarker; pub mod bindings; mod block_extraction; @@ -30,7 +30,11 @@ pub(crate) use context::TestContext; use mp2_common::{proof::ProofWithVK, types::HashOutput}; use plonky2::plonk::config::GenericHashOut; +/// Testing maximum columns +pub(crate) const TEST_MAX_COLUMNS: usize = 32; + type ColumnIdentifier = u64; +type PublicParameters = mp2_v1::api::PublicParameters; fn cell_tree_proof_to_hash(proof: &[u8]) -> HashOutput { let root_pi = ProofWithVK::deserialize(proof) @@ -38,7 +42,7 @@ fn cell_tree_proof_to_hash(proof: &[u8]) -> HashOutput { .proof .public_inputs; verifiable_db::cells_tree::PublicInputs::from_slice(&root_pi) - .root_hash_hashout() + .node_hash() .to_bytes() .try_into() .unwrap() @@ -50,7 +54,7 @@ fn row_tree_proof_to_hash(proof: &[u8]) -> HashOutput { .proof .public_inputs; verifiable_db::row_tree::PublicInputs::from_slice(&root_pi) - .root_hash_hashout() + .root_hash() .to_bytes() .try_into() .unwrap() @@ -65,39 +69,21 @@ pub fn mkdir_all(params_path_str: &str) -> Result<()> { } #[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TableInfo { +#[serde(bound = "T: Serialize + DeserializeOwned")] +pub struct TableInfo { pub columns: TableColumns, + pub row_unique_id: TableRowUniqueID, // column to do queries over for numerical values, NOT secondary index pub value_column: String, pub public_name: String, pub contract_address: Address, pub chain_id: u64, - pub source: TableSource, + pub source: T, } -impl TableInfo { +impl TableInfo { pub fn metadata_hash(&self) -> MetadataHash { - match &self.source { - TableSource::Mapping((mapping, _)) => { - let slot = SlotInputs::Mapping(mapping.slot); - metadata_hash(slot, &self.contract_address, self.chain_id, vec![]) - } - // mapping with length not tested right now - TableSource::SingleValues(args) => { - let slot = SlotInputs::Simple(args.slots.clone()); - metadata_hash(slot, &self.contract_address, self.chain_id, vec![]) - } - TableSource::Merge(merge) => { - let single = SlotInputs::Simple(merge.single.slots.clone()); - let mapping = SlotInputs::Mapping(merge.mapping.slot); - merge_metadata_hash( - self.contract_address, - self.chain_id, - vec![], - single, - mapping, - ) - } - } + self.source + .metadata_hash(self.contract_address, self.chain_id) } } diff --git a/mp2-v1/tests/common/rowtree.rs b/mp2-v1/tests/common/rowtree.rs index c00f89b21..a0744d9c5 100644 --- a/mp2-v1/tests/common/rowtree.rs +++ b/mp2-v1/tests/common/rowtree.rs @@ -1,27 +1,30 @@ use alloy::primitives::U256; use log::debug; -use mp2_common::proof::ProofWithVK; +use mp2_common::{proof::ProofWithVK, types::MAPPING_KEY_LEN}; use mp2_v1::{ api::{self, CircuitInput}, indexing::{ block::BlockPrimaryIndex, cell::Cell, index::IndexNode, - row::{RowPayload, RowTree, RowTreeKey, ToNonce}, + row::{RowTreeKey, ToNonce}, + }, + values_extraction::{ + row_unique_data_for_mapping_leaf, row_unique_data_for_mapping_of_mappings_leaf, + row_unique_data_for_single_leaf, }, }; use plonky2::plonk::config::GenericHashOut; -use ryhope::{ - storage::{ - pgsql::PgsqlStorage, - updatetree::{Next, UpdateTree}, - RoEpochKvStorage, - }, - MerkleTreeKvDb, +use ryhope::storage::{ + updatetree::{Next, UpdateTree}, + RoEpochKvStorage, +}; +use verifiable_db::{ + cells_tree, + row_tree::{self, extract_hash_from_proof}, }; -use verifiable_db::{cells_tree, row_tree::extract_hash_from_proof}; -use crate::common::row_tree_proof_to_hash; +use crate::common::{row_tree_proof_to_hash, table::TableRowUniqueID}; use super::{ proof_storage::{CellProofIdentifier, ProofKey, ProofStorage, RowProofIdentifier}, @@ -41,7 +44,7 @@ impl SecondaryIndexCell { } pub fn cell(&self) -> Cell { - self.0.clone() + self.0 } pub fn rest(&self) -> RowTreeKeyNonce { self.1.clone() @@ -66,9 +69,6 @@ impl From<&SecondaryIndexCell> for RowTreeKey { } } -pub type RowStorage = PgsqlStorage>; -pub type MerkleRowTree = MerkleTreeKvDb, RowStorage>; - impl TestContext { /// Given a row tree (i.e. secondary index tree) and its update tree, prove /// it. @@ -90,7 +90,60 @@ impl TestContext { let id = row.secondary_index_column; // Sec. index value let value = row.secondary_index_value(); - let multiplier = table.columns.column_info(id).multiplier; + let column_info = table.columns.column_info(id); + let multiplier = column_info.multiplier; + let row_unique_data = match table.row_unique_id { + TableRowUniqueID::Single => row_unique_data_for_single_leaf(), + TableRowUniqueID::Mapping(key_column_id) => { + let mapping_key: [_; MAPPING_KEY_LEN] = row + .column_value(key_column_id) + .unwrap_or_else(|| { + panic!("Cannot fetch the mapping key: key_column_id = {key_column_id}") + }) + .to_be_bytes(); + debug!( + "FETCHED mapping key to compute row_unique_data: mapping_key = {:?}", + hex::encode(mapping_key), + ); + row_unique_data_for_mapping_leaf(&mapping_key) + } + TableRowUniqueID::MappingOfMappings(outer_key_column_id, inner_key_column_id) => { + let [outer_mapping_key, inner_mapping_key]: [[_; MAPPING_KEY_LEN]; 2] = [outer_key_column_id, inner_key_column_id].map(|key_column_id| { + row.column_value(key_column_id) + .unwrap_or_else(|| { + panic!("Cannot fetch the key of mapping of mappings: key_column_id = {key_column_id}") + }) + .to_be_bytes() + }); + debug!( + "FETCHED mapping of mappings keys to compute row_unique_data: outer_key = {:?}, inner_key = {:?}", + hex::encode(outer_mapping_key), + hex::encode(inner_mapping_key), + ); + + row_unique_data_for_mapping_of_mappings_leaf( + &outer_mapping_key, + &inner_mapping_key, + ) + } + TableRowUniqueID::Receipt(tx_index_id, gas_used_id) => { + let [tx_index, gas_used]: [[_; MAPPING_KEY_LEN]; 2] = [tx_index_id, gas_used_id].map(|column_id| { + row.column_value(column_id) + .unwrap_or_else(|| { + panic!("Cannot fetch the key of receipt column: column_id = {column_id}") + }) + .to_be_bytes() + }); + debug!( + "FETCHED receipt values to compute row_unique_data: tx_index = {:?}, gas_used = {:?}", + hex::encode(tx_index), + hex::encode(gas_used), + ); + + // The receipt row unique id is computed in the same way as mapping of mappings + row_unique_data_for_mapping_of_mappings_leaf(&tx_index, &gas_used) + } + }; // NOTE remove that when playing more with sec. index assert!(!multiplier, "secondary index should be individual type"); // find where the root cells proof has been stored. This comes from looking up the @@ -126,15 +179,15 @@ impl TestContext { row.cells, ); - { - let pvk = ProofWithVK::deserialize(&cell_tree_proof)?; - let pis = cells_tree::PublicInputs::from_slice(&pvk.proof().public_inputs); - debug!( - " Cell Root SPLIT digest: multiplier {:?}, individual {:?}", - pis.multiplier_digest_point(), - pis.individual_digest_point() - ); - } + let cells_tree_proof_with_vk = ProofWithVK::deserialize(&cell_tree_proof)?; + let cells_tree_pi = cells_tree::PublicInputs::from_slice( + &cells_tree_proof_with_vk.proof().public_inputs, + ); + debug!( + " Cell Root SPLIT digest:\n\tindividual_value {:?}\n\tmultiplier_value {:?}", + cells_tree_pi.individual_values_digest_point(), + cells_tree_pi.multiplier_values_digest_point(), + ); let proof = if context.is_leaf() { // Prove a leaf @@ -150,16 +203,29 @@ impl TestContext { id, value, multiplier, + row_unique_data, cell_tree_proof, ) .unwrap(), ); debug!("Before proving leaf node row tree key {:?}", k); - self.b + let proof = self + .b .bench("indexing::row_tree::leaf", || { api::generate_proof(self.params(), inputs) }) - .expect("while proving leaf") + .expect("while proving leaf"); + let pproof = ProofWithVK::deserialize(&proof).unwrap(); + let pi = verifiable_db::row_tree::PublicInputs::from_slice( + &pproof.proof().public_inputs, + ); + debug!( + "FINISH proving row leaf -->\n\tid = {:?}\n\tindividual digest = {:?}\n\tmultiplier digest = {:?}", + id, + pi.individual_digest_point(), + pi.multiplier_digest_point(), + ); + proof } else if context.is_partial() { let child_key = context .left @@ -179,6 +245,16 @@ impl TestContext { .storage .get_proof_exact(&ProofKey::Row(proof_key.clone())) .expect("UT guarantees proving in order"); + { + let child_pi = ProofWithVK::deserialize(&child_proof).unwrap(); + let child_pi = + row_tree::PublicInputs::from_slice(&child_pi.proof().public_inputs); + debug!( + "BEFORE proving row partial node -->\n\tis_mulitplier = {}\n\tchild_individual_digest = {:?}", + multiplier, + child_pi.individual_digest_point(), + ); + } let inputs = CircuitInput::RowsTree( verifiable_db::row_tree::CircuitInput::partial( @@ -186,6 +262,7 @@ impl TestContext { value, multiplier, context.left.is_some(), + row_unique_data, child_proof, cell_tree_proof, ) @@ -228,6 +305,7 @@ impl TestContext { id, value, multiplier, + row_unique_data, left_proof, right_proof, cell_tree_proof, @@ -276,7 +354,7 @@ impl TestContext { let pi = verifiable_db::row_tree::PublicInputs::from_slice(&pproof.proof().public_inputs); debug!( "[--] FINAL MERKLE DIGEST VALUE --> {:?} ", - pi.rows_digest_field() + pi.individual_digest_point() ); if root_proof_key.primary != primary { debug!("[--] NO UPDATES on row this turn? row.root().primary = {} vs new primary proving step {}",root_proof_key.primary,primary); @@ -314,7 +392,7 @@ impl TestContext { ); Ok(IndexNode { - identifier: table.columns.primary_column().identifier, + identifier: table.columns.primary_column().identifier(), value: U256::from(primary).into(), row_tree_root_key: root_proof_key.tree_key, row_tree_hash: table.row.root_data().await?.unwrap().hash, diff --git a/mp2-v1/tests/common/storage_trie.rs b/mp2-v1/tests/common/storage_trie.rs index e1984eccb..8cdebd755 100644 --- a/mp2-v1/tests/common/storage_trie.rs +++ b/mp2-v1/tests/common/storage_trie.rs @@ -1,25 +1,24 @@ //! Storage trie for proving tests -use super::{benchmarker::Benchmarker, TestContext}; +use super::{benchmarker::Benchmarker, PublicParameters, TestContext}; use alloy::{ eips::BlockNumberOrTag, primitives::{Address, U256}, }; + use log::debug; use mp2_common::{ - eth::{ProofQuery, StorageSlot}, + eth::{ProofQuery, StorageSlot, StorageSlotNode}, mpt_sequential::{MPT_BRANCH_RLP_SIZE, MPT_EXTENSION_RLP_SIZE}, proof::ProofWithVK, utils::{keccak256, Endianness, Packer}, }; use mp2_v1::{ - api::{generate_proof, CircuitInput, PublicParameters}, + api::{generate_proof, CircuitInput}, length_extraction, - values_extraction::{ - self, identifier_for_mapping_key_column, identifier_for_mapping_value_column, - identifier_single_var_column, - }, + values_extraction::{self, StorageSlotInfo}, }; +use plonky2::field::types::PrimeField64; use rlp::{Prototype, Rlp}; use std::collections::HashMap; @@ -38,9 +37,9 @@ struct ProvingContext<'a> { contract_address: &'a Address, chain_id: u64, params: &'a PublicParameters, - slots: &'a HashMap, - variable_slot: Option, + slots: &'a HashMap, b: &'a Benchmarker, + variable_slot: Option, } impl<'a> ProvingContext<'a> { @@ -49,27 +48,19 @@ impl<'a> ProvingContext<'a> { contract_address: &'a Address, chain_id: u64, params: &'a PublicParameters, - slots: &'a HashMap, + slots: &'a HashMap, variable_slot: Option, bench: &'a Benchmarker, ) -> Self { Self { contract_address, + chain_id, params, slots, variable_slot, b: bench, - chain_id, } } - - /// Check if it's the simple slot type during proving. - fn is_simple_slot(&self) -> bool { - // Has 1 slot to prove at least. - let slot = self.slots.iter().next().unwrap().1; - - slot.is_simple_slot() - } } /// Trie node type @@ -167,22 +158,16 @@ impl TrieNode { .collect(); // Build the branch circuit input. - let (name, input) = if ctx.is_simple_slot() { - ( - "indexing::extraction::mpt::branch::variable", - values_extraction::CircuitInput::new_single_variable_branch(node, child_proofs), - ) - } else { - ( - "indexing::extraction::mpt::branch::mapping", - values_extraction::CircuitInput::new_mapping_variable_branch(node, child_proofs), - ) - }; - let input = CircuitInput::ValuesExtraction(input); + let input = CircuitInput::ValuesExtraction(values_extraction::CircuitInput::new_branch( + node, + child_proofs, + )); // Generate the proof. ctx.b - .bench(name, || generate_proof(ctx.params, input)) + .bench("indexing::extraction::mpt::branch", || { + generate_proof(ctx.params, input) + }) .unwrap() } @@ -215,54 +200,120 @@ impl TrieNode { let node = self.raw.clone(); - // Find the storage slot for this leaf node. - let slot = ctx.slots.get(&node).unwrap(); + // Find the storage slot information for this leaf node. + let slot_info = ctx.slots.get(&node).unwrap(); // Build the leaf circuit input. - let (name, input) = match slot { - StorageSlot::Simple(slot) => { - let slot = *slot as u8; - let column_id = - identifier_single_var_column(slot, ctx.contract_address, ctx.chain_id, vec![]); - ( - "indexing::extraction::mpt::leaf::single", + let (name, input) = match slot_info.slot() { + // Simple variable slot + StorageSlot::Simple(slot) => ( + "indexing::extraction::mpt::leaf::single_var", + values_extraction::CircuitInput::new_single_variable_leaf( + node.clone(), + *slot as u8, + slot_info.evm_word(), + slot_info.table_info().to_vec(), + ), + ), + // Mapping variable + StorageSlot::Mapping(mapping_key, slot) => ( + "indexing::extraction::mpt::leaf::mapping_var", + values_extraction::CircuitInput::new_mapping_variable_leaf( + node.clone(), + *slot as u8, + mapping_key.clone(), + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + slot_info.evm_word(), + slot_info.table_info().to_vec(), + ), + ), + StorageSlot::Node(StorageSlotNode::Mapping(parent, inner_mapping_key)) => { + match &**parent { + // Mapping of single value mappings + StorageSlot::Mapping(outer_mapping_key, slot) => ( + "indexing::extraction::mpt::leaf::mapping_of_single_value_mappings", + values_extraction::CircuitInput::new_mapping_of_mappings_leaf( + node.clone(), + *slot as u8, + ( + outer_mapping_key.clone(), + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + ( + inner_mapping_key.clone(), + slot_info + .inner_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + slot_info.evm_word(), + slot_info.table_info().to_vec(), + ), + ), + _ => unreachable!(), + } + } + StorageSlot::Node(StorageSlotNode::Struct(parent, _)) => match &**parent { + // Simple Struct + StorageSlot::Simple(slot) => ( + "indexing::extraction::mpt::leaf::single_struct", values_extraction::CircuitInput::new_single_variable_leaf( node.clone(), - slot, - column_id, + *slot as u8, + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), - ) - } - StorageSlot::Mapping(mapping_key, slot) => { - let slot = *slot as u8; - let key_id = identifier_for_mapping_key_column( - slot, - ctx.contract_address, - ctx.chain_id, - vec![], - ); - let value_id = identifier_for_mapping_value_column( - slot, - ctx.contract_address, - ctx.chain_id, - vec![], - ); - ( - "indexing::extraction::mpt::leaf::mapping", + ), + // Mapping Struct + StorageSlot::Mapping(mapping_key, slot) => ( + "indexing::extraction::mpt::leaf::mapping_struct", values_extraction::CircuitInput::new_mapping_variable_leaf( node.clone(), - slot, + *slot as u8, mapping_key.clone(), - key_id, - value_id, + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + slot_info.evm_word(), + slot_info.table_info().to_vec(), ), - ) - } + ), + // Mapping of struct mappings + StorageSlot::Node(StorageSlotNode::Mapping(grand, inner_mapping_key)) => { + match &**grand { + StorageSlot::Mapping(outer_mapping_key, slot) => ( + "indexing::extraction::mpt::leaf::mapping_of_struct_mappings", + values_extraction::CircuitInput::new_mapping_of_mappings_leaf( + node.clone(), + *slot as u8, + ( + outer_mapping_key.clone(), + slot_info + .outer_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + ( + inner_mapping_key.clone(), + slot_info + .inner_key_id(ctx.contract_address, ctx.chain_id, vec![]) + .unwrap(), + ), + slot_info.evm_word(), + slot_info.table_info().to_vec(), + ), + ), + _ => unreachable!(), + } + } + _ => unreachable!(), + }, }; let input = CircuitInput::ValuesExtraction(input); // Generate the proof. - let proof = ctx .b .bench(name, || generate_proof(ctx.params, input)) @@ -272,10 +323,24 @@ impl TrieNode { let list: Vec> = rlp::decode_list(&node); let value: Vec = rlp::decode(&list[1]).unwrap(); debug!( - "[+] [+] MPT SLOT {:?} -> value {:?} value.digest() = {:?}", - slot.slot(), + "[+] [+] MPT SLOT {} -> identifiers {:?} value {:?} value.digest() = {:?}", + slot_info.slot().slot(), + slot_info + .table_columns(ctx.contract_address, ctx.chain_id, vec![]) + .extracted_columns() + .iter() + .filter_map(|column| { + let check_one = column.extraction_id()[7].0 as u8 == slot_info.slot().slot(); + let check_two = column.location_offset().0 as u32 == slot_info.evm_word(); + if check_one && check_two { + Some(column.identifier().to_canonical_u64()) + } else { + None + } + }) + .collect::>(), U256::from_be_slice(&value), - pi.values_digest() + pi.values_digest(), ); proof } @@ -301,13 +366,15 @@ impl TrieNode { let slot = ctx.slots.get(&node).unwrap(); // Build the leaf circuit input. - let input = match slot { + let input = match slot.slot() { StorageSlot::Simple(slot) => { length_extraction::LengthCircuitInput::new_leaf(*slot as u8, node, variable_slot) } StorageSlot::Mapping(_, slot) => { length_extraction::LengthCircuitInput::new_leaf(*slot as u8, node, variable_slot) } + // TODO: Fix when updating the length circuit. + _ => unimplemented!(), }; let input = CircuitInput::LengthExtraction(input); @@ -372,7 +439,7 @@ pub(crate) struct TestStorageTrie { /// Root of this trie root: Option, /// Storage slot map indexed by the raw node - slots: HashMap, + slots: HashMap, } impl TestStorageTrie { @@ -395,8 +462,8 @@ impl TestStorageTrie { /// If the current trie already has a root (initialized by a slot before), the new slot must satisfy: /// - It's the same type of storage slot as previous ones (simple or mapping). /// - The node path has the same root of the current trie. - pub(crate) fn add_slot(&mut self, slot: StorageSlot, mut nodes: Vec) { - self.check_new_slot(&slot, &nodes); + pub(crate) fn add_slot(&mut self, slot: StorageSlotInfo, mut nodes: Vec) { + self.check_new_slot(slot.slot(), &nodes); // Save the slot to a map and index by the leaf node. let insert_result = self.slots.insert(nodes[0].clone(), slot); @@ -419,11 +486,12 @@ impl TestStorageTrie { ctx: &TestContext, contract_address: &Address, bn: BlockNumberOrTag, - slot: usize, + slot_info: StorageSlotInfo, ) { - log::debug!("Querying the simple slot `{slot:?}` of the contract `{contract_address}` from the test context's RPC"); + let storage_slot = slot_info.slot(); + log::debug!("Querying the slot `{storage_slot:?}` of the contract `{contract_address}` from the test context's RPC"); - let query = ProofQuery::new_simple_slot(*contract_address, slot); + let query = ProofQuery::new(*contract_address, storage_slot.clone()); let response = ctx.query_mpt_proof(&query, bn).await; // Get the nodes to prove. Reverse to the sequence from leaf to root. @@ -434,14 +502,12 @@ impl TestStorageTrie { .map(|node| node.to_vec()) .collect(); - let slot = StorageSlot::Simple(slot); - log::debug!( - "Simple slot {slot:?} queried, appending `{}` proof nodes to the trie", + "Storage slot {storage_slot:?} queried, appending `{}` proof nodes to the trie", nodes.len() ); - self.add_slot(slot, nodes); + self.add_slot(slot_info, nodes); } /// Generate the proof for the trie. @@ -488,11 +554,21 @@ impl TestStorageTrie { fn check_new_slot(&self, new_slot: &StorageSlot, new_nodes: &[RawNode]) { if let Some((_, slot)) = self.slots.iter().next() { // The new slot must be the same type. - match (slot, new_slot) { - (&StorageSlot::Simple(_), &StorageSlot::Simple(_)) => (), - (&StorageSlot::Mapping(_, slot), &StorageSlot::Mapping(_, new_slot)) => { - // Must have the same slot number for the mapping type. - assert_eq!(slot, new_slot); + let current_slot = slot.slot(); + match (current_slot.is_simple_slot(), new_slot.is_simple_slot()) { + // We could combine the different simple slots. + (true, true) => (), + (false, false) => { + assert_eq!( + current_slot.slot(), + new_slot.slot(), + "Mapping slot number must be same in a storage trie", + ); + assert_eq!( + current_slot.mapping_keys().len(), + new_slot.mapping_keys().len(), + "Mapping keys must have the same number in a storage trie", + ); } _ => panic!("Add the different type of storage slots: {slot:?}, {new_slot:?}"), } diff --git a/mp2-v1/tests/common/table.rs b/mp2-v1/tests/common/table.rs index f8a66fc22..5785d3926 100644 --- a/mp2-v1/tests/common/table.rs +++ b/mp2-v1/tests/common/table.rs @@ -1,4 +1,4 @@ -use anyhow::{ensure, Context}; +use anyhow::{ensure, Context, Result}; use bb8::Pool; use bb8_postgres::{tokio_postgres::NoTls, PostgresConnectionManager}; use futures::{ @@ -8,21 +8,20 @@ use futures::{ use itertools::Itertools; use log::debug; use mp2_v1::indexing::{ - block::{BlockPrimaryIndex, BlockTreeKey}, + block::{BlockPrimaryIndex, BlockTreeKey, MerkleIndexTree}, + build_trees, cell::{self, Cell, CellTreeKey, MerkleCell, MerkleCellTree}, index::IndexNode, - row::{CellCollection, Row, RowTreeKey}, + load_trees, + row::{CellCollection, MerkleRowTree, Row, RowTreeKey}, ColumnID, }; use parsil::symbols::{ColumnKind, ContextProvider, ZkColumn, ZkTable}; + use ryhope::{ - storage::{ - pgsql::{SqlServerConnection, SqlStorageSettings}, - updatetree::UpdateTree, - EpochKvStorage, RoEpochKvStorage, TreeTransactionalStorage, - }, + storage::{updatetree::UpdateTree, EpochKvStorage, RoEpochKvStorage, TreeTransactionalStorage}, tree::scapegoat::Alpha, - Epoch, InitSettings, + UserEpoch, }; use serde::{Deserialize, Serialize}; use std::{hash::Hash, iter::once}; @@ -33,8 +32,6 @@ use super::{ MAX_NUM_COLUMNS, MAX_NUM_ITEMS_PER_OUTPUT, MAX_NUM_OUTPUTS, MAX_NUM_PREDICATE_OPS, MAX_NUM_RESULT_OPS, }, - index_tree::MerkleIndexTree, - rowtree::MerkleRowTree, ColumnIdentifier, }; @@ -56,13 +53,33 @@ impl IndexType { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct TableColumn { pub name: String, - pub identifier: ColumnID, + pub identifier: u64, pub index: IndexType, /// multiplier means if this columns come from a "merged" table, then it either come from a /// table a or table b. One of these table is the "multiplier" table, the other is not. pub multiplier: bool, } +impl TableColumn { + pub fn identifier(&self) -> ColumnID { + self.identifier + } +} + +/// Table Row unique ID is used to compute the unique data of a row when proving for the cells. +/// It corresponds to the different types of storage slot as: +/// Single slot - row_unique_data_for_single_leaf() +/// Mapping slot - row_unique_data_for_mapping_leaf(mapping_key) +/// Mapping of mappings slot - row_unique_data_for_mapping_of_mappings_leaf(outer_mapping_key, inner_mapping_key) +/// We save the column IDs for fetching the cell value to compute this row unique data. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum TableRowUniqueID { + Single, + Mapping(ColumnID), + MappingOfMappings(ColumnID, ColumnID), + Receipt(ColumnID, ColumnID), +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TableColumns { pub primary: TableColumn, @@ -80,14 +97,14 @@ impl TableColumns { self.rest.clone() } pub fn column_id_of_cells_index(&self, key: CellTreeKey) -> Option { - self.rest.get(key - 1).map(|tc| tc.identifier) + self.rest.get(key - 1).map(|tc| tc.identifier()) } pub fn column_info(&self, identifier: ColumnIdentifier) -> TableColumn { self.rest .iter() .chain(once(&self.secondary)) - .find(|c| c.identifier == identifier) - .unwrap_or_else(|| panic!("can't find cell from identifier {}", identifier)) + .find(|c| c.identifier() == identifier) + .expect("can't find cell from identifier") .clone() } pub fn ordered_cells( @@ -103,17 +120,21 @@ impl TableColumns { pub fn cells_tree_index_of(&self, identifier: ColumnIdentifier) -> usize { match identifier { // TODO this will be problematic in the CSV case - _ if identifier == self.primary.identifier => panic!( - "should not call the position on primary index since should not be included in cells tree" + _ if identifier == self.primary.identifier() => panic!( + "should not call the position on primary index since should not be included in cells tree: {} == {}", + identifier, + self.primary.identifier(), ), - _ if identifier == self.secondary.identifier => panic!( - "should not call the position on secondary index since should not be included in cells tree" + _ if identifier == self.secondary.identifier() => panic!( + "should not call the position on secondary index since should not be included in cells tree: {} == {}", + identifier, + self.secondary.identifier(), ), _ => self .rest .iter() .enumerate() - .find(|(_, c)| c.identifier == identifier) + .find(|(_, c)| c.identifier() == identifier) // + 1 because sbbst starts at 1 not zero .map(|(i, _)| i+1) .expect("can't find index of identfier"), @@ -121,9 +142,9 @@ impl TableColumns { } pub fn self_assert(&self) { for column in self.non_indexed_columns() { - let idx = self.cells_tree_index_of(column.identifier); + let idx = self.cells_tree_index_of(column.identifier()); let id = self.column_id_of_cells_index(idx).unwrap(); - assert!(column.identifier == id); + assert!(column.identifier() == id); } } } @@ -131,12 +152,12 @@ impl TableColumns { impl From<&TableColumns> for ColumnIDs { fn from(columns: &TableColumns) -> Self { ColumnIDs::new( - columns.primary.identifier, - columns.secondary.identifier, + columns.primary.identifier(), + columns.secondary.identifier(), columns .non_indexed_columns() .into_iter() - .map(|column| column.identifier) + .map(|column| column.identifier()) .collect_vec(), ) } @@ -153,10 +174,12 @@ async fn new_db_pool(db_url: &str) -> anyhow::Result { .context("while creating the db_pool")?; Ok(db_pool) } + pub struct Table { pub(crate) genesis_block: BlockPrimaryIndex, pub(crate) public_name: TableID, pub(crate) columns: TableColumns, + pub(crate) row_unique_id: TableRowUniqueID, // NOTE: there is no cell tree because it's small and can be reconstructed // on the fly very quickly. Otherwise, we would need to store one cell tree per row // and that means one sql table per row which would be untenable. @@ -167,7 +190,7 @@ pub struct Table { pub(crate) db_pool: DBPool, } -fn row_table_name(name: &str) -> String { +pub(crate) fn row_table_name(name: &str) -> String { format!("row_{}", name) } fn index_table_name(name: &str) -> String { @@ -175,32 +198,25 @@ fn index_table_name(name: &str) -> String { } impl Table { - pub async fn load(public_name: String, columns: TableColumns) -> anyhow::Result { + pub async fn load( + public_name: String, + columns: TableColumns, + row_unique_id: TableRowUniqueID, + ) -> Result { let db_url = std::env::var("DB_URL").unwrap_or("host=localhost dbname=storage".to_string()); - let row_tree = MerkleRowTree::new( - InitSettings::MustExist, - SqlStorageSettings { - table: row_table_name(&public_name), - source: SqlServerConnection::NewConnection(db_url.clone()), - }, - ) - .await - .unwrap(); - let index_tree = MerkleIndexTree::new( - InitSettings::MustExist, - SqlStorageSettings { - source: SqlServerConnection::NewConnection(db_url.clone()), - table: index_table_name(&public_name), - }, + let (index_tree, row_tree) = load_trees( + db_url.as_str(), + index_table_name(&public_name), + row_table_name(&public_name), ) - .await - .unwrap(); + .await?; let genesis = index_tree.storage_state().await?.shift; columns.self_assert(); Ok(Self { db_pool: new_db_pool(&db_url).await?, columns, + row_unique_id, genesis_block: genesis as BlockPrimaryIndex, public_name, row: row_tree, @@ -212,40 +228,38 @@ impl Table { row_table_name(&self.public_name) } - pub async fn new(genesis_block: u64, root_table_name: String, columns: TableColumns) -> Self { + pub(crate) fn index_table_name(&self) -> String { + index_table_name(&self.public_name) + } + + pub async fn new( + genesis_block: u64, + root_table_name: String, + columns: TableColumns, + row_unique_id: TableRowUniqueID, + ) -> Result { let db_url = std::env::var("DB_URL").unwrap_or("host=localhost dbname=storage".to_string()); - let db_settings_index = SqlStorageSettings { - source: SqlServerConnection::NewConnection(db_url.clone()), - table: index_table_name(&root_table_name), - }; - let db_settings_row = SqlStorageSettings { - source: SqlServerConnection::NewConnection(db_url.clone()), - table: row_table_name(&root_table_name), - }; - - let row_tree = ryhope::new_row_tree( - genesis_block as Epoch, + let (index_tree, row_tree) = build_trees( + db_url.as_str(), + index_table_name(&root_table_name), + row_table_name(&root_table_name), + genesis_block as UserEpoch, Alpha::new(0.8), - db_settings_row, true, ) - .await - .unwrap(); - let index_tree = ryhope::new_index_tree(genesis_block as Epoch, db_settings_index, true) - .await - .unwrap(); - + .await?; columns.self_assert(); - Self { + Ok(Self { db_pool: new_db_pool(&db_url) .await .expect("unable to create db pool"), columns, + row_unique_id, genesis_block: genesis_block as BlockPrimaryIndex, public_name: root_table_name, row: row_tree, index: index_tree, - } + }) } // Function to call each time we need to build the index tree, i.e. for each row and @@ -261,7 +275,7 @@ impl Table { .columns .non_indexed_columns() .iter() - .map(|tc| tc.identifier) + .map(|tc| tc.identifier()) .filter_map(|id| cells.find_by_column(id).map(|info| (id, info))) .map(|(id, info)| cell::MerkleCell::new(id, info.value, info.primary)) .collect::>(); @@ -368,8 +382,7 @@ impl Table { &mut self, new_primary: BlockPrimaryIndex, updates: Vec, - ) -> anyhow::Result { - let current_epoch = self.row.current_epoch(); + ) -> Result { let out = self .row .in_transaction(|t| { @@ -432,15 +445,11 @@ impl Table { .map(|plan| RowUpdateResult { updates: plan }); { // debugging + if out.is_err() { + println!("Out was an error: {:?}", out); + } println!("\n+++++++++++++++++++++++++++++++++\n"); let root = self.row.root_data().await?.unwrap(); - let new_epoch = self.row.current_epoch(); - assert!( - current_epoch != new_epoch, - "new epoch {} vs previous epoch {}", - new_epoch, - current_epoch - ); println!( " ++ After row update, row cell tree root tree proof hash = {:?}", hex::encode(root.cell_root_hash.unwrap().0) @@ -493,7 +502,7 @@ pub enum TreeRowUpdate { Deletion(RowTreeKey), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct RowUpdateResult { // There is only a single row key for a table that we update continuously // so no need to track all the rows that have been updated in the result @@ -603,7 +612,7 @@ impl TableColumns { impl TableColumn { pub fn to_zkcolumn(&self) -> ZkColumn { ZkColumn { - id: self.identifier, + id: self.identifier(), kind: match self.index { IndexType::Primary => ColumnKind::PrimaryIndex, IndexType::Secondary => ColumnKind::SecondaryIndex, diff --git a/mp2-v1/tests/common/values_extraction.rs b/mp2-v1/tests/common/values_extraction.rs index 6a6aaff78..4366a10a6 100644 --- a/mp2-v1/tests/common/values_extraction.rs +++ b/mp2-v1/tests/common/values_extraction.rs @@ -1,54 +1,44 @@ //! Test utilities for Values Extraction (C.1) use super::{storage_trie::TestStorageTrie, TestContext}; -use alloy::{ - eips::BlockNumberOrTag, - primitives::{Address, U256}, - providers::Provider, -}; +use alloy::{eips::BlockNumberOrTag, primitives::Address, providers::Provider}; +use itertools::Itertools; use log::info; -use mp2_common::{ - eth::{ProofQuery, StorageSlot}, - mpt_sequential::utils::bytes_to_nibbles, - F, -}; -use mp2_v1::values_extraction::public_inputs::PublicInputs; +use mp2_common::{mpt_sequential::utils::bytes_to_nibbles, F}; +use mp2_v1::values_extraction::{public_inputs::PublicInputs, StorageSlotInfo}; use plonky2::field::types::Field; -type MappingKey = Vec; - impl TestContext { - /// Generate the Values Extraction (C.1) proof for single variables. - pub(crate) async fn prove_single_values_extraction( + /// Generate the Values Extraction proof for single or mapping variables. + pub(crate) async fn prove_values_extraction( &self, contract_address: &Address, bn: BlockNumberOrTag, - slots: &[u8], + slots: &[StorageSlotInfo], ) -> Vec { // Initialize the test trie. let mut trie = TestStorageTrie::new(); info!("Initialized the test storage trie"); // Query the slot and add the node path to the trie. - for slot in slots { - trie.query_proof_and_add_slot(self, contract_address, bn, *slot as usize) + for slot_info in slots { + trie.query_proof_and_add_slot(self, contract_address, bn, slot_info.clone()) .await; } let chain_id = self.rpc.get_chain_id().await.unwrap(); - info!("Prove the test storage trie including the simple slots {slots:?}"); let proof_value = trie.prove_value(contract_address, chain_id, self.params(), &self.b); // Check the public inputs. let pi = PublicInputs::new(&proof_value.proof().public_inputs); - assert_eq!(pi.n(), F::from_canonical_usize(slots.len())); assert_eq!(pi.root_hash(), trie.root_hash()); + assert_eq!(pi.n(), F::from_canonical_usize(slots.len())); { - let exp_key = StorageSlot::Simple(slots[0] as usize).mpt_key_vec(); - let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) + let exp_key = slots[0].slot().mpt_key_vec(); + let exp_key = bytes_to_nibbles(&exp_key) .into_iter() .map(F::from_canonical_u8) - .collect(); + .collect_vec(); let (key, ptr) = pi.mpt_key_info(); assert_eq!(key, exp_key); @@ -57,69 +47,4 @@ impl TestContext { proof_value.serialize().unwrap() } - - /// Generate the Values Extraction (C.1) proof for mapping variables. - pub(crate) async fn prove_mapping_values_extraction( - &self, - contract_address: &Address, - slot: u8, - mapping_keys: Vec, - ) -> Vec { - let slot = slot as usize; - - let first_mapping_key = mapping_keys[0].clone(); - let storage_slot_number = mapping_keys.len(); - - // Initialize the test trie. - let mut trie = TestStorageTrie::new(); - info!("mapping mpt proving: Initialized the test storage trie"); - - // Query the slot and add the node path to the trie. - for mapping_key in mapping_keys { - let query = ProofQuery::new_mapping_slot(*contract_address, slot, mapping_key.clone()); - let response = self - .query_mpt_proof(&query, BlockNumberOrTag::Number(self.block_number().await)) - .await; - - // Get the nodes to prove. Reverse to the sequence from leaf to root. - let nodes: Vec<_> = response.storage_proof[0] - .proof - .iter() - .rev() - .map(|node| node.to_vec()) - .collect(); - - let sslot = StorageSlot::Mapping(mapping_key.clone(), slot); - info!( - "Save the mapping key {:?} (value {}) on slot {} to the test storage trie", - U256::from_be_slice(&mapping_key), - response.storage_proof[0].value, - slot - ); - - trie.add_slot(sslot, nodes); - } - - let chain_id = self.rpc.get_chain_id().await.unwrap(); - info!("Prove the test storage trie including the mapping slots ({slot}, ...)"); - let proof = trie.prove_value(contract_address, chain_id, self.params(), &self.b); - - // Check the public inputs. - let pi = PublicInputs::new(&proof.proof().public_inputs); - assert_eq!(pi.n(), F::from_canonical_usize(storage_slot_number)); - assert_eq!(pi.root_hash(), trie.root_hash()); - { - let exp_key = StorageSlot::Mapping(first_mapping_key, slot).mpt_key_vec(); - let exp_key: Vec<_> = bytes_to_nibbles(&exp_key) - .into_iter() - .map(F::from_canonical_u8) - .collect(); - - let (key, ptr) = pi.mpt_key_info(); - assert_eq!(key, exp_key); - assert_eq!(ptr, F::NEG_ONE); - } - - proof.serialize().expect("can't serialize mpt proof") - } } diff --git a/mp2-v1/tests/integrated_tests.rs b/mp2-v1/tests/integrated_tests.rs index 8ce01bcb4..b98b2a6ef 100644 --- a/mp2-v1/tests/integrated_tests.rs +++ b/mp2-v1/tests/integrated_tests.rs @@ -24,6 +24,8 @@ use common::{ MAX_NUM_ITEMS_PER_OUTPUT, MAX_NUM_OUTPUTS, MAX_NUM_PLACEHOLDERS, MAX_NUM_PREDICATE_OPS, MAX_NUM_RESULT_OPS, }, + slot_info::{SimpleMapping, SimpleNestedMapping, StructMapping, StructNestedMapping}, + table_source::{MappingExtractionArgs, MergeSource, SingleExtractionArgs, TableSource}, TableIndexing, }, context::{self, ParamsType, TestContextConfig}, @@ -33,6 +35,8 @@ use common::{ }; use envconfig::Envconfig; use log::info; + +use mp2_common::eth::EventLogInfo; use parsil::{ assembler::DynamicCircuitPis, parse_and_validate, @@ -40,6 +44,7 @@ use parsil::{ utils::ParsilSettingsBuilder, PlaceholderSettings, }; +use serde::{de::DeserializeOwned, Serialize}; use test_log::test; use verifiable_db::query::universal_circuit::universal_circuit_inputs::Placeholders; @@ -68,7 +73,9 @@ pub(crate) mod common; const PROOF_STORE_FILE: &str = "test_proofs.store"; const MAPPING_TABLE_INFO_FILE: &str = "mapping_column_info.json"; +const MAPPING_OF_MAPPING_TABLE_INFO_FILE: &str = "mapping_of_mapping_column_info.json"; const MERGE_TABLE_INFO_FILE: &str = "merge_column_info.json"; +const RECEIPT_TABLE_INFO_FILE: &str = "receipt_column_info.json"; #[test(tokio::test)] #[ignore] @@ -85,48 +92,109 @@ async fn integrated_indexing() -> Result<()> { ctx.build_params(ParamsType::Indexing).unwrap(); info!("Params built"); + + let (mut receipt, genesis) = + TableIndexing::>::receipt_test_case(0, 0, &mut ctx).await?; + let changes = vec![ + ChangeType::Receipt(1, 10), + ChangeType::Receipt(10, 1), + ChangeType::Receipt(5, 5), + ]; + receipt.run(&mut ctx, genesis, changes.clone()).await?; + // NOTE: to comment to avoid very long tests... - let (mut single, genesis) = TableIndexing::single_value_test_case(&mut ctx).await?; + let (mut single, genesis) = + TableIndexing::::single_value_test_case(&mut ctx).await?; let changes = vec![ ChangeType::Update(UpdateType::Rest), ChangeType::Silent, ChangeType::Update(UpdateType::SecondaryIndex), ]; single.run(&mut ctx, genesis, changes.clone()).await?; - let (mut mapping, genesis) = TableIndexing::mapping_test_case(&mut ctx).await?; + let (mut mapping, genesis) = + TableIndexing::>::mapping_value_test_case(&mut ctx) + .await?; let changes = vec![ ChangeType::Insertion, ChangeType::Update(UpdateType::Rest), - ChangeType::Silent, ChangeType::Update(UpdateType::SecondaryIndex), ChangeType::Deletion, + ChangeType::Silent, ]; mapping.run(&mut ctx, genesis, changes).await?; - let (mut merged, genesis) = TableIndexing::merge_table_test_case(&mut ctx).await?; + let (mut mapping, genesis) = + TableIndexing::>::mapping_struct_test_case(&mut ctx) + .await?; let changes = vec![ ChangeType::Insertion, ChangeType::Update(UpdateType::Rest), + ChangeType::Deletion, + ChangeType::Silent, + ]; + mapping.run(&mut ctx, genesis, changes).await?; + + let (mut mapping_of_single_value_mappings, genesis) = + TableIndexing::>::mapping_of_single_value_mappings_test_case(&mut ctx) + .await?; + let changes = vec![ + ChangeType::Insertion, ChangeType::Update(UpdateType::Rest), + ChangeType::Update(UpdateType::SecondaryIndex), + ChangeType::Deletion, ChangeType::Silent, + ]; + mapping_of_single_value_mappings + .run(&mut ctx, genesis, changes) + .await?; + + let (mut mapping_of_struct_mappings, genesis) = TableIndexing::< + MappingExtractionArgs, + >::mapping_of_struct_mappings_test_case( + &mut ctx + ) + .await?; + let changes = vec![ + ChangeType::Insertion, + ChangeType::Update(UpdateType::Rest), + ChangeType::Update(UpdateType::SecondaryIndex), ChangeType::Deletion, + ChangeType::Silent, ]; + mapping_of_struct_mappings + .run(&mut ctx, genesis, changes) + .await?; + + let (mut merged, genesis) = + TableIndexing::::merge_table_test_case(&mut ctx).await?; + let changes = vec![ChangeType::Update(UpdateType::Rest), ChangeType::Silent]; merged.run(&mut ctx, genesis, changes).await?; // save columns information and table information in JSON so querying test can pick up write_table_info(MAPPING_TABLE_INFO_FILE, mapping.table_info())?; write_table_info(MERGE_TABLE_INFO_FILE, merged.table_info())?; + write_table_info( + MAPPING_OF_MAPPING_TABLE_INFO_FILE, + mapping_of_struct_mappings.table_info(), + )?; + write_table_info(RECEIPT_TABLE_INFO_FILE, receipt.table_info())?; + Ok(()) } -async fn integrated_querying(table_info: TableInfo) -> Result<()> { +async fn integrated_querying(table_info: TableInfo) -> Result<()> { let storage = ProofKV::new_from_env(PROOF_STORE_FILE)?; info!("Loading Anvil and contract"); let mut ctx = context::new_local_chain(storage).await; info!("Building querying params"); ctx.build_params(ParamsType::Query).unwrap(); info!("Params built"); - let table = Table::load(table_info.public_name.clone(), table_info.columns.clone()).await?; + let table = Table::load( + table_info.public_name.clone(), + table_info.columns.clone(), + table_info.row_unique_id.clone(), + ) + .await?; dbg!(&table.public_name); test_query(&mut ctx, table, table_info).await?; Ok(()) @@ -137,7 +205,8 @@ async fn integrated_querying(table_info: TableInfo) -> Result<()> { async fn integrated_querying_mapping_table() -> Result<()> { let _ = env_logger::try_init(); info!("Running QUERY test for mapping table"); - let table_info = read_table_info(MAPPING_TABLE_INFO_FILE)?; + let table_info: TableInfo> = + read_table_info(MAPPING_TABLE_INFO_FILE)?; integrated_querying(table_info).await } @@ -146,7 +215,25 @@ async fn integrated_querying_mapping_table() -> Result<()> { async fn integrated_querying_merged_table() -> Result<()> { let _ = env_logger::try_init(); info!("Running QUERY test for merged table"); - let table_info = read_table_info(MERGE_TABLE_INFO_FILE)?; + let table_info: TableInfo = read_table_info(MERGE_TABLE_INFO_FILE)?; + integrated_querying(table_info).await +} + +#[test(tokio::test)] +#[ignore] +async fn integrated_querying_mapping_of_mappings_table() -> Result<()> { + let _ = env_logger::try_init(); + info!("Running QUERY test for merged table"); + let table_info: TableInfo> = + read_table_info(MAPPING_OF_MAPPING_TABLE_INFO_FILE)?; + integrated_querying(table_info).await +} +#[test(tokio::test)] +#[ignore] +async fn integrated_querying_receipt_table() -> Result<()> { + let _ = env_logger::try_init(); + info!("Running QUERY test for receipt table"); + let table_info: TableInfo> = read_table_info(RECEIPT_TABLE_INFO_FILE)?; integrated_querying(table_info).await } @@ -162,7 +249,10 @@ fn table_info_path(f: &str) -> PathBuf { path } -fn write_table_info(f: &str, info: TableInfo) -> Result<()> { +fn write_table_info( + f: &str, + info: TableInfo, +) -> Result<()> { let full_path = table_info_path(f); let file = File::create(full_path)?; let writer = BufWriter::new(file); @@ -170,11 +260,11 @@ fn write_table_info(f: &str, info: TableInfo) -> Result<()> { Ok(()) } -fn read_table_info(f: &str) -> Result { +fn read_table_info(f: &str) -> Result> { let full_path = table_info_path(f); let file = File::open(full_path)?; let reader = BufReader::new(file); - let info = serde_json::from_reader(reader)?; + let info: TableInfo = serde_json::from_reader(reader)?; Ok(info) } diff --git a/parsil/src/bracketer.rs b/parsil/src/bracketer.rs index 7a4908716..44bb9174a 100644 --- a/parsil/src/bracketer.rs +++ b/parsil/src/bracketer.rs @@ -1,12 +1,79 @@ use alloy::primitives::U256; -use ryhope::{KEY, PAYLOAD, VALID_FROM, VALID_UNTIL}; +use ryhope::{ + mapper_table_name, INCREMENTAL_EPOCH, KEY, PAYLOAD, USER_EPOCH, VALID_FROM, VALID_UNTIL, +}; use verifiable_db::query::utils::QueryBounds; use crate::{symbols::ContextProvider, ParsilSettings}; +/// Return two queries, respectively returning the largest primary index value smaller than the +/// given lower bound, and the smallest primary index value larger than the given higher bound. +/// +/// The method returns also a preliminary query to be run in order to compute the value of +/// the epoch parameter to be provided to the two queries. Such a parameter is the actual +/// epoch in the DB that corresponds to the `block_number` provided as an argument to this +/// method. Note that the epoch parameter is the same for both queries, so the preliminary +/// query can be just run once and the result used for either of the two queries. +/// +/// If the lower or higher bound are the extrema of the U256 definition domain, +/// the associated query is `None`, reflecting the impossibility for a node +/// satisfying the condition to exist in the database. +pub fn bracket_primary_index( + table_name: &str, + block_number: i64, + bounds: &QueryBounds, +) -> (String, Option, Option) { + let min_bound = bounds.min_query_primary(); + let max_bound = bounds.max_query_primary(); + let mapper_table_name = mapper_table_name(table_name); + + let preliminary_query = format!(" + SELECT {INCREMENTAL_EPOCH} as epoch FROM {mapper_table_name} WHERE {USER_EPOCH} = {block_number} LIMIT 1; + " + ); + + // A simple alias for the primary index values + let primary_index = format!("({PAYLOAD} -> 'row_tree_root_primary')::NUMERIC"); + + // Select the largest of all the primary index values that remains smaller than + // the provided primay index lower bound if it is provided. + let largest_below = if min_bound == U256::ZERO { + None + } else { + Some(format!( + "SELECT {KEY} FROM + {table_name} + WHERE {VALID_FROM} <= $1 AND {VALID_UNTIL} >= $1 + AND {primary_index} < '{min_bound}'::DECIMAL + ORDER BY {KEY} DESC LIMIT 1" + )) + }; + + // Symmetric situation for the upper bound. + let smallest_above = if max_bound == U256::MAX { + None + } else { + Some(format!( + "SELECT {KEY} FROM + {table_name} + WHERE {VALID_FROM} <= $1 AND {VALID_UNTIL} >= $1 + AND {primary_index} > '{max_bound}'::DECIMAL + ORDER BY {KEY} ASC LIMIT 1" + )) + }; + + (preliminary_query, largest_below, smallest_above) +} + /// Return two queries, respectively returning the largest sec. ind. value smaller than the /// given lower bound, and the smallest sec. ind. value larger than the given higher bound. /// +/// The method returns also a preliminary query to be run in order to compute the value of +/// the epoch parameter to be provided to the two queries. Such a parameter is the actual +/// epoch in the DB that corresponds to the `block_number` provided as an argument to this +/// method. Note that the epoch parameter is the same for both queries, so the preliminary +/// query can be just run once and the result used for either of the two queries. +/// /// If the lower or higher bound are the extrema of the U256 definition domain, /// the associated query is `None`, reflecting the impossibility for a node /// satisfying the condition to exist in the database. @@ -15,7 +82,7 @@ pub fn bracket_secondary_index( settings: &ParsilSettings, block_number: i64, bounds: &QueryBounds, -) -> (Option, Option) { +) -> (String, Option, Option) { let secondary_lo = bounds.min_query_secondary().value(); let secondary_hi = bounds.max_query_secondary().value(); _bracket_secondary_index( @@ -33,11 +100,17 @@ pub(crate) fn _bracket_secondary_index( block_number: i64, secondary_lo: &U256, secondary_hi: &U256, -) -> (Option, Option) { +) -> (String, Option, Option) { let zk_table = settings.context.fetch_table(table_name).unwrap(); let zktable_name = &zk_table.zktable_name; + let mapper_table_name = mapper_table_name(zktable_name); let sec_ind_column = zk_table.secondary_index_column().id; + let preliminary_query = format!(" + SELECT {INCREMENTAL_EPOCH} as epoch FROM {mapper_table_name} WHERE {USER_EPOCH} = {block_number} LIMIT 1; + " + ); + // A simple alias for the sec. ind. values let sec_index = format!("({PAYLOAD} -> 'cells' -> '{sec_ind_column}' ->> 'value')::NUMERIC"); @@ -46,19 +119,27 @@ pub(crate) fn _bracket_secondary_index( let largest_below = if *secondary_lo == U256::ZERO { None } else { - Some(format!("SELECT {KEY} FROM {zktable_name} - WHERE {sec_index} < '{secondary_lo}'::DECIMAL AND {VALID_FROM} <= {block_number} AND {VALID_UNTIL} >= {block_number} - ORDER BY {sec_index} DESC LIMIT 1")) + Some(format!( + "SELECT {KEY} FROM + {zktable_name} + WHERE {VALID_FROM} <= $1 AND {VALID_UNTIL} >= $1 + AND {sec_index} < '{secondary_lo}'::DECIMAL + ORDER BY {sec_index} DESC LIMIT 1" + )) }; // Symmetric situation for the upper bound. let smallest_above = if *secondary_hi == U256::MAX { None } else { - Some(format!("SELECT {KEY} FROM {zktable_name} - WHERE {sec_index} > '{secondary_hi}'::DECIMAL AND {VALID_FROM} <= {block_number} AND {VALID_UNTIL} >= {block_number} - ORDER BY {sec_index} ASC LIMIT 1")) + Some(format!( + "SELECT {KEY} FROM + {zktable_name} + WHERE {VALID_FROM} <= $1 AND {VALID_UNTIL} >= $1 + AND {sec_index} > '{secondary_hi}'::DECIMAL + ORDER BY {sec_index} ASC LIMIT 1" + )) }; - (largest_below, smallest_above) + (preliminary_query, largest_below, smallest_above) } diff --git a/parsil/src/executor.rs b/parsil/src/executor.rs index 597617b9e..0f6ed18e0 100644 --- a/parsil/src/executor.rs +++ b/parsil/src/executor.rs @@ -3,11 +3,14 @@ //! row tree tables. use alloy::primitives::U256; use anyhow::*; -use ryhope::{EPOCH, KEY, PAYLOAD, VALID_FROM, VALID_UNTIL}; +use ryhope::{ + mapper_table_name, EPOCH, INCREMENTAL_EPOCH, KEY, PAYLOAD, USER_EPOCH, VALID_FROM, VALID_UNTIL, +}; use sqlparser::ast::{ BinaryOperator, CastKind, DataType, Distinct, ExactNumberInfo, Expr, Function, FunctionArg, - FunctionArgExpr, FunctionArgumentList, FunctionArguments, GroupByExpr, Ident, ObjectName, - Query, Select, SelectItem, SetExpr, TableAlias, TableFactor, TableWithJoins, Value, + FunctionArgExpr, FunctionArgumentList, FunctionArguments, GroupByExpr, Ident, Join, + JoinConstraint, JoinOperator, ObjectName, Query, Select, SelectItem, SetExpr, TableAlias, + TableFactor, TableWithJoins, Value, }; use std::collections::HashMap; use verifiable_db::query::{ @@ -17,7 +20,7 @@ use verifiable_db::query::{ use crate::{ placeholders, - symbols::{ColumnKind, ContextProvider}, + symbols::{ColumnKind, ContextProvider, ZkTable}, utils::str_to_u256, visitor::{AstMutator, VisitMut}, ParsilSettings, @@ -263,12 +266,26 @@ pub fn convert_number_string(expr: &mut Expr) -> Result<()> { Ok(()) } -/// When a function that may return a float is encountered, it is wrapped in a -/// call to `FLOOR`. +/// When a function that may return a float is encountered, (i.e., AVG), it is replaced +/// with a call to integer division DIV. fn convert_funcalls(expr: &mut Expr) -> Result<()> { if let Some(replacement) = match expr { Expr::Function(Function { name, .. }) => match name.to_string().to_uppercase().as_str() { - "AVG" => funcall("FLOOR", vec![expr.clone()]).into(), + "AVG" => { + // Replace AVG(expr) with DIV(SUM(expr)/COUNT(expr)) + // replace AVG in `expr` with `SUM` + let mut sum_expr = expr.clone(); + if let Expr::Function(Function { name, .. }) = &mut sum_expr { + *name = ObjectName(vec![Ident::from("SUM")]); + } + // replace AVG in `expr` with `COUNT` + let mut count_expr = expr.clone(); + if let Expr::Function(Function { name, .. }) = &mut count_expr { + *name = ObjectName(vec![Ident::from("COUNT")]); + } + // Add DIV operation + funcall("DIV", vec![sum_expr, count_expr]).into() + } _ => None, }, _ => None, @@ -279,30 +296,116 @@ fn convert_funcalls(expr: &mut Expr) -> Result<()> { Ok(()) } -fn expand_block_range(settings: &ParsilSettings) -> Expr { - funcall( - "generate_series", - vec![ - funcall( - "GREATEST", - vec![ - Expr::Identifier(Ident::new(VALID_FROM)), - Expr::Value(Value::Placeholder( - settings.placeholders.min_block_placeholder.to_owned(), - )), - ], - ), - funcall( - "LEAST", - vec![ - Expr::Identifier(Ident::new(VALID_UNTIL)), - Expr::Value(Value::Placeholder( - settings.placeholders.max_block_placeholder.to_owned(), - )), - ], - ), - ], - ) +/// Build the subquery that will be used as the source of epochs and block numbers +/// in the internal queries generated by the executor visitors implemented in this module. +/// More specifically, this method builds the following JOIN table: +/// {table} JOIN ( +/// SELECT {USER_EPOCH}, {INCREMENTAL_EPOCH} FROM {mapper_table} +/// WHERE {USER_EPOCH} >= $min_block AND {USER_EPOCH} <= $max_block +/// ) ON {VALID_FROM} <= {INCREMENTAL_EPOCH} AND {VALID_UNTIL} >= {INCREMENTAL_EPOCH} +fn executor_range_table( + settings: &ParsilSettings, + table: &ZkTable, +) -> TableWithJoins { + let mapper_table_name = mapper_table_name(&table.zktable_name); + TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(table.zktable_name.clone())]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![Join { + relation: TableFactor::Derived { + lateral: false, + subquery: Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: None, + top: None, + projection: vec![ + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(USER_EPOCH))), + SelectItem::UnnamedExpr(Expr::Identifier(Ident::new( + INCREMENTAL_EPOCH, + ))), + ], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(mapper_table_name)]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + prewhere: None, + selection: Some(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Value(Value::Placeholder( + settings.placeholders.min_block_placeholder.to_owned(), + ))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Value(Value::Placeholder( + settings.placeholders.max_block_placeholder.to_owned(), + ))), + }), + }), + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + }))), + order_by: None, + limit: None, + limit_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + }), + // Subqueries *MUST* have an alias in PgSQL + alias: Some(TableAlias { + name: Ident::new("_mapper"), + columns: vec![], + }), + }, + join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_FROM))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Identifier(Ident::new(INCREMENTAL_EPOCH))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_UNTIL))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Identifier(Ident::new(INCREMENTAL_EPOCH))), + }), + })), + }], + } } /// Generate an [`Expr`] encoding for `PAYLOAD -> cells -> '{id}' -> value @@ -391,6 +494,151 @@ impl<'a, C: ContextProvider> KeyFetcher<'a, C> { Ok(()) } + + const MIN_EPOCH_ALIAS: &'static str = "min_epoch"; + const MAX_EPOCH_ALIAS: &'static str = "max_epoch"; + + fn expand_block_range() -> Expr { + funcall( + "generate_series", + vec![ + funcall( + "GREATEST", + vec![ + Expr::Identifier(Ident::new(VALID_FROM)), + Expr::Identifier(Ident::new(Self::MIN_EPOCH_ALIAS)), + ], + ), + funcall( + "LEAST", + vec![ + Expr::Identifier(Ident::new(VALID_UNTIL)), + Expr::Identifier(Ident::new(Self::MAX_EPOCH_ALIAS)), + ], + ), + ], + ) + } + + // Build the subquery that will be used as the source of epochs and block numbers + // in the internal queries generated by the executor visitors implemented in this module. + // More specifically, this method builds the following JOIN table: + // {table} JOIN ( + // SELECT MIN{INCREMENTAL_EPOCH} as {MIN_EPOCH_ALIAS}, MAX{INCREMENTAL_EPOCH} as {MAX_EPOCH_ALIAS} + // FROM {mapper_table} + // WHERE {USER_EPOCH} >= $min_block AND {USER_EPOCH} <= $max_block + // ) ON {VALID_FROM} <= {MAX_EPOCH_ALIAS} AND {VALID_UNTIL} >= {MIN_EPOCH_ALIAS} + fn range_table(&self, table: &ZkTable) -> TableWithJoins { + let mapper_table_name = mapper_table_name(&table.zktable_name); + TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(table.zktable_name.clone())]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![Join { + relation: TableFactor::Derived { + lateral: false, + subquery: Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: None, + top: None, + projection: vec![ + SelectItem::ExprWithAlias { + expr: funcall( + "MIN", + vec![Expr::Identifier(Ident::new(INCREMENTAL_EPOCH))], + ), + alias: Ident::new(Self::MIN_EPOCH_ALIAS), + }, + SelectItem::ExprWithAlias { + expr: funcall( + "MAX", + vec![Expr::Identifier(Ident::new(INCREMENTAL_EPOCH))], + ), + alias: Ident::new(Self::MAX_EPOCH_ALIAS), + }, + ], + into: None, + from: vec![TableWithJoins { + relation: TableFactor::Table { + name: ObjectName(vec![Ident::new(mapper_table_name)]), + alias: None, + args: None, + with_hints: vec![], + version: None, + with_ordinality: false, + partitions: vec![], + }, + joins: vec![], + }], + lateral_views: vec![], + prewhere: None, + selection: Some(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Value(Value::Placeholder( + self.settings.placeholders.min_block_placeholder.to_owned(), + ))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(USER_EPOCH))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Value(Value::Placeholder( + self.settings.placeholders.max_block_placeholder.to_owned(), + ))), + }), + }), + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + }))), + order_by: None, + limit: None, + limit_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + }), + // Subqueries *MUST* have an alias in PgSQL + alias: Some(TableAlias { + name: Ident::new("_mapper"), + columns: vec![], + }), + }, + join_operator: JoinOperator::Inner(JoinConstraint::On(Expr::BinaryOp { + left: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_FROM))), + op: BinaryOperator::LtEq, + right: Box::new(Expr::Identifier(Ident::new(Self::MAX_EPOCH_ALIAS))), + }), + op: BinaryOperator::And, + right: Box::new(Expr::BinaryOp { + left: Box::new(Expr::Identifier(Ident::new(VALID_UNTIL))), + op: BinaryOperator::GtEq, + right: Box::new(Expr::Identifier(Ident::new(Self::MIN_EPOCH_ALIAS))), + }), + })), + }], + } + } } impl AstMutator for KeyFetcher<'_, C> { type Error = anyhow::Error; @@ -442,7 +690,7 @@ impl AstMutator for KeyFetcher<'_, C> { std::iter::once(SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY)))) .chain(std::iter::once( SelectItem::ExprWithAlias { - expr: expand_block_range(self.settings), + expr: Self::expand_block_range(), alias: Ident::new(EPOCH) } )) @@ -455,9 +703,23 @@ impl AstMutator for KeyFetcher<'_, C> { .unwrap_or(column.name.as_str()), ); match column.kind { - // primary index column := generate_series(VALID_FROM, VALID_UNTIL) AS name + // primary index column := $MIN_BLOCK AS name. + // We return a constant value as a trick to avoid extracting USER_EPOCH from + // epoch mapper table, which would require a costly JOIN. + // Indeed, given that: + // - The filtering over the primary index have already been applied in + // the epoch mapper table + // - This column is later ignored in the overall query + // We just need to provide as block_number a column value that satisfies the + // filtering over the primary index specified in the existing query, + // which is `block_number >= $MIN_BLOCK AND block_number <= $MAX_BLOCK`, as + // any other predicate is removed from the query by the isolator + // ToDo: remove this column once we merge the new version of the isolator, + // which will remove the block_number range filtering ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { - expr: expand_block_range(self.settings), + expr: Expr::Value(Value::Placeholder( + self.settings.placeholders.min_block_placeholder.to_owned(), + )), alias, }, // other columns := payload->'cells'->'id'->'value' AS name @@ -480,18 +742,7 @@ impl AstMutator for KeyFetcher<'_, C> { top: None, projection: select_items, into: None, - from: vec![TableWithJoins { - relation: TableFactor::Table { - name: ObjectName(vec![Ident::new(table.zktable_name)]), - alias: None, - args: None, - with_hints: vec![], - version: None, - with_ordinality: false, - partitions: vec![], - }, - joins: vec![], - }], + from: vec![self.range_table(&table)], lateral_views: vec![], prewhere: None, selection: None, @@ -532,6 +783,147 @@ impl AstMutator for KeyFetcher<'_, C> { } } +/// Implementation of `post_table_factor` shared both by `Executor` and by +/// `ExecutorWithKey`. If the flag `return_keys` is true, `key` and `epoch` +/// columns are returned as well as `SELECT` items in the constructed sub-query, +/// as required in the `ExecutorWithKey` implementation of `post_table_factor` +fn post_table_factor( + settings: &ParsilSettings, + table_factor: &mut TableFactor, + return_keys: bool, +) -> Result<()> { + if let Some(replacement) = match &table_factor { + TableFactor::Table { + name, alias, args, .. + } => { + // In this case, we handle + // + // ... FROM table [AS alias [(col1, // col2, ...)]] + // + // so both the table name and its columns may be aliased. + if args.is_some() { + unreachable!() + } else { + // The actual table being referenced + let concrete_table_name = &name.0[0].value; + + // Fetch all the column declared in this table + let table = settings.context.fetch_table(concrete_table_name)?; + let table_columns = &table.columns; + + // Extract the apparent table name (either the concrete one + // or its alia), and, if they exist, the aliased column + // names. + let (apparent_table_name, column_aliases) = if let Some(table_alias) = alias { + ( + table_alias.name.value.to_owned(), + if table_alias.columns.is_empty() { + None + } else { + table_alias.columns.clone().into() + }, + ) + } else { + (concrete_table_name.to_owned(), None) + }; + + // Create one `SelectItem` for each column of the table, as they have to be returned + // in `SELECT` in the constructed sub-query + let current_columns_select_items = + table_columns.iter().enumerate().map(|(i, column)| { + let alias = Ident::new( + column_aliases + .as_ref() + .map(|a| a[i].value.as_str()) + .unwrap_or(column.name.as_str()), + ); + match column.kind { + // primary index column := USER_EPOCH AS name + ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { + expr: Expr::Identifier(Ident::new(USER_EPOCH)), + alias, + }, + // other columns := PAYLOAD->'cells'->'id'->'value' AS name + ColumnKind::SecondaryIndex | ColumnKind::Standard => { + SelectItem::ExprWithAlias { + expr: fetch_from_payload(column.id), + alias, + } + } + } + }); + + let select_items = if return_keys { + // Insert the `key` and `epoch` columns in the selected values... + std::iter::once(SelectItem::UnnamedExpr(Expr::Identifier(Ident::new(KEY)))) + .chain(std::iter::once(SelectItem::ExprWithAlias { + expr: Expr::Identifier(Ident::new(USER_EPOCH)), + alias: Ident::new(EPOCH), + })) + .chain(current_columns_select_items) + .collect() + } else { + current_columns_select_items.collect() + }; + + Some(TableFactor::Derived { + lateral: false, + subquery: Box::new(Query { + with: None, + body: Box::new(SetExpr::Select(Box::new(Select { + distinct: None, + top: None, + projection: select_items, + into: None, + from: vec![executor_range_table(settings, &table)], + lateral_views: vec![], + prewhere: None, + selection: None, + group_by: GroupByExpr::Expressions(vec![], vec![]), + cluster_by: vec![], + distribute_by: vec![], + sort_by: vec![], + having: None, + named_window: vec![], + qualify: None, + window_before_qualify: false, + value_table_mode: None, + connect_by: None, + }))), + order_by: None, + limit: None, + limit_by: vec![], + offset: None, + fetch: None, + locks: vec![], + for_clause: None, + settings: None, + format_clause: None, + }), + // Subqueries *MUST* have an alias in PgSQL + alias: Some(TableAlias { + name: Ident::new(apparent_table_name), + columns: vec![], + }), + }) + } + } + TableFactor::Derived { .. } => None, + TableFactor::TableFunction { .. } => todo!(), + TableFactor::Function { .. } => todo!(), + TableFactor::UNNEST { .. } => todo!(), + TableFactor::JsonTable { .. } => todo!(), + TableFactor::NestedJoin { .. } => todo!(), + TableFactor::Pivot { .. } => todo!(), + TableFactor::Unpivot { .. } => todo!(), + TableFactor::MatchRecognize { .. } => todo!(), + } { + *table_factor = replacement; + } + + Ok(()) +} + struct Executor<'a, C: ContextProvider> { settings: &'a ParsilSettings, } @@ -552,135 +944,7 @@ impl AstMutator for Executor<'_, C> { } fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { - if let Some(replacement) = match &table_factor { - TableFactor::Table { - name, alias, args, .. - } => { - // In this case, we handle - // - // ... FROM table [AS alias [(col1, // col2, ...)]] - // - // so both the table name and its columns may be aliased. - if args.is_some() { - unreachable!() - } else { - // The actual table being referenced - let concrete_table_name = &name.0[0].value; - - // Fetch all the column declared in this table - let table = self.settings.context.fetch_table(concrete_table_name)?; - let table_columns = &table.columns; - - // Extract the apparent table name (either the concrete one - // or its alia), and, if they exist, the aliased column - // names. - let (apparent_table_name, column_aliases) = if let Some(table_alias) = alias { - ( - table_alias.name.value.to_owned(), - if table_alias.columns.is_empty() { - None - } else { - table_alias.columns.clone().into() - }, - ) - } else { - (concrete_table_name.to_owned(), None) - }; - - let select_items = table_columns - .iter() - .enumerate() - .map(|(i, column)| { - let alias = Ident::new( - column_aliases - .as_ref() - .map(|a| a[i].value.as_str()) - .unwrap_or(column.name.as_str()), - ); - match column.kind { - // primary index column := generate_series(VALID_FROM, VALID_UNTIL) AS name - ColumnKind::PrimaryIndex => SelectItem::ExprWithAlias { - expr: expand_block_range(self.settings), - alias, - }, - // other columns := PAYLOAD->'cells'->'id'->'value' AS name - ColumnKind::SecondaryIndex | ColumnKind::Standard => { - SelectItem::ExprWithAlias { - expr: fetch_from_payload(column.id), - alias, - } - } - } - }) - .collect(); - - Some(TableFactor::Derived { - lateral: false, - subquery: Box::new(Query { - with: None, - body: Box::new(SetExpr::Select(Box::new(Select { - distinct: None, - top: None, - projection: select_items, - into: None, - from: vec![TableWithJoins { - relation: TableFactor::Table { - name: ObjectName(vec![Ident::new(table.zktable_name)]), - alias: None, - args: None, - with_hints: vec![], - version: None, - with_ordinality: false, - partitions: vec![], - }, - joins: vec![], - }], - lateral_views: vec![], - prewhere: None, - selection: None, - group_by: GroupByExpr::Expressions(vec![], vec![]), - cluster_by: vec![], - distribute_by: vec![], - sort_by: vec![], - having: None, - named_window: vec![], - qualify: None, - window_before_qualify: false, - value_table_mode: None, - connect_by: None, - }))), - order_by: None, - limit: None, - limit_by: vec![], - offset: None, - fetch: None, - locks: vec![], - for_clause: None, - settings: None, - format_clause: None, - }), - // Subqueries *MUST* have an alias in PgSQL - alias: Some(TableAlias { - name: Ident::new(apparent_table_name), - columns: vec![], - }), - }) - } - } - TableFactor::Derived { .. } => None, - TableFactor::TableFunction { .. } => todo!(), - TableFactor::Function { .. } => todo!(), - TableFactor::UNNEST { .. } => todo!(), - TableFactor::JsonTable { .. } => todo!(), - TableFactor::NestedJoin { .. } => todo!(), - TableFactor::Pivot { .. } => todo!(), - TableFactor::Unpivot { .. } => todo!(), - TableFactor::MatchRecognize { .. } => todo!(), - } { - *table_factor = replacement; - } - - Ok(()) + post_table_factor(self.settings, table_factor, false) } } @@ -707,10 +971,7 @@ impl AstMutator for ExecutorWithKey<'_, C> { } fn post_table_factor(&mut self, table_factor: &mut TableFactor) -> Result<()> { - let mut key_fetcher = KeyFetcher { - settings: self.settings, - }; - key_fetcher.post_table_factor(table_factor) + post_table_factor(self.settings, table_factor, true) } fn post_select(&mut self, select: &mut Select) -> Result<()> { @@ -829,49 +1090,3 @@ pub fn generate_query_keys( TranslatedQuery::make(SafeQuery::ZkQuery(key_query), settings) } - -/// Return two queries, respectively returning the largest sec. ind. value -/// smaller than the given lower bound, and the smallest sec. ind. value larger -/// than the given higher bound. -/// -/// If the lower or higher bound are the extrema of the U256 definition domain, -/// the associated query is `None`, reflecting the impossibility for a node -/// satisfying the condition to exist in the database. -pub fn bracket_secondary_index( - table_name: &str, - settings: &ParsilSettings, - block_number: i64, - secondary_lo: U256, - secondary_hi: U256, -) -> (Option, Option) { - let sec_ind_column = settings - .context - .fetch_table(table_name) - .unwrap() - .secondary_index_column() - .id; - - // A simple alias for the sec. ind. values - let sec_index = format!("({PAYLOAD} -> 'cells' -> '{sec_ind_column}' ->> 'value')::NUMERIC"); - - // Select the largest of all the sec. ind. values that remains smaller than - // the provided sec. ind. lower bound if it is provided, -1 otherwise. - let largest_below = if secondary_lo == U256::MIN { - None - } else { - Some(format!("SELECT key FROM {table_name} - WHERE {sec_index} < '{secondary_lo}'::DECIMAL AND {VALID_FROM} <= {block_number} AND {VALID_UNTIL} >= {block_number} - ORDER BY {sec_index} DESC LIMIT 1")) - }; - - // Symmetric situation for the upper bound. - let smallest_above = if secondary_hi == U256::MAX { - None - } else { - Some(format!("SELECT key FROM {table_name} - WHERE {sec_index} > '{secondary_hi}'::DECIMAL AND {VALID_FROM} <= {block_number} AND {VALID_UNTIL} >= {block_number} - ORDER BY {sec_index} ASC LIMIT 1")) - }; - - (largest_below, smallest_above) -} diff --git a/parsil/src/main.rs b/parsil/src/main.rs index 6a3978c01..79c659690 100644 --- a/parsil/src/main.rs +++ b/parsil/src/main.rs @@ -6,8 +6,9 @@ use assembler::assemble_static; use clap::{Parser, Subcommand}; use log::Level; use parsil::queries::{core_keys_for_index_tree, core_keys_for_row_tree}; -use ryhope::{tree::sbbst::NodeIdx, Epoch}; -use symbols::FileContextProvider; +use ryhope::{tree::sbbst::NodeIdx, UserEpoch}; +use sqlparser::ast::Query; +use symbols::{ContextProvider, FileContextProvider}; use utils::{parse_and_validate, ParsilSettings, PlaceholderSettings}; mod assembler; @@ -85,12 +86,14 @@ enum Command { to_keys: bool, }, Core { + /// The query to execute if tree_type is "row", or the table name if + /// tree_type is "index" #[arg(long, short = 'Q')] request: String, /// The epoch at which to run the query #[arg(short = 'E', long)] - epoch: Epoch, + epoch: UserEpoch, /// Primary index lower bound #[arg(short = 'm', long)] @@ -216,9 +219,11 @@ fn main() -> Result<()> { // todo!(), // )? } - "index" => { - core_keys_for_index_tree(epoch, (min_block as NodeIdx, max_block as NodeIdx))? - } + "index" => core_keys_for_index_tree( + epoch, + (min_block as NodeIdx, max_block as NodeIdx), + &request, + )?, _ => unreachable!(), }; diff --git a/parsil/src/queries.rs b/parsil/src/queries.rs index 506fdb731..fcf83a671 100644 --- a/parsil/src/queries.rs +++ b/parsil/src/queries.rs @@ -3,42 +3,48 @@ use crate::{keys_in_index_boundaries, symbols::ContextProvider, ParsilSettings}; use anyhow::*; -use ryhope::{tree::sbbst::NodeIdx, Epoch, EPOCH, KEY, VALID_FROM, VALID_UNTIL}; +use ryhope::{ + mapper_table_name, tree::sbbst::NodeIdx, UserEpoch, EPOCH, INCREMENTAL_EPOCH, KEY, USER_EPOCH, +}; use verifiable_db::query::{ universal_circuit::universal_circuit_inputs::Placeholders, utils::QueryBounds, }; -/// Return a query read to be injected in the wide lineage computation for the +/// Return a query ready to be injected in the wide lineage computation for the /// index tree. /// /// * execution_epoch: the epoch (block number) at which the query is executed; /// * query_epoch_bounds: the min. and max. block numbers onto which the query -/// is executed. +/// is executed; +/// * table_name: the name of the index tree table over which the query is executed; pub fn core_keys_for_index_tree( - execution_epoch: Epoch, + execution_epoch: UserEpoch, query_epoch_bounds: (NodeIdx, NodeIdx), + table_name: &str, ) -> Result { let (query_min_block, query_max_block) = query_epoch_bounds; - ensure!( - query_max_block as i64 <= execution_epoch, - "query can not be executed in the past ({} < {})", - execution_epoch, - query_max_block - ); - // Integer default to i32 in PgSQL, they must be cast to i64, a.k.a. BIGINT. - Ok(format!( - "SELECT {}::BIGINT as {EPOCH}, - generate_series( - GREATEST((SELECT MIN({VALID_FROM}))::BIGINT, {}::BIGINT), - LEAST((SELECT MAX({VALID_UNTIL}))::BIGINT, {}::BIGINT)) AS {KEY}", - execution_epoch, + let mapper_table_name = mapper_table_name(table_name); + + let (lower_epoch, higher_epoch) = ( query_min_block, query_max_block.min( execution_epoch .try_into() - .with_context(|| format!("unable to convert {} to i64", execution_epoch))? - ) + .with_context(|| format!("unable to convert {} to i64", execution_epoch))?, + ), + ); + + // Integer default to i32 in PgSQL, they must be cast to i64, a.k.a. BIGINT. + Ok(format!( + " + SELECT {execution_epoch}::BIGINT as {EPOCH}, + {USER_EPOCH} as {KEY} + FROM {mapper_table_name} + WHERE {USER_EPOCH} >= {lower_epoch}::BIGINT AND {USER_EPOCH} <= {higher_epoch}::BIGINT + AND NOT {INCREMENTAL_EPOCH} = 0 + ORDER BY {USER_EPOCH} + " )) } diff --git a/ryhope/src/error.rs b/ryhope/src/error.rs index 64e287388..ffba2fbfe 100644 --- a/ryhope/src/error.rs +++ b/ryhope/src/error.rs @@ -1,6 +1,8 @@ use thiserror::Error; use tokio_postgres::error::Error as PgError; +use crate::IncrementalEpoch; + #[derive(Error, Debug)] pub enum RyhopeError { /// An error that occured while interacting with the DB. @@ -34,6 +36,12 @@ pub enum RyhopeError { #[error("key not found in tree")] KeyNotFound, + + #[error("Current epoch is undefined: internal epoch is {0}, but no corresponding user epoch was found")] + CurrenEpochUndefined(IncrementalEpoch), + + #[error("Error in epoch mapper operation: {0}")] + EpochMapperError(String), } impl RyhopeError { pub fn from_db>(msg: S, err: PgError) -> Self { @@ -64,6 +72,10 @@ impl RyhopeError { pub fn fatal>(msg: S) -> Self { RyhopeError::Fatal(msg.as_ref().to_string()) } + + pub fn epoch_error>(msg: S) -> Self { + RyhopeError::EpochMapperError(msg.as_ref().to_string()) + } } pub fn ensure>(cond: bool, msg: S) -> Result<(), RyhopeError> { diff --git a/ryhope/src/lib.rs b/ryhope/src/lib.rs index 3caaf90f7..f493fb443 100644 --- a/ryhope/src/lib.rs +++ b/ryhope/src/lib.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, fmt::Debug, + future::Future, hash::Hash, marker::PhantomData, }; @@ -34,10 +35,30 @@ pub const EPOCH: &str = "__epoch"; pub const VALID_FROM: &str = "__valid_from"; /// The column containing the last epoch of validity of the row in the zkTable pub const VALID_UNTIL: &str = "__valid_until"; +/// The column containing epoch values that are meaningful for the user-exposed table +pub const USER_EPOCH: &str = "__user_epoch"; +/// The column containing the incremental epochs employed in the zkTable +pub const INCREMENTAL_EPOCH: &str = "__incremental_epoch"; + +/// A timestamp in a versioned storage. It corresponds to the actual epochs used internally in +/// the storage implementations, which are assumed to be sequential. +/// Using a signed type allows for easy detection & debugging of erroneous subtractions. +pub type IncrementalEpoch = i64; + +/// Represents the epochs of the storage as defined by the user. +/// The storages provided here allows to decouple these epochs from the `IncrementalEpoch`s +/// being used internally, allowing users to define epochs for the storage which are not +/// necessarily incremental. The only assumption is that these user-defined epochs +/// are monotonically increasing. +pub type UserEpoch = i64; + +pub fn mapper_table_name(table_name: &str) -> String { + format!("{}_mapper", table_name) +} -/// A timestamp in a versioned storage. Using a signed type allows for easy -/// detection & debugging of erroneous subtractions. -pub type Epoch = i64; +pub(crate) fn metadata_table_name(table_name: &str) -> String { + format!("{}_meta", table_name) +} /// A payload attached to a node, that may need to compute aggregated values /// from the bottom of the tree to the top. If not, simply do not override the @@ -73,13 +94,13 @@ pub enum InitSettings { MustNotExist(T), /// Fail to initialize if the tree already exists, create with the given /// state and starting at the given epoch otherwise. - MustNotExistAt(T, Epoch), + MustNotExistAt(T, UserEpoch), /// Ensure that the tree is re-created with the given settings, erasing it /// if it exists. Reset(T), /// Ensure that the tree is re-created with the given settings and at the /// given initial epoch, erasing it if it exists. - ResetAt(T, Epoch), + ResetAt(T, UserEpoch), } /// An `MerkleTreeKvDb` wraps together: @@ -206,7 +227,7 @@ where } /// Return the key mapped to the root of the Merkle tree at the given epoch. - pub async fn root_at(&self, epoch: Epoch) -> Result, RyhopeError> { + pub async fn root_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { self.tree.root(&self.storage.view_at(epoch)).await } @@ -220,7 +241,7 @@ where } /// Return the payload of the Merkle tree root at the given epoch. - pub async fn root_data_at(&self, epoch: Epoch) -> Result, RyhopeError> { + pub async fn root_data_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { Ok( if let Some(root) = self.tree.root(&self.storage.view_at(epoch)).await? { self.storage.data().try_fetch_at(&root, epoch).await? @@ -254,7 +275,7 @@ where pub async fn try_fetch_with_context_at( &self, k: &T::Key, - epoch: Epoch, + epoch: UserEpoch, ) -> Result, V)>, RyhopeError> { if let Some(ctx) = self .tree @@ -282,7 +303,7 @@ where pub async fn fetch_with_context_at( &self, k: &T::Key, - epoch: Epoch, + epoch: UserEpoch, ) -> Result, V)>, RyhopeError> { self.try_fetch_with_context_at(k, epoch).await } @@ -307,7 +328,7 @@ where pub async fn node_context_at( &self, k: &T::Key, - epoch: Epoch, + epoch: UserEpoch, ) -> Result>, RyhopeError> { self.tree .node_context(k, &self.storage.view_at(epoch)) @@ -326,7 +347,7 @@ where pub async fn lineage_at( &self, k: &T::Key, - epoch: Epoch, + epoch: UserEpoch, ) -> Result>, RyhopeError> { let s = TreeStorageView::<'_, T, S>::new(&self.storage, epoch); self.tree.lineage(k, &s).await @@ -337,7 +358,7 @@ where pub async fn ascendance_at>( &self, ks: I, - epoch: Epoch, + epoch: UserEpoch, ) -> Result, RyhopeError> { self.tree.ascendance(ks, &self.view_at(epoch)).await } @@ -350,16 +371,18 @@ where /// Return an epoch-locked, read-only, [`TreeStorage`] offering a view on /// this Merkle tree as it was at the given epoch. - pub fn view_at(&self, epoch: Epoch) -> TreeStorageView<'_, T, S> { + pub fn view_at(&self, epoch: UserEpoch) -> TreeStorageView<'_, T, S> { TreeStorageView::<'_, T, S>::new(&self.storage, epoch) } /// Return the update tree generated by the transaction defining the given /// epoch. - pub async fn diff_at(&self, epoch: Epoch) -> Result>, RyhopeError> { - if epoch > self.current_epoch() { - Ok(None) - } else { + pub async fn diff_at( + &self, + epoch: UserEpoch, + ) -> Result>, RyhopeError> { + let current_epoch = self.current_epoch().await?; + Ok(if epoch <= current_epoch { let dirtied = self.storage.born_at(epoch).await; let s = TreeStorageView::<'_, T, S>::new(&self.storage, epoch); @@ -371,8 +394,10 @@ where } let ut = UpdateTree::from_paths(paths, epoch); - Ok(Some(ut)) - } + Some(ut) + } else { + None + }) } } @@ -388,19 +413,19 @@ impl< { pub async fn wide_update_trees_at( &self, - at: Epoch, + at: UserEpoch, keys_query: &S::KeySource, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> Result>, RyhopeError> { self.storage .wide_update_trees(at, &self.tree, keys_query, bounds) .await } - pub async fn try_fetch_many_at + Send>( + pub async fn try_fetch_many_at + Send>( &self, data: I, - ) -> Result, V)>, RyhopeError> + ) -> Result, V)>, RyhopeError> where ::IntoIter: Send, { @@ -409,9 +434,9 @@ impl< pub async fn wide_lineage_between( &self, - at: Epoch, + at: UserEpoch, keys_query: &S::KeySource, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> Result, RyhopeError> { self.storage .wide_lineage_between(at, &self.tree, keys_query, bounds) @@ -432,31 +457,39 @@ impl< > RoEpochKvStorage for MerkleTreeKvDb { /// Return the first registered time stamp of the storage - fn initial_epoch(&self) -> Epoch { + fn initial_epoch(&self) -> impl Future + Send { self.storage.data().initial_epoch() } - fn current_epoch(&self) -> Epoch { + fn current_epoch(&self) -> impl Future> + Send { self.storage.data().current_epoch() } - async fn try_fetch_at(&self, k: &T::Key, epoch: Epoch) -> Result, RyhopeError> { + async fn try_fetch_at(&self, k: &T::Key, epoch: UserEpoch) -> Result, RyhopeError> { self.storage.data().try_fetch_at(k, epoch).await } - async fn size_at(&self, epoch: Epoch) -> usize { + async fn try_fetch(&self, k: &T::Key) -> Result, RyhopeError> { + self.storage.data().try_fetch(k).await + } + + async fn size_at(&self, epoch: UserEpoch) -> usize { self.storage.data().size_at(epoch).await } - async fn keys_at(&self, epoch: Epoch) -> Vec { + async fn size(&self) -> usize { + self.storage.data().size().await + } + + async fn keys_at(&self, epoch: UserEpoch) -> Vec { self.storage.data().keys_at(epoch).await } - async fn random_key_at(&self, epoch: Epoch) -> Option { + async fn random_key_at(&self, epoch: UserEpoch) -> Option { self.storage.data().random_key_at(epoch).await } - async fn pairs_at(&self, epoch: Epoch) -> Result, RyhopeError> { + async fn pairs_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { self.storage.data().pairs_at(epoch).await } } @@ -505,10 +538,15 @@ impl< /// Rollback this storage to the given epoch. Please note that this is a /// destructive and irreversible operation; to merely get a view on the /// storage at a given epoch, use the `view_at` method. - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { trace!("[MerkleTreeKvDb] rolling back to {epoch}"); self.storage.rollback_to(epoch).await } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { + trace!("[MerkleTreeKvDb] rolling back"); + self.storage.rollback().await + } } // Transaction-related operations must be forwared both to the node and the data @@ -522,7 +560,7 @@ impl< { async fn start_transaction(&mut self) -> Result<(), RyhopeError> { trace!("[MerkleTreeKvDb] calling start_transaction"); - self.storage.start_transaction()?; + self.storage.start_transaction().await?; Ok(()) } @@ -535,7 +573,7 @@ impl< } } - let update_tree = UpdateTree::from_paths(paths, self.current_epoch() + 1); + let update_tree = UpdateTree::from_paths(paths, self.current_epoch().await?); let plan = update_tree.clone().into_workplan(); @@ -564,7 +602,7 @@ impl< } } - let update_tree = UpdateTree::from_paths(paths, self.current_epoch() + 1); + let update_tree = UpdateTree::from_paths(paths, self.current_epoch().await?); let plan = update_tree.clone().into_workplan(); self.aggregate(plan.clone()).await?; self.storage.commit_in(tx).await?; @@ -572,14 +610,14 @@ impl< Ok(update_tree) } - fn commit_success(&mut self) { + async fn commit_success(&mut self) { trace!("[MerkleTreeKvDb] triggering commit_success"); - self.storage.commit_success() + self.storage.commit_success().await } - fn commit_failed(&mut self) { + async fn commit_failed(&mut self) { trace!("[MerkleTreeKvDb] triggering commit_failed"); - self.storage.commit_failed() + self.storage.commit_failed().await } } @@ -605,20 +643,20 @@ impl< pub async fn new_index_tree< V: NodePayload + Send + Sync, S: TransactionalStorage - + TreeStorage + + TreeStorage + PayloadStorage + FromSettings, >( - genesis_block: Epoch, + genesis_block: UserEpoch, storage_settings: S::Settings, reset_if_exist: bool, -) -> Result, RyhopeError> { +) -> Result, RyhopeError> { if genesis_block <= 0 { return Err(RyhopeError::fatal("the genesis block must be positive")); } let initial_epoch = genesis_block - 1; - let tree_settings = sbbst::Tree::with_shift(initial_epoch.try_into().unwrap()); + let tree_settings = sbbst::EpochTree::with_shift(initial_epoch.try_into().unwrap()); MerkleTreeKvDb::new( if reset_if_exist { @@ -647,7 +685,7 @@ pub async fn new_row_tree< + PayloadStorage + FromSettings>, >( - genesis_block: Epoch, + genesis_block: UserEpoch, alpha: scapegoat::Alpha, storage_settings: S::Settings, reset_if_exist: bool, diff --git a/ryhope/src/storage/memory.rs b/ryhope/src/storage/memory.rs index b6bc878fd..7715a00af 100644 --- a/ryhope/src/storage/memory.rs +++ b/ryhope/src/storage/memory.rs @@ -1,15 +1,15 @@ use serde::{Deserialize, Serialize}; -use std::collections::HashSet; +use std::collections::{BTreeSet, HashSet}; use std::hash::Hash; use std::{collections::HashMap, fmt::Debug}; use crate::error::{ensure, RyhopeError}; use crate::tree::TreeTopology; -use crate::{Epoch, InitSettings}; +use crate::{IncrementalEpoch, InitSettings, UserEpoch}; use super::{ - EpochKvStorage, EpochStorage, FromSettings, PayloadStorage, RoEpochKvStorage, - TransactionalStorage, TreeStorage, + EpochKvStorage, EpochMapper, EpochStorage, FromSettings, PayloadStorage, RoEpochKvStorage, + RoSharedEpochMapper, SharedEpochMapper, TransactionalStorage, TreeStorage, }; /// A RAM-backed implementation of a transactional epoch storage for a single value. @@ -30,27 +30,55 @@ where in_tx: bool, /// The successive states of the persisted value. ts: Vec>, - /// The initial epoch - epoch_offset: Epoch, + /// The shared data structure used to map epochs + epoch_mapper: RoSharedEpochMapper, } impl VersionedStorage where - T: Debug + Send + Sync + Clone + Serialize + for<'a> Deserialize<'a>, + T: Debug + Send + Sync + Clone + Serialize + for<'b> Deserialize<'b>, { - fn new_at(initial_state: T, epoch: Epoch) -> Self { + fn new(initial_state: T, epoch_mapper: RoSharedEpochMapper) -> Self { Self { in_tx: false, ts: vec![Some(initial_state)], - epoch_offset: epoch, + epoch_mapper, } } + + fn inner_epoch(&self) -> IncrementalEpoch { + (self.ts.len() - 1).try_into().unwrap() + } + + fn fetch_at_incremental_epoch(&self, epoch: IncrementalEpoch) -> Result { + assert!(epoch >= 0); + self.ts[epoch as usize].clone().ok_or(RyhopeError::internal( + "No entry found in storage for epoch {epoch}", + )) + } + + fn rollback_to_incremental_epoch( + &mut self, + epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + ensure( + epoch <= self.inner_epoch(), + format!( + "unable to rollback to epoch `{}` more recent than current epoch `{}`", + epoch, + self.inner_epoch() + ), + )?; + + self.ts.resize((epoch + 1).try_into().unwrap(), None); + Ok(()) + } } impl TransactionalStorage for VersionedStorage where - T: Debug + Send + Sync + Clone + Serialize + for<'a> Deserialize<'a>, + T: Debug + Send + Sync + Clone + Serialize + for<'b> Deserialize<'b>, { - fn start_transaction(&mut self) -> Result<(), RyhopeError> { + async fn start_transaction(&mut self) -> Result<(), RyhopeError> { if self.in_tx { return Err(RyhopeError::AlreadyInTransaction); } @@ -74,17 +102,28 @@ where impl EpochStorage for VersionedStorage where - T: Debug + Send + Sync + Clone + Serialize + for<'a> Deserialize<'a>, + T: Debug + Send + Sync + Clone + Serialize + for<'b> Deserialize<'b>, { - fn current_epoch(&self) -> Epoch { - let inner_epoch: Epoch = (self.ts.len() - 1).try_into().unwrap(); - inner_epoch + self.epoch_offset + async fn current_epoch(&self) -> Result { + self.epoch_mapper + .try_to_user_epoch(self.inner_epoch()) + .await + .ok_or(RyhopeError::CurrenEpochUndefined(self.inner_epoch())) } - async fn fetch_at(&self, epoch: Epoch) -> Result { - let epoch = epoch - self.epoch_offset; - assert!(epoch >= 0); - Ok(self.ts[epoch as usize].clone().unwrap()) + async fn fetch_at(&self, epoch: UserEpoch) -> Result { + let epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "IncrementalEpoch not found for epoch {epoch}" + )))?; + self.fetch_at_incremental_epoch(epoch) + } + + async fn fetch(&self) -> Result { + self.fetch_at_incremental_epoch(self.inner_epoch()) } async fn store(&mut self, t: T) -> Result<(), RyhopeError> { @@ -94,24 +133,21 @@ where Ok(()) } - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError> { - ensure( - epoch >= self.epoch_offset, - format!("unable to rollback before epoch {}", self.epoch_offset), - )?; - - let epoch = epoch - self.epoch_offset; - ensure( - epoch <= self.current_epoch(), - format!( - "unable to rollback to epoch `{}` more recent than current epoch `{}`", - epoch, - self.current_epoch() - ), - )?; + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { + let inner_epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "trying to rollback to an invalid epoch {}", + epoch + )))?; + self.rollback_to_incremental_epoch(inner_epoch) + } - self.ts.resize((epoch + 1).try_into().unwrap(), None); - Ok(()) + async fn rollback(&mut self) -> Result<(), RyhopeError> { + ensure(self.inner_epoch() > 0, "unable to rollback before epoch 0")?; + self.rollback_to_incremental_epoch(self.inner_epoch() - 1) } } @@ -126,70 +162,113 @@ where /// as there is (at least for now) a usecase where a tree is non-empty at epoch /// 0. #[derive(Debug)] -pub struct VersionedKvStorage { +pub struct VersionedKvStorage< + K: Hash + Eq + Clone + Debug + Send + Sync, + V: Clone + Debug + Send + Sync, +> { /// In the diffs, the value carried by the insertion/modification of a key /// is represented as a Some, whereas a deletion is represented by /// associating k to None. mem: Vec>>, - /// The initial epoch - epoch_offset: Epoch, + /// The shared data structure used to map epochs + epoch_mapper: RoSharedEpochMapper, } -impl Default for VersionedKvStorage { +impl Default + for VersionedKvStorage +{ fn default() -> Self { Self::new() } } -impl VersionedKvStorage { +impl + VersionedKvStorage +{ pub fn new() -> Self { - Self::new_at(0) + let epoch_mapper = SharedEpochMapper::new(InMemoryEpochMapper::new_at(0)); + Self::new_with_mapper(epoch_mapper) } - pub fn new_at(initial_epoch: Epoch) -> Self { + pub fn new_with_mapper(mapper: RoSharedEpochMapper) -> Self { VersionedKvStorage { mem: vec![Default::default()], - epoch_offset: initial_epoch, + epoch_mapper: mapper, } } - pub fn new_epoch(&mut self) { + fn new_epoch(&mut self) { self.mem.push(Default::default()); } -} - -impl RoEpochKvStorage for VersionedKvStorage -where - K: Hash + Eq + Clone + Debug + Send + Sync, - V: Clone + Debug + Send + Sync, -{ - fn initial_epoch(&self) -> Epoch { - self.epoch_offset - } - fn current_epoch(&self) -> Epoch { + fn inner_epoch(&self) -> IncrementalEpoch { // There is a 1-1 mapping between the epoch and the position in the list of // diffs; epoch 0 being the initial empty state. - let inner_epoch: Epoch = (self.mem.len() - 1) as Epoch; - inner_epoch + self.epoch_offset + (self.mem.len() - 1).try_into().unwrap() } - async fn try_fetch_at(&self, k: &K, epoch: Epoch) -> Result, RyhopeError> { - assert!(epoch >= self.epoch_offset); - let epoch = epoch - self.epoch_offset; + fn try_fetch_at_incremental_epoch(&self, k: &K, epoch: IncrementalEpoch) -> Option { + assert!(epoch >= 0); // To fetch a key at a given epoch, the list of diffs up to the // requested epoch is iterated in reverse. The first occurence of k, // i.e. the most recent one, will be the current value. // // If this occurence is a None, it means that k has been deleted. - for i in (0..=epoch as usize).rev() { let maybe = self.mem[i].get(k); if let Some(found) = maybe { - return Ok(found.to_owned()); + return found.to_owned(); }; } - Ok(None) + None + } + + fn rollback_to_incremental_epoch( + &mut self, + epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + ensure(epoch >= 0, "unable to rollback before epoch 0")?; + ensure( + epoch <= self.inner_epoch(), + format!( + "unable to rollback to epoch `{}` more recent than current epoch `{}`", + epoch, + self.inner_epoch() + ), + )?; + + self.mem.truncate((epoch + 1).try_into().unwrap()); + + Ok(()) + } +} + +impl RoEpochKvStorage for VersionedKvStorage +where + K: Hash + Eq + Clone + Debug + Send + Sync, + V: Clone + Debug + Send + Sync, +{ + async fn initial_epoch(&self) -> UserEpoch { + self.epoch_mapper.to_user_epoch(0).await as UserEpoch + } + + async fn current_epoch(&self) -> Result { + self.epoch_mapper + .try_to_user_epoch(self.inner_epoch()) + .await + .ok_or(RyhopeError::CurrenEpochUndefined(self.inner_epoch())) + } + + async fn try_fetch(&self, k: &K) -> Result, RyhopeError> { + Ok(self.try_fetch_at_incremental_epoch(k, self.inner_epoch())) + } + + async fn try_fetch_at(&self, k: &K, epoch: UserEpoch) -> Result, RyhopeError> { + Ok(self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .and_then(|inner_epoch| self.try_fetch_at_incremental_epoch(k, inner_epoch))) } // Expensive, but only used in test context. @@ -209,24 +288,24 @@ where count } - async fn size_at(&self, epoch: Epoch) -> usize { - assert!(epoch >= self.epoch_offset); - let epoch = epoch - self.epoch_offset; + async fn size_at(&self, epoch: UserEpoch) -> usize { + let inner_epoch = self.epoch_mapper.to_incremental_epoch(epoch).await; + assert!(inner_epoch >= 0); // To fetch a key at a given epoch, the list of diffs up to the let mut keys = HashSet::new(); - for i in 0..=epoch as usize { + for i in 0..=inner_epoch as usize { keys.extend(self.mem[i].keys()) } keys.len() } - async fn keys_at(&self, epoch: Epoch) -> Vec { - assert!(epoch >= self.epoch_offset); - let epoch = epoch - self.epoch_offset; + async fn keys_at(&self, epoch: UserEpoch) -> Vec { + let inner_epoch = self.epoch_mapper.to_incremental_epoch(epoch).await; + assert!(inner_epoch >= 0); let mut keys = HashSet::new(); - for i in 0..=epoch as usize { + for i in 0..=inner_epoch as usize { for (k, v) in self.mem[i].iter() { if v.is_some() { keys.insert(k); @@ -239,25 +318,36 @@ where keys.into_iter().cloned().collect() } - async fn random_key_at(&self, epoch: Epoch) -> Option { - assert!(epoch >= self.epoch_offset); - let epoch = epoch - self.epoch_offset; + async fn random_key_at(&self, epoch: UserEpoch) -> Option { + self.epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .and_then(|inner_epoch| { + assert!(inner_epoch >= 0); - for i in (0..=epoch as usize).rev() { - for (k, v) in self.mem[i].iter() { - if v.is_some() { - return Some(k.clone()); + for i in (0..=inner_epoch as usize).rev() { + for (k, v) in self.mem[i].iter() { + if v.is_some() { + return Some(k.clone()); + } + } } - } - } - None + None + }) } - async fn pairs_at(&self, epoch: Epoch) -> Result, RyhopeError> { - assert!(epoch >= self.epoch_offset); + async fn pairs_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { + let inner_epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "IncrementalEpoch not found for epoch {epoch}" + )))?; + assert!(inner_epoch >= 0); let mut pairs = HashMap::new(); - for i in 0..=epoch as usize { + for i in 0..=inner_epoch as usize { for (k, v) in self.mem[i].iter() { if let Some(v) = v.clone() { pairs.insert(k.clone(), v); @@ -292,55 +382,497 @@ where Ok(()) } - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { + let inner_epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "Try to rollback to an invalid epoch {epoch}" + )))?; + self.rollback_to_incremental_epoch(inner_epoch) + } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { + ensure(self.inner_epoch() > 0, "unable to rollback before epoch 0")?; + self.rollback_to_incremental_epoch(self.inner_epoch() - 1) + } +} + +/// Item representing a mapping between a `UserEpoch` and an `IncrementalEpoch`, which +/// is stored in an instance of `InMemoryEpochMapper`. The item can be `Complete` or +/// `Partial`, depending on whether it contains both a `UserEpoch` and an `IncrementalEpoch` +/// or only one of the 2. +/// Partial `EpochMapItem`s will never be stored as entries of `InMemoryEpochMapper`: they will +/// be employed only to implement the lookup methods defined in `EpochMapper` trait, which finds +/// the epoch mapping corresponding to either a given `UserEpoch` or a given `IncrementalEpoch`. +/// In layman terms, since both `UserEpoch`s and `IncrementalEpoch`s are expected to be monotonically +/// increasing in an epoch mapper, the epoch mappings can be easily kept sorted by both `UserEpoch` and +/// `IncrementalEpoch`. Therefore, finding an entry corresponding to a given `UserEpoch` (resp. `IncrementalEpoch`) +/// can be efficienctly done as follow: +/// - Define a Partial `EpochMapItem` wrapping the given `UserEpoch` (resp. `IncrementalEpoch`) +/// - Find the mapping with the given `UserEpoch` (resp. `IncrementalEpoch`) in the sorted set by compare +/// the defined Partial `EpochMapItem` with other entries found in the epoch mapper (which are all Complete); +/// the comparison is done by looking only at their `UserEpoch` (resp. `IncrementalEpoch`) values +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum EpochMapItem { + PartialUser(UserEpoch), + PartialIncremental(IncrementalEpoch), + Complete(UserEpoch, IncrementalEpoch), +} + +impl EpochMapItem { + /// Convert an `EpochMapItem` to the wrapped `UserEpoch` and + /// `IncrementalEpoch`. This method is expected to be called + /// only for complete `EpochMapItem`s, i.e., ones that wrap + /// both a `UserEpoch` and an `IncrementalEpoch`; + /// the method will panic if this assumption is not satisfied + fn to_epochs(self) -> (UserEpoch, IncrementalEpoch) { + if let EpochMapItem::Complete(user_epoch, incremental_epoch) = self { + (user_epoch, incremental_epoch) + } else { + panic!("Invalid `EpochMapItem` being unpacked") + } + } +} + +impl PartialOrd for EpochMapItem { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for EpochMapItem { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Implement the partial order relationship employed to compare + // `EpochMapItem`s. It is partial since by construction we will never + // compare 2 Partial `EpochMapItem`s + match (self, other) { + ( + EpochMapItem::PartialUser(first_user_epoch), + EpochMapItem::Complete(second_user_epoch, _), + ) => first_user_epoch.cmp(second_user_epoch), + ( + EpochMapItem::PartialIncremental(first_incremental_epoch), + EpochMapItem::Complete(_, second_incremental_epoch), + ) => first_incremental_epoch.cmp(second_incremental_epoch), + ( + EpochMapItem::Complete(first_user_epoch, _), + EpochMapItem::PartialUser(second_user_epoch), + ) => first_user_epoch.cmp(second_user_epoch), + ( + EpochMapItem::Complete(_, first_incremental_epoch), + EpochMapItem::PartialIncremental(second_incremental_epoch), + ) => first_incremental_epoch.cmp(second_incremental_epoch), + ( + EpochMapItem::Complete(first_user_epoch, first_incremental_epoch), + EpochMapItem::Complete(second_user_epoch, second_incremental_epoch), + ) => { + let user_epoch_cmp = first_user_epoch.cmp(second_user_epoch); + let incremental_epoch_cmp = first_incremental_epoch.cmp(second_incremental_epoch); + assert_eq!( + user_epoch_cmp, incremental_epoch_cmp, + "Breaking invariant of `EpochMapper`: both `UserEpoch` and `IncrementalEpoch` + must be monotonically increasing" + ); + user_epoch_cmp + } + _ => + // all other cases are partial `EpochMapItem`s, which are never compared + { + unreachable!() + } + } + } +} + +#[derive(Clone, Debug)] +/// Data structure employed both for in-memory implementation of an `EpochMapper`, +/// and as a memory cache for the DB-based `EpochMapper` implementation. +/// The flag `IS_CACHE` is employed to specify whether the data structure is employed +/// as a cache or as a standalone in-memory `EpochMapper`. +/// It basically handles two types of epochs mappings, depending on how the epoch maps +/// are inserted by users: +/// +/// - If the `UserEpoch`s being inserted are all incrementals, starting from an +/// initial offset, then an optimized implementation is employed for this conversion +/// - Otherwise, there is a more generic implementation that can handle any monotonically +/// increasing sequence of `UserEpoch`s +/// +/// The first implementation is used while the `UserEpoch`s being inserted followed the +/// incremental pattern; as soon as a non-incremental `UserEpoch` is inserted, then the +/// implementation falls back to the more generic generic implementation +pub struct InMemoryEpochMapperGeneric { + // Generic implementation to map monotonically increasing `UserEpoch`s to `IncrementalEpoch`s + generic_map: BTreeSet, + // Optimized implementation for incremental `UserEpoch`s + incremental_epochs_map: Option, +} +/// In-memory implementation of `EpochMapper`, which allows to map a +/// `UserEpoch` to an `IncrementalEpoch` used by storages +pub type InMemoryEpochMapper = InMemoryEpochMapperGeneric; +/// In-memory cache of the DB-based implementation of `EpochMapper` +pub(crate) type EpochMapperCache = + InMemoryEpochMapperGeneric; + +#[derive(Clone, Debug)] +/// Data structure employed to map `UserEpoch`s with `IncrementalEpoch`s in case +/// `UserEpoch`s are all sequential. In this case, it is sufficient to simply store: +/// - The initial offset to convert between `UserEpoch`s and `IncrementalEpoch`s +/// - The last inserted `UserEpoch` +struct IncrementalEpochMap { + offset: UserEpoch, + last_epoch: UserEpoch, +} + +impl + InMemoryEpochMapperGeneric +{ + pub(crate) fn new_at(initial_epoch: UserEpoch) -> Self { + // by default, we assume epochs are incremental, so we initialize + // the optimized epochs map + Self { + generic_map: BTreeSet::new(), + incremental_epochs_map: Some(IncrementalEpochMap { + offset: initial_epoch, + last_epoch: initial_epoch, + }), + } + } + + pub(crate) fn initial_epoch(&self) -> UserEpoch { + match self.incremental_epochs_map { + Some(IncrementalEpochMap { + offset: initial_epoch, + .. + }) => initial_epoch, + None => { + let (initial_epoch, initial_inner_epoch) = + self.generic_map.iter().next().expect( + "Initial epoch is always expected to be inserted at build-time in the storage", + ).to_epochs(); + assert_eq!(initial_inner_epoch, 0); + initial_epoch + } + } + } + + pub(crate) fn last_epoch(&self) -> UserEpoch { + match self.incremental_epochs_map { + Some(IncrementalEpochMap { last_epoch, .. }) => last_epoch, + None => { + self.generic_map + .iter() + .next_back() + .expect( + "No epoch found in `InMemoryEpochMapper`, + it is assumed there is always at least one epoch", + ) + .to_epochs() + .0 + } + } + } + + /// Return the maximum number of epoch mapping entries that can be stored in `self`, if any. + fn max_number_of_entries(&self) -> Option { + (IS_CACHE && self.incremental_epochs_map.is_none()).then_some(MAX_ENTRIES) + } + + fn try_to_user_epoch_inner(&self, epoch: IncrementalEpoch) -> Option { + match self.incremental_epochs_map { + Some(IncrementalEpochMap { + offset: initial_epoch, + last_epoch, + }) => { + let user_epoch = epoch + initial_epoch; + // return `user_epoch` only if it is at most `last_epoch` + (user_epoch <= last_epoch).then_some(user_epoch) + } + None => { + // To lookup an `IncrementalEpoch` in `self.generic_map`, we build + // an instance of `EpochMapItem::PartialIncremental` for the + // `IncrementalEpoch` `epoch`. + // The partial order relationship defined for `EpochMapItem` allows to + // efficiently find in the `BTreeSet` the epoch map with `IncrementalEpoch` + // corresponding to `epoch`, if any + let epoch_map_item = EpochMapItem::PartialIncremental(epoch); + self.generic_map + .get(&epoch_map_item) + .map(|item| item.to_epochs().0) + } + } + } + + /// Add a new epoch mapping for `IncrementalEpoch` `epoch`, assuming that `UserEpoch`s + /// are also computed incrementally from an initial shift. If there is already a mapping for + /// `IncrementalEpoch` `epoch`, then this function has no side effects, because it is assumed + /// that the mapping has already been provided according to another, non-incremental, logic. + /// This function returns the `UserEpoch` being mapper to `epoch`, in case a new mapping + /// is actually inserted. + pub(crate) fn new_incremental_epoch(&mut self, epoch: IncrementalEpoch) -> Option { + // compute last arbitrary epoch having been inserted in the map + let last_epoch = self.last_epoch(); + // check if `epoch` has already been inserted in the map + match self.try_to_user_epoch_inner(epoch) { + Some(matched_epoch) => { + // `epoch` has already been inserted, only check that + // `matched_epoch` corresponds to the last inserted `UserEpoch` + assert_eq!(last_epoch, matched_epoch); + None + } + None => { + // get arbitrary epoch corresponding to the new incremental epoch. + // in this implementation, it is computed assuming that also + // `UserEpoch`s are incremental, and so the epoch to be inserted + // is simply `last_epoch + 1` + let mapped_epoch = last_epoch + 1; + // add the epoch mapping to `self` + self.add_epoch(mapped_epoch, epoch) + .ok() + .map(|_| mapped_epoch) + } + } + } + + pub(crate) fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { + // first, check that we are rolling back to a valid epoch + let last_epoch = self.last_epoch(); ensure( - epoch >= self.epoch_offset, - format!("unable to rollback before epoch {}", self.epoch_offset), + epoch <= last_epoch, + "cannot rollback to epoch greater than last epoch", )?; - - let epoch = epoch - self.epoch_offset; + let initial_epoch = self.initial_epoch(); ensure( - epoch <= self.current_epoch(), - format!( - "unable to rollback to epoch `{}` more recent than current epoch `{}`", - epoch, - self.current_epoch() - ), + epoch >= initial_epoch, + "cannot rollback to epoch smaller than initial epoch", )?; + match self.incremental_epochs_map.as_mut() { + Some(IncrementalEpochMap { last_epoch, .. }) => { + *last_epoch = epoch; + } + None => { + // first, check that the epoch we are rolling back to exists + ensure( + self.generic_map.contains(&EpochMapItem::PartialUser(epoch)), + format!("Trying to rollback to non-existing epoch {epoch}"), + )?; + // now, erase all epochs greater than `epoch` + while self.generic_map.last().unwrap().to_epochs().0 > epoch { + self.generic_map.pop_last(); + } + } + } - self.mem.truncate((epoch + 1).try_into().unwrap()); + Ok(()) + } + + // Move from the optimized implementation for incremental `UserEpoch`s to the generic map + // implementation. This method is called when a request to add a non-incremental `UserEpoch` + // is detected + fn falback_to_generic_map(&mut self) { + let IncrementalEpochMap { + offset: initial_epoch, + last_epoch, + } = self.incremental_epochs_map.take().unwrap(); + self.generic_map = (initial_epoch..=last_epoch) + .enumerate() + .take(self.max_number_of_entries().unwrap_or( + usize::MAX, // this is practically unbounded + )) // fill up to the maximum number of entries allowed to be stored, if any + .map(|(i, epoch)| EpochMapItem::Complete(epoch, i as IncrementalEpoch)) + .collect(); + } + + // Add new mapping `user_epoch -> incremental_epoch` to `self` to the generic map implementation; + // this method has to be called only when the caller knows that the generic map implementation is + // used to map `UserEpoch`s to `IncrementalEpoch`s + fn add_epoch_to_generic_map( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + // if we are replacing an existing `IncrementalEpoch`, ensure that + // we remove the old mapping entry + if let Some(epoch) = self.try_to_user_epoch_inner(incremental_epoch) { + let epoch_map_item = EpochMapItem::Complete(epoch, incremental_epoch); + self.generic_map.remove(&epoch_map_item); + } + + self.generic_map + .insert(EpochMapItem::Complete(user_epoch, incremental_epoch)); + + // check if we need to remove an item since we got to the maximum number of entries allowed + // to be stored + if let Some(max_entries) = self.max_number_of_entries() { + if self.generic_map.len() > max_entries { + // remove the second item in the mapping (as the first one contains the initial epoch) + let second_item = *self.generic_map.iter().nth(1).unwrap(); + self.generic_map.remove(&second_item); + } + } + + Ok(()) + } + + fn add_epoch( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + match self.incremental_epochs_map { + Some(IncrementalEpochMap { + offset: initial_epoch, + last_epoch, + }) => { + ensure(user_epoch >= initial_epoch, + format!("Trying to insert an epoch {user_epoch} smaller than initial epoch {initial_epoch}") + )?; + // we need to fallback to the generic map implementation if: + // - either we are insering a new `user_epoch` which is no longer incremental + // - or we are updating the last inserted `incremental_epoch` with a bigger `user_epoch` + let last_incremental_epoch = last_epoch - initial_epoch; + if user_epoch > last_epoch + 1 + || (last_incremental_epoch == incremental_epoch && user_epoch > last_epoch) + { + // fallback to generic map + self.falback_to_generic_map(); + self.add_epoch_to_generic_map(user_epoch, incremental_epoch)?; + } else { + // In all other cases, we need to check that + // `incremental_epoch == user_epoch - initial_epoch`, to keep the epochs + // incremental + ensure(user_epoch - initial_epoch == incremental_epoch, + format!( + "Trying to insert an invalid incremental epoch: expected {}, found {incremental_epoch}", + user_epoch - initial_epoch, + ))?; + // If we are adding a new `user_epoch`, we update `last_epoch`; + // otherwise, it's a no-operation + if user_epoch == last_epoch + 1 { + self.incremental_epochs_map.as_mut().unwrap().last_epoch = user_epoch; + } + } + } + None => { + self.add_epoch_to_generic_map(user_epoch, incremental_epoch)?; + } + } Ok(()) } } +impl EpochMapper + for InMemoryEpochMapperGeneric +{ + async fn try_to_incremental_epoch(&self, epoch: UserEpoch) -> Option { + match self.incremental_epochs_map { + Some(IncrementalEpochMap { + offset: initial_epoch, + last_epoch, + }) => (epoch <= last_epoch && epoch >= initial_epoch).then(|| epoch - initial_epoch), + None => { + // To lookup an`UserEpoch` in `self.generic_map`, we build + // an instance of `EpochMapItem::PartialUser` for the + // `UserEpoch` `epoch`. + // The partial order relationship defined for `EpochMapItem` allows to + // efficiently find in the `BTreeSet` the epoch map with `UserEpoch` + // corresponding to `epoch`, if any + let epoch_map_item = EpochMapItem::PartialUser(epoch); + self.generic_map + .get(&epoch_map_item) + .map(|item| item.to_epochs().1) + } + } + } + + async fn try_to_user_epoch(&self, epoch: IncrementalEpoch) -> Option { + self.try_to_user_epoch_inner(epoch) + } + + async fn add_epoch_map( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + self.add_epoch(user_epoch, incremental_epoch) + } +} + /// A RAM-backed storage for tree data. -pub struct InMemory { +pub struct InMemory { /// Storage for tree state. state: VersionedStorage<::State>, /// Storage for topological data. nodes: VersionedKvStorage<::Key, ::Node>, /// Storage for node-associated data. data: VersionedKvStorage<::Key, V>, + /// Mapper between used-defined epochs and internal incremental epochs + epoch_mapper: SharedEpochMapper, /// Whether a transaction is currently opened. in_tx: bool, } -impl InMemory { - pub fn new(tree_state: T::State) -> Self { - Self::new_at(tree_state, 0) + +impl + InMemory +{ + /// Initialize a new `InMemory` storage with read-only epoch mapper + pub fn new_with_mapper( + tree_state: T::State, + epoch_mapper: SharedEpochMapper, + ) -> Self { + Self { + state: VersionedStorage::new(tree_state, (&epoch_mapper).into()), + nodes: VersionedKvStorage::new_with_mapper((&epoch_mapper).into()), + data: VersionedKvStorage::new_with_mapper((&epoch_mapper).into()), + epoch_mapper, + in_tx: false, + } } - pub fn new_at(tree_state: T::State, initial_epoch: Epoch) -> Self { + pub fn new_with_epoch(tree_state: T::State, initial_epoch: UserEpoch) -> Self { + let epoch_mapper = SharedEpochMapper::new(InMemoryEpochMapper::new_at(initial_epoch)); Self { - state: VersionedStorage::new_at(tree_state, initial_epoch), - nodes: VersionedKvStorage::new_at(initial_epoch), - data: VersionedKvStorage::new_at(initial_epoch), + state: VersionedStorage::new(tree_state, (&epoch_mapper).into()), + nodes: VersionedKvStorage::new_with_mapper((&epoch_mapper).into()), + data: VersionedKvStorage::new_with_mapper((&epoch_mapper).into()), + epoch_mapper, in_tx: false, } } } -impl FromSettings for InMemory { +impl FromSettings + for InMemory +{ + type Settings = SharedEpochMapper; + + async fn from_settings( + init_settings: InitSettings, + storage_settings: Self::Settings, + ) -> Result { + match init_settings { + InitSettings::MustExist => unimplemented!(), + InitSettings::MustNotExist(tree_state) | InitSettings::Reset(tree_state) => { + Ok(Self::new_with_mapper(tree_state, storage_settings)) + } + InitSettings::MustNotExistAt(tree_state, initial_epoch) + | InitSettings::ResetAt(tree_state, initial_epoch) => { + // check that initial_epoch is in epoch_mapper + ensure( + storage_settings.read_access_ref().await.initial_epoch() == initial_epoch, + format!("Initial epoch {initial_epoch} not found in the epoch mapper provided as input") + )?; + Ok(Self::new_with_mapper(tree_state, storage_settings)) + } + } + } +} + +impl FromSettings + for InMemory +{ type Settings = (); async fn from_settings( @@ -350,17 +882,17 @@ impl FromSettings for InMemory match init_settings { InitSettings::MustExist => unimplemented!(), InitSettings::MustNotExist(tree_state) | InitSettings::Reset(tree_state) => { - Ok(Self::new(tree_state)) + Ok(Self::new_with_epoch(tree_state, 0)) } InitSettings::MustNotExistAt(tree_state, initial_epoch) | InitSettings::ResetAt(tree_state, initial_epoch) => { - Ok(Self::new_at(tree_state, initial_epoch)) + Ok(Self::new_with_epoch(tree_state, initial_epoch)) } } } } -impl TreeStorage for InMemory +impl TreeStorage for InMemory where T: TreeTopology, T::Node: Clone, @@ -368,6 +900,7 @@ where { type StateStorage = VersionedStorage; type NodeStorage = VersionedKvStorage; + type EpochMapper = SharedEpochMapper; fn nodes(&self) -> &Self::NodeStorage { &self.nodes @@ -385,28 +918,43 @@ where &mut self.state } - async fn born_at(&self, epoch: Epoch) -> Vec { - assert!(epoch >= self.nodes.epoch_offset); - self.nodes.mem[(epoch - self.nodes.epoch_offset) as usize] + async fn born_at(&self, epoch: UserEpoch) -> Vec { + let inner_epoch = self.epoch_mapper.to_incremental_epoch(epoch).await; + assert!(inner_epoch >= 0); + self.nodes.mem[inner_epoch as usize] .keys() .cloned() .collect() } - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { println!("Rolling back to {epoch}"); self.state.rollback_to(epoch).await?; self.nodes.rollback_to(epoch).await?; self.data.rollback_to(epoch).await?; - assert_eq!(self.state.current_epoch(), self.nodes.current_epoch()); - assert_eq!(self.state.current_epoch(), self.data.current_epoch()); + // Rollback epoch_mapper as well + self.epoch_mapper + .apply_fn(|mapper| mapper.rollback_to(epoch)) + .await?; + + assert_eq!(self.state.inner_epoch(), self.nodes.inner_epoch()); + assert_eq!(self.state.inner_epoch(), self.data.inner_epoch()); Ok(()) } + + fn epoch_mapper(&self) -> &Self::EpochMapper { + &self.epoch_mapper + } + + fn epoch_mapper_mut(&mut self) -> &mut Self::EpochMapper { + &mut self.epoch_mapper + } } -impl PayloadStorage<::Key, V> for InMemory +impl PayloadStorage<::Key, V> + for InMemory where T: TreeTopology, ::Key: Clone, @@ -423,20 +971,33 @@ where } } -impl TransactionalStorage for InMemory +impl TransactionalStorage for InMemory where T: TreeTopology, V: Clone + Debug + Send + Sync, { - fn start_transaction(&mut self) -> Result<(), RyhopeError> { + async fn start_transaction(&mut self) -> Result<(), RyhopeError> { if self.in_tx { return Err(RyhopeError::AlreadyInTransaction); } - self.state.start_transaction()?; + self.state.start_transaction().await?; self.data.new_epoch(); self.nodes.new_epoch(); self.in_tx = true; + + let new_epoch = self.state.inner_epoch(); + assert_eq!(new_epoch, self.nodes.inner_epoch()); + assert_eq!(new_epoch, self.data.inner_epoch()); + + // add new_epoch to epoch mapper, if it is not READ_ONLY + self.epoch_mapper + .apply_fn(|mapper| { + mapper.new_incremental_epoch(new_epoch); + Ok(()) + }) + .await?; + Ok(()) } diff --git a/ryhope/src/storage/mod.rs b/ryhope/src/storage/mod.rs index dfadd5312..74ced8915 100755 --- a/ryhope/src/storage/mod.rs +++ b/ryhope/src/storage/mod.rs @@ -7,7 +7,10 @@ use std::{ fmt::Debug, future::Future, hash::Hash, + ops::DerefMut, + sync::Arc, }; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use tokio_postgres::Transaction; use view::TreeStorageView; @@ -15,7 +18,7 @@ use self::updatetree::UpdateTree; use crate::{ error::RyhopeError, tree::{NodeContext, TreeTopology}, - Epoch, InitSettings, + IncrementalEpoch, InitSettings, UserEpoch, }; pub mod memory; @@ -54,10 +57,10 @@ where K: Debug + Hash + Eq + Clone + Sync + Send, { /// The keys touched by the query itself - pub core_keys: Vec<(Epoch, K)>, + pub core_keys: Vec<(UserEpoch, K)>, /// An epoch -> (K -> NodeContext, K -> Payload) mapping #[allow(clippy::type_complexity)] - epoch_lineages: HashMap>, HashMap)>, + epoch_lineages: HashMap>, HashMap)>, } impl WideLineage { @@ -68,7 +71,7 @@ impl WideLineage { self.core_keys.len() } - pub fn ctx_and_payload_at(&self, epoch: Epoch, key: &K) -> Option<(NodeContext, V)> { + pub fn ctx_and_payload_at(&self, epoch: UserEpoch, key: &K) -> Option<(NodeContext, V)> { match ( self.node_context_at(epoch, key), self.payload_at(epoch, key), @@ -77,13 +80,13 @@ impl WideLineage { _ => None, } } - pub fn node_context_at(&self, epoch: Epoch, key: &K) -> Option> { + pub fn node_context_at(&self, epoch: UserEpoch, key: &K) -> Option> { self.epoch_lineages .get(&epoch) .and_then(|h| h.0.get(key)) .cloned() } - pub fn payload_at(&self, epoch: Epoch, key: &K) -> Option { + pub fn payload_at(&self, epoch: UserEpoch, key: &K) -> Option { self.epoch_lineages .get(&epoch) .and_then(|h| h.1.get(key)) @@ -91,7 +94,7 @@ impl WideLineage { } /// Returns the list of keys touching the query associated with each epoch - pub fn keys_by_epochs(&self) -> HashMap> { + pub fn keys_by_epochs(&self) -> HashMap> { self.core_keys .iter() .fold(HashMap::new(), |mut acc, (epoch, k)| { @@ -99,7 +102,7 @@ impl WideLineage { acc }) } - pub fn update_tree_for(&self, epoch: Epoch) -> Option> { + pub fn update_tree_for(&self, epoch: UserEpoch) -> Option> { let epoch_data = self.epoch_lineages.get(&epoch)?; let all_paths = self .core_keys @@ -135,6 +138,129 @@ impl WideLineage { } } +// An `EpochMapper` allows to map `UserEpoch` to `IncrementalEpoch` of +// a `TreeStorage`, and vice versa +pub trait EpochMapper: Sized + Send + Sync + Clone + Debug { + fn try_to_incremental_epoch( + &self, + epoch: UserEpoch, + ) -> impl Future> + Send; + + fn to_incremental_epoch( + &self, + epoch: UserEpoch, + ) -> impl Future + Send { + async move { + self.try_to_incremental_epoch(epoch) + .await + .unwrap_or_else(|| panic!("IncrementalEpoch corresponding to {epoch} not found")) + } + } + + fn try_to_user_epoch( + &self, + epoch: IncrementalEpoch, + ) -> impl Future> + Send; + + fn to_user_epoch(&self, epoch: IncrementalEpoch) -> impl Future + Send { + async move { + self.try_to_user_epoch(epoch) + .await + .unwrap_or_else(|| panic!("UserEpoch corresponding to {epoch} not found")) + } + } + + fn add_epoch_map( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> impl Future> + Send; +} + +/// Wrapper data structure to safely use an instance of an `EpochMapper` shared among multiple +/// threads. The `READ_ONLY` flag specifies whether the wrapped `EpochMapper` can be +/// modified or not by callers of this wrapper, that is if `READ_ONLY` is `true`, then callers +// this wrapper can only access the `EpochMapper` without modifying it +#[derive(Clone, Debug)] +pub struct SharedEpochMapper(Arc>); + +pub(crate) type RoSharedEpochMapper = SharedEpochMapper; + +impl From<&SharedEpochMapper> + for RoSharedEpochMapper +{ + fn from(value: &SharedEpochMapper) -> Self { + Self(value.0.clone()) + } +} + +impl SharedEpochMapper { + pub(crate) fn new(mapper: T) -> Self { + Self(Arc::new(RwLock::new(mapper))) + } + + /// Get a writable access to the underlying `EpochMapper`, if `SharedEpochMapper` + /// is not READ_ONLY. Returns `None` if `SharedEpochMapper` is instead `READ_ONLY`. + pub(crate) async fn write_access_ref(&mut self) -> Option> { + if !READ_ONLY { + Some(self.0.write().await) + } else { + None + } + } + + pub(crate) async fn read_access_ref(&self) -> RwLockReadGuard { + self.0.read().await + } + + pub(crate) async fn apply_fn Result<(), RyhopeError>>( + &mut self, + mut f: Fn, + ) -> Result<(), RyhopeError> + where + T: 'static, + { + if let Some(mut mapper) = self.write_access_ref().await { + f(mapper.deref_mut()) + } else { + Ok(()) + } + } +} + +impl AsRef> for SharedEpochMapper { + fn as_ref(&self) -> &RwLock { + &self.0 + } +} + +impl EpochMapper for SharedEpochMapper { + async fn try_to_incremental_epoch(&self, epoch: UserEpoch) -> Option { + self.0.read().await.try_to_incremental_epoch(epoch).await + } + + async fn try_to_user_epoch(&self, epoch: IncrementalEpoch) -> Option { + self.0.read().await.try_to_user_epoch(epoch).await + } + + async fn add_epoch_map( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + // add new epoch mapping only if `self` is not READ_ONLY + if !READ_ONLY { + self.0 + .write() + .await + .add_epoch_map(user_epoch, incremental_epoch) + .await + } else { + Ok(()) + } + } +} + /// A `TreeStorage` stores all data related to the tree structure, i.e. (i) the /// state of the tree structure, (ii) the putative metadata associated to the /// tree nodes. @@ -144,12 +270,20 @@ pub trait TreeStorage: Sized + Send + Sync { /// A storage backend for the underlying tree nodes type NodeStorage: EpochKvStorage + Send + Sync; + type EpochMapper: EpochMapper; + /// Return a handle to the state storage. fn state(&self) -> &Self::StateStorage; /// Return a mutable handle to the state storage. fn state_mut(&mut self) -> &mut Self::StateStorage; + /// Return a handle to the epoch mapper. + fn epoch_mapper(&self) -> &Self::EpochMapper; + + /// Return a mutable handle to the epoch mapper. + fn epoch_mapper_mut(&mut self) -> &mut Self::EpochMapper; + /// Return a handle to the nodes storage. fn nodes(&self) -> &Self::NodeStorage; @@ -157,19 +291,22 @@ pub trait TreeStorage: Sized + Send + Sync { fn nodes_mut(&mut self) -> &mut Self::NodeStorage; /// Return a list of the nodes “born” (i.e. dirtied) at `epoch`. - fn born_at(&self, epoch: Epoch) -> impl Future>; + fn born_at(&self, epoch: UserEpoch) -> impl Future>; /// Rollback this tree one epoch in the past - fn rollback(&mut self) -> impl Future> { - self.rollback_to(self.nodes().current_epoch() - 1) + fn rollback(&mut self) -> impl Future> { + async move { + self.rollback_to(self.nodes().current_epoch().await? - 1) + .await + } } /// Rollback this tree to the given epoch - fn rollback_to(&mut self, epoch: Epoch) -> impl Future>; + fn rollback_to(&mut self, epoch: UserEpoch) -> impl Future>; /// Return an epoch-locked, read-only, [`TreeStorage`] offering a view on /// this Merkle tree as it was at the given epoch. - fn view_at<'a>(&'a self, epoch: Epoch) -> TreeStorageView<'a, T, Self> + fn view_at<'a>(&'a self, epoch: UserEpoch) -> TreeStorageView<'a, T, Self> where T: 'a, { @@ -192,16 +329,16 @@ pub trait EpochStorage Dese where Self: Send + Sync, { - /// Return the current epoch of the storage - fn current_epoch(&self) -> Epoch; + /// Return the current epoch of the storage. It returns an error + /// if the current epoch is undefined, which might happen when the epochs + /// are handled by another storage. + fn current_epoch(&self) -> impl Future> + Send; /// Return the value stored at the current epoch. - fn fetch(&self) -> impl Future> + Send { - async { self.fetch_at(self.current_epoch()).await } - } + fn fetch(&self) -> impl Future> + Send; /// Return the value stored at the given epoch. - fn fetch_at(&self, epoch: Epoch) -> impl Future> + Send; + fn fetch_at(&self, epoch: UserEpoch) -> impl Future> + Send; /// Set the stored value at the current epoch. fn store(&mut self, t: T) -> impl Future> + Send; @@ -219,12 +356,10 @@ where } /// Roll back this storage one epoch in the past. - fn rollback(&mut self) -> impl Future> { - self.rollback_to(self.current_epoch() - 1) - } + fn rollback(&mut self) -> impl Future>; /// Roll back this storage to the given epoch - fn rollback_to(&mut self, epoch: Epoch) -> impl Future>; + fn rollback_to(&mut self, epoch: UserEpoch) -> impl Future>; } /// A read-only, versioned, KV storage. Intended to be implemented in @@ -237,23 +372,23 @@ where V: Send + Sync, { /// Return the first registered time stamp of the storage - fn initial_epoch(&self) -> Epoch; + fn initial_epoch(&self) -> impl Future + Send; - /// Return the current time stamp of the storage - fn current_epoch(&self) -> Epoch; + /// Return the current time stamp of the storage. It returns an error + /// if the current epoch is undefined, which might happen when the epochs + /// are handled by another storage. + fn current_epoch(&self) -> impl Future> + Send; /// Return the value associated to `k` at the current epoch if it exists, /// `None` otherwise. - fn try_fetch(&self, k: &K) -> impl Future, RyhopeError>> + Send { - async { self.try_fetch_at(k, self.current_epoch()).await } - } + fn try_fetch(&self, k: &K) -> impl Future, RyhopeError>> + Send; /// Return the value associated to `k` at the given `epoch` if it exists, /// `None` otherwise. fn try_fetch_at( &self, k: &K, - epoch: Epoch, + epoch: UserEpoch, ) -> impl Future, RyhopeError>> + Send; /// Return whether the given key is present at the current epoch. @@ -262,28 +397,33 @@ where } /// Return whether the given key is present at the given epoch. - fn contains_at(&self, k: &K, epoch: Epoch) -> impl Future> { + fn contains_at( + &self, + k: &K, + epoch: UserEpoch, + ) -> impl Future> { async move { self.try_fetch_at(k, epoch).await.map(|x| x.is_some()) } } /// Return the number of stored K/V pairs at the current epoch. - fn size(&self) -> impl Future { - self.size_at(self.current_epoch()) - } + fn size(&self) -> impl Future; /// Return the number of stored K/V pairs at the given epoch. - fn size_at(&self, epoch: Epoch) -> impl Future; + fn size_at(&self, epoch: UserEpoch) -> impl Future; /// Return all the keys existing at the given epoch. - fn keys_at(&self, epoch: Epoch) -> impl Future>; + fn keys_at(&self, epoch: UserEpoch) -> impl Future>; /// Return a key alive at epoch, if any. - fn random_key_at(&self, epoch: Epoch) -> impl Future>; + fn random_key_at(&self, epoch: UserEpoch) -> impl Future>; /// Return all the valid key/value pairs at the given `epoch`. /// /// NOTE: be careful when using this function, it is not lazy. - fn pairs_at(&self, epoch: Epoch) -> impl Future, RyhopeError>>; + fn pairs_at( + &self, + epoch: UserEpoch, + ) -> impl Future, RyhopeError>>; } /// A versioned KV storage only allowed to mutate entries only in the current @@ -333,20 +473,18 @@ pub trait EpochKvStorage: /// Rollback this storage one epoch back. Please note that this is a /// destructive and irreversible operation. - fn rollback(&mut self) -> impl Future> { - self.rollback_to(self.current_epoch() - 1) - } + fn rollback(&mut self) -> impl Future>; /// Rollback this storage to the given epoch. Please note that this is a /// destructive and irreversible operation. - fn rollback_to(&mut self, epoch: Epoch) -> impl Future>; + fn rollback_to(&mut self, epoch: UserEpoch) -> impl Future>; } /// Characterizes a trait allowing for epoch-based atomic updates. pub trait TransactionalStorage { /// Start a new transaction, defining a transition between the storage at /// two epochs. - fn start_transaction(&mut self) -> Result<(), RyhopeError>; + fn start_transaction(&mut self) -> impl Future>; /// Closes the current transaction and commit to the new state at the new /// epoch. @@ -363,7 +501,7 @@ pub trait TransactionalStorage { Fut: Future>, { async { - self.start_transaction()?; + self.start_transaction().await?; f(self).await?; self.commit_transaction().await } @@ -388,12 +526,12 @@ pub trait SqlTransactionStorage: TransactionalStorage { /// This hook **MUST** be called after the **SUCCESSFUL** execution of the /// transaction given to [`commit_in`]. It **MUST NOT** be called if the /// transaction execution failed. - fn commit_success(&mut self); + fn commit_success(&mut self) -> impl Future; /// This hook **MUST** be called after the **FAILED** execution of the /// transaction given to [`commit_in`]. It **MUST NOT** be called if the /// transaction execution is successful. - fn commit_failed(&mut self); + fn commit_failed(&mut self) -> impl Future; } /// Similar to [`TransactionalStorage`], but returns a [`Minitree`] of the @@ -478,12 +616,12 @@ pub trait SqlTreeTransactionalStorage impl Future; /// This hook **MUST** be called after the **FAILED** execution of the /// transaction given to [`commit_in`]. It **MUST NOT** be called if the /// transaction execution is successful. - fn commit_failed(&mut self); + fn commit_failed(&mut self) -> impl Future; } /// The meta-operations trait gathers high-level operations that may be @@ -499,18 +637,18 @@ pub trait MetaOperations: /// by the union of all the paths-to-the-root for the given keys. fn wide_lineage_between( &self, - at: Epoch, + at: UserEpoch, t: &T, keys: &Self::KeySource, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> impl Future, RyhopeError>>; fn wide_update_trees( &self, - at: Epoch, + at: UserEpoch, t: &T, keys: &Self::KeySource, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> impl Future>, RyhopeError>> { async move { let wide_lineage = self.wide_lineage_between(at, t, keys, bounds).await?; @@ -524,11 +662,11 @@ pub trait MetaOperations: } } #[allow(clippy::type_complexity)] - fn try_fetch_many_at + Send>( + fn try_fetch_many_at + Send>( &self, t: &T, data: I, - ) -> impl Future, V)>, RyhopeError>> + Send + ) -> impl Future, V)>, RyhopeError>> + Send where ::IntoIter: Send; } diff --git a/ryhope/src/storage/pgsql/epoch_mapper.rs b/ryhope/src/storage/pgsql/epoch_mapper.rs new file mode 100644 index 000000000..9c1e2fbf4 --- /dev/null +++ b/ryhope/src/storage/pgsql/epoch_mapper.rs @@ -0,0 +1,400 @@ +use anyhow::Context; +use std::{collections::BTreeSet, sync::Arc}; +use tokio::sync::RwLock; +use tokio_postgres::{Row, Transaction}; + +use crate::{ + error::{ensure, RyhopeError}, + mapper_table_name, + storage::{memory::EpochMapperCache, EpochMapper}, + IncrementalEpoch, UserEpoch, INCREMENTAL_EPOCH, USER_EPOCH, +}; + +use super::storages::DBPool; + +pub(crate) const INITIAL_INCREMENTAL_EPOCH: IncrementalEpoch = 0; + +/// Implementation of `EpochMapper` persisted to a Postgres DB +#[derive(Clone, Debug)] +pub struct EpochMapperStorage { + /// A pointer to the DB client + db: DBPool, + /// The table in which the data must be persisted + table: String, + in_tx: bool, + /// Set of `UserEpoch`s being updated in the cache since the last commit to the DB + dirty: BTreeSet, + // Internal cache used to store the mappings between `UserEpoch`s and `IncrementalEpoch`s + // already fetched from the DB. The main purpose of the cache is avoiding the need to run + // a SQL query to the DB each time an epoch translation is needed. + // The current cache implementation relies on the assumption that the epoch mapper is an + // append-only storage, that is: + // - Once a mapping between a `UserEpoch` and an `IncrementalEpoch` is add to the DB, it is + // no longer modified + // - An existing mapping between a `UserEpoch` and an `IncrementalEpoch` is never deleted, + // unless with a rollback operation + // This assumption allows to ensure that whenever a data is read from the DB and moved to the + // cache, it never gets outdated, unless a rollback occurs. + // Note that, while the underlying DB storage could be shared among multiple `EpochMapperStorage`s, + // the cache is private to each instance of `EpochMapperStorage`, and it is handled uniquely by the + // current `EpochMapperStorage`. The usage of a `RwLock` data structure to wrap the cache is only + // an implementation detail to be able to update the cache also in methods of `EpochMapper` trait + // which aren't expected to modify the `EpochMapper` + pub(super) cache: Arc>>, +} + +impl EpochMapperStorage { + /// Upper bound on the number of epoch mappings that can be stored in an `EpochMapperCache` + /// to avoid a blowup in memory consumption; the cache will be wiped as soon as the number of + /// epoch mappings found goes beyond this value + const MAX_CACHE_ENTRIES: usize = 1000000; + + pub(crate) fn mapper_table_name(&self) -> String { + mapper_table_name(&self.table) + } + + pub(crate) async fn new_from_table(table: String, db: DBPool) -> Result { + let cache = { + let connection = db + .get() + .await + .map_err(|err| RyhopeError::from_bb8("getting a connection", err))?; + let mapper_table_name = mapper_table_name(table.as_str()); + let rows = connection + .query( + &format!( + "SELECT {USER_EPOCH}, {INCREMENTAL_EPOCH} FROM {} ORDER BY {USER_EPOCH}", + mapper_table_name, + ), + &[], + ) + .await + .context("while fetching incremental epoch") + .unwrap(); + ensure( + !rows.is_empty(), + format!("Loading from empty table {mapper_table_name}"), + )?; + let read_row = |row: &Row| { + let user_epoch = row.get::<_, i64>(0) as UserEpoch; + let incremental_epoch = row.get::<_, i64>(1) as IncrementalEpoch; + (user_epoch, incremental_epoch) + }; + let (user_epoch, incremental_epoch) = read_row(&rows[0]); + ensure( + incremental_epoch == INITIAL_INCREMENTAL_EPOCH, + format!("Wrong initial epoch found in table {mapper_table_name}"), + )?; + let mut cache = EpochMapperCache::new_at(user_epoch); + for row in &rows[1..] { + let (user_epoch, incremental_epoch) = read_row(row); + cache.add_epoch_map(user_epoch, incremental_epoch).await?; + } + cache + }; + Ok(Self { + db, + table, + in_tx: false, + dirty: Default::default(), + cache: Arc::new(RwLock::new(cache)), + }) + } + + pub(crate) async fn new( + table: String, + db: DBPool, + initial_epoch: UserEpoch, + ) -> Result { + // Add initial epoch to cache + let mapper_table_name = mapper_table_name(table.as_str()); + Ok(if EXTERNAL_EPOCH_MAPPER { + // Initialize from mapper table + let mapper = Self::new_from_table(table, db).await?; + // check that there is a mapping initial_epoch -> INITIAL_INCREMENTAL_EPOCH + ensure( + mapper.try_to_incremental_epoch(initial_epoch).await + == Some(INITIAL_INCREMENTAL_EPOCH), + "No initial epoch {initial_epoch} found in mapping table {mapper_table_name}", + )?; + mapper + } else { + // add epoch map for `initial_epoch` to the DB + db.get() + .await + .map_err(|err| RyhopeError::from_bb8("getting a connection", err))? + .query( + &format!( + "INSERT INTO {} ({USER_EPOCH}, {INCREMENTAL_EPOCH}) + VALUES ($1, $2)", + mapper_table_name, + ), + &[&(initial_epoch as UserEpoch), &INITIAL_INCREMENTAL_EPOCH], + ) + .await + .map_err(|err| { + RyhopeError::from_db(format!("Inserting epochs in {mapper_table_name}"), err) + })?; + let cache = EpochMapperCache::new_at(initial_epoch); + Self { + db, + table, + in_tx: false, + dirty: Default::default(), + cache: Arc::new(RwLock::new(cache)), + } + }) + } + + /// Add a new epoch mapping for `IncrementalEpoch` `epoch`, assuming that `UserEpoch`s + /// are also computed incrementally from an initial shift. If there is already a mapping for + /// `IncrementalEpoch` `epoch`, then this function has no side effects, because it is assumed + /// that the mapping has already been provided according to another logic. + pub(crate) async fn new_incremental_epoch( + &mut self, + epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + if let Some(mapped_epoch) = self.cache.write().await.new_incremental_epoch(epoch) { + // if a new mapping is actually added to the cache, then we add the `UserEpoch` + // of this mapping to the `dirty` set, so that it is later committed to the DB + self.dirty.insert(mapped_epoch); + } + Ok(()) + } + + pub(crate) fn start_transaction(&mut self) -> Result<(), RyhopeError> { + if self.in_tx { + return Err(RyhopeError::AlreadyInTransaction); + } + self.in_tx = true; + Ok(()) + } + + pub(crate) async fn commit_in_transaction( + &mut self, + db_tx: &mut Transaction<'_>, + ) -> Result<(), RyhopeError> { + // build the set of epoch mappings (user_epoch, incremental_epoch) to be written to the DB + let mut rows_to_insert = vec![]; + for &user_epoch in self.dirty.iter() { + let incremental_epoch = self + .cache + .read() + .await + .try_to_incremental_epoch(user_epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "Epoch {user_epoch} not found in cache" + )))?; + rows_to_insert.push(format!("({user_epoch}, {incremental_epoch})")); + } + + // Insert in the DB table with a single query + db_tx + .query( + &format!( + "INSERT INTO {} ({USER_EPOCH}, {INCREMENTAL_EPOCH}) + VALUES {}", + self.mapper_table_name(), + rows_to_insert.join(",") + ), + &[], + ) + .await + .map_err(|err| { + RyhopeError::from_db( + format!("Inserting new epochs in {}", self.mapper_table_name()), + err, + ) + })?; + + Ok(()) + } + + pub(crate) async fn latest_epoch(&self) -> UserEpoch { + // always fetch it from the DB as it might be outdated in cache + let connection = self.db.get().await.unwrap(); + let row = connection + .query_opt( + &format!( + "SELECT {USER_EPOCH}, {INCREMENTAL_EPOCH} FROM {} + WHERE {USER_EPOCH} = + (SELECT MAX({USER_EPOCH}) FROM {})", + self.mapper_table_name(), + self.mapper_table_name(), + ), + &[], + ) + .await + .context("while fetching incremental epoch") + .unwrap(); + if let Some(row) = row { + let user_epoch = row.get::<_, i64>(0) as UserEpoch; + let incremental_epoch = row.get::<_, i64>(1); + self.cache + .write() + .await + .add_epoch_map(user_epoch, incremental_epoch) + .await + .context("while adding mapping to cache") + .unwrap(); + user_epoch + } else { + unreachable!( + "There should always be at least one row in mapper table {}", + self.mapper_table_name() + ); + } + } + + pub(crate) fn commit_success(&mut self) { + self.dirty.clear(); + self.in_tx = false; + } + + pub(crate) async fn commit_failed(&mut self) { + // revert mappings inserted in the cache since the last commit. + // we rollback to the smallest epoch found in dirty, if any + if let Some(epoch) = self.dirty.pop_first() { + self.cache + .write() + .await + .rollback_to(epoch) + .expect("Cannot rollback to older epoch {epoch}"); + } + self.dirty.clear(); + self.in_tx = false; + } + + /// Rollback `self` to `UserEpoch` epoch. If `EXTERNAL_EPOCH_MAPPER` is true, then + /// this method only rollbacks the cache, as the DB is expected to be rolled back + /// by an external `EpochMapperStorage`; otherwise, the DB is also rolled back + /// by this method. Thus, this implementation of rollback currently works under the + /// assumption that the rollback operation will consistently be called also over + /// the external `EpochMapperStorage`, otherwise the rollback will not be effective + /// even for the current storage (as it will only wipe the cache, but no the DB) + pub(crate) async fn rollback_to( + &mut self, + epoch: UserEpoch, + ) -> Result<(), RyhopeError> { + // rollback the cache + self.cache.write().await.rollback_to(epoch)?; + if !EXTERNAL_EPOCH_MAPPER { + // rollback also DB + let connection = self + .db + .get() + .await + .map_err(|err| RyhopeError::from_bb8("getting connection", err))?; + connection + .query( + &format!( + "DELETE FROM {} WHERE {USER_EPOCH} > $1", + self.mapper_table_name() + ), + &[&(epoch)], + ) + .await + .map_err(|err| { + RyhopeError::from_db( + format!( + "Rolling back epoch mapper table {}", + self.mapper_table_name() + ), + err, + ) + })?; + } + + Ok(()) + } +} + +impl EpochMapper for EpochMapperStorage { + async fn try_to_incremental_epoch(&self, epoch: UserEpoch) -> Option { + let result = self + .cache + .read() + .await + .try_to_incremental_epoch(epoch) + .await; + if result.is_none() { + let connection = self.db.get().await.unwrap(); + let row = connection + .query_opt( + &format!( + "SELECT {INCREMENTAL_EPOCH} FROM {} WHERE {USER_EPOCH} = $1", + self.mapper_table_name() + ), + &[&(epoch)], + ) + .await + .context("while fetching incremental epoch") + .unwrap(); + if let Some(row) = row { + let incremental_epoch = row.get::<_, i64>(0) as IncrementalEpoch; + self.cache + .write() + .await + .add_epoch_map(epoch, incremental_epoch) + .await + .context("while adding mapping to cache") + .unwrap(); + Some(incremental_epoch) + } else { + None + } + } else { + result + } + } + + async fn try_to_user_epoch(&self, epoch: IncrementalEpoch) -> Option { + let result = self.cache.read().await.try_to_user_epoch(epoch).await; + if result.is_none() { + let connection = self.db.get().await.unwrap(); + let row = connection + .query_opt( + &format!( + "SELECT {USER_EPOCH} FROM {} WHERE {INCREMENTAL_EPOCH} = $1", + self.mapper_table_name() + ), + &[&(epoch)], + ) + .await + .context("while fetching incremental epoch") + .unwrap(); + if let Some(row) = row { + let user_epoch = row.get::<_, i64>(0) as UserEpoch; + self.cache + .write() + .await + .add_epoch_map(user_epoch, epoch) + .await + .context("while adding mapping to cache") + .unwrap(); + Some(user_epoch) + } else { + None + } + } else { + result + } + } + + async fn add_epoch_map( + &mut self, + user_epoch: UserEpoch, + incremental_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + // add to cache + self.cache + .write() + .await + .add_epoch_map(user_epoch, incremental_epoch) + .await?; + // add arbitrary epoch to dirty set + self.dirty.insert(user_epoch); + Ok(()) + } +} diff --git a/ryhope/src/storage/pgsql/mod.rs b/ryhope/src/storage/pgsql/mod.rs index 7f2208178..94a63f90c 100644 --- a/ryhope/src/storage/pgsql/mod.rs +++ b/ryhope/src/storage/pgsql/mod.rs @@ -1,28 +1,28 @@ use self::storages::{CachedDbStore, CachedDbTreeStore, DbConnector}; use super::{ - EpochStorage, FromSettings, MetaOperations, PayloadStorage, SqlTransactionStorage, - TransactionalStorage, TreeStorage, WideLineage, + EpochMapper, EpochStorage, FromSettings, MetaOperations, PayloadStorage, SharedEpochMapper, + SqlTransactionStorage, TransactionalStorage, TreeStorage, WideLineage, }; use crate::{ error::{ensure, RyhopeError}, + mapper_table_name, metadata_table_name, storage::pgsql::storages::DBPool, tree::{NodeContext, TreeTopology}, - Epoch, InitSettings, KEY, PAYLOAD, VALID_FROM, VALID_UNTIL, + IncrementalEpoch, InitSettings, UserEpoch, INCREMENTAL_EPOCH, KEY, PAYLOAD, USER_EPOCH, + VALID_FROM, VALID_UNTIL, }; use bb8_postgres::PostgresConnectionManager; +use epoch_mapper::{EpochMapperStorage, INITIAL_INCREMENTAL_EPOCH}; use futures::TryFutureExt; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{ - collections::HashSet, - fmt::Debug, - future::Future, - sync::{Arc, Mutex}, -}; +use std::{collections::HashSet, fmt::Debug, future::Future, sync::Arc}; use storages::{NodeProjection, PayloadProjection}; +use tokio::sync::RwLock; use tokio_postgres::{NoTls, Transaction}; use tracing::*; +mod epoch_mapper; mod storages; const MAX_PGSQL_BIGINT: i64 = i64::MAX; @@ -102,7 +102,10 @@ pub trait PayloadInDb: Clone + Send + Sync + Debug + Serialize + for<'a> Deseria impl Deserialize<'a>> PayloadInDb for T {} /// If it exists, remove the given table from the current database. -async fn delete_storage_table(db: DBPool, table: &str) -> Result<(), RyhopeError> { +async fn delete_storage_table( + db: DBPool, + table: &str, +) -> Result<(), RyhopeError> { let connection = db.get().await.unwrap(); connection .execute(&format!("DROP TABLE IF EXISTS {}", table), &[]) @@ -110,10 +113,37 @@ async fn delete_storage_table(db: DBPool, table: &str) -> Result<(), RyhopeError .map_err(|err| RyhopeError::from_db(format!("unable to delete table `{table}`"), err)) .map(|_| ())?; connection - .execute(&format!("DROP TABLE IF EXISTS {}_meta", table), &[]) + .execute( + &format!("DROP TABLE IF EXISTS {}", metadata_table_name(table)), + &[], + ) .await .map_err(|err| RyhopeError::from_db(format!("unable to delete table `{table}`"), err)) - .map(|_| ()) + .map(|_| ())?; + if EXTERNAL_EPOCH_MAPPER { + // The epoch mapper is external, so we just need to delete the view + let mapper_table_alias = mapper_table_name(table); + connection + .execute(&format!("DROP VIEW IF EXISTS {mapper_table_alias}"), &[]) + .await + .map_err(|err| { + RyhopeError::from_db(format!("unable to delete view `{mapper_table_alias}`"), err) + }) + .map(|_| ()) + } else { + // The epoch mapper is internal, so we directly erase the table + let mapper_table_name = mapper_table_name(table); + connection + .execute( + &format!("DROP TABLE IF EXISTS {mapper_table_name} CASCADE"), + &[], + ) + .await + .map_err(|err| { + RyhopeError::from_db(format!("unable to delete table `{mapper_table_name}`"), err) + }) + .map(|_| ()) + } } /// Keeps track of which kind of operation came into the cache @@ -159,9 +189,13 @@ pub struct SqlStorageSettings { pub table: String, /// A way to connect to the DB server pub source: SqlServerConnection, + /// In case an external epoch mapper is employed for this storage, + /// this field contains the name of the table providing such an epoch mapper. + /// It is None if the epoch mapper is handled internally by the storage + pub external_mapper: Option, } -pub struct PgsqlStorage +pub struct PgsqlStorage where T: TreeTopology + DbConnector, T::Key: ToFromBytea, @@ -173,18 +207,21 @@ where /// A connection to the PostgreSQL server db: DBPool, /// The current epoch - epoch: i64, + epoch: IncrementalEpoch, + /// Epoch mapper + epoch_mapper: SharedEpochMapper, /// Tree state information state: CachedDbStore, /// Topological information - tree_store: Arc>>, + tree_store: Arc>>, nodes: NodeProjection, payloads: PayloadProjection, /// If any, the transaction progress in_tx: bool, } -impl FromSettings for PgsqlStorage +impl FromSettings + for PgsqlStorage where T: TreeTopology + DbConnector, T::Key: ToFromBytea, @@ -198,6 +235,24 @@ where init_settings: InitSettings, storage_settings: Self::Settings, ) -> Result { + // check consistency between `EXTERNAL_EPOCH_MAPPER` and `storage_settings.external_mapper`. + // This check is not relevant if `init_settings` is `MustExist`, as in this case we don't need + // to create a new mapping table or view. + if let InitSettings::MustExist = init_settings { + } else { + match ( + EXTERNAL_EPOCH_MAPPER, + storage_settings.external_mapper.is_some(), + ) { + (true, false) => Err(RyhopeError::internal( + "No external mapper table provided for a storage with external epoch mapper", + ))?, + (false, true) => Err(RyhopeError::internal( + "External mapper table provided for a storage with no external epoch mapper", + ))?, + _ => {} + } + }; match init_settings { InitSettings::MustExist => { Self::load_existing(&storage_settings.source, storage_settings.table).await @@ -208,6 +263,7 @@ where storage_settings.table, tree_state, 0, + storage_settings.external_mapper, ) .await } @@ -217,6 +273,7 @@ where storage_settings.table, tree_state, epoch, + storage_settings.external_mapper, ) .await } @@ -226,6 +283,7 @@ where storage_settings.table, tree_settings, 0, + storage_settings.external_mapper, ) .await } @@ -235,6 +293,7 @@ where storage_settings.table, tree_settings, initial_epoch, + storage_settings.external_mapper, ) .await } @@ -252,7 +311,10 @@ async fn fetch_epoch_data(db: DBPool, table: &str) -> Result<(i64, i64), RyhopeE let connection = db.get().await.unwrap(); connection .query_one( - &format!("SELECT MIN({VALID_FROM}), MAX({VALID_UNTIL}) FROM {table}_meta",), + &format!( + "SELECT MIN({VALID_FROM}), MAX({VALID_UNTIL}) FROM {}", + metadata_table_name(table) + ), &[], ) .await @@ -260,7 +322,8 @@ async fn fetch_epoch_data(db: DBPool, table: &str) -> Result<(i64, i64), RyhopeE .map_err(|err| RyhopeError::from_db("fetching current epoch data", err)) } -impl std::fmt::Display for PgsqlStorage +impl std::fmt::Display + for PgsqlStorage where T: TreeTopology + DbConnector, T::Key: ToFromBytea, @@ -272,7 +335,7 @@ where write!(f, "PgSqlStorage {}@{}", self.table, self.epoch) } } -impl PgsqlStorage +impl PgsqlStorage where T: TreeTopology + DbConnector, T::Key: ToFromBytea, @@ -288,7 +351,8 @@ where db_src: &SqlServerConnection, table: String, tree_state: T::State, - epoch: Epoch, + epoch: UserEpoch, + mapper_table: Option, ) -> Result { debug!("creating new table for `{table}` at epoch {epoch}"); let db_pool = Self::init_db_pool(db_src).await?; @@ -297,13 +361,18 @@ where fetch_epoch_data(db_pool.clone(), &table).await.is_err(), format!("table `{table}` already exists"), )?; - Self::create_tables(db_pool.clone(), &table).await?; + Self::create_tables(db_pool.clone(), &table, mapper_table).await?; + + let epoch_mapper = SharedEpochMapper::new( + EpochMapperStorage::new::(table.clone(), db_pool.clone(), epoch) + .await?, + ); - let tree_store = Arc::new(Mutex::new(CachedDbTreeStore::new( - epoch, - epoch, + let tree_store = Arc::new(RwLock::new(CachedDbTreeStore::new( + INITIAL_INCREMENTAL_EPOCH, table.clone(), db_pool.clone(), + (&epoch_mapper).into(), ))); let nodes = NodeProjection { wrapped: tree_store.clone(), @@ -315,13 +384,19 @@ where let r = Self { table: table.clone(), db: db_pool.clone(), - epoch, + epoch: 0, in_tx: false, tree_store, nodes, payloads, - state: CachedDbStore::with_value(epoch, table.clone(), db_pool.clone(), tree_state) - .await?, + state: CachedDbStore::with_value( + table.clone(), + db_pool.clone(), + tree_state, + (&epoch_mapper).into(), + ) + .await?, + epoch_mapper, }; Ok(r) } @@ -337,11 +412,31 @@ where let (initial_epoch, latest_epoch) = fetch_epoch_data(db_pool.clone(), &table).await?; debug!("loading `{table}`; latest epoch is {latest_epoch}"); - let tree_store = Arc::new(Mutex::new(CachedDbTreeStore::new( - initial_epoch, + ensure( + initial_epoch == INITIAL_INCREMENTAL_EPOCH, + format!( + "Wrong internal initial epoch found for existing table {table}: + expected {INITIAL_INCREMENTAL_EPOCH}, found {initial_epoch}" + ), + )?; + let epoch_mapper = + EpochMapperStorage::new_from_table(table.clone(), db_pool.clone()).await?; + let latest_epoch_in_mapper = epoch_mapper + .to_incremental_epoch(epoch_mapper.latest_epoch().await) + .await; + ensure( + latest_epoch_in_mapper == latest_epoch, + format!( + "Mismatch between the latest internal epoch in mapper table and the latest epoch + found in the storage: {latest_epoch_in_mapper} != {latest_epoch}" + ), + )?; + let epoch_mapper = SharedEpochMapper::new(epoch_mapper); + let tree_store = Arc::new(RwLock::new(CachedDbTreeStore::new( latest_epoch, table.clone(), db_pool.clone(), + (&epoch_mapper).into(), ))); let nodes = NodeProjection { wrapped: tree_store.clone(), @@ -354,7 +449,13 @@ where table: table.clone(), db: db_pool.clone(), epoch: latest_epoch, - state: CachedDbStore::new(initial_epoch, latest_epoch, table.clone(), db_pool.clone()), + state: CachedDbStore::new( + latest_epoch, + table.clone(), + db_pool.clone(), + (&epoch_mapper).into(), + ), + epoch_mapper, tree_store, nodes, payloads, @@ -370,19 +471,29 @@ where db_src: &SqlServerConnection, table: String, tree_state: T::State, - initial_epoch: Epoch, + initial_epoch: UserEpoch, + mapper_table: Option, ) -> Result { debug!("resetting table `{table}` at epoch {initial_epoch}"); let db_pool = Self::init_db_pool(db_src).await?; - delete_storage_table(db_pool.clone(), &table).await?; - Self::create_tables(db_pool.clone(), &table).await?; + delete_storage_table::(db_pool.clone(), &table).await?; + Self::create_tables(db_pool.clone(), &table, mapper_table).await?; - let tree_store = Arc::new(Mutex::new(CachedDbTreeStore::new( - initial_epoch, - initial_epoch, + let epoch_mapper = SharedEpochMapper::new( + EpochMapperStorage::new::( + table.clone(), + db_pool.clone(), + initial_epoch, + ) + .await?, + ); + + let tree_store = Arc::new(RwLock::new(CachedDbTreeStore::new( + INITIAL_INCREMENTAL_EPOCH, table.clone(), db_pool.clone(), + (&epoch_mapper).into(), ))); let nodes = NodeProjection { wrapped: tree_store.clone(), @@ -394,14 +505,15 @@ where let r = Self { table: table.clone(), db: db_pool.clone(), - epoch: initial_epoch, + epoch: INITIAL_INCREMENTAL_EPOCH, state: CachedDbStore::with_value( - initial_epoch, table.clone(), db_pool.clone(), tree_state, + (&epoch_mapper).into(), ) .await?, + epoch_mapper, tree_store, nodes, payloads, @@ -458,7 +570,11 @@ where /// the tree at the given epoch range. /// /// Will fail if the CREATE is not valid (e.g. the table already exists) - async fn create_tables(db: DBPool, table: &str) -> Result<(), RyhopeError> { + async fn create_tables( + db: DBPool, + table: &str, + mapper_table: Option, + ) -> Result<(), RyhopeError> { let node_columns = >::columns() .iter() .map(|(name, t)| format!("{name} {t},")) @@ -482,11 +598,42 @@ where .map(|_| ()) .map_err(|err| RyhopeError::from_db(format!("creating table `{table}`"), err))?; + // create index on `VALID_FROM` + connection + .execute( + &format!("CREATE INDEX {table}_index_from ON {table} ({VALID_FROM})"), + &[], + ) + .await + .map(|_| ()) + .map_err(|err| { + RyhopeError::from_db( + format!("unable to create index on table `{table}` for {VALID_FROM}"), + err, + ) + })?; + + // create index on `VALID_UNTIL` + connection + .execute( + &format!("CREATE INDEX {table}_index_until ON {table} ({VALID_UNTIL})"), + &[], + ) + .await + .map(|_| ()) + .map_err(|err| { + RyhopeError::from_db( + format!("unable to create index on table `{table}` for {VALID_UNTIL}"), + err, + ) + })?; + // The meta table will store everything related to the tree itself. + let meta_table = metadata_table_name(table); connection .execute( &format!( - "CREATE TABLE {table}_meta ( + "CREATE TABLE {meta_table} ( {VALID_FROM} BIGINT NOT NULL UNIQUE, {VALID_UNTIL} BIGINT DEFAULT -1, {PAYLOAD} JSONB)" @@ -495,9 +642,72 @@ where ) .await .map(|_| ()) - .map_err(|err| RyhopeError::from_db(format!("creating table `{table}_meta`"), err))?; + .map_err(|err| RyhopeError::from_db(format!("creating table `{meta_table}`"), err))?; - Ok(()) + Ok(())?; + + // create index on `VALID_UNTIL` + connection + .execute( + &format!("CREATE INDEX {meta_table}_index_until ON {meta_table} ({VALID_UNTIL})"), + &[], + ) + .await + .map(|_| ()) + .map_err(|err| { + RyhopeError::from_db( + format!("unable to create index on table `{meta_table}` for {VALID_UNTIL}"), + err, + ) + })?; + + // Create the mapper table if the mapper table is not external, otherwise + // create a view for the mapper table name expected for `table` to `mapper_table`. + if EXTERNAL_EPOCH_MAPPER { + ensure( + mapper_table.is_some(), + "No mapper table name provided for storage with external epoch mapper", + )?; + let mapper_table_alias = mapper_table_name(table); + let mapper_table_name = mapper_table_name(mapper_table.unwrap().as_str()); + connection + .execute( + &format!( + " + CREATE VIEW {mapper_table_alias} AS + SELECT {USER_EPOCH}, {INCREMENTAL_EPOCH} FROM {mapper_table_name}" + ), + &[], + ) + .await + .map(|_| ()) + .map_err(|err| { + RyhopeError::from_db( + format!("unable to create view for `{mapper_table_alias}`"), + err, + ) + }) + } else { + let mapper_table_name = mapper_table_name(table); + connection + .execute( + &format!( + "CREATE TABLE {mapper_table_name} ( + {USER_EPOCH} BIGINT NOT NULL UNIQUE, + {INCREMENTAL_EPOCH} BIGINT NOT NULL UNIQUE + )" + ), + &[], + ) + .await + .map(|_| ()) + .map_err(|err| { + RyhopeError::from_db( + format!("unable to create table `{mapper_table_name}`"), + err, + ) + }) + } } /// Close the lifetim of a row to `self.epoch`. @@ -566,33 +776,15 @@ where // Collect all the keys found in the caches let mut cached_keys = HashSet::new(); { - cached_keys.extend(self.tree_store.lock().unwrap().nodes_cache.keys().cloned()); + cached_keys.extend(self.tree_store.read().await.nodes_cache.keys().cloned()); } { - cached_keys.extend( - self.tree_store - .lock() - .map_err(|e| { - RyhopeError::fatal(format!("failed to lock tree store mutex: {e:?}")) - })? - .payload_cache - .keys() - .cloned(), - ); + cached_keys.extend(self.tree_store.read().await.payload_cache.keys().cloned()); } for k in cached_keys { - let node_value = { self.tree_store.lock().unwrap().nodes_cache.get(&k).cloned() }; - let data_value = { - self.tree_store - .lock() - .map_err(|e| { - RyhopeError::fatal(format!("failed to lock tree store mutex: {e:?}")) - })? - .payload_cache - .get(&k) - .cloned() - }; + let node_value = { self.tree_store.read().await.nodes_cache.get(&k).cloned() }; + let data_value = { self.tree_store.read().await.payload_cache.get(&k).cloned() }; match (node_value, data_value) { // Nothing or a combination of read-only operations, do nothing @@ -654,13 +846,20 @@ where (_, Some(None)) => unreachable!(), } } + // add new incremental epoch to `epoch_mapper` (unless an an epoch map for `self.epoch + 1` + // have already been added to `self.epoch_mapper`) and commit the new epoch map to DB + let new_epoch = self.epoch + 1; + if let Some(mut mapper) = self.epoch_mapper.write_access_ref().await { + mapper.new_incremental_epoch(new_epoch).await?; + mapper.commit_in_transaction(db_tx).await?; + } self.state.commit_in(db_tx).await?; trace!("[{}] commit successful.", self.table); Ok(()) } // FIXME: should return Result - fn on_commit_success(&mut self) { + async fn on_commit_success(&mut self) { assert!(self.in_tx); trace!( "[{self}] commit succesful; updating inner state - current epoch {}", @@ -668,23 +867,34 @@ where ); self.in_tx = false; self.epoch += 1; - self.state.commit_success(); - self.tree_store.lock().unwrap().new_epoch(); + self.state.commit_success().await; + self.epoch_mapper + .apply_fn(|mapper| { + mapper.commit_success(); + Ok(()) + }) + .await + .unwrap(); + self.tree_store.write().await.new_epoch(); } - fn on_commit_failed(&mut self) { + async fn on_commit_failed(&mut self) { assert!(self.in_tx); trace!( "[{self}] commit failed; updating inner state - current epoch {}", self.epoch ); self.in_tx = false; - self.state.commit_failed(); - self.tree_store.lock().unwrap().clear(); + self.state.commit_failed().await; + if let Some(mut mapper) = self.epoch_mapper.write_access_ref().await { + mapper.commit_failed().await; + } + self.tree_store.write().await.clear(); } } -impl TransactionalStorage for PgsqlStorage +impl TransactionalStorage + for PgsqlStorage where V: Send + Sync, T: DbConnector, @@ -692,13 +902,16 @@ where T::Node: Send + Sync + Clone, T::State: Send + Sync + Clone, { - fn start_transaction(&mut self) -> Result<(), RyhopeError> { + async fn start_transaction(&mut self) -> Result<(), RyhopeError> { if self.in_tx { return Err(RyhopeError::AlreadyInTransaction); } trace!("[{self}] starting a new transaction"); self.in_tx = true; - self.state.start_transaction()?; + self.epoch_mapper + .apply_fn(|mapper| mapper.start_transaction()) + .await?; + self.state.start_transaction().await?; Ok(()) } @@ -722,15 +935,16 @@ where .await .map_err(|err| RyhopeError::from_db("committing transaction", err)); if err.is_ok() { - self.on_commit_success(); + self.on_commit_success().await; } else { - self.on_commit_failed(); + self.on_commit_failed().await; } err } } -impl SqlTransactionStorage for PgsqlStorage +impl SqlTransactionStorage + for PgsqlStorage where V: Send + Sync, T: DbConnector, @@ -743,18 +957,19 @@ where self.commit_in_transaction(tx).await } - fn commit_success(&mut self) { + async fn commit_success(&mut self) { trace!("[{self}] API-facing commit_success called"); - self.on_commit_success(); + self.on_commit_success().await; } - fn commit_failed(&mut self) { + async fn commit_failed(&mut self) { trace!("[{self}] API-facing commit_failed called"); - self.on_commit_failed() + self.on_commit_failed().await } } -impl TreeStorage for PgsqlStorage +impl TreeStorage + for PgsqlStorage where T: TreeTopology + DbConnector, V: PayloadInDb + Send, @@ -764,6 +979,7 @@ where { type StateStorage = CachedDbStore; type NodeStorage = NodeProjection; + type EpochMapper = SharedEpochMapper; fn state(&self) -> &Self::StateStorage { &self.state @@ -781,12 +997,13 @@ where &mut self.nodes } - async fn born_at(&self, epoch: Epoch) -> Vec { + async fn born_at(&self, epoch: UserEpoch) -> Vec { + let inner_epoch = self.epoch_mapper.to_incremental_epoch(epoch).await; let connection = self.db.get().await.unwrap(); connection .query( &format!("SELECT {KEY} FROM {} WHERE {VALID_FROM}=$1", self.table), - &[&epoch], + &[&inner_epoch], ) .await .expect("while fetching newborns from database") @@ -795,23 +1012,64 @@ where .collect::>() } - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { self.state.rollback_to(epoch).await?; - self.tree_store.lock().unwrap().rollback_to(epoch).await?; - self.epoch = epoch; + let inner_epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "IncrementalEpoch for epoch {epoch} not found" + )))?; + self.tree_store + .write() + .await + .rollback_to(inner_epoch) + .await?; + self.epoch = inner_epoch; + + // rollback epoch mapper + self.epoch_mapper + .as_ref() + .write() + .await + .rollback_to::(epoch) + .await?; // Ensure epochs coherence + assert_eq!(self.epoch, self.tree_store.read().await.current_epoch()); assert_eq!( - self.state.current_epoch(), - self.tree_store.lock().unwrap().current_epoch() + self.epoch_mapper + .to_incremental_epoch(self.state.current_epoch().await?) + .await, + self.epoch + ); + assert_eq!( + self.epoch_mapper + .to_incremental_epoch( + self.epoch_mapper + .read_access_ref() + .await + .latest_epoch() + .await + ) + .await, + self.epoch, ); - assert_eq!(self.state.current_epoch(), self.epoch); - Ok(()) } + + fn epoch_mapper(&self) -> &Self::EpochMapper { + &self.epoch_mapper + } + + fn epoch_mapper_mut(&mut self) -> &mut Self::EpochMapper { + &mut self.epoch_mapper + } } -impl PayloadStorage for PgsqlStorage +impl PayloadStorage + for PgsqlStorage where Self: TreeStorage, T: TreeTopology + DbConnector, @@ -832,7 +1090,8 @@ where } } -impl MetaOperations for PgsqlStorage +impl MetaOperations + for PgsqlStorage where Self: TreeStorage, T: TreeTopology + DbConnector, @@ -846,10 +1105,10 @@ where async fn wide_lineage_between( &self, - at: Epoch, + at: UserEpoch, t: &T, keys: &Self::KeySource, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> Result, RyhopeError> { let r = t .wide_lineage_between( @@ -864,16 +1123,26 @@ where Ok(r) } - fn try_fetch_many_at::Key)> + Send>( + fn try_fetch_many_at::Key)> + Send>( &self, t: &T, data: I, - ) -> impl Future, V)>, RyhopeError>> + Send + ) -> impl Future, V)>, RyhopeError>> + Send where ::IntoIter: Send, { trace!("[{self}] fetching many contexts & payloads",); let table = self.table.to_owned(); - async move { t.fetch_many_at(self, self.db.clone(), &table, data).await } + async move { + let mut data_with_incremental_epochs = vec![]; + for (epoch, key) in data { + // add current (epoch, key) pair to data to be fetched only if `epoch` is found in the epoch mapper + if let Some(inner_epoch) = self.epoch_mapper.try_to_incremental_epoch(epoch).await { + data_with_incremental_epochs.push((epoch, inner_epoch, key)); + } + } + t.fetch_many_at(self, self.db.clone(), &table, data_with_incremental_epochs) + .await + } } } diff --git a/ryhope/src/storage/pgsql/storages.rs b/ryhope/src/storage/pgsql/storages.rs index c61d02eea..d9fc04e6c 100644 --- a/ryhope/src/storage/pgsql/storages.rs +++ b/ryhope/src/storage/pgsql/storages.rs @@ -1,14 +1,16 @@ use crate::{ error::{ensure, RyhopeError}, + mapper_table_name, storage::{ - EpochKvStorage, EpochStorage, RoEpochKvStorage, SqlTransactionStorage, - TransactionalStorage, TreeStorage, WideLineage, + EpochKvStorage, EpochMapper, EpochStorage, RoEpochKvStorage, RoSharedEpochMapper, + SqlTransactionStorage, TransactionalStorage, TreeStorage, WideLineage, }, tree::{ sbbst::{self, NodeIdx}, scapegoat, NodeContext, TreeTopology, }, - Epoch, EPOCH, KEY, PAYLOAD, VALID_FROM, VALID_UNTIL, + IncrementalEpoch, UserEpoch, EPOCH, INCREMENTAL_EPOCH, KEY, PAYLOAD, USER_EPOCH, VALID_FROM, + VALID_UNTIL, }; use bb8::Pool; use bb8_postgres::PostgresConnectionManager; @@ -20,13 +22,16 @@ use std::{ fmt::Debug, future::Future, marker::PhantomData, - sync::{Arc, Mutex}, + sync::Arc, }; use tokio::sync::RwLock; use tokio_postgres::{self, NoTls, Row, Transaction}; use tracing::*; -use super::{CachedValue, PayloadInDb, ToFromBytea, MAX_PGSQL_BIGINT}; +use super::{ + epoch_mapper::{EpochMapperStorage, INITIAL_INCREMENTAL_EPOCH}, + metadata_table_name, CachedValue, PayloadInDb, ToFromBytea, MAX_PGSQL_BIGINT, +}; pub type DBPool = Pool>; @@ -66,7 +71,7 @@ where db_tx: &tokio_postgres::Transaction<'_>, table: &str, k: &Self::Key, - birth_epoch: Epoch, + birth_epoch: IncrementalEpoch, v: &Self::Node, ) -> impl Future>; @@ -76,7 +81,7 @@ where db_tx: &tokio_postgres::Transaction<'_>, table: &str, k: &Self::Key, - epoch: Epoch, + epoch: IncrementalEpoch, v: V, ) -> impl Future> { async move { @@ -99,13 +104,13 @@ where db: DBPool, table: &str, k: &Self::Key, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> impl Future, RyhopeError>> + Send; fn fetch_all_keys( db: DBPool, table: &str, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> impl Future, RyhopeError>> + Send { async move { let connection = db.get().await.unwrap(); @@ -129,7 +134,7 @@ where fn fetch_a_key( db: DBPool, table: &str, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> impl Future, RyhopeError>> + Send { async move { let connection = db.get().await.unwrap(); @@ -153,7 +158,7 @@ where fn fetch_all_pairs( db: DBPool, table: &str, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> impl Future, RyhopeError>> + std::marker::Send { async move { let connection = db.get().await.unwrap(); @@ -178,7 +183,7 @@ where db: DBPool, table: &str, k: &Self::Key, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> impl std::future::Future, RyhopeError>> + std::marker::Send { async move { let connection = db.get().await.unwrap(); @@ -217,23 +222,27 @@ where db: DBPool, table: &str, keys_query: &str, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), // we keep `UserEpoch` here because we need to do ranges + // over epochs in this operation ) -> impl Future, RyhopeError>>; /// Return the value associated to the given key at the given epoch. #[allow(clippy::type_complexity)] - fn fetch_many_at, I: IntoIterator + Send>( + fn fetch_many_at< + S: TreeStorage, + I: IntoIterator + Send, + >( &self, s: &S, db: DBPool, table: &str, data: I, - ) -> impl Future, V)>, RyhopeError>> + Send; + ) -> impl Future, V)>, RyhopeError>> + Send; } /// Implementation of a [`DbConnector`] for a tree over `K` with empty nodes. /// Only applies to the SBBST for now. -impl DbConnector for sbbst::Tree +impl DbConnector for sbbst::Tree where V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, { @@ -245,7 +254,7 @@ where db: DBPool, table: &str, k: &NodeIdx, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> Result, RyhopeError> { let connection = db.get().await.unwrap(); connection @@ -271,7 +280,7 @@ where db_tx: &tokio_postgres::Transaction<'_>, table: &str, k: &NodeIdx, - birth_epoch: Epoch, + birth_epoch: IncrementalEpoch, _n: &(), ) -> Result<(), RyhopeError> { db_tx @@ -295,17 +304,14 @@ where db: DBPool, table: &str, keys_query: &str, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> Result, RyhopeError> { - // In the SBBST case, parsil will not be able to inject the table name; - // so we do it here. - let keys_query = format!("{keys_query} FROM {table}"); // Execute `keys_query` to retrieve the core keys from the DB let core_keys = db .get() .await .map_err(|err| RyhopeError::from_bb8("getting a connection", err))? - .query(&keys_query, &[]) + .query(&keys_query.to_string(), &[]) .await .map_err(|err| { RyhopeError::from_db( @@ -327,12 +333,20 @@ where } // Fetch all the payloads for the wide lineage in one fell swoop - let payload_query = format!( - "SELECT - {KEY}, generate_series(GREATEST({VALID_FROM}, $1), LEAST({VALID_UNTIL}, $2)) AS epoch, {PAYLOAD} - FROM {table} - WHERE NOT ({VALID_FROM} > $2 OR {VALID_UNTIL} < $1) AND {KEY} = ANY($3)", - ); + let mapper_table_name = mapper_table_name(table); + let payload_query = format!(" + SELECT + {KEY}, + generate_series(GREATEST({VALID_FROM}, min_epoch), LEAST({VALID_UNTIL}, max_epoch)) AS epoch, + {PAYLOAD} + FROM {table} CROSS JOIN + (SELECT MIN({INCREMENTAL_EPOCH}) as min_epoch, MAX({INCREMENTAL_EPOCH}) as max_epoch + FROM {mapper_table_name} + WHERE {USER_EPOCH} >= $1 AND {USER_EPOCH} <= $2) as mapper_range + WHERE {VALID_FROM} <= mapper_range.max_epoch AND {VALID_UNTIL} >= mapper_range.min_epoch + AND {KEY} = ANY($3) + ; + "); let rows = db .get() .await @@ -354,11 +368,19 @@ where // Assemble the final result #[allow(clippy::type_complexity)] let mut epoch_lineages: HashMap< - Epoch, + UserEpoch, (HashMap>, HashMap), > = HashMap::new(); for row in &rows { let epoch = row.get::<_, i64>("epoch"); + // convert incremental epoch to user epoch + let epoch = s + .epoch_mapper() + .try_to_user_epoch(epoch as IncrementalEpoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "UserEpoch corresponding to epoch {epoch} not found" + )))?; let key = NodeIdx::from_bytea(row.get::<_, Vec>(KEY)); let payload = Self::payload_from_row(row)?; @@ -377,21 +399,20 @@ where async fn fetch_many_at< S: TreeStorage, - I: IntoIterator + Send, + I: IntoIterator + Send, >( &self, s: &S, db: DBPool, table: &str, data: I, - ) -> Result, V)>, RyhopeError> { - let data = data.into_iter().collect::>(); + ) -> Result, V)>, RyhopeError> { let connection = db.get().await.unwrap(); let immediate_table = data - .iter() - .map(|(epoch, key)| { + .into_iter() + .map(|(user_epoch, incremental_epoch, key)| { format!( - "({epoch}::BIGINT, '\\x{}'::BYTEA)", + "({user_epoch}::BIGINT, {incremental_epoch}::BIGINT, '\\x{}'::BYTEA)", hex::encode(key.to_bytea()) ) }) @@ -399,26 +420,28 @@ where let mut r = Vec::new(); for row in connection - .query( - &dbg!(format!( - "SELECT batch.key, batch.epoch, {table}.{PAYLOAD} FROM - (VALUES {}) AS batch (epoch, key) + .query( + &dbg!(format!( + "SELECT batch.key, batch.user_epoch, {table}.{PAYLOAD} FROM + (VALUES {}) AS batch (user_epoch, incremental_epoch, key) LEFT JOIN {table} ON - batch.key = {table}.{KEY} AND {table}.{VALID_FROM} <= batch.epoch AND batch.epoch <= {table}.{VALID_UNTIL}", - immediate_table - )), - &[], - ) + batch.key = {table}.{KEY} AND {table}.{VALID_FROM} <= batch.incremental_epoch + AND batch.incremental_epoch <= {table}.{VALID_UNTIL}", + immediate_table + )), + &[], + ) .await .map_err(|err| RyhopeError::from_db("fetching payload from DB", err))? - .iter() { - let k = Self::Key::from_bytea(row.get::<_, Vec>(0)); - let epoch = row.get::<_, Epoch>(1); - let v = row.get::<_, Option>>(2).map(|x| x.0); - if let Some(v) = v { - r.push((epoch, self.node_context(&k, s).await?.unwrap() , v)); - } + .iter() + { + let k = Self::Key::from_bytea(row.get::<_, Vec>(0)); + let epoch = row.get::<_, UserEpoch>(1); + let v = row.get::<_, Option>>(2).map(|x| x.0); + if let Some(v) = v { + r.push((epoch, self.node_context(&k, s).await?.unwrap(), v)); } + } Ok(r) } } @@ -450,7 +473,7 @@ where db: DBPool, table: &str, k: &K, - epoch: Epoch, + epoch: IncrementalEpoch, ) -> Result, RyhopeError> { let connection = db.get().await.unwrap(); connection @@ -496,7 +519,7 @@ where db_tx: &tokio_postgres::Transaction<'_>, table: &str, k: &K, - birth_epoch: Epoch, + birth_epoch: IncrementalEpoch, n: &Self::Node, ) -> Result<(), RyhopeError> { db_tx @@ -524,11 +547,11 @@ where async fn wide_lineage_between>( &self, - _: &S, + s: &S, db: DBPool, table: &str, keys_query: &str, - bounds: (Epoch, Epoch), + bounds: (UserEpoch, UserEpoch), ) -> Result, RyhopeError> { ensure( !keys_query.contains('$'), @@ -547,8 +570,11 @@ where LEFT_CHILD = LEFT_CHILD, RIGHT_CHILD = RIGHT_CHILD, SUBTREE_SIZE = SUBTREE_SIZE, + INCREMENTAL_EPOCH = INCREMENTAL_EPOCH, + USER_EPOCH = USER_EPOCH, max_depth = 2, zk_table = table, + mapper_table_name = mapper_table_name(table), core_keys_query = keys_query, ); let connection = db.get().await.unwrap(); @@ -569,7 +595,7 @@ where let mut core_keys = Vec::new(); #[allow(clippy::type_complexity)] let mut epoch_lineages: HashMap< - Epoch, + UserEpoch, (HashMap>, HashMap), > = HashMap::new(); @@ -580,6 +606,14 @@ where let epoch = row.try_get::<_, i64>(EPOCH).map_err(|err| { RyhopeError::invalid_format(format!("fetching `epoch` from {row:?}"), err) })?; + // convert incremental epoch to user epoch + let epoch = s + .epoch_mapper() + .try_to_user_epoch(epoch as IncrementalEpoch) + .await + .ok_or(RyhopeError::epoch_error(format!( + "UserEpoch corresponding to epoch {epoch} not found" + )))?; let node = >::node_from_row(row); let payload = Self::payload_from_row(row)?; if is_core { @@ -607,21 +641,20 @@ where async fn fetch_many_at< S: TreeStorage, - I: IntoIterator + Send, + I: IntoIterator + Send, >( &self, _s: &S, db: DBPool, table: &str, data: I, - ) -> Result, V)>, RyhopeError> { - let data = data.into_iter().collect::>(); + ) -> Result, V)>, RyhopeError> { let connection = db.get().await.unwrap(); let immediate_table = data - .iter() - .map(|(epoch, key)| { + .into_iter() + .map(|(user_epoch, incremental_epoch, key)| { format!( - "({epoch}::BIGINT, '\\x{}'::BYTEA)", + "({user_epoch}::BIGINT, {incremental_epoch}::BIGINT, '\\x{}'::BYTEA)", hex::encode(key.to_bytea()) ) }) @@ -632,12 +665,13 @@ where .query( &format!( "SELECT - batch.key, batch.epoch, {table}.{PAYLOAD}, + batch.key, batch.user_epoch, {table}.{PAYLOAD}, {table}.{PARENT}, {table}.{LEFT_CHILD}, {table}.{RIGHT_CHILD} FROM - (VALUES {}) AS batch (epoch, key) + (VALUES {}) AS batch (user_epoch, incremental_epoch, key) LEFT JOIN {table} ON - batch.key = {table}.{KEY} AND {table}.{VALID_FROM} <= batch.epoch AND batch.epoch <= {table}.{VALID_UNTIL}", + batch.key = {table}.{KEY} AND {table}.{VALID_FROM} <= batch.incremental_epoch + AND batch.incremental_epoch <= {table}.{VALID_UNTIL}", immediate_table ), &[], @@ -647,7 +681,7 @@ where .iter() { let k = Self::Key::from_bytea(row.get::<_, Vec>(0)); - let epoch = row.get::<_, Epoch>(1); + let epoch = row.get::<_, UserEpoch>(1); let v = row.get::<_, Option>>(2).map(|x| x.0); if let Some(v) = v { r.push(( @@ -670,27 +704,32 @@ where pub struct CachedDbStore Deserialize<'a>> { /// A pointer to the DB client db: DBPool, - /// The first valid epoch - initial_epoch: Epoch, /// Whether a transaction is in process in_tx: bool, /// True if the wrapped state has been modified dirty: bool, /// The current epoch - epoch: Epoch, + epoch: IncrementalEpoch, /// The table in which the data must be persisted table: String, + // epoch mapper + epoch_mapper: RoSharedEpochMapper, pub(super) cache: RwLock>, } impl Deserialize<'a>> CachedDbStore { - pub fn new(initial_epoch: Epoch, current_epoch: Epoch, table: String, db: DBPool) -> Self { + pub fn new( + current_epoch: UserEpoch, + table: String, + db: DBPool, + mapper: RoSharedEpochMapper, + ) -> Self { Self { - initial_epoch, db, in_tx: false, dirty: false, epoch: current_epoch, table, + epoch_mapper: mapper, cache: RwLock::new(None), } } @@ -699,19 +738,20 @@ impl Deserialize<'a>> Cache /// immediately persisted, as the DB representation of the payload must be /// valid even if it is never modified further by the user. pub async fn with_value( - initial_epoch: Epoch, table: String, db: DBPool, t: T, + mapper: RoSharedEpochMapper, ) -> Result { + let initial_epoch = INITIAL_INCREMENTAL_EPOCH; { let connection = db.get().await.unwrap(); connection .query( &format!( - "INSERT INTO {}_meta ({VALID_FROM}, {VALID_UNTIL}, {PAYLOAD}) - VALUES ($1, $1, $2)", - table + "INSERT INTO {} ({VALID_FROM}, {VALID_UNTIL}, {PAYLOAD}) + VALUES ($1, $1, $2)", + metadata_table_name(table.as_str()) ), &[&initial_epoch, &Json(t.clone())], ) @@ -723,11 +763,11 @@ impl Deserialize<'a>> Cache Ok(Self { db, - initial_epoch, in_tx: false, dirty: true, epoch: initial_epoch, table, + epoch_mapper: mapper, cache: RwLock::new(Some(t)), }) } @@ -739,14 +779,15 @@ impl Deserialize<'a>> Cache ensure(self.in_tx, "not in a transaction")?; trace!("[{self}] commiting in transaction"); + let meta_table = metadata_table_name(&self.table); + if self.dirty { let state = self.cache.read().await.clone(); db_tx .query( &format!( - "INSERT INTO {}_meta ({VALID_FROM}, {VALID_UNTIL}, {PAYLOAD}) - VALUES ($1, $1, $2)", - self.table + "INSERT INTO {meta_table} ({VALID_FROM}, {VALID_UNTIL}, {PAYLOAD}) + VALUES ($1, $1, $2)" ), &[&(self.epoch + 1), &Json(state)], ) @@ -758,8 +799,7 @@ impl Deserialize<'a>> Cache db_tx .query( &format!( - "UPDATE {}_meta SET {VALID_UNTIL} = $1 + 1 WHERE {VALID_UNTIL} = $1", - self.table + "UPDATE {meta_table} SET {VALID_UNTIL} = $1 + 1 WHERE {VALID_UNTIL} = $1" ), &[&(self.epoch)], ) @@ -787,13 +827,99 @@ impl Deserialize<'a>> Cache self.dirty = false; self.in_tx = false; } + + async fn fetch_at_inner(&self, epoch: IncrementalEpoch) -> Result { + trace!("[{self}] fetching payload at {}", epoch); + let meta_table = metadata_table_name(&self.table); + let connection = self + .db + .get() + .await + .expect("Failed to get DB connection from pool"); + connection + .query_one( + &format!( + "SELECT {PAYLOAD} FROM {meta_table} WHERE {VALID_FROM} <= $1 AND $1 <= {VALID_UNTIL}" + ), + &[&epoch], + ) + .await + .and_then(|row| row.try_get::<_, Json>(0)) + .map(|x| x.0) + .map_err(|err| RyhopeError::from_db( + format!( + "Fetching state from `{meta_table}` at epoch `{epoch}`" + ), err + )) + } + + async fn rollback_to_incremental_epoch( + &mut self, + new_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { + ensure( + new_epoch < self.epoch, + format!( + "unable to rollback into the future: requested epoch ({}) > current epoch ({})", + new_epoch, self.epoch + ), + )?; + ensure( + new_epoch >= INITIAL_INCREMENTAL_EPOCH, + format!( + "unable to rollback to {} before initial epoch {}", + new_epoch, INITIAL_INCREMENTAL_EPOCH + ), + )?; + + let _ = self.cache.get_mut().take(); + let meta_table = metadata_table_name(&self.table); + let mut connection = self.db.get().await.unwrap(); + let db_tx = connection + .transaction() + .await + .expect("unable to create DB transaction"); + // Roll back all the nodes that would still have been alive + db_tx + .query( + &format!("UPDATE {meta_table} SET {VALID_UNTIL} = $1 WHERE {VALID_UNTIL} > $1"), + &[&new_epoch], + ) + .await + .map_err(|err| { + RyhopeError::from_db( + format!("Rolling back alive nodes to epoch {new_epoch} in table {meta_table}"), + err, + ) + })?; + // Delete nodes that would not have been born yet + db_tx + .query( + &format!("DELETE FROM {meta_table} WHERE {VALID_FROM} > $1"), + &[&new_epoch], + ) + .await + .map_err(|err| { + RyhopeError::from_db( + format!("Deleting nodes born after epoch {new_epoch} from table {meta_table}"), + err, + ) + })?; + db_tx + .commit() + .await + .map_err(|err| RyhopeError::from_db("committing", err))?; + self.epoch = new_epoch; + + Ok(()) + } } impl TransactionalStorage for CachedDbStore where T: Debug + Clone + Serialize + for<'a> Deserialize<'a> + Send + Sync, { - fn start_transaction(&mut self) -> Result<(), RyhopeError> { + async fn start_transaction(&mut self) -> Result<(), RyhopeError> { trace!("[{self}] starting transaction"); if self.in_tx { return Err(RyhopeError::AlreadyInTransaction); @@ -847,12 +973,12 @@ where self.commit_in_transaction(tx).await } - fn commit_success(&mut self) { + async fn commit_success(&mut self) { trace!("[{self}] commit_success"); self.on_commit_success() } - fn commit_failed(&mut self) { + async fn commit_failed(&mut self) { trace!("[{self}] commit_failed"); self.on_commit_failed() } @@ -865,7 +991,7 @@ where async fn fetch(&self) -> Result { trace!("[{self}] fetching payload"); if self.cache.read().await.is_none() { - let state = self.fetch_at(self.epoch).await?; + let state = self.fetch_at_inner(self.epoch).await?; let _ = self.cache.write().await.replace(state.clone()); Ok(state) } else { @@ -873,29 +999,15 @@ where } } - async fn fetch_at(&self, epoch: Epoch) -> Result { - trace!("[{self}] fetching payload at {}", epoch); - let connection = self.db.get().await.unwrap(); - connection - .query_one( - &format!( - "SELECT {PAYLOAD} FROM {}_meta WHERE {VALID_FROM} <= $1 AND $1 <= {VALID_UNTIL}", - self.table, - ), - &[&epoch], - ) + async fn fetch_at(&self, epoch: UserEpoch) -> Result { + let epoch = self + .epoch_mapper + .try_to_incremental_epoch(epoch) .await - .and_then(|row| row.try_get::<_, Json>(0)) - .map(|x| x.0) - .map_err(|err| { - RyhopeError::from_db( - format!( - "failed to fetch state from `{}_meta` at epoch `{}`", - self.table, - epoch - ), - err) - }) + .ok_or(RyhopeError::epoch_error(format!( + "IncrementalEpoch not found for epoch {epoch}" + )))?; + self.fetch_at_inner(epoch).await } async fn store(&mut self, t: T) -> Result<(), RyhopeError> { @@ -905,64 +1017,30 @@ where Ok(()) } - fn current_epoch(&self) -> Epoch { - self.epoch - } - - async fn rollback_to(&mut self, new_epoch: Epoch) -> Result<(), RyhopeError> { - ensure( - new_epoch >= self.initial_epoch, - format!( - "unable to rollback to {} before initial epoch {}", - new_epoch, self.initial_epoch - ), - )?; - ensure( - new_epoch < self.current_epoch(), - format!( - "unable to rollback into the future: requested epoch ({}) > current epoch ({})", - new_epoch, - self.current_epoch() - ), - )?; - - let _ = self.cache.get_mut().take(); - let mut connection = self.db.get().await.unwrap(); - let db_tx = connection - .transaction() + async fn current_epoch(&self) -> Result { + self.epoch_mapper + .try_to_user_epoch(self.epoch) .await - .expect("unable to create DB transaction"); - // Roll back all the nodes that would still have been alive - db_tx - .query( - &format!( - "UPDATE {}_meta SET {VALID_UNTIL} = $1 WHERE {VALID_UNTIL} > $1", - self.table - ), - &[&new_epoch], - ) - .await - .map_err(|err| { - RyhopeError::from_db(format!("time-stamping `{}_meta`", self.table), err) - })?; - // Delete nodes that would not have been born yet - db_tx - .query( - &format!("DELETE FROM {}_meta WHERE {VALID_FROM} > $1", self.table), - &[&new_epoch], - ) - .await - .map_err(|err| { - RyhopeError::from_db(format!("reaping nodes `{}_meta`", self.table), err) - })?; + .ok_or(RyhopeError::CurrenEpochUndefined(self.epoch)) + } - db_tx - .commit() + async fn rollback_to(&mut self, new_epoch: UserEpoch) -> Result<(), RyhopeError> { + let inner_epoch = self + .epoch_mapper + .try_to_incremental_epoch(new_epoch) .await - .map_err(|err| RyhopeError::from_db("committing transaction", err))?; - self.epoch = new_epoch; + .ok_or(RyhopeError::epoch_error(format!( + "IncrementalEpoch not found for epoch {new_epoch}" + )))?; + self.rollback_to_incremental_epoch(inner_epoch).await + } - Ok(()) + async fn rollback(&mut self) -> Result<(), RyhopeError> { + ensure( + self.epoch > INITIAL_INCREMENTAL_EPOCH, + "cannot rollback before initial epoch", + )?; + self.rollback_to_incremental_epoch(self.epoch - 1).await } } @@ -975,14 +1053,14 @@ where T::Key: ToFromBytea, V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, { - /// The initial epoch - initial_epoch: Epoch, /// The latest *commited* epoch - epoch: Epoch, + epoch: UserEpoch, /// A pointer to the DB client db: DBPool, /// DB backing this cache table: String, + // Epoch mapper + epoch_mapper: RoSharedEpochMapper, /// Operations pertaining to the in-process transaction. pub(super) nodes_cache: HashMap>>, pub(super) payload_cache: HashMap>>, @@ -1004,13 +1082,18 @@ where T::Key: ToFromBytea, V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, { - pub fn new(initial_epoch: Epoch, current_epoch: Epoch, table: String, db: DBPool) -> Self { + pub fn new( + current_epoch: IncrementalEpoch, + table: String, + db: DBPool, + mapper: RoSharedEpochMapper, + ) -> Self { trace!("[{}] initializing CachedDbTreeStore", table); CachedDbTreeStore { - initial_epoch, epoch: current_epoch, table, db: db.clone(), + epoch_mapper: mapper, nodes_cache: Default::default(), payload_cache: Default::default(), _p: PhantomData, @@ -1022,24 +1105,20 @@ where self.payload_cache.clear(); } - pub fn new_epoch(&mut self) { + pub(crate) fn new_epoch(&mut self) { self.clear(); self.epoch += 1; } - pub fn initial_epoch(&self) -> Epoch { - self.initial_epoch - } - - pub fn current_epoch(&self) -> Epoch { + pub(crate) fn current_epoch(&self) -> IncrementalEpoch { self.epoch } - pub async fn size(&self) -> usize { - self.size_at(self.epoch).await + async fn size(&self) -> usize { + self.size_at(self.current_epoch()).await } - pub async fn size_at(&self, epoch: Epoch) -> usize { + async fn size_at(&self, epoch: IncrementalEpoch) -> usize { let connection = self.db.get().await.unwrap(); connection .query_one( @@ -1057,13 +1136,16 @@ where .unwrap() } - pub(super) async fn rollback_to(&mut self, new_epoch: Epoch) -> Result<(), RyhopeError> { + pub(super) async fn rollback_to( + &mut self, + new_epoch: IncrementalEpoch, + ) -> Result<(), RyhopeError> { trace!("[{self}] rolling back to {new_epoch}"); ensure( - new_epoch >= self.initial_epoch, + new_epoch >= INITIAL_INCREMENTAL_EPOCH, format!( "unable to rollback to {} before initial epoch {}", - new_epoch, self.initial_epoch + new_epoch, INITIAL_INCREMENTAL_EPOCH ), )?; ensure( @@ -1124,7 +1206,7 @@ where T::Key: ToFromBytea, V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, { - pub(super) wrapped: Arc>>, + pub(super) wrapped: Arc>>, } impl std::fmt::Display for NodeProjection where @@ -1133,81 +1215,150 @@ where V: PayloadInDb, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/Nodes", self.wrapped.lock().unwrap()) + write!(f, "{}/Nodes", self.wrapped.as_ref().blocking_read()) + } +} + +impl NodeProjection +where + T: TreeTopology + DbConnector, + T::Key: ToFromBytea, + V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, +{ + async fn try_fetch_at_incremental_epoch( + &self, + k: &T::Key, + epoch: IncrementalEpoch, + ) -> Result, RyhopeError> { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); + Ok(if epoch == self.wrapped.read().await.current_epoch() { + // Directly returns the value if it is already in cache, fetch it from + // the DB otherwise. + let value = self.wrapped.read().await.nodes_cache.get(k).cloned(); + if let Some(Some(cached_value)) = value { + Some(cached_value.into_value()) + } else if let Some(value) = T::fetch_node_at(db, &table, k, epoch).await? { + self.wrapped + .write() + .await + .nodes_cache + .insert(k.clone(), Some(CachedValue::Read(value.clone()))); + Some(value) + } else { + None + } + } else { + T::fetch_node_at(db, &table, k, epoch).await? + }) } } + impl RoEpochKvStorage for NodeProjection where T: TreeTopology + DbConnector, T::Key: ToFromBytea, V: PayloadInDb, { - delegate::delegate! { - to self.wrapped.lock().unwrap() { - fn initial_epoch(&self) -> Epoch ; - fn current_epoch(&self) -> Epoch ; - async fn size(&self) -> usize; - async fn size_at(&self, epoch: Epoch) -> usize; - } + async fn initial_epoch(&self) -> UserEpoch { + self.wrapped + .read() + .await + .epoch_mapper + .to_user_epoch(INITIAL_INCREMENTAL_EPOCH) + .await as UserEpoch + } + + async fn current_epoch(&self) -> Result { + let inner_epoch = self.wrapped.read().await.current_epoch(); + self.wrapped + .read() + .await + .epoch_mapper + .try_to_user_epoch(inner_epoch) + .await + .ok_or(RyhopeError::CurrenEpochUndefined(inner_epoch)) } - fn try_fetch_at( + async fn size(&self) -> usize { + self.wrapped.read().await.size().await + } + + async fn size_at(&self, epoch: UserEpoch) -> usize { + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await as UserEpoch; + self.wrapped.read().await.size_at(inner_epoch).await + } + + async fn try_fetch_at( &self, k: &T::Key, - epoch: Epoch, - ) -> impl Future, RyhopeError>> + Send { + epoch: UserEpoch, + ) -> Result, RyhopeError> { trace!("[{self}] fetching {k:?}@{epoch}",); - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); - async move { - if epoch == self.current_epoch() { - // Directly returns the value if it is already in cache, fetch it from - // the DB otherwise. - let value = self.wrapped.lock().unwrap().nodes_cache.get(k).cloned(); - Ok(if let Some(Some(cached_value)) = value { - Some(cached_value.into_value()) - } else if let Some(value) = T::fetch_node_at(db, &table, k, epoch).await.unwrap() { - let mut guard = self.wrapped.lock().unwrap(); - guard - .nodes_cache - .insert(k.clone(), Some(CachedValue::Read(value.clone()))); - Some(value) - } else { - None - }) - } else { - T::fetch_node_at(db, &table, k, epoch).await - } + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await; + if let Some(epoch) = inner_epoch { + self.try_fetch_at_incremental_epoch(k, epoch).await + } else { + Ok(None) } } - async fn keys_at(&self, epoch: Epoch) -> Vec { - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); + async fn keys_at(&self, epoch: UserEpoch) -> Vec { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); - T::fetch_all_keys(db, &table, epoch).await.unwrap() + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; + + T::fetch_all_keys(db, &table, inner_epoch).await.unwrap() } - async fn random_key_at(&self, epoch: Epoch) -> Option { - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); + async fn random_key_at(&self, epoch: UserEpoch) -> Option { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); - T::fetch_a_key(db, &table, epoch).await.unwrap() + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; + + T::fetch_a_key(db, &table, inner_epoch).await.unwrap() } - async fn pairs_at(&self, _epoch: Epoch) -> Result, RyhopeError> { + async fn pairs_at(&self, _epoch: UserEpoch) -> Result, RyhopeError> { unimplemented!("should never be used"); } async fn try_fetch(&self, k: &T::Key) -> Result, RyhopeError> { - self.try_fetch_at(k, self.current_epoch()).await + let current_epoch = self.wrapped.read().await.current_epoch(); + self.try_fetch_at_incremental_epoch(k, current_epoch).await } async fn contains(&self, k: &T::Key) -> Result { self.try_fetch(k).await.map(|x| x.is_some()) } - async fn contains_at(&self, k: &T::Key, epoch: Epoch) -> Result { + async fn contains_at(&self, k: &T::Key, epoch: UserEpoch) -> Result { self.try_fetch_at(k, epoch).await.map(|x| x.is_some()) } } @@ -1218,48 +1369,34 @@ where T::Node: Sync + Clone, V: PayloadInDb, { - delegate::delegate! { - to self.wrapped.lock().unwrap() { - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError>; - } - } - - fn remove(&mut self, k: T::Key) -> impl Future> + Send { + async fn remove(&mut self, k: T::Key) -> Result<(), RyhopeError> { trace!("[{self}] removing {k:?} from cache",); - self.wrapped.lock().unwrap().nodes_cache.insert(k, None); - async { Ok(()) } + self.wrapped.write().await.nodes_cache.insert(k, None); + Ok(()) } - fn update( - &mut self, - k: T::Key, - new_value: T::Node, - ) -> impl Future> + Send { + async fn update(&mut self, k: T::Key, new_value: T::Node) -> Result<(), RyhopeError> { trace!("[{self}] updating cache {k:?} -> {new_value:?}"); // If the operation is already present from a read, replace it with the // new value. self.wrapped - .lock() - .unwrap() + .write() + .await .nodes_cache .insert(k, Some(CachedValue::Written(new_value))); - async { Ok(()) } + Ok(()) } - fn store( - &mut self, - k: T::Key, - value: T::Node, - ) -> impl Future> + Send { + async fn store(&mut self, k: T::Key, value: T::Node) -> Result<(), RyhopeError> { trace!("[{self}] storing {k:?} -> {value:?} in cache"); // If the operation is already present from a read, replace it with the // new value. self.wrapped - .lock() - .unwrap() + .write() + .await .nodes_cache .insert(k, Some(CachedValue::Written(value))); - async { Ok(()) } + Ok(()) } async fn update_with( @@ -1277,6 +1414,23 @@ where Ok(()) } } + + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; + self.wrapped.write().await.rollback_to(inner_epoch).await + } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { + let inner_epoch = self.wrapped.read().await.current_epoch(); + ensure(inner_epoch > 0, "cannot rollback past the initial epoch")?; + self.wrapped.write().await.rollback_to(inner_epoch).await + } } /// A wrapper around a [`CachedDbTreeStore`] to make it appear as a KV store for @@ -1291,7 +1445,7 @@ where T::Key: ToFromBytea, V: Debug + Clone + Send + Sync + Serialize + for<'a> Deserialize<'a>, { - pub(super) wrapped: Arc>>, + pub(super) wrapped: Arc>>, } impl std::fmt::Display for PayloadProjection where @@ -1300,75 +1454,148 @@ where V: PayloadInDb, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}/Payload", self.wrapped.lock().unwrap()) + write!(f, "{}/Payload", self.wrapped.blocking_read()) } } -impl RoEpochKvStorage for PayloadProjection +impl PayloadProjection where T: TreeTopology + DbConnector, T::Key: ToFromBytea, V: PayloadInDb, { - delegate::delegate! { - to self.wrapped.lock().unwrap() { - fn initial_epoch(&self) -> Epoch ; - fn current_epoch(&self) -> Epoch ; - async fn size(&self) -> usize ; - async fn size_at(&self, epoch: Epoch) -> usize ; - } - } - - fn try_fetch_at( + async fn try_fetch_at_incremental_epoch( &self, k: &T::Key, - epoch: Epoch, - ) -> impl Future, RyhopeError>> + Send { - trace!("[{self}] attempting to fetch payload for {k:?}@{epoch}"); - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); - async move { - if epoch == self.current_epoch() { - // Directly returns the value if it is already in cache, fetch it from - // the DB otherwise. - let value = self.wrapped.lock().unwrap().payload_cache.get(k).cloned(); - if let Some(Some(cached_value)) = value { - Ok(Some(cached_value.into_value())) - } else if let Some(value) = T::fetch_payload_at(db, &table, k, epoch).await.unwrap() - { - let mut guard = self.wrapped.lock().unwrap(); - guard - .payload_cache - .insert(k.clone(), Some(CachedValue::Read(value.clone()))); - Ok(Some(value)) - } else { - Ok(None) - } + epoch: IncrementalEpoch, + ) -> Result, RyhopeError> { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); + Ok(if epoch == self.wrapped.read().await.current_epoch() { + // Directly returns the value if it is already in cache, fetch it from + // the DB otherwise. + let value = self.wrapped.read().await.payload_cache.get(k).cloned(); + if let Some(Some(cached_value)) = value { + Some(cached_value.into_value()) + } else if let Some(value) = T::fetch_payload_at(db, &table, k, epoch).await? { + self.wrapped + .write() + .await + .payload_cache + .insert(k.clone(), Some(CachedValue::Read(value.clone()))); + Some(value) } else { - T::fetch_payload_at(db, &table, k, epoch).await + None } + } else { + T::fetch_payload_at(db, &table, k, epoch).await? + }) + } +} + +impl RoEpochKvStorage for PayloadProjection +where + T: TreeTopology + DbConnector, + T::Key: ToFromBytea, + V: PayloadInDb, +{ + async fn initial_epoch(&self) -> UserEpoch { + self.wrapped + .read() + .await + .epoch_mapper + .to_user_epoch(INITIAL_INCREMENTAL_EPOCH) + .await as UserEpoch + } + + async fn current_epoch(&self) -> Result { + let inner_epoch = self.wrapped.read().await.current_epoch(); + self.wrapped + .read() + .await + .epoch_mapper + .try_to_user_epoch(inner_epoch as IncrementalEpoch) + .await + .ok_or(RyhopeError::CurrenEpochUndefined(inner_epoch)) + } + + async fn size(&self) -> usize { + self.wrapped.read().await.size().await + } + + async fn size_at(&self, epoch: UserEpoch) -> usize { + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await as UserEpoch; + self.wrapped.read().await.size_at(inner_epoch).await + } + + async fn try_fetch_at(&self, k: &T::Key, epoch: UserEpoch) -> Result, RyhopeError> { + trace!("[{self}] attempting to fetch payload for {k:?}@{epoch}"); + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .try_to_incremental_epoch(epoch) + .await; + if let Some(epoch) = inner_epoch { + self.try_fetch_at_incremental_epoch(k, epoch).await + } else { + Ok(None) } } - async fn keys_at(&self, epoch: Epoch) -> Vec { - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); + async fn try_fetch(&self, k: &T::Key) -> Result, RyhopeError> { + let current_epoch = self.wrapped.read().await.current_epoch(); + self.try_fetch_at_incremental_epoch(k, current_epoch).await + } + + async fn keys_at(&self, epoch: UserEpoch) -> Vec { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); + + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; - T::fetch_all_keys(db, &table, epoch).await.unwrap() + T::fetch_all_keys(db, &table, inner_epoch).await.unwrap() } - async fn random_key_at(&self, epoch: Epoch) -> Option { - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); + async fn random_key_at(&self, epoch: UserEpoch) -> Option { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; - T::fetch_a_key(db, &table, epoch).await.unwrap() + T::fetch_a_key(db, &table, inner_epoch).await.unwrap() } - async fn pairs_at(&self, epoch: Epoch) -> Result, RyhopeError> { - let db = self.wrapped.lock().unwrap().db.clone(); - let table = self.wrapped.lock().unwrap().table.to_owned(); + async fn pairs_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { + let db = self.wrapped.read().await.db.clone(); + let table = self.wrapped.read().await.table.to_owned(); + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; - T::fetch_all_pairs(db, &table, epoch).await + T::fetch_all_pairs(db, &table, inner_epoch).await } } impl EpochKvStorage for PayloadProjection @@ -1378,47 +1605,50 @@ where T::Node: Sync + Clone, V: PayloadInDb, { - delegate::delegate! { - to self.wrapped.lock().unwrap() { - async fn rollback_to(&mut self, epoch: Epoch) -> Result<(), RyhopeError>; - } - } - - fn remove(&mut self, k: T::Key) -> impl Future> + Send { + async fn remove(&mut self, k: T::Key) -> Result<(), RyhopeError> { trace!("[{self}] removing {k:?} from cache"); - self.wrapped.lock().unwrap().nodes_cache.insert(k, None); - async { Ok(()) } + self.wrapped.write().await.nodes_cache.insert(k, None); + Ok(()) } - fn update( - &mut self, - k: T::Key, - new_value: V, - ) -> impl Future> + Send { + async fn update(&mut self, k: T::Key, new_value: V) -> Result<(), RyhopeError> { trace!("[{self}] updating cache {k:?} -> {new_value:?}"); // If the operation is already present from a read, replace it with the // new value. self.wrapped - .lock() - .unwrap() + .write() + .await .payload_cache .insert(k, Some(CachedValue::Written(new_value))); - async { Ok(()) } + Ok(()) } - fn store( - &mut self, - k: T::Key, - value: V, - ) -> impl Future> + Send { + async fn store(&mut self, k: T::Key, value: V) -> Result<(), RyhopeError> { trace!("[{self}] storing {k:?} -> {value:?} in cache",); // If the operation is already present from a read, replace it with the // new value. self.wrapped - .lock() - .unwrap() + .write() + .await .payload_cache .insert(k, Some(CachedValue::Written(value))); - async { Ok(()) } + Ok(()) + } + + async fn rollback_to(&mut self, epoch: UserEpoch) -> Result<(), RyhopeError> { + let inner_epoch = self + .wrapped + .read() + .await + .epoch_mapper + .to_incremental_epoch(epoch) + .await; + self.wrapped.write().await.rollback_to(inner_epoch).await + } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { + let inner_epoch = self.wrapped.read().await.current_epoch(); + ensure(inner_epoch > 0, "cannot rollback past the initial epoch")?; + self.wrapped.write().await.rollback_to(inner_epoch).await } } diff --git a/ryhope/src/storage/pgsql/wide_lineage.sql b/ryhope/src/storage/pgsql/wide_lineage.sql index 699d0093d..091478224 100644 --- a/ryhope/src/storage/pgsql/wide_lineage.sql +++ b/ryhope/src/storage/pgsql/wide_lineage.sql @@ -80,11 +80,16 @@ WITH RECURSIVE descendance (is_core, {KEY}, {PARENT}, {LEFT_CHILD}, {RIGHT_CHILD MAX(is_core) AS is_core, -- Expand the epoch ranges [[{VALID_FROM}, {VALID_UNTIL}]] into -- ({VALID_UNTIL} - {VALID_FROM}) individual rows, clamped within - -- [[min_block, max_block]] - generate_series(GREATEST({VALID_FROM}, $1), LEAST({VALID_UNTIL}, $2)) AS {EPOCH}, + -- [[min_epoch, max_epoch]] + generate_series(GREATEST({VALID_FROM}, mapper_range.min_epoch), LEAST({VALID_UNTIL}, mapper_range.max_epoch)) AS {EPOCH}, -- Normal columns {KEY}, {PARENT}, {LEFT_CHILD}, {RIGHT_CHILD}, {SUBTREE_SIZE}, {PAYLOAD} FROM - descendance + descendance JOIN ( + SELECT MIN({INCREMENTAL_EPOCH}) as min_epoch, MAX({INCREMENTAL_EPOCH}) as max_epoch + FROM {mapper_table_name} + WHERE {USER_EPOCH} >= $1 AND {USER_EPOCH} <= $2 + ) AS mapper_range + ON {VALID_FROM} <= mapper_range.max_epoch AND {VALID_UNTIL} >= mapper_range.min_epoch -- Results must be deduplicated according to this tuple of attributes - GROUP BY (is_core, {KEY}, {PARENT}, {LEFT_CHILD}, {RIGHT_CHILD}, {SUBTREE_SIZE}, {VALID_FROM}, {VALID_UNTIL}, {PAYLOAD}) + GROUP BY (is_core, {KEY}, {PARENT}, {LEFT_CHILD}, {RIGHT_CHILD}, {SUBTREE_SIZE}, {VALID_FROM}, {VALID_UNTIL}, min_epoch, max_epoch, {PAYLOAD}) diff --git a/ryhope/src/storage/tests.rs b/ryhope/src/storage/tests.rs index 6ed519e2d..005b476b4 100644 --- a/ryhope/src/storage/tests.rs +++ b/ryhope/src/storage/tests.rs @@ -13,14 +13,16 @@ use crate::{ storage::{ memory::InMemory, pgsql::{PgsqlStorage, SqlServerConnection, SqlStorageSettings}, - EpochKvStorage, PayloadStorage, RoEpochKvStorage, SqlTreeTransactionalStorage, TreeStorage, + EpochKvStorage, EpochMapper, PayloadStorage, RoEpochKvStorage, SqlTreeTransactionalStorage, + TreeStorage, }, tree::{ - sbbst::{self, Tree}, + sbbst, scapegoat::{self, Alpha}, PrintableTree, TreeTopology, }, - Epoch, InitSettings, MerkleTreeKvDb, NodePayload, EPOCH, KEY, VALID_FROM, VALID_UNTIL, + IncrementalEpoch, InitSettings, MerkleTreeKvDb, NodePayload, UserEpoch, EPOCH, KEY, VALID_FROM, + VALID_UNTIL, }; use super::TreeTransactionalStorage; @@ -33,12 +35,12 @@ impl NodePayload for usize {} impl NodePayload for String {} impl NodePayload for i64 {} -async fn _storage_in_memory(initial_epoch: Epoch) -> Result<()> { +async fn _storage_in_memory(initial_epoch: UserEpoch) -> Result<()> { type K = String; type V = usize; type TestTree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new( InitSettings::ResetAt(scapegoat::Tree::empty(Alpha::new(0.8)), initial_epoch), @@ -96,12 +98,12 @@ async fn shifted_storage_in_memory() -> Result<()> { _storage_in_memory(388).await } -async fn _storage_in_pgsql(initial_epoch: Epoch) -> Result<()> { +async fn _storage_in_pgsql(initial_epoch: UserEpoch) -> Result<()> { type K = String; type V = usize; type TestTree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let table = format!("simple_{}", initial_epoch); let mut s = MerkleTreeKvDb::::new( @@ -109,6 +111,7 @@ async fn _storage_in_pgsql(initial_epoch: Epoch) -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: table.clone(), + external_mapper: None, }, ) .await?; @@ -121,6 +124,7 @@ async fn _storage_in_pgsql(initial_epoch: Epoch) -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table, + external_mapper: None, }, ) .await?; @@ -255,15 +259,16 @@ impl From<&str> for ShaizedString { #[tokio::test] async fn sbbst_storage_in_pgsql() -> Result<()> { type V = ShaizedString; - type TestTree = sbbst::Tree; - type SqlStorage = PgsqlStorage; - type RamStorage = InMemory; + type TestTree = sbbst::IncrementalTree; + type SqlStorage = PgsqlStorage; + type RamStorage = InMemory; let mut s_psql = MerkleTreeKvDb::::new( - InitSettings::Reset(sbbst::Tree::empty()), + InitSettings::Reset(TestTree::empty()), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "simple_sbbst".to_string(), + external_mapper: None, }, ) .await?; @@ -299,6 +304,7 @@ async fn sbbst_storage_in_pgsql() -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "simple_sbbst".to_string(), + external_mapper: None, }, ) .await?; @@ -316,11 +322,9 @@ async fn sbbst_storage_in_pgsql() -> Result<()> { s_psql.diff_at(i).await?.unwrap().print(); } - let mut s_ram = MerkleTreeKvDb::::new( - InitSettings::Reset(sbbst::Tree::empty()), - (), - ) - .await?; + let mut s_ram = + MerkleTreeKvDb::::new(InitSettings::Reset(TestTree::empty()), ()) + .await?; s_ram .in_transaction(|t| { Box::pin(async { @@ -418,7 +422,7 @@ async fn hashes() -> Result<()> { type V = ShaizedString; type Tree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new( InitSettings::ResetAt(Tree::empty(Alpha::fully_balanced()), 392), @@ -458,7 +462,7 @@ async fn hashes_pgsql() -> Result<()> { type V = ShaizedString; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; { let mut s = MerkleTreeKvDb::::new( @@ -466,6 +470,7 @@ async fn hashes_pgsql() -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "test_hashes".into(), + external_mapper: None, }, ) .await?; @@ -501,6 +506,7 @@ async fn hashes_pgsql() -> Result<()> { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "test_hashes".into(), + external_mapper: None, }, ) .await?; @@ -528,11 +534,11 @@ async fn hashes_pgsql() -> Result<()> { } #[tokio::test] -async fn sbbst_requires_sequential_keys() -> Result<()> { - type Tree = sbbst::Tree; +async fn incremental_sbbst_requires_sequential_keys() -> Result<()> { + type Tree = sbbst::IncrementalTree; type V = i64; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::with_shift_and_capacity(10, 0)), @@ -550,18 +556,176 @@ async fn sbbst_requires_sequential_keys() -> Result<()> { Ok(()) } +#[tokio::test] +async fn epoch_sbbst_can_use_non_sequential_keys() -> Result<()> { + type Tree = sbbst::EpochTree; + type V = i64; + + type Storage = InMemory; + + let mut s = MerkleTreeKvDb::::new( + InitSettings::Reset(Tree::with_shift_and_capacity(10, 0)), + (), + ) + .await?; + + s.start_transaction().await?; + assert!(s.store(2, 2).await.is_err()); // try insert key smaller than initial shift + assert!(s.store(12, 2).await.is_ok()); + assert!(s.store(11, 2).await.is_err()); // try insert key smaller than previous one + assert!(s.store(14, 2).await.is_ok()); + assert!(s.store(15, 2).await.is_ok()); + s.commit_transaction().await?; + + // check that values have been inserted + assert_eq!(s.try_fetch(&12).await?.unwrap(), 2); + assert_eq!(s.try_fetch(&14).await?.unwrap(), 2); + assert_eq!(s.try_fetch(&15).await?.unwrap(), 2); + + // chekc that 11 has not been inserted + assert!(s.try_fetch(&11).await?.is_none()); + Ok(()) +} + +#[tokio::test] +async fn epoch_sbbst_over_pgsql_with_non_sequential_keys() -> Result<()> { + type Tree = sbbst::EpochTree; + type V = i64; + + type Storage = PgsqlStorage; + + let mut s = MerkleTreeKvDb::::new( + InitSettings::Reset(Tree::with_shift_and_capacity(10, 0)), + SqlStorageSettings { + table: "epoch_sbbst".to_string(), + source: SqlServerConnection::NewConnection(db_url()), + external_mapper: None, + }, + ) + .await?; + + s.start_transaction().await?; + assert!(s.store(2, 2).await.is_err()); // try insert key smaller than initial shift + assert!(s.store(12, 2).await.is_ok()); + assert!(s.store(11, 2).await.is_err()); // try insert key smaller than previous one + s.commit_transaction().await?; + + // start a new transaction + s.start_transaction().await?; + assert!(s.store(14, 2).await.is_ok()); + s.commit_transaction().await?; + + // check that values have been inserted + assert_eq!(s.try_fetch(&12).await?.unwrap(), 2); + assert_eq!(s.try_fetch(&14).await?.unwrap(), 2); + + // check that 11 has not been inserted + assert!(s.try_fetch(&11).await?.is_none()); + + assert_eq!(s.storage.epoch_mapper().to_incremental_epoch(12).await, 1); + assert_eq!(s.storage.epoch_mapper().to_incremental_epoch(14).await, 2); + assert!(s + .storage + .epoch_mapper() + .try_to_incremental_epoch(11) + .await + .is_none()); + + Ok(()) +} + +#[tokio::test] +async fn test_caching_mechanism() -> Result<()> { + const MAX_ENTRIES: usize = 10; + const INITIAL_EPOCH: UserEpoch = 4; + + // test that we never erase from an `InMemoryEpochMapper` + let mut epoch_mapper = crate::storage::memory::InMemoryEpochMapper::new_at(INITIAL_EPOCH); + + for i in 0..2 * MAX_ENTRIES { + epoch_mapper + .add_epoch_map(INITIAL_EPOCH + i as UserEpoch, i as IncrementalEpoch) + .await?; + } + + assert_eq!(epoch_mapper.initial_epoch(), INITIAL_EPOCH); + for i in 0..2 * MAX_ENTRIES { + // check that no epoch has been erased from the storage + assert!(epoch_mapper + .try_to_incremental_epoch(INITIAL_EPOCH + i as UserEpoch) + .await + .is_some()) + } + + // test that epochs are not erased from cache if we insert them sequentially + let mut epoch_cache = + crate::storage::memory::EpochMapperCache::::new_at(INITIAL_EPOCH); + + for i in 0..2 * MAX_ENTRIES { + epoch_cache + .add_epoch_map(INITIAL_EPOCH + i as UserEpoch, i as IncrementalEpoch) + .await?; + } + + assert_eq!(epoch_cache.initial_epoch(), INITIAL_EPOCH); + println!("{}", epoch_cache.last_epoch()); + for i in 0..2 * MAX_ENTRIES { + // check that no epoch has been erased from the storage + assert!( + epoch_cache + .try_to_incremental_epoch(INITIAL_EPOCH + i as UserEpoch) + .await + .is_some(), + "failed for epoch {i}" + ); + } + + // now, insert epochs not sequentially, and test that epochs starts to be erased + for i in 0..MAX_ENTRIES { + epoch_cache + .add_epoch_map( + (3 * MAX_ENTRIES as UserEpoch + INITIAL_EPOCH) * (i + 1) as UserEpoch, + (2 * MAX_ENTRIES + i) as IncrementalEpoch, + ) + .await?; + } + + assert_eq!(epoch_cache.initial_epoch(), INITIAL_EPOCH); + // count number of epochs still in the storage + let mut num_epochs = 0; + for i in 0..2 * MAX_ENTRIES { + num_epochs += epoch_cache + .try_to_incremental_epoch(INITIAL_EPOCH + i as UserEpoch) + .await + .is_some() as usize; + } + for i in 0..MAX_ENTRIES { + num_epochs += epoch_cache + .try_to_incremental_epoch( + (3 * MAX_ENTRIES as UserEpoch + INITIAL_EPOCH) * (i + 1) as UserEpoch, + ) + .await + .is_some() as usize; + } + + assert_eq!(num_epochs, MAX_ENTRIES); + + Ok(()) +} + #[tokio::test] async fn thousand_rows() -> Result<()> { type K = i64; type V = usize; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::fully_balanced())), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "thousand".to_string(), + external_mapper: None, }, ) .await?; @@ -612,14 +776,13 @@ async fn thousand_rows() -> Result<()> { #[tokio::test] async fn aggregation_memory() -> Result<()> { - type Tree = sbbst::Tree; + type Tree = sbbst::IncrementalTree; type V = MinMaxi64; - type Storage = InMemory; + type Storage = InMemory; let mut s = - MerkleTreeKvDb::::new(InitSettings::Reset(sbbst::Tree::empty()), ()) - .await?; + MerkleTreeKvDb::::new(InitSettings::Reset(Tree::empty()), ()).await?; s.in_transaction(|s| { Box::pin(async { @@ -645,15 +808,16 @@ async fn aggregation_memory() -> Result<()> { #[tokio::test] async fn aggregation_pgsql() -> Result<()> { - type Tree = sbbst::Tree; + type Tree = sbbst::IncrementalTree; type V = MinMaxi64; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::ResetAt(Tree::empty(), 32), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "agg".to_string(), + external_mapper: None, }, ) .await?; @@ -685,7 +849,7 @@ async fn test_rollback< S: EpochKvStorage + TreeTransactionalStorage + Send + Sync, >( s: &mut S, - initial_epoch: Epoch, + initial_epoch: UserEpoch, ) { for i in 0..3 { s.in_transaction(|s| { @@ -699,7 +863,7 @@ async fn test_rollback< .unwrap(); } - assert_eq!(s.current_epoch(), 3 + initial_epoch); + assert_eq!(s.current_epoch().await.unwrap(), 3 + initial_epoch); assert_eq!(s.size().await, 6); for i in 0..=5 { assert!(s.contains(&i.into()).await.unwrap()); @@ -709,7 +873,7 @@ async fn test_rollback< s.rollback_to(1 + initial_epoch) .await .unwrap_or_else(|_| panic!("failed to rollback to {}", 1 + initial_epoch)); - assert_eq!(s.current_epoch(), 1 + initial_epoch); + assert_eq!(s.current_epoch().await.unwrap(), 1 + initial_epoch); assert_eq!(s.size().await, 2); for i in 0..=5 { if i <= 1 { @@ -721,13 +885,15 @@ async fn test_rollback< // rollback once to reach to epoch 0 s.rollback().await.unwrap(); - assert_eq!(s.current_epoch(), initial_epoch); + println!("Rollbacked to initial epoch"); + assert_eq!(s.current_epoch().await.unwrap(), initial_epoch); assert_eq!(s.size().await, 0); for i in 0..=5 { assert!(!s.contains(&i.into()).await.unwrap()); } // Can not rollback before epoch 0 + println!("Rolling back before initial epoch"); assert!(s.rollback().await.is_err()); } @@ -737,7 +903,7 @@ async fn rollback_memory() { type V = MinMaxi64; type Tree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::new(0.7))), (), @@ -754,9 +920,9 @@ async fn rollback_memory_at() { type V = MinMaxi64; type Tree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; - const INITIAL_EPOCH: Epoch = 4875; + const INITIAL_EPOCH: UserEpoch = 4875; let mut s = MerkleTreeKvDb::::new( InitSettings::ResetAt(Tree::empty(Alpha::new(0.7)), INITIAL_EPOCH), (), @@ -773,12 +939,13 @@ async fn rollback_psql() { type V = MinMaxi64; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::new(0.7))), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "rollback".to_string(), + external_mapper: None, }, ) .await @@ -793,13 +960,14 @@ async fn rollback_psql_at() { type V = MinMaxi64; type Tree = scapegoat::Tree; - const INITIAL_EPOCH: Epoch = 4875; - type Storage = PgsqlStorage; + const INITIAL_EPOCH: UserEpoch = 4875; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::ResetAt(Tree::empty(Alpha::new(0.7)), INITIAL_EPOCH), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "rollback_at".to_string(), + external_mapper: None, }, ) .await @@ -810,9 +978,9 @@ async fn rollback_psql_at() { #[tokio::test] async fn context_at() { - type Tree = sbbst::Tree; + type Tree = sbbst::IncrementalTree; type V = MinMaxi64; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new(InitSettings::Reset(Tree::empty()), ()) .await .unwrap(); @@ -862,7 +1030,7 @@ async fn initial_state() { type K = i64; type V = MinMaxi64; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; // Create an empty tree { @@ -871,6 +1039,7 @@ async fn initial_state() { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "empty_tree".to_string(), + external_mapper: None, }, ) .await @@ -883,6 +1052,7 @@ async fn initial_state() { SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "empty_tree".to_string(), + external_mapper: None, }, ) .await @@ -897,9 +1067,9 @@ async fn initial_state() { #[tokio::test] async fn dirties() { - type Tree = sbbst::Tree; + type Tree = sbbst::IncrementalTree; type V = MinMaxi64; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new(InitSettings::Reset(Tree::empty()), ()) .await .unwrap(); @@ -947,16 +1117,17 @@ async fn grouped_txs() -> Result<()> { type K = i64; type V = MinMaxi64; - type SbbstTree = sbbst::Tree; - type SbbstStorage = PgsqlStorage; + type SbbstTree = sbbst::EpochTree; + type SbbstStorage = PgsqlStorage; type ScapeTree = scapegoat::Tree; - type ScapeStorage = PgsqlStorage; + type ScapeStorage = PgsqlStorage; let mut t1 = MerkleTreeKvDb::::new( - InitSettings::Reset(Tree::empty()), + InitSettings::Reset(SbbstTree::empty()), SqlStorageSettings { table: "nested_sbbst".into(), source: SqlServerConnection::Pool(db_pool.clone()), + external_mapper: None, }, ) .await @@ -967,6 +1138,7 @@ async fn grouped_txs() -> Result<()> { SqlStorageSettings { table: "nested_scape".into(), source: SqlServerConnection::Pool(db_pool.clone()), + external_mapper: Some("nested_sbbst".into()), }, ) .await @@ -983,7 +1155,6 @@ async fn grouped_txs() -> Result<()> { t2.start_transaction().await?; t1.store(1, 456.into()).await?; - t1.store(2, 789.into()).await?; t2.store(8786384, 456.into()).await?; t2.store(4, 329.into()).await?; @@ -997,14 +1168,14 @@ async fn grouped_txs() -> Result<()> { tx.commit().await?; - t1.commit_success(); - t2.commit_success(); + t1.commit_success().await; + t2.commit_success().await; // The commited root must be equal to its in-flight snapshot let commited_root = t1.root().await.unwrap().unwrap(); assert_eq!(commited_root, in_flight_root); // Sizes must have been commited coorectly - assert_eq!(t1.size().await, 2); + assert_eq!(t1.size().await, 1); assert_eq!(t2.size().await, 3); assert!(t2.try_fetch(&4).await.unwrap().is_some()); @@ -1016,7 +1187,6 @@ async fn grouped_txs() -> Result<()> { t2.start_transaction().await?; t1.store(3, 456.into()).await?; - t1.store(4, 789.into()).await?; t2.store(578943, 542.into()).await?; t2.store(943, commited_root.into()).await?; @@ -1025,11 +1195,11 @@ async fn grouped_txs() -> Result<()> { t2.commit_in(&mut tx).await?; tx.rollback().await?; - t1.commit_failed(); - t2.commit_failed(); + t1.commit_failed().await; + t2.commit_failed().await; // Size should not have changed - assert_eq!(t1.size().await, 2); + assert_eq!(t1.size().await, 1); assert_eq!(t2.size().await, 3); // Old data must still be there @@ -1048,13 +1218,14 @@ async fn fetch_many() { type K = String; type V = usize; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::never_balanced())), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "many".to_string(), + external_mapper: None, }, ) .await @@ -1089,17 +1260,17 @@ async fn fetch_many() { let many = s .try_fetch_many_at([ // OK - (1i64, "restera".to_string()), + (1i64 as UserEpoch, "restera".to_string()), // OK - (2i64, "restera".to_string()), + (2i64 as UserEpoch, "restera".to_string()), // non-existing epoch - (4i64, "restera".to_string()), + (4i64 as UserEpoch, "restera".to_string()), // does not exist yet - (1i64, "car".to_string()), + (1i64 as UserEpoch, "car".to_string()), // OK - (2i64, "car".to_string()), + (2i64 as UserEpoch, "car".to_string()), // non-existing key - (1i64, "meumeu".to_string()), + (1i64 as UserEpoch, "meumeu".to_string()), ]) .await .unwrap() @@ -1111,12 +1282,9 @@ async fn fetch_many() { assert_eq!( many, [ - (1i64, "restera".to_string(), 12), - (2i64, "restera".to_string(), 12), - (2i64, "car".to_string(), 0), - // This should not exist, but as we use infinity to mark alive - // nodes, it will still appear - (4i64, "restera".to_string(), 12), + (1i64 as UserEpoch, "restera".to_string(), 12), + (2i64 as UserEpoch, "restera".to_string(), 12), + (2i64 as UserEpoch, "car".to_string(), 0), ] .into_iter() .collect::>() @@ -1128,13 +1296,14 @@ async fn wide_update_trees() { type K = String; type V = usize; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::never_balanced())), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "wide".to_string(), + external_mapper: None, }, ) .await @@ -1189,13 +1358,14 @@ async fn all_pgsql() { type K = String; type V = usize; type Tree = scapegoat::Tree; - type Storage = PgsqlStorage; + type Storage = PgsqlStorage; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::never_balanced())), SqlStorageSettings { source: SqlServerConnection::NewConnection(db_url()), table: "fetch_all".to_string(), + external_mapper: None, }, ) .await @@ -1261,7 +1431,7 @@ async fn all_memory() { type K = String; type V = usize; type Tree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; let mut s = MerkleTreeKvDb::::new( InitSettings::Reset(Tree::empty(Alpha::never_balanced())), diff --git a/ryhope/src/storage/updatetree.rs b/ryhope/src/storage/updatetree.rs index 72051e148..338f63df4 100644 --- a/ryhope/src/storage/updatetree.rs +++ b/ryhope/src/storage/updatetree.rs @@ -1,7 +1,7 @@ use crate::{ error::RyhopeError, tree::{NodeContext, TreeTopology}, - Epoch, + UserEpoch, }; use futures::{future::BoxFuture, FutureExt}; use serde::{Deserialize, Serialize}; @@ -18,7 +18,7 @@ use super::TreeStorage; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UpdateTree { /// The epoch stemming from the application of this update tree - epoch: Epoch, + epoch: UserEpoch, /// An arena-like storage of all the nodes in the tree nodes: Vec>, /// key -> arena index mapping @@ -36,13 +36,18 @@ pub struct UpdateTreeNode { /// Whether this node is a leaf of an update path is_path_end: bool, } + impl UpdateTreeNode { fn is_leaf(&self) -> bool { self.children.is_empty() } + + pub fn k(&self) -> K { + self.k.clone() + } } -impl UpdateTree { +impl UpdateTree { pub fn root(&self) -> &K { &self.nodes[0].k } @@ -64,11 +69,36 @@ impl UpdateTree { pub fn nodes(&self) -> impl Iterator { self.nodes.iter().map(|n| &n.k) } + pub fn get_node(&self, key: &K) -> Option<&UpdateTreeNode> { + self.idx.get(key).map(|idx| self.node(*idx)) + } + pub fn get_node_mut(&mut self, key: &K) -> Option<&mut UpdateTreeNode> { + let idx = self.idx.get(key).cloned(); + idx.map(|index| self.node_mut(index)) + } + + pub fn get_child_keys(&self, node: &UpdateTreeNode) -> Vec { + node.children + .iter() + .map(|idx| self.node(*idx).k()) + .collect() + } + + pub fn get_parent_key(&self, key: &K) -> Option { + let idx = self.idx.get(key); + if let Some(&idx) = idx { + self.nodes[idx] + .parent + .map(|parent_idx| self.nodes[parent_idx].k()) + } else { + None + } + } } impl UpdateTree { /// Create an empty `UpdateTree`. - fn empty(epoch: Epoch) -> Self { + fn empty(epoch: UserEpoch) -> Self { Self { epoch, nodes: Vec::new(), @@ -77,7 +107,7 @@ impl UpdateTree { } /// Instantiate a new `UpdateTree` containing all the provided paths. - pub fn from_paths>>(paths: I, epoch: Epoch) -> Self { + pub fn from_paths>>(paths: I, epoch: UserEpoch) -> Self { let mut paths = paths.into_iter(); if let Some(path) = paths.next() { let mut r = Self::from_path(path, epoch); @@ -92,7 +122,7 @@ impl UpdateTree { /// Instantiate a new `UpdateTree` from a seminal path from the root to a /// node. - pub fn from_path(mut path: Vec, epoch: Epoch) -> Self { + pub fn from_path(mut path: Vec, epoch: UserEpoch) -> Self { path.reverse(); if let Some(root_k) = path.pop() { let mut tree = UpdateTree { @@ -162,7 +192,7 @@ impl UpdateTree { } /// Return the epoch generated by this tree. - pub fn epoch(&self) -> Epoch { + pub fn epoch(&self) -> UserEpoch { self.epoch } @@ -277,7 +307,7 @@ impl UpdateTree { pub async fn from_tree + Sync, S: TreeStorage>( t: &T, s: &S, - epoch: Epoch, + epoch: UserEpoch, ) -> Self { let mut r = Self::empty(epoch); if let Some(root) = t.root(s).await.unwrap() { @@ -293,7 +323,7 @@ impl UpdateTree { /// /// This method assumes that the given map correctly encodes a binary tree /// and will not perform any check. - pub fn from_map(epoch: Epoch, root: &K, nodes: &HashMap>) -> Self { + pub fn from_map(epoch: UserEpoch, root: &K, nodes: &HashMap>) -> Self { let mut r = Self::empty(epoch); r.rec_from_map(root, nodes, None); r @@ -428,6 +458,11 @@ impl UpdatePlan { &self.t } + /// Return a mutable reference to the tree this plan is built around. + pub fn tree_mut(&mut self) -> &mut UpdateTree { + &mut self.t + } + /// Mark the given item as having been completed. Its dependent will not be /// generated by the iterator until the item has been marked as completed. pub fn done(&mut self, item: &WorkplanItem) -> Result<(), RyhopeError> { @@ -621,7 +656,10 @@ mod tests { #[tokio::test] async fn from_tree() { let t = sbbst::Tree; - let storage = InMemory::::new(sbbst::Tree::with_capacity(10)); + let storage = InMemory::::new_with_epoch( + sbbst::IncrementalTree::with_capacity(10), + 0, + ); let ut = UpdateTree::from_tree(&t, &storage, 1).await; ut.print(); } diff --git a/ryhope/src/storage/view.rs b/ryhope/src/storage/view.rs index f5395679f..5b9950333 100644 --- a/ryhope/src/storage/view.rs +++ b/ryhope/src/storage/view.rs @@ -1,10 +1,10 @@ //! This module offers facilities to “time-travel”, i.e. access the successive //! states of a tree at given epochs. -use std::{collections::HashMap, fmt::Debug, marker::PhantomData}; +use std::{collections::HashMap, fmt::Debug, future::Future, marker::PhantomData}; use serde::{Deserialize, Serialize}; -use crate::{error::RyhopeError, tree::TreeTopology, Epoch}; +use crate::{error::RyhopeError, tree::TreeTopology, UserEpoch}; use super::{EpochKvStorage, EpochStorage, RoEpochKvStorage, TransactionalStorage, TreeStorage}; @@ -17,7 +17,7 @@ pub struct StorageView< /// The wrapped [`EpochStorage`] &'s S, /// The target epoch - Epoch, + UserEpoch, /// [ignore] PhantomData, ); @@ -29,7 +29,7 @@ impl< where T: Send, { - fn start_transaction(&mut self) -> Result<(), RyhopeError> { + async fn start_transaction(&mut self) -> Result<(), RyhopeError> { unimplemented!("storage views are read only") } @@ -45,11 +45,11 @@ impl< where T: Send, { - fn current_epoch(&self) -> Epoch { - self.1 + async fn current_epoch(&self) -> Result { + Ok(self.1) } - async fn fetch_at(&self, epoch: Epoch) -> Result { + async fn fetch_at(&self, epoch: UserEpoch) -> Result { if epoch != self.1 { unimplemented!( "this storage view is locked at {}; {epoch} unreachable", @@ -68,7 +68,11 @@ where unimplemented!("storage views are read only") } - async fn rollback_to(&mut self, _epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, _epoch: UserEpoch) -> Result<(), RyhopeError> { + unimplemented!("storage views are read only") + } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { unimplemented!("storage views are read only") } } @@ -78,7 +82,7 @@ pub struct KvStorageAt<'a, T: TreeTopology, S: RoEpochKvStorage /// The wrapped [`RoEpochKvStorage`] wrapped: &'a S, /// The epoch at which the wrapped storage is being looked at - current_epoch: Epoch, + current_epoch: UserEpoch, /// [ignore] _p: PhantomData, } @@ -86,15 +90,19 @@ pub struct KvStorageAt<'a, T: TreeTopology, S: RoEpochKvStorage impl + Sync> RoEpochKvStorage for KvStorageAt<'_, T, S> { - fn initial_epoch(&self) -> Epoch { + fn initial_epoch(&self) -> impl Future + Send { self.wrapped.initial_epoch() } - fn current_epoch(&self) -> Epoch { - self.current_epoch + async fn current_epoch(&self) -> Result { + Ok(self.current_epoch) } - async fn try_fetch_at(&self, k: &T::Key, epoch: Epoch) -> Result, RyhopeError> { + async fn try_fetch_at( + &self, + k: &T::Key, + epoch: UserEpoch, + ) -> Result, RyhopeError> { if epoch > self.current_epoch { unimplemented!( "this storage view is locked at {}; {epoch} unreachable", @@ -105,19 +113,27 @@ impl + Sync> RoEpochKvStor } } - async fn size_at(&self, epoch: Epoch) -> usize { + async fn try_fetch(&self, k: &T::Key) -> Result, RyhopeError> { + self.wrapped.try_fetch_at(k, self.current_epoch).await + } + + async fn size(&self) -> usize { + self.wrapped.size_at(self.current_epoch).await + } + + async fn size_at(&self, epoch: UserEpoch) -> usize { self.wrapped.size_at(epoch).await } - async fn keys_at(&self, epoch: Epoch) -> Vec { + async fn keys_at(&self, epoch: UserEpoch) -> Vec { self.wrapped.keys_at(epoch).await } - async fn random_key_at(&self, epoch: Epoch) -> Option { + async fn random_key_at(&self, epoch: UserEpoch) -> Option { self.wrapped.random_key_at(epoch).await } - async fn pairs_at(&self, epoch: Epoch) -> Result, RyhopeError> { + async fn pairs_at(&self, epoch: UserEpoch) -> Result, RyhopeError> { if epoch > self.current_epoch { unimplemented!( "this storage view is locked at {}; {epoch} unreachable", @@ -144,7 +160,11 @@ impl + Sync> EpochKvStorag unimplemented!("storage views are read only") } - async fn rollback_to(&mut self, _epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, _epoch: UserEpoch) -> Result<(), RyhopeError> { + unimplemented!("storage views are read only") + } + + async fn rollback(&mut self) -> Result<(), RyhopeError> { unimplemented!("storage views are read only") } } @@ -154,17 +174,18 @@ pub struct TreeStorageView<'a, T: TreeTopology, S: TreeStorage> { /// The wrapped [`TreeStorage`] pub wrapped: &'a S, /// The target epoch - pub epoch: Epoch, + pub epoch: UserEpoch, /// A wrapper over the state storage of `wrapped` pub state: StorageView<'a, T::State, S::StateStorage>, /// A wrapper over the node storage of `wrapped` pub nodes: KvStorageAt<'a, T, S::NodeStorage>, + epoch_mapper: &'a S::EpochMapper, /// [ignore] pub _t: PhantomData, } impl<'a, T: TreeTopology + 'a, S: TreeStorage + 'a> TreeStorageView<'a, T, S> { /// Create a new view on `s` locked at `epoch`. - pub fn new(s: &'a S, epoch: Epoch) -> Self { + pub fn new(s: &'a S, epoch: UserEpoch) -> Self { Self { wrapped: s, epoch, @@ -174,6 +195,7 @@ impl<'a, T: TreeTopology + 'a, S: TreeStorage + 'a> TreeStorageView<'a, T, S> current_epoch: epoch, _p: PhantomData, }, + epoch_mapper: s.epoch_mapper(), _t: PhantomData, } } @@ -186,6 +208,7 @@ where { type StateStorage = StorageView<'a, T::State, S::StateStorage>; type NodeStorage = KvStorageAt<'a, T, S::NodeStorage>; + type EpochMapper = S::EpochMapper; fn state(&self) -> &Self::StateStorage { &self.state @@ -203,11 +226,19 @@ where unimplemented!("storage views are read only") } - async fn born_at(&self, epoch: Epoch) -> Vec { + async fn born_at(&self, epoch: UserEpoch) -> Vec { self.wrapped.born_at(epoch).await } - async fn rollback_to(&mut self, _epoch: Epoch) -> Result<(), RyhopeError> { + async fn rollback_to(&mut self, _epoch: UserEpoch) -> Result<(), RyhopeError> { + unimplemented!("storage views are read only") + } + + fn epoch_mapper(&self) -> &Self::EpochMapper { + self.epoch_mapper + } + + fn epoch_mapper_mut(&mut self) -> &mut Self::EpochMapper { unimplemented!("storage views are read only") } } diff --git a/ryhope/src/tests/example.rs b/ryhope/src/tests/example.rs index e38705216..1015fe6a1 100644 --- a/ryhope/src/tests/example.rs +++ b/ryhope/src/tests/example.rs @@ -13,7 +13,7 @@ async fn run() -> Result<()> { type V = usize; type RowTree = scapegoat::Tree; - type Storage = InMemory; + type Storage = InMemory; let mut tree = MerkleTreeKvDb::::new( InitSettings::Reset(scapegoat::Tree::empty(Alpha::new(0.5))), (), @@ -23,7 +23,7 @@ async fn run() -> Result<()> { println!("Insertion of some (key,value) pairs"); println!( "Current version of the tree before insertion: {}", - tree.current_epoch() + tree.current_epoch().await.unwrap() ); let res = tree @@ -35,10 +35,10 @@ async fn run() -> Result<()> { .await .expect("this should work"); - let first_stamp = tree.current_epoch(); + let first_stamp = tree.current_epoch().await?; println!( "Current version of the tree after insertion: {}", - tree.current_epoch() + first_stamp ); println!("Tree of keys to update:"); @@ -77,7 +77,7 @@ async fn run() -> Result<()> { } // Printing the tree at its previous versions - println!("tree at {} is now:", tree.current_epoch()); + println!("tree at {} is now:", tree.current_epoch().await?); tree.tree().print(&tree.storage).await; println!("tree at epoch {first_stamp} was:"); @@ -88,14 +88,10 @@ async fn run() -> Result<()> { "The update tree from {first_stamp} to {} was:", first_stamp + 1 ); - tree.diff_at(first_stamp + 1) - .await - .unwrap() - .unwrap() - .print(); + tree.diff_at(first_stamp + 1).await?.unwrap().print(); println!("The update tree from 0 to 1 was:",); - tree.diff_at(1).await.unwrap().unwrap().print(); + tree.diff_at(1).await?.unwrap().print(); Ok(()) } diff --git a/ryhope/src/tests/trees.rs b/ryhope/src/tests/trees.rs index c2a62bd42..642eb2fca 100644 --- a/ryhope/src/tests/trees.rs +++ b/ryhope/src/tests/trees.rs @@ -5,10 +5,16 @@ mod sbbst { tree::{sbbst, MutableTree, TreeTopology}, }; - fn sbbst_in_memory(shift: usize, n: usize) -> (sbbst::Tree, InMemory) { + fn sbbst_in_memory( + shift: usize, + n: usize, + ) -> ( + sbbst::IncrementalTree, + InMemory, + ) { ( - sbbst::Tree, - InMemory::new(sbbst::Tree::with_shift_and_capacity(shift, n)), + sbbst::IncrementalTree::default(), + InMemory::new_with_epoch(sbbst::IncrementalTree::with_shift_and_capacity(shift, n), 0), ) } @@ -47,7 +53,7 @@ mod sbbst { let (mut t, mut s) = sbbst_in_memory(1000, 6); assert_eq!(t.size(&s).await.unwrap(), 6); - s.start_transaction().unwrap(); + s.start_transaction().await.unwrap(); t.insert(1007, &mut s).await.unwrap(); s.commit_transaction().await.unwrap(); assert_eq!(t.size(&s).await.unwrap(), 7); @@ -89,8 +95,11 @@ mod scapegoat { + Send, >( a: Alpha, - ) -> (scapegoat::Tree, InMemory, ()>) { - (Default::default(), InMemory::new(scapegoat::Tree::empty(a))) + ) -> (scapegoat::Tree, InMemory, (), false>) { + ( + Default::default(), + InMemory::new_with_epoch(scapegoat::Tree::empty(a), 0), + ) } #[tokio::test] @@ -101,7 +110,7 @@ mod scapegoat { assert_eq!(t.size(&s).await.unwrap(), 0); - s.start_transaction()?; + s.start_transaction().await?; t.insert("adsfda".into(), &mut s).await?; assert_eq!(t.size(&s).await.unwrap(), 1); @@ -129,8 +138,8 @@ mod scapegoat { let (mut bbst, mut bs) = scapegaot_in_memory::(Alpha::fully_balanced()); let (mut list, mut ls) = scapegaot_in_memory::(Alpha::never_balanced()); - bs.start_transaction().unwrap(); - ls.start_transaction().unwrap(); + bs.start_transaction().await.unwrap(); + ls.start_transaction().await.unwrap(); for i in 0..128 { bbst.insert(i, &mut bs).await.unwrap(); list.insert(i, &mut ls).await.unwrap(); @@ -149,7 +158,7 @@ mod scapegoat { let (mut t, mut s) = scapegaot_in_memory::(Alpha::new(0.5)); - s.start_transaction()?; + s.start_transaction().await?; for i in 0..20 { t.insert("A".repeat(i), &mut s).await.unwrap(); t.print(&s).await; diff --git a/ryhope/src/tree/sbbst.rs b/ryhope/src/tree/sbbst.rs index 881414e79..c68747538 100644 --- a/ryhope/src/tree/sbbst.rs +++ b/ryhope/src/tree/sbbst.rs @@ -51,12 +51,13 @@ //! parent = parent(s_tree, parent) use super::{MutableTree, NodeContext, NodePath, TreeTopology}; use crate::{ - error::RyhopeError, - storage::{EpochKvStorage, EpochStorage, TreeStorage}, + error::{ensure, RyhopeError}, + storage::{EpochKvStorage, EpochMapper, EpochStorage, TreeStorage}, tree::PrintableTree, + IncrementalEpoch, UserEpoch, }; use serde::{Deserialize, Serialize}; -use std::collections::HashSet; +use std::{collections::HashSet, future::Future}; /// Represents a user-facing index, in the shift+1..max range. pub type NodeIdx = usize; @@ -111,19 +112,31 @@ pub struct State { impl State { pub fn root(&self) -> NodeIdx { - self.outer_root() + self.outer_idx(self.inner_root()).0 } - pub fn ascendance>(&self, ns: I) -> HashSet { + async fn root_with_mapper(&self, mapper: &M) -> NodeIdx { + self.outer_root(mapper).await + } + + pub async fn ascendance>(&self, ns: I) -> HashSet { + self.ascendance_with_mapper(ns, self).await + } + + async fn ascendance_with_mapper, M: IndexMapper>( + &self, + ns: I, + mapper: &M, + ) -> HashSet { let mut ascendance = HashSet::new(); let inner_max = self.inner_max(); for n in ns.into_iter() { - let inner_idx = self.inner_idx(n); + let inner_idx = mapper.to_inner_idx(OuterIdx(n)).await; if inner_idx <= inner_max { if let Some(lineage) = self.lineage_inner(&inner_idx) { for n in lineage.into_full_path() { if n <= inner_max { - ascendance.insert(self.outer_idx(n)); + ascendance.insert(mapper.to_outer_idx(n).await.0); } } } @@ -133,8 +146,12 @@ impl State { ascendance } - pub fn parent(&self, n: NodeIdx) -> Option { - let n = self.inner_idx(n); + pub async fn parent(&self, n: NodeIdx) -> Option { + self.parent_with_mapper(n, self).await + } + + async fn parent_with_mapper(&self, n: NodeIdx, mapper: &M) -> Option { + let n = mapper.to_inner_idx(OuterIdx(n)).await; if n > self.inner_max() { panic!("{n:?} not in tree"); } @@ -148,18 +165,26 @@ impl State { parent = parent_in_saturated(parent); } - Some(self.outer_idx(parent)) + Some(mapper.to_outer_idx(parent).await.0) + } + + pub async fn lineage(&self, n: &NodeIdx) -> Option> { + self.lineage_with_mapper(n, self).await } - pub fn lineage(&self, n: &NodeIdx) -> Option> { - if let Some(lineage_inner) = self.lineage_inner(&self.inner_idx(*n)) { + async fn lineage_with_mapper( + &self, + n: &NodeIdx, + mapper: &M, + ) -> Option> { + if let Some(lineage_inner) = self.lineage_inner(&mapper.to_inner_idx(OuterIdx(*n)).await) { let mut ascendance = vec![]; for n in lineage_inner.ascendance { - ascendance.push(self.outer_idx(n)); + ascendance.push(mapper.to_outer_idx(n).await.0); } Some(NodePath { ascendance, - target: self.outer_idx(lineage_inner.target), + target: mapper.to_outer_idx(lineage_inner.target).await.0, }) } else { None @@ -167,14 +192,29 @@ impl State { } pub fn node_context(&self, k: &NodeIdx) -> Option> { - if let Some(inner) = self.node_context_inner(&self.inner_idx(*k)) { - let parent_outer = inner.parent.map(|parent| self.outer_idx(parent)); + // Not a simple call to `node_context_with_mapper` since we need a non-async version + // to be employed in circuits + self.node_context_inner(&self.inner_idx(OuterIdx(*k))) + .map(|inner| NodeContext { + node_id: self.outer_idx(inner.node_id).0, + parent: inner.parent.map(|idx| self.outer_idx(idx).0), + left: inner.left.map(|idx| self.outer_idx(idx).0), + right: inner.right.map(|idx| self.outer_idx(idx).0), + }) + } - let left_outer = inner.left.map(|left| self.outer_idx(left)); - let right_outer = inner.right.map(|right| self.outer_idx(right)); + async fn node_context_with_mapper( + &self, + k: &NodeIdx, + mapper: &M, + ) -> Option> { + if let Some(inner) = self.node_context_inner(&mapper.to_inner_idx(OuterIdx(*k)).await) { + let parent_outer = mapper.to_outer_idx_map(inner.parent).await.map(|idx| idx.0); + let left_outer = mapper.to_outer_idx_map(inner.left).await.map(|idx| idx.0); + let right_outer = mapper.to_outer_idx_map(inner.right).await.map(|idx| idx.0); Some(NodeContext { - node_id: self.outer_idx(inner.node_id), + node_id: mapper.to_outer_idx(inner.node_id).await.0, parent: parent_outer, left: left_outer, right: right_outer, @@ -184,9 +224,20 @@ impl State { } } - pub fn children(&self, n: &NodeIdx) -> Option<(Option, Option)> { - if let Some((l, r)) = self.children_inner(&self.inner_idx(*n)) { - Some((l.map(|l| self.outer_idx(l)), r.map(|r| self.outer_idx(r)))) + pub async fn children(&self, n: &NodeIdx) -> Option<(Option, Option)> { + self.children_with_mapper(n, self).await + } + + async fn children_with_mapper( + &self, + n: &NodeIdx, + mapper: &M, + ) -> Option<(Option, Option)> { + if let Some((l, r)) = self.children_inner(&mapper.to_inner_idx(OuterIdx(*n)).await) { + Some(( + mapper.to_outer_idx_map(l).await.map(|idx| idx.0), + mapper.to_outer_idx_map(r).await.map(|idx| idx.0), + )) } else { None } @@ -204,17 +255,10 @@ impl State { 0 }) } - /// Re-shift an index from the canonical range to the actual one - fn outer_idx(&self, n: InnerIdx) -> NodeIdx { - (n + self.shift).0 - } + /// Return the root of the tree, as a shifted node index. - fn outer_root(&self) -> NodeIdx { - self.outer_idx(self.inner_root()) - } - /// Un-shift an index into the canonical range - fn inner_idx(&self, n: NodeIdx) -> InnerIdx { - InnerIdx(n - self.shift) + async fn outer_root(&self, mapper: &M) -> NodeIdx { + mapper.to_outer_idx(self.inner_root()).await.0 } fn parent_inner(&self, n: InnerIdx) -> Option { @@ -300,11 +344,70 @@ impl State { None } } + + fn inner_idx(&self, outer_idx: OuterIdx) -> InnerIdx { + InnerIdx(outer_idx.0 - self.shift) + } + + fn outer_idx(&self, inner_idx: InnerIdx) -> OuterIdx { + OuterIdx((inner_idx + self.shift).0) + } +} + +trait IndexMapper: Sized + Send + Sync + Clone { + fn to_inner_idx(&self, outer_idx: OuterIdx) -> impl Future + Send; + + fn to_outer_idx(&self, inner_idx: InnerIdx) -> impl Future + Send; + + /// Apply `to_outer_idx` to `inner_idx` if it is `Some`, otherwise return `None`. + fn to_outer_idx_map( + &self, + inner_idx: Option, + ) -> impl Future> + Send { + async move { + match inner_idx { + Some(inner_idx) => Some(self.to_outer_idx(inner_idx).await), + None => None, + } + } + } +} + +impl IndexMapper for T { + async fn to_inner_idx(&self, outer_idx: OuterIdx) -> InnerIdx { + InnerIdx( + self.to_incremental_epoch(outer_idx.0 as UserEpoch) + .await + .try_into() + .unwrap(), + ) + } + + async fn to_outer_idx(&self, inner_idx: InnerIdx) -> OuterIdx { + OuterIdx(self.to_user_epoch(inner_idx.0 as IncrementalEpoch).await as usize) + } +} + +impl IndexMapper for State { + async fn to_inner_idx(&self, outer_idx: OuterIdx) -> InnerIdx { + self.inner_idx(outer_idx) + } + + async fn to_outer_idx(&self, inner_idx: InnerIdx) -> OuterIdx { + self.outer_idx(inner_idx) + } } #[derive(Default)] -pub struct Tree; -impl Tree { +pub struct Tree; + +/// Type alias to represent a generic sbbst with incremental keys +pub type IncrementalTree = Tree; +/// Type alias to represent a generic sbbst with monotonically increasing keys being +/// used as epochs of the storage +pub type EpochTree = Tree; + +impl Tree { pub fn empty() -> State { State { max: InnerIdx(0), @@ -332,9 +435,37 @@ impl Tree { shift: 0, } } + + async fn to_inner_idx>( + &self, + s: &S, + state: &State, + n: OuterIdx, + ) -> InnerIdx { + if IS_EPOCH_TREE { + s.epoch_mapper().to_inner_idx(n).await + } else { + state.to_inner_idx(n).await + } + } + + async fn to_outer_idx>( + &self, + s: &S, + state: &State, + n: InnerIdx, + ) -> OuterIdx { + if IS_EPOCH_TREE { + s.epoch_mapper().to_outer_idx(n).await + } else { + state.to_outer_idx(n).await + } + } } -async fn shift>(s: &S) -> Result { +async fn shift>>( + s: &S, +) -> Result { s.state().fetch().await.map(|s| s.shift) } @@ -371,50 +502,75 @@ fn children_inner_in_saturated(n: &InnerIdx) -> Option<(InnerIdx, InnerIdx)> { Some((maybe_left, maybe_right)) } -impl TreeTopology for Tree { +impl TreeTopology for Tree { /// Max, shift type State = State; type Key = NodeIdx; type Node = (); - async fn size>(&self, s: &S) -> Result { + async fn size>(&self, s: &S) -> Result { s.state().fetch().await.map(|s| s.inner_max().0) } - async fn ascendance, I: IntoIterator>( + async fn ascendance, I: IntoIterator>( &self, ns: I, s: &S, ) -> Result, RyhopeError> { - s.state().fetch().await.map(|s| s.ascendance(ns)) + let state = s.state().fetch().await?; + Ok(if IS_EPOCH_TREE { + state.ascendance_with_mapper(ns, s.epoch_mapper()).await + } else { + state.ascendance(ns).await + }) } - async fn root>(&self, s: &S) -> Result, RyhopeError> { - s.state().fetch().await.map(|s| Some(s.root())) + async fn root>(&self, s: &S) -> Result, RyhopeError> { + let state = s.state().fetch().await?; + Ok(Some(if IS_EPOCH_TREE { + state.root_with_mapper(s.epoch_mapper()).await + } else { + state.root() + })) } - async fn parent>( + async fn parent>( &self, n: NodeIdx, s: &S, ) -> Result, RyhopeError> { - s.state().fetch().await.map(|s| s.parent(n)) + let state = s.state().fetch().await?; + Ok(if IS_EPOCH_TREE { + state.parent_with_mapper(n, s.epoch_mapper()).await + } else { + state.parent(n).await + }) } - async fn lineage>( + async fn lineage>( &self, n: &NodeIdx, s: &S, ) -> Result>, RyhopeError> { - s.state().fetch().await.map(|s| s.lineage(n)) + let state = s.state().fetch().await?; + Ok(if IS_EPOCH_TREE { + state.lineage_with_mapper(n, s.epoch_mapper()).await + } else { + state.lineage(n).await + }) } - async fn children>( + async fn children>( &self, n: &NodeIdx, s: &S, ) -> Result, Option)>, RyhopeError> { - s.state().fetch().await.map(|s| s.children(n)) + let state = s.state().fetch().await?; + Ok(if IS_EPOCH_TREE { + state.children_with_mapper(n, s.epoch_mapper()).await + } else { + state.children(n).await + }) } async fn node_context>( @@ -422,24 +578,27 @@ impl TreeTopology for Tree { k: &NodeIdx, s: &S, ) -> Result>, RyhopeError> { - s.state().fetch().await.map(|s| s.node_context(k)) + let state = s.state().fetch().await?; + Ok(if IS_EPOCH_TREE { + state.node_context_with_mapper(k, s.epoch_mapper()).await + } else { + state.node_context(k) + }) } - async fn contains>( + async fn contains>( &self, k: &NodeIdx, s: &S, ) -> Result { - s.state() - .fetch() - .await - .map(|s| s.inner_idx(*k) <= s.inner_max()) + let state = s.state().fetch().await?; + Ok(self.to_inner_idx(s, &state, OuterIdx(*k)).await <= state.inner_max()) } } -impl MutableTree for Tree { +impl MutableTree for Tree { // The SBBST only support appending exactly after the current largest key. - async fn insert>( + async fn insert>( &mut self, k: NodeIdx, s: &mut S, @@ -454,22 +613,42 @@ impl MutableTree for Tree { )?; let state = s.state().fetch().await?; - if state.inner_idx(k) != state.inner_max() + 1 { - return Err(RyhopeError::fatal(format!( - "invalid insert in SBBST: trying to insert {}, but next insert should be {} (shift = {})", - k, - state.outer_idx(state.inner_max() +1), - state.shift, - ))); + // compute the inner key of the next item to be inserted + let expected_inner_k = state.inner_max() + 1; + if IS_EPOCH_TREE { + // we need to check that k >= last epoch inserted + let max_outer = s.epoch_mapper().to_outer_idx(state.inner_max()).await; + ensure( + max_outer <= OuterIdx(k), + format!( + "Trying to insert an epoch {k} smaller than a previous inserted epoch {}", + max_outer.0 + ), + )?; + // in this case, k must be mapped to `expected_inner_k` in the epoch mapper + s.epoch_mapper_mut() + .add_epoch_map(k as UserEpoch, expected_inner_k.0 as IncrementalEpoch) + .await?; } else { - s.state_mut().update(|state| state.max += 1).await?; + // in this case, we need to check that the inner key corresponding to k + // is equal to `expected_inner_k` + let inner_k = state.to_inner_idx(OuterIdx(k)).await; + ensure(inner_k == expected_inner_k, + format!( + "invalid insert in SBBST: trying to insert {}, but next insert should be {} (shift = {})", + k, + state.to_outer_idx(expected_inner_k).await.0, + state.shift, + ), + )?; } + s.state_mut().update(|state| state.max += 1).await?; s.nodes_mut().store(k, ()).await?; Ok(self.lineage(&k, s).await?.unwrap()) } - async fn delete>( + async fn delete>( &mut self, _k: &NodeIdx, _: &mut S, @@ -478,8 +657,8 @@ impl MutableTree for Tree { } } -impl PrintableTree for Tree { - async fn tree_to_string>(&self, s: &S) -> String { +impl PrintableTree for Tree { + async fn tree_to_string>(&self, s: &S) -> String { let mut r = String::new(); let state = s.state().fetch().await.unwrap(); @@ -490,7 +669,11 @@ impl PrintableTree for Tree { let maybe_left = rank * (1 << (layer + 1)) + (1 << layer); if maybe_left <= state.inner_max().0 { let n = InnerIdx(maybe_left); - r.push_str(&format!("{}{}", state.outer_idx(n), spacing)) + r.push_str(&format!( + "{}{}", + self.to_outer_idx(s, &state, n).await.0, + spacing + )) } } r.push('\n'); diff --git a/ryhope/src/tree/scapegoat.rs b/ryhope/src/tree/scapegoat.rs index b97165277..4ff0cea9c 100644 --- a/ryhope/src/tree/scapegoat.rs +++ b/ryhope/src/tree/scapegoat.rs @@ -312,7 +312,7 @@ impl Deserialize } } } else { - return Err(RyhopeError::fatal("the tree is empty")); + Err(RyhopeError::fatal("the tree is empty")) } } diff --git a/verifiable-db/Cargo.toml b/verifiable-db/Cargo.toml index 8b50d33d5..aa0a77052 100644 --- a/verifiable-db/Cargo.toml +++ b/verifiable-db/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +num.workspace = true alloy.workspace = true anyhow.workspace = true bincode.workspace = true @@ -27,6 +28,7 @@ futures.workspace = true rand.workspace = true serial_test.workspace = true tokio.workspace = true +mp2_v1 = { path = "../mp2-v1" } [features] original_poseidon = ["mp2_common/original_poseidon"] diff --git a/verifiable-db/src/block_tree/api.rs b/verifiable-db/src/block_tree/api.rs index 33f5c6e54..642426912 100644 --- a/verifiable-db/src/block_tree/api.rs +++ b/verifiable-db/src/block_tree/api.rs @@ -3,6 +3,7 @@ use crate::extraction::{ExtractionPI, ExtractionPIWrap}; use super::{ + empty::{EmptyCircuit, RecursiveEmptyInput, RecursiveEmptyWires}, leaf::{LeafCircuit, RecursiveLeafInput, RecursiveLeafWires}, membership::{MembershipCircuit, MembershipWires}, parent::{ParentCircuit, RecursiveParentInput, RecursiveParentWires}, @@ -40,6 +41,10 @@ pub enum CircuitInput { witness: MembershipCircuit, right_child_proof: Vec, }, + Empty { + witness: EmptyCircuit, + extraction_proof: Vec, + }, } impl CircuitInput { @@ -104,6 +109,22 @@ impl CircuitInput { right_child_proof, } } + + /// Create a circuit input for proving an "empty" node. So we prove a node that contained no values + /// so it isn't added to the tree. Used to not break the IVC proof. + pub fn new_empty( + index_identifier: u64, + tree_hash: &HashOutput, + extraction_proof: Vec, + ) -> Self { + CircuitInput::Empty { + witness: EmptyCircuit { + index_identifier: F::from_canonical_u64(index_identifier), + h_old: HashOut::::from_bytes(tree_hash.into()), + }, + extraction_proof, + } + } } /// Main struct holding the different circuit parameters for each of the circuits defined here. @@ -116,14 +137,15 @@ where leaf: CircuitWithUniversalVerifier>, parent: CircuitWithUniversalVerifier>, membership: CircuitWithUniversalVerifier, + empty: CircuitWithUniversalVerifier>, set: RecursiveCircuits, } const BLOCK_INDEX_IO_LEN: usize = PublicInputs::::TOTAL_LEN; /// Number of circuits in the set -/// 1 leaf + 1 parent + 1 membership -const CIRCUIT_SET_SIZE: usize = 3; +/// 1 leaf + 1 parent + 1 membership + 1 empty +const CIRCUIT_SET_SIZE: usize = 4; impl PublicParameters where @@ -146,12 +168,14 @@ where let leaf = builder.build_circuit((extraction_set.clone(), rows_tree_set.clone())); let parent = builder.build_circuit((extraction_set.clone(), rows_tree_set.clone())); let membership = builder.build_circuit(()); + let empty = builder.build_circuit(extraction_set.clone()); // Build the circuit set. let circuits = vec![ prepare_recursive_circuit_for_circuit_set(&leaf), prepare_recursive_circuit_for_circuit_set(&parent), prepare_recursive_circuit_for_circuit_set(&membership), + prepare_recursive_circuit_for_circuit_set(&empty), ]; let set = RecursiveCircuits::::new(circuits); @@ -159,6 +183,7 @@ where leaf, parent, membership, + empty, set, } } @@ -204,6 +229,10 @@ where witness, right_child_proof, } => self.generate_membership_proof(witness, right_child_proof), + CircuitInput::Empty { + witness, + extraction_proof, + } => self.genererate_empty_proof(witness, extraction_proof, extraction_set), } } @@ -262,6 +291,24 @@ where .generate_proof(&self.membership, [child_proof], [&child_vk], witness)?; ProofWithVK::from((proof, self.membership.circuit_data().verifier_only.clone())).serialize() } + + fn genererate_empty_proof( + &self, + witness: EmptyCircuit, + extraction_proof: Vec, + extraction_set: &RecursiveCircuits, + ) -> Result> { + let extraction_proof = ProofWithVK::deserialize(&extraction_proof)?; + + let empty = RecursiveEmptyInput { + witness, + extraction_proof, + extraction_set: extraction_set.clone(), + }; + + let proof = self.set.generate_proof(&self.empty, [], [], empty)?; + ProofWithVK::from((proof, self.empty.circuit_data().verifier_only.clone())).serialize() + } } #[cfg(test)] @@ -274,7 +321,10 @@ mod tests { *, }; use crate::{ - block_tree::leaf::tests::{compute_expected_hash, compute_expected_set_digest}, + block_tree::{ + compute_final_digest, + leaf::tests::{compute_expected_hash, compute_expected_set_digest}, + }, extraction, row_tree, }; use mp2_common::{ @@ -288,13 +338,12 @@ mod tests { iop::target::Target, plonk::config::Hasher, }; - use plonky2_ecgfp5::curve::curve::Point; use rand::{rngs::ThreadRng, thread_rng, Rng}; use recursion_framework::framework_testing::TestingRecursiveCircuits; use std::iter; const EXTRACTION_IO_LEN: usize = extraction::test::PublicInputs::::TOTAL_LEN; - const ROWS_TREE_IO_LEN: usize = row_tree::PublicInputs::::TOTAL_LEN; + const ROWS_TREE_IO_LEN: usize = row_tree::PublicInputs::::total_len(); struct TestBuilder where @@ -346,10 +395,9 @@ mod tests { fn generate_rows_tree_proof( &self, rng: &mut ThreadRng, - row_digest: &[F], is_merge_case: bool, ) -> Result { - let pi = random_rows_tree_pi(rng, row_digest, is_merge_case); + let pi = random_rows_tree_pi(rng, is_merge_case); let proof = self .rows_tree_set @@ -358,20 +406,23 @@ mod tests { Ok(ProofWithVK::from((proof[0].clone(), vk))) } + fn generate_leaf_proof( &self, rng: &mut ThreadRng, block_id: F, block_number: U256, ) -> Result { - let row_digest = Point::sample(rng).to_weierstrass().to_fields(); + let rows_tree_proof = self.generate_rows_tree_proof(rng, true)?; + let rows_tree_pi = + row_tree::PublicInputs::from_slice(&rows_tree_proof.proof.public_inputs); + let final_digest = compute_final_digest(true, &rows_tree_pi) + .to_weierstrass() + .to_fields(); let extraction_proof = - self.generate_extraction_proof(rng, block_number, &row_digest, true)?; - let rows_tree_proof = self.generate_rows_tree_proof(rng, &row_digest, true)?; + self.generate_extraction_proof(rng, block_number, &final_digest, true)?; let extraction_pi = extraction::test::PublicInputs::from_slice(&extraction_proof.proof.public_inputs); - let rows_tree_pi = - row_tree::PublicInputs::from_slice(&rows_tree_proof.proof.public_inputs); let input = CircuitInput::new_leaf( block_id.to_canonical_u64(), @@ -438,8 +489,12 @@ mod tests { } // Check new node digest { - let exp_digest = - compute_expected_set_digest(block_id, block_number.to_vec(), rows_tree_pi); + let exp_digest = compute_expected_set_digest( + true, + block_id, + block_number.to_vec(), + rows_tree_pi, + ); assert_eq!(pi.new_value_set_digest_point(), exp_digest.to_weierstrass()); } @@ -458,14 +513,16 @@ mod tests { left_child: HashOut, right_child: HashOut, ) -> Result { - let row_digest = Point::sample(rng).to_weierstrass().to_fields(); + let rows_tree_proof = self.generate_rows_tree_proof(rng, false)?; + let rows_tree_pi = + row_tree::PublicInputs::from_slice(&rows_tree_proof.proof.public_inputs); + let final_digest = compute_final_digest(false, &rows_tree_pi) + .to_weierstrass() + .to_fields(); let extraction_proof = - self.generate_extraction_proof(rng, block_number, &row_digest, false)?; - let rows_tree_proof = self.generate_rows_tree_proof(rng, &row_digest, false)?; + self.generate_extraction_proof(rng, block_number, &final_digest, false)?; let extraction_pi = extraction::test::PublicInputs::from_slice(&extraction_proof.proof.public_inputs); - let rows_tree_pi = - row_tree::PublicInputs::from_slice(&rows_tree_proof.proof.public_inputs); let old_rows_tree_hash = HashOut::from_vec(random_vector::(NUM_HASH_OUT_ELTS).to_fields()); @@ -553,8 +610,12 @@ mod tests { } // Check new node digest { - let exp_digest = - compute_expected_set_digest(block_id, block_number.to_vec(), rows_tree_pi); + let exp_digest = compute_expected_set_digest( + false, + block_id, + block_number.to_vec(), + rows_tree_pi, + ); assert_eq!(pi.new_value_set_digest_point(), exp_digest.to_weierstrass()); } diff --git a/verifiable-db/src/block_tree/empty.rs b/verifiable-db/src/block_tree/empty.rs new file mode 100644 index 000000000..56dc2f133 --- /dev/null +++ b/verifiable-db/src/block_tree/empty.rs @@ -0,0 +1,283 @@ +//! Module with the circuit used when we don't update the Block tree. For instance in the case of Receipts +//! if there are no relevent event logs in a block we still have to advance the IVC proof + +use super::public_inputs::PublicInputs; +use crate::extraction::{ExtractionPI, ExtractionPIWrap}; + +use anyhow::Result; +use mp2_common::{ + default_config, + poseidon::H, + proof::ProofWithVK, + public_inputs::PublicInputCommon, + serialization::{deserialize, serialize}, + types::CBuilder, + u256::CircuitBuilderU256, + utils::ToTargets, + CHasher, C, D, F, +}; +use plonky2::{ + hash::hash_types::{HashOut, HashOutTarget}, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::{circuit_builder::CircuitBuilder, config::Hasher, proof::ProofWithPublicInputsTarget}, +}; + +use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; +use recursion_framework::{ + circuit_builder::CircuitLogicWires, + framework::{ + RecursiveCircuits, RecursiveCircuitsVerifierGagdet, RecursiveCircuitsVerifierTarget, + }, +}; +use serde::{Deserialize, Serialize}; +use std::{iter, marker::PhantomData}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EmptyWires { + /// Identifier of the block number column + pub(crate) index_identifier: Target, + /// The old root of the tree, + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + pub(crate) h_old: HashOutTarget, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EmptyCircuit { + /// Identifier of the block number column + pub(crate) index_identifier: F, + /// The old root of the tree, + pub(crate) h_old: HashOut, +} + +impl EmptyCircuit { + fn build(b: &mut CBuilder, extraction_pi: &[Target]) -> EmptyWires { + let zero_256 = b.zero_u256(); + let curve_zero = b.curve_zero(); + let index_identifier = b.add_virtual_target(); + + let extraction_pi = E::PI::from_slice(extraction_pi); + + let block_number = extraction_pi.primary_index_value(); + + // Compute the hash of table metadata, to be exposed as public input to prove to + // the verifier that we extracted the correct storage slots and we place the data + // in the expected columns of the constructed tree; we add also the identifier + // of the block number column to the table metadata. + // metadata_hash = H(extraction_proof.DM || block_id) + let inputs = extraction_pi + .metadata_set_digest() + .to_targets() + .iter() + .cloned() + .chain(iter::once(index_identifier)) + .collect(); + let metadata_hash = b.hash_n_to_hash_no_pad::(inputs).elements; + + let h_old = b.add_virtual_hash(); + + // Register the public inputs. + PublicInputs::new( + &h_old.to_targets(), + &h_old.to_targets(), + &zero_256.to_targets(), + &zero_256.to_targets(), + &block_number, + &extraction_pi.commitment(), + &extraction_pi.prev_commitment(), + &metadata_hash, + &curve_zero.to_targets(), + ) + .register(b); + + EmptyWires { + index_identifier, + h_old, + } + } + + /// Assign the wires. + fn assign(&self, pw: &mut PartialWitness, wires: &EmptyWires) { + pw.set_target(wires.index_identifier, self.index_identifier); + pw.set_hash_target(wires.h_old, self.h_old); + } +} + +#[derive(Clone, Serialize, Deserialize)] +pub(crate) struct RecursiveEmptyWires { + empty_wires: EmptyWires, + extraction_verifier: RecursiveCircuitsVerifierTarget, + _e: PhantomData, +} + +#[derive(Clone, Debug)] +pub(crate) struct RecursiveEmptyInput { + pub(crate) witness: EmptyCircuit, + pub(crate) extraction_proof: ProofWithVK, + pub(crate) extraction_set: RecursiveCircuits, +} + +impl CircuitLogicWires for RecursiveEmptyWires +where + [(); E::PI::TOTAL_LEN]:, + [(); >::HASH_SIZE]:, +{ + // Final extraction circuit set + rows tree circuit set + type CircuitBuilderParams = RecursiveCircuits; + + type Inputs = RecursiveEmptyInput; + + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + + fn circuit_logic( + builder: &mut CircuitBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; 0], + builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + let extraction_verifier = + RecursiveCircuitsVerifierGagdet::::new( + default_config(), + &builder_parameters, + ); + let extraction_verifier = extraction_verifier.verify_proof_in_circuit_set(builder); + let extraction_pi = + extraction_verifier.get_public_input_targets::(); + + let empty_wires = EmptyCircuit::build::(builder, extraction_pi); + + RecursiveEmptyWires { + empty_wires, + extraction_verifier, + _e: PhantomData, + } + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> Result<()> { + inputs.witness.assign(pw, &self.empty_wires); + + let (proof, vd) = inputs.extraction_proof.into(); + self.extraction_verifier + .set_target(pw, &inputs.extraction_set, &proof, &vd) + } +} + +#[cfg(test)] +mod tests { + use crate::block_tree::{ + leaf::tests::compute_expected_hash, + tests::{TestPIField, TestPITargets}, + }; + + use super::{super::tests::random_extraction_pi, *}; + use alloy::primitives::U256; + use mp2_common::{ + digest::Digest, + utils::{Fieldable, ToFields}, + }; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::random_vector, + }; + use plonky2::{field::types::Field, hash::hash_types::NUM_HASH_OUT_ELTS}; + use rand::{thread_rng, Rng}; + + #[derive(Clone, Debug)] + struct TestEmptyCircuit<'a> { + c: EmptyCircuit, + extraction_pi: &'a [F], + } + + impl UserCircuit for TestEmptyCircuit<'_> { + // Parent node wires + extraction public inputs + type Wires = (EmptyWires, Vec); + + fn build(b: &mut CBuilder) -> Self::Wires { + let extraction_pi = b.add_virtual_targets(TestPITargets::TOTAL_LEN); + + let empty_wires = EmptyCircuit::build::(b, &extraction_pi); + + (empty_wires, extraction_pi) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.c.assign(pw, &wires.0); + + assert_eq!(wires.1.len(), TestPITargets::TOTAL_LEN); + pw.set_target_arr(&wires.1, self.extraction_pi); + } + } + + #[test] + fn test_block_index_parent_circuit() { + test_empty_circuit(); + } + + fn test_empty_circuit() { + let mut rng = thread_rng(); + + let index_identifier = rng.gen::().to_field(); + + let h_old = HashOut::from_vec(random_vector::(NUM_HASH_OUT_ELTS).to_fields()); + + let extraction_pi = + &random_extraction_pi(&mut rng, U256::from(1), &Digest::NEUTRAL.to_fields(), false); + + let test_circuit = TestEmptyCircuit { + c: EmptyCircuit { + index_identifier, + h_old, + }, + extraction_pi, + }; + + let proof = run_circuit::(test_circuit); + let pi = PublicInputs::from_slice(&proof.public_inputs); + let extraction_pi = TestPIField::from_slice(extraction_pi); + + let block_number = extraction_pi.block_number_raw(); + + // Check old hash + { + assert_eq!(pi.h_old, h_old.to_fields()); + } + // Check new hash + { + assert_eq!(pi.h_new, h_old.to_fields()); + } + // Check minimum block number + { + assert_eq!(pi.min, [F::ZERO; 8]); + } + // Check maximum block number + { + assert_eq!(pi.max, [F::ZERO; 8]); + } + // Check block number + { + assert_eq!(pi.block_number, block_number); + } + // Check block hash + { + assert_eq!(pi.block_hash, extraction_pi.block_hash_raw()); + } + // Check previous block hash + { + assert_eq!(pi.prev_block_hash, extraction_pi.prev_block_hash_raw()); + } + // Check metadata hash + { + let exp_hash = compute_expected_hash(&extraction_pi, index_identifier); + + assert_eq!(pi.metadata_hash, exp_hash.elements); + } + // Check new node digest + { + assert_eq!( + pi.new_value_set_digest_point(), + Digest::NEUTRAL.to_weierstrass() + ); + } + } +} diff --git a/verifiable-db/src/block_tree/leaf.rs b/verifiable-db/src/block_tree/leaf.rs index d2e1e055c..809113fe0 100644 --- a/verifiable-db/src/block_tree/leaf.rs +++ b/verifiable-db/src/block_tree/leaf.rs @@ -2,7 +2,7 @@ //! an existing node (or if there is no existing node, which happens for the //! first block number). -use super::{compute_index_digest, public_inputs::PublicInputs}; +use super::{compute_final_digest_target, compute_index_digest, public_inputs::PublicInputs}; use crate::{ extraction::{ExtractionPI, ExtractionPIWrap}, row_tree, @@ -10,7 +10,6 @@ use crate::{ use anyhow::Result; use mp2_common::{ default_config, - group_hashing::CircuitBuilderGroupHashing, poseidon::{empty_poseidon_hash, H}, proof::ProofWithVK, public_inputs::PublicInputCommon, @@ -55,15 +54,12 @@ impl LeafCircuit { let extraction_pi = E::PI::from_slice(extraction_pi); let rows_tree_pi = row_tree::PublicInputs::::from_slice(rows_tree_pi); + let final_digest = compute_final_digest_target::(b, &extraction_pi, &rows_tree_pi); // in our case, the extraction proofs extracts from the blockchain and sets // the block number as the primary index let index_value = extraction_pi.primary_index_value(); - // Enforce that the data extracted from the blockchain is the same as the data - // employed to build the rows tree for this node. - b.connect_curve_points(extraction_pi.value_set_digest(), rows_tree_pi.rows_digest()); - // Compute the hash of table metadata, to be exposed as public input to prove to // the verifier that we extracted the correct storage slots and we place the data // in the expected columns of the constructed tree; we add also the identifier @@ -82,7 +78,7 @@ impl LeafCircuit { let inputs = iter::once(index_identifier) .chain(index_value.iter().cloned()) .collect(); - let node_digest = compute_index_digest(b, inputs, rows_tree_pi.rows_digest()); + let node_digest = compute_index_digest(b, inputs, final_digest); // Compute hash of the inserted node // node_min = block_number @@ -101,12 +97,6 @@ impl LeafCircuit { .collect(); let h_new = b.hash_n_to_hash_no_pad::(inputs).to_targets(); - // check that the rows tree built is for a merged table iff we extract data from MPT for a merged table - b.connect( - rows_tree_pi.is_merge_case().target, - extraction_pi.is_merge_case().target, - ); - // Register the public inputs. PublicInputs::new( &h_new, @@ -170,7 +160,7 @@ where _verified_proofs: [&ProofWithPublicInputsTarget; 0], builder_parameters: Self::CircuitBuilderParams, ) -> Self { - const ROWS_TREE_IO: usize = row_tree::PublicInputs::::TOTAL_LEN; + const ROWS_TREE_IO: usize = row_tree::PublicInputs::::total_len(); let extraction_verifier = RecursiveCircuitsVerifierGagdet::::new( @@ -212,29 +202,24 @@ where #[cfg(test)] pub mod tests { - use crate::{ - block_tree::tests::{TestPIField, TestPITargets}, - extraction, - }; - use super::{ super::tests::{random_extraction_pi, random_rows_tree_pi}, *, }; + use crate::{ + block_tree::{ + compute_final_digest, + tests::{TestPIField, TestPITargets}, + }, + extraction, + }; use alloy::primitives::U256; use mp2_common::{ poseidon::{hash_to_int_value, H}, utils::{Fieldable, ToFields}, }; - use mp2_test::{ - circuit::{run_circuit, UserCircuit}, - utils::weierstrass_to_point, - }; - use plonky2::{ - field::types::{Field, Sample}, - hash::hash_types::HashOut, - plonk::config::Hasher, - }; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{field::types::Field, hash::hash_types::HashOut, plonk::config::Hasher}; use plonky2_ecgfp5::curve::{curve::Point, scalar_field::Scalar}; use rand::{thread_rng, Rng}; @@ -252,6 +237,7 @@ pub mod tests { } pub fn compute_expected_set_digest( + is_merge_case: bool, identifier: F, value: Vec, rows_tree_pi: row_tree::PublicInputs, @@ -262,8 +248,7 @@ pub mod tests { let hash = H::hash_no_pad(&inputs); let int = hash_to_int_value(hash); let scalar = Scalar::from_noncanonical_biguint(int); - let point = rows_tree_pi.rows_digest_field(); - let point = weierstrass_to_point(&point); + let point = compute_final_digest(is_merge_case, &rows_tree_pi); point * scalar } #[derive(Clone, Debug)] @@ -279,7 +264,7 @@ pub mod tests { fn build(b: &mut CBuilder) -> Self::Wires { let extraction_pi = b.add_virtual_targets(TestPITargets::TOTAL_LEN); - let rows_tree_pi = b.add_virtual_targets(row_tree::PublicInputs::::TOTAL_LEN); + let rows_tree_pi = b.add_virtual_targets(row_tree::PublicInputs::::total_len()); let leaf_wires = LeafCircuit::build::(b, &extraction_pi, &rows_tree_pi); @@ -292,21 +277,34 @@ pub mod tests { assert_eq!(wires.1.len(), TestPITargets::TOTAL_LEN); pw.set_target_arr(&wires.1, self.extraction_pi); - assert_eq!(wires.2.len(), row_tree::PublicInputs::::TOTAL_LEN); + assert_eq!(wires.2.len(), row_tree::PublicInputs::::total_len()); pw.set_target_arr(&wires.2, self.rows_tree_pi); } } #[test] fn test_block_index_leaf_circuit() { + test_leaf_circuit(true); + test_leaf_circuit(false); + } + + fn test_leaf_circuit(is_merge_case: bool) { let mut rng = thread_rng(); let block_id = rng.gen::().to_field(); let block_number = U256::from_limbs(rng.gen::<[u64; 4]>()); - let row_digest = Point::sample(&mut rng).to_weierstrass().to_fields(); - let extraction_pi = &random_extraction_pi(&mut rng, block_number, &row_digest, false); - let rows_tree_pi = &random_rows_tree_pi(&mut rng, &row_digest, false); + let rows_tree_pi = &random_rows_tree_pi(&mut rng, is_merge_case); + let final_digest = compute_final_digest( + is_merge_case, + &row_tree::PublicInputs::from_slice(rows_tree_pi), + ); + let extraction_pi = &random_extraction_pi( + &mut rng, + block_number, + &final_digest.to_fields(), + is_merge_case, + ); let test_circuit = TestLeafCircuit { c: LeafCircuit { @@ -372,8 +370,12 @@ pub mod tests { } // Check new node digest { - let exp_digest = - compute_expected_set_digest(block_id, block_number.to_vec(), rows_tree_pi); + let exp_digest = compute_expected_set_digest( + is_merge_case, + block_id, + block_number.to_vec(), + rows_tree_pi, + ); assert_eq!(pi.new_value_set_digest_point(), exp_digest.to_weierstrass()); } } diff --git a/verifiable-db/src/block_tree/mod.rs b/verifiable-db/src/block_tree/mod.rs index 34f172404..6070aa4a4 100644 --- a/verifiable-db/src/block_tree/mod.rs +++ b/verifiable-db/src/block_tree/mod.rs @@ -1,15 +1,39 @@ mod api; +mod empty; mod leaf; mod membership; mod parent; mod public_inputs; +use std::iter::once; + +use crate::{ + extraction::{ExtractionPI, ExtractionPIWrap}, + row_tree, +}; pub use api::{CircuitInput, PublicParameters}; -use mp2_common::{poseidon::hash_to_int_target, CHasher, D, F}; -use plonky2::{iop::target::Target, plonk::circuit_builder::CircuitBuilder}; +use itertools::Itertools; +use mp2_common::{ + group_hashing::{ + circuit_hashed_scalar_mul, field_hashed_scalar_mul, weierstrass_to_point, + CircuitBuilderGroupHashing, + }, + poseidon::{empty_poseidon_hash, hash_to_int_target, hash_to_int_value, H}, + types::CBuilder, + utils::{ToFields, ToTargets}, + CHasher, D, F, +}; +use plonky2::{ + field::types::Field, + iop::target::Target, + plonk::{circuit_builder::CircuitBuilder, config::Hasher}, +}; use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; -use plonky2_ecgfp5::gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}; +use plonky2_ecgfp5::{ + curve::{curve::Point, scalar_field::Scalar}, + gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, +}; pub use public_inputs::PublicInputs; /// Common function to compute the digest of the block tree which uses a special format using @@ -25,20 +49,111 @@ pub(crate) fn compute_index_digest( b.curve_scalar_mul(base, &scalar) } +/// Compute the final digest value. +pub fn compute_final_digest( + is_merge_case: bool, + rows_tree_pi: &row_tree::PublicInputs, +) -> Point { + let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); + if !is_merge_case { + return individual_digest; + } + // Compute the final row digest from rows_tree_proof for merge case: + // row_id_multiplier = H2Int(H("") || rows_tree_proof.multiplier_counter) + let empty_hash = empty_poseidon_hash(); + let inputs = empty_hash + .to_fields() + .into_iter() + .chain(once(rows_tree_pi.multiplier_counter())) + .collect_vec(); + let hash = H::hash_no_pad(&inputs); + let row_id_multiplier = hash_to_int_value(hash); + // multiplier_digest = rows_tree_proof.row_id_multiplier * rows_tree_proof.multiplier_vd + let multiplier_vd = weierstrass_to_point(&rows_tree_pi.multiplier_digest_point()); + let row_id_multiplier = Scalar::from_noncanonical_biguint(row_id_multiplier); + let multiplier_digest = multiplier_vd * row_id_multiplier; + // rows_digest_merge = multiplier_digest * rows_tree_proof.DR + let individual_digest = weierstrass_to_point(&rows_tree_pi.individual_digest_point()); + field_hashed_scalar_mul(multiplier_digest.to_fields(), individual_digest) +} + +/// Compute the final digest target. +pub(crate) fn compute_final_digest_target( + b: &mut CBuilder, + extraction_pi: &E::PI<'_>, + rows_tree_pi: &row_tree::PublicInputs, +) -> CurveTarget +where + E: ExtractionPIWrap, +{ + // Compute the final row digest from rows_tree_proof for merge case: + // row_id_multiplier = H2Int(H("") || rows_tree_proof.multiplier_counter) + let empty_hash = b.constant_hash(*empty_poseidon_hash()); + let inputs = empty_hash + .to_targets() + .into_iter() + .chain(once(rows_tree_pi.multiplier_counter_target())) + .collect(); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id_multiplier = hash_to_int_target(b, hash); + // multiplier_digest = row_id_multiplier * rows_tree_proof.multiplier_vd + let multiplier_vd = rows_tree_pi.multiplier_digest_target(); + let row_id_multiplier = b.biguint_to_nonnative(&row_id_multiplier); + let multiplier_digest = b.curve_scalar_mul(multiplier_vd, &row_id_multiplier); + // rows_digest_merge = multiplier_digest * rows_tree_proof.DR + let individual_digest = rows_tree_pi.individual_digest_target(); + let rows_digest_merge = circuit_hashed_scalar_mul(b, multiplier_digest, individual_digest); + + // Choose the final row digest depending on whether we are in merge case or not: + // final_digest = extraction_proof.is_merge ? rows_digest_merge : rows_tree_proof.DR + let final_digest = b.curve_select( + extraction_pi.is_merge_case(), + rows_digest_merge, + individual_digest, + ); + + // Enforce that the data extracted from the blockchain is the same as the data + // employed to build the rows tree for this node: + // assert final_digest == extraction_proof.DV + b.connect_curve_points(final_digest, extraction_pi.value_set_digest()); + + // Enforce that if we aren't in merge case, then no cells were accumulated in + // multiplier digest: + // assert extraction_proof.is_merge or rows_tree_proof.multiplier_vd == 0 + let curve_zero = b.curve_zero(); + let is_multiplier_vd_zero = b.curve_eq(rows_tree_pi.multiplier_digest_target(), curve_zero); + let acc = b.or(extraction_pi.is_merge_case(), is_multiplier_vd_zero); + b.assert_one(acc.target); + + final_digest +} + #[cfg(test)] pub(crate) mod tests { + use super::*; + use crate::row_tree; use alloy::primitives::U256; - use mp2_common::{keccak::PACKED_HASH_LEN, utils::ToFields, F}; - use mp2_test::utils::random_vector; + use mp2_common::{ + keccak::PACKED_HASH_LEN, + types::CBuilder, + utils::{FromFields, ToFields}, + C, F, + }; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::random_vector, + }; use plonky2::{ field::types::{Field, Sample}, hash::hash_types::NUM_HASH_OUT_ELTS, - iop::target::Target, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, }; use plonky2_ecgfp5::curve::curve::Point; - use rand::{rngs::ThreadRng, Rng}; - - use crate::row_tree; + use rand::{rngs::ThreadRng, thread_rng, Rng}; + use std::array; pub(crate) type TestPITargets<'a> = crate::extraction::test::PublicInputs<'a, Target>; pub(crate) type TestPIField<'a> = crate::extraction::test::PublicInputs<'a, F>; @@ -71,15 +186,16 @@ pub(crate) mod tests { } /// Generate a random rows tree public inputs. - pub(crate) fn random_rows_tree_pi( - rng: &mut ThreadRng, - row_digest: &[F], - is_merge_case: bool, - ) -> Vec { - let h = random_vector::(NUM_HASH_OUT_ELTS).to_fields(); - let [min, max] = [0; 2].map(|_| U256::from_limbs(rng.gen::<[u64; 4]>()).to_fields()); - let is_merge = [F::from_canonical_usize(is_merge_case as usize)]; - row_tree::PublicInputs::new(&h, row_digest, &min, &max, &is_merge).to_vec() + pub(crate) fn random_rows_tree_pi(rng: &mut ThreadRng, is_merge_case: bool) -> Vec { + let [min, max] = array::from_fn(|_| rng.gen()); + let multiplier_digest = if is_merge_case { + Point::rand() + } else { + Point::NEUTRAL + }; + let mulitplier_cnt = rng.gen_range(1..100); + + row_tree::PublicInputs::sample(multiplier_digest, min, max, mulitplier_cnt) } /// Generate a random extraction public inputs. @@ -103,4 +219,68 @@ pub(crate) mod tests { ) .to_vec() } + + #[derive(Clone, Debug)] + struct TestFinalDigestCircuit<'a> { + extraction_pi: &'a [F], + rows_tree_pi: &'a [F], + } + + impl UserCircuit for TestFinalDigestCircuit<'_> { + // Extraction PI + rows tree PI + type Wires = (Vec, Vec); + + fn build(b: &mut CBuilder) -> Self::Wires { + let extraction_pi = b.add_virtual_targets(TestPITargets::TOTAL_LEN); + let rows_tree_pi = b.add_virtual_targets(row_tree::PublicInputs::::total_len()); + + let final_digest = compute_final_digest_target::( + b, + &TestPITargets::from_slice(&extraction_pi), + &row_tree::PublicInputs::from_slice(&rows_tree_pi), + ); + + b.register_curve_public_input(final_digest); + + (extraction_pi, rows_tree_pi) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + pw.set_target_arr(&wires.0, self.extraction_pi); + pw.set_target_arr(&wires.1, self.rows_tree_pi); + } + } + + #[test] + fn test_block_tree_final_digest() { + test_final_digest(true); + test_final_digest(false); + } + + fn test_final_digest(is_merge_case: bool) { + let rng = &mut thread_rng(); + + let rows_tree_pi = &random_rows_tree_pi(rng, is_merge_case); + let exp_final_digest = compute_final_digest( + is_merge_case, + &row_tree::PublicInputs::from_slice(rows_tree_pi), + ); + let block_number = U256::from_limbs(rng.gen()); + let extraction_pi = &random_extraction_pi( + rng, + block_number, + &exp_final_digest.to_fields(), + is_merge_case, + ); + + let test_circuit = TestFinalDigestCircuit { + extraction_pi, + rows_tree_pi, + }; + + let proof = run_circuit::(test_circuit); + let final_digest = Point::from_fields(&proof.public_inputs); + + assert_eq!(final_digest, exp_final_digest); + } } diff --git a/verifiable-db/src/block_tree/parent.rs b/verifiable-db/src/block_tree/parent.rs index 254e0687d..08f1ee52c 100644 --- a/verifiable-db/src/block_tree/parent.rs +++ b/verifiable-db/src/block_tree/parent.rs @@ -1,7 +1,7 @@ //! This circuit is employed when the new node is inserted as parent of an existing node, //! referred to as old node. -use super::{compute_index_digest, public_inputs::PublicInputs}; +use super::{compute_final_digest_target, compute_index_digest, public_inputs::PublicInputs}; use crate::{ extraction::{ExtractionPI, ExtractionPIWrap}, row_tree, @@ -10,7 +10,6 @@ use alloy::primitives::U256; use anyhow::Result; use mp2_common::{ default_config, - group_hashing::CircuitBuilderGroupHashing, poseidon::{empty_poseidon_hash, H}, proof::ProofWithVK, public_inputs::PublicInputCommon, @@ -84,13 +83,10 @@ impl ParentCircuit { let extraction_pi = E::PI::from_slice(extraction_pi); let rows_tree_pi = row_tree::PublicInputs::::from_slice(rows_tree_pi); + let final_digest = compute_final_digest_target::(b, &extraction_pi, &rows_tree_pi); let block_number = extraction_pi.primary_index_value(); - // Enforce that the data extracted from the blockchain is the same as the data - // employed to build the rows tree for this node. - b.connect_curve_points(extraction_pi.value_set_digest(), rows_tree_pi.rows_digest()); - // Compute the hash of table metadata, to be exposed as public input to prove to // the verifier that we extracted the correct storage slots and we place the data // in the expected columns of the constructed tree; we add also the identifier @@ -110,7 +106,7 @@ impl ParentCircuit { let inputs = iter::once(index_identifier) .chain(block_number.iter().cloned()) .collect(); - let node_digest = compute_index_digest(b, inputs, rows_tree_pi.rows_digest()); + let node_digest = compute_index_digest(b, inputs, final_digest); // We recompute the hash of the old node to bind the `old_min` and `old_max` // values to the hash of the old tree. @@ -152,12 +148,6 @@ impl ParentCircuit { .collect(); let h_new = b.hash_n_to_hash_no_pad::(inputs).elements; - // check that the rows tree built is for a merged table iff we extract data from MPT for a merged table - b.connect( - rows_tree_pi.is_merge_case().target, - extraction_pi.is_merge_case().target, - ); - // Register the public inputs. PublicInputs::new( &h_new, @@ -236,7 +226,7 @@ where _verified_proofs: [&ProofWithPublicInputsTarget; 0], builder_parameters: Self::CircuitBuilderParams, ) -> Self { - const ROWS_TREE_IO: usize = row_tree::PublicInputs::::TOTAL_LEN; + const ROWS_TREE_IO: usize = row_tree::PublicInputs::::total_len(); let extraction_verifier = RecursiveCircuitsVerifierGagdet::::new( @@ -280,6 +270,7 @@ where #[cfg(test)] mod tests { use crate::block_tree::{ + compute_final_digest, leaf::tests::{compute_expected_hash, compute_expected_set_digest}, tests::{TestPIField, TestPITargets}, }; @@ -296,10 +287,7 @@ mod tests { circuit::{run_circuit, UserCircuit}, utils::random_vector, }; - use plonky2::{ - field::types::Sample, hash::hash_types::NUM_HASH_OUT_ELTS, plonk::config::Hasher, - }; - use plonky2_ecgfp5::curve::curve::Point; + use plonky2::{hash::hash_types::NUM_HASH_OUT_ELTS, plonk::config::Hasher}; use rand::{thread_rng, Rng}; #[derive(Clone, Debug)] @@ -315,7 +303,7 @@ mod tests { fn build(b: &mut CBuilder) -> Self::Wires { let extraction_pi = b.add_virtual_targets(TestPITargets::TOTAL_LEN); - let rows_tree_pi = b.add_virtual_targets(row_tree::PublicInputs::::TOTAL_LEN); + let rows_tree_pi = b.add_virtual_targets(row_tree::PublicInputs::::total_len()); let parent_wires = ParentCircuit::build::(b, &extraction_pi, &rows_tree_pi); @@ -329,25 +317,36 @@ mod tests { assert_eq!(wires.1.len(), TestPITargets::TOTAL_LEN); pw.set_target_arr(&wires.1, self.extraction_pi); - assert_eq!(wires.2.len(), row_tree::PublicInputs::::TOTAL_LEN); + assert_eq!(wires.2.len(), row_tree::PublicInputs::::total_len()); pw.set_target_arr(&wires.2, self.rows_tree_pi); } } #[test] fn test_block_index_parent_circuit() { + test_parent_circuit(true); + test_parent_circuit(false); + } + + fn test_parent_circuit(is_merge_case: bool) { let mut rng = thread_rng(); let index_identifier = rng.gen::().to_field(); - let [old_index_value, old_min, old_max] = - [0; 3].map(|_| U256::from_limbs(rng.gen::<[u64; 4]>())); + let [old_index_value, old_min, old_max] = [0; 3].map(|_| U256::from_limbs(rng.gen())); let [left_child, right_child, old_rows_tree_hash] = [0; 3].map(|_| HashOut::from_vec(random_vector::(NUM_HASH_OUT_ELTS).to_fields())); - let row_digest = Point::sample(&mut rng).to_weierstrass().to_fields(); - let extraction_pi = - &random_extraction_pi(&mut rng, old_max + U256::from(1), &row_digest, true); - let rows_tree_pi = &random_rows_tree_pi(&mut rng, &row_digest, true); + let rows_tree_pi = &random_rows_tree_pi(&mut rng, is_merge_case); + let final_digest = compute_final_digest( + is_merge_case, + &row_tree::PublicInputs::from_slice(rows_tree_pi), + ); + let extraction_pi = &random_extraction_pi( + &mut rng, + old_max + U256::from(1), + &final_digest.to_fields(), + is_merge_case, + ); let test_circuit = TestParentCircuit { c: ParentCircuit { @@ -433,8 +432,12 @@ mod tests { } // Check new node digest { - let exp_digest = - compute_expected_set_digest(index_identifier, block_number.to_vec(), rows_tree_pi); + let exp_digest = compute_expected_set_digest( + is_merge_case, + index_identifier, + block_number.to_vec(), + rows_tree_pi, + ); assert_eq!(pi.new_value_set_digest_point(), exp_digest.to_weierstrass()); } diff --git a/verifiable-db/src/cells_tree/api.rs b/verifiable-db/src/cells_tree/api.rs index 8cb5fb56a..0eb9beabf 100644 --- a/verifiable-db/src/cells_tree/api.rs +++ b/verifiable-db/src/cells_tree/api.rs @@ -113,7 +113,7 @@ pub fn build_circuits_params() -> PublicParameters { PublicParameters::build() } -const NUM_IO: usize = PublicInputs::::TOTAL_LEN; +const NUM_IO: usize = PublicInputs::::total_len(); /// Number of circuits in the set /// 1 leaf + 1 full node + 1 partial node + 1 empty node @@ -211,21 +211,21 @@ impl PublicParameters { pub fn extract_hash_from_proof(proof: &[u8]) -> Result> { let p = ProofWithVK::deserialize(proof)?; - Ok(PublicInputs::from_slice(&p.proof.public_inputs).root_hash_hashout()) + Ok(PublicInputs::from_slice(&p.proof.public_inputs).node_hash()) } + #[cfg(test)] mod tests { use super::*; + use itertools::Itertools; use mp2_common::{ - group_hashing::{add_curve_point, map_to_curve_point}, poseidon::{empty_poseidon_hash, H}, - utils::{Fieldable, ToFields}, + utils::ToFields, }; use plonky2::{field::types::PrimeField64, plonk::config::Hasher}; - use plonky2_ecgfp5::curve::curve::{Point, WeierstrassPoint}; - use rand::{thread_rng, Rng}; + use plonky2_ecgfp5::curve::curve::WeierstrassPoint; use serial_test::serial; - use std::iter; + use std::iter::once; #[test] #[serial] @@ -248,11 +248,12 @@ mod tests { fn generate_leaf_proof(params: &PublicParameters) -> Vec { // Build the circuit input. - let mut rng = thread_rng(); - let identifier: F = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let value_fields = value.to_fields(); - let input = CircuitInput::leaf(identifier.to_canonical_u64(), value, false); + let is_multiplier = false; + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + let values_digests = cell.split_values_digest(); + let input = CircuitInput::leaf(id.to_canonical_u64(), value, false); // Generate proof. let proof = params.generate_proof(input).unwrap(); @@ -263,27 +264,37 @@ mod tests { .proof .public_inputs; let pi = PublicInputs::from_slice(&pi); + // Check the node hash { let empty_hash = empty_poseidon_hash(); - let inputs: Vec<_> = empty_hash + let inputs = empty_hash .elements .iter() .cloned() .chain(empty_hash.elements) - .chain(iter::once(identifier)) - .chain(value_fields.clone()) - .collect(); + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); // TODO: Fix to employ the same hash method in the ryhope tree library. let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - { - let inputs: Vec<_> = iter::once(identifier).chain(value_fields).collect(); - let exp_digest = map_to_curve_point(&inputs).to_weierstrass(); - - assert_eq!(pi.individual_digest_point(), exp_digest); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!(pi.individual_counter(), F::ONE - multiplier_cnt); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), multiplier_cnt); proof } @@ -302,9 +313,20 @@ mod tests { let empty_hash = empty_poseidon_hash(); assert_eq!(pi.h, empty_hash.elements); } - { - assert_eq!(pi.individual_digest_point(), WeierstrassPoint::NEUTRAL); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + WeierstrassPoint::NEUTRAL + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + WeierstrassPoint::NEUTRAL + ); + // Check individual counter + assert_eq!(pi.individual_counter(), F::ZERO); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), F::ZERO); proof } @@ -321,11 +343,12 @@ mod tests { .collect(); // Build the circuit input. - let mut rng = thread_rng(); - let identifier: F = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let packed_value = value.to_fields(); - let input = CircuitInput::full(identifier.to_canonical_u64(), value, false, child_proofs); + let is_multiplier = false; + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + let values_digests = cell.split_values_digest(); + let input = CircuitInput::full(id.to_canonical_u64(), value, false, child_proofs); // Generate proof. let proof = params.generate_proof(input).unwrap(); @@ -336,32 +359,50 @@ mod tests { .proof .public_inputs; let pi = PublicInputs::from_slice(&pi); + + let values_digests = child_pis.iter().fold(values_digests, |acc, pi| { + acc.accumulate(&pi.split_values_digest_point()) + }); + + // Check the node hash { - let inputs: Vec<_> = child_pis[0] - .h_raw() + let inputs = child_pis[0] + .to_node_hash_raw() .iter() - .chain(child_pis[1].h_raw()) + .chain(child_pis[1].to_node_hash_raw()) .cloned() - .chain(iter::once(identifier)) - .chain(packed_value.clone()) - .collect(); + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); // TODO: Fix to employ the same hash method in the ryhope tree library. let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - { - let child_digests: Vec<_> = child_pis + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!( + pi.individual_counter(), + child_pis.iter().fold(F::ONE - multiplier_cnt, |acc, pi| acc + + pi.individual_counter()), + ); + // Check multiplier counter + assert_eq!( + pi.multiplier_counter(), + child_pis .iter() - .map(|pi| Point::decode(pi.individual_digest_point().encode()).unwrap()) - .collect(); - let inputs: Vec<_> = iter::once(identifier).chain(packed_value).collect(); - let exp_digest = map_to_curve_point(&inputs); - let exp_digest = - add_curve_point(&[exp_digest, child_digests[0], child_digests[1]]).to_weierstrass(); - - assert_eq!(pi.individual_digest_point(), exp_digest); - } + .fold(multiplier_cnt, |acc, pi| acc + pi.multiplier_counter()), + ); proof } @@ -375,11 +416,12 @@ mod tests { let child_pi = PublicInputs::from_slice(&child_pi); // Build the circuit input. - let mut rng = thread_rng(); - let identifier: F = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let packed_value = value.to_fields(); - let input = CircuitInput::partial(identifier.to_canonical_u64(), value, false, child_proof); + let is_multiplier = false; + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + let values_digests = cell.split_values_digest(); + let input = CircuitInput::partial(id.to_canonical_u64(), value, false, child_proof); // Generate proof. let proof = params.generate_proof(input).unwrap(); @@ -390,29 +432,46 @@ mod tests { .proof .public_inputs; let pi = PublicInputs::from_slice(&pi); + + let values_digests = values_digests.accumulate(&child_pi.split_values_digest_point()); + + // Check the node hash { let empty_hash = empty_poseidon_hash(); - let inputs: Vec<_> = child_pi - .h_raw() + let inputs = child_pi + .to_node_hash_raw() .iter() .cloned() .chain(empty_hash.elements) - .chain(iter::once(identifier)) - .chain(packed_value.clone()) - .collect(); + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); // TODO: Fix to employ the same hash method in the ryhope tree library. let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - { - let child_digest = Point::decode(child_pi.individual_digest_point().encode()).unwrap(); - let inputs: Vec<_> = iter::once(identifier).chain(packed_value).collect(); - let exp_digest = map_to_curve_point(&inputs); - let exp_digest = add_curve_point(&[exp_digest, child_digest]).to_weierstrass(); - - assert_eq!(pi.individual_digest_point(), exp_digest); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!( + pi.individual_counter(), + F::ONE - multiplier_cnt + child_pi.individual_counter(), + ); + // Check multiplier counter + assert_eq!( + pi.multiplier_counter(), + multiplier_cnt + child_pi.multiplier_counter(), + ); proof } diff --git a/verifiable-db/src/cells_tree/empty_node.rs b/verifiable-db/src/cells_tree/empty_node.rs index d0d770b1f..212a297a8 100644 --- a/verifiable-db/src/cells_tree/empty_node.rs +++ b/verifiable-db/src/cells_tree/empty_node.rs @@ -23,11 +23,14 @@ impl EmptyNodeCircuit { let empty_hash = empty_poseidon_hash(); let h = b.constant_hash(*empty_hash).elements; - // dc = CURVE_ZERO - let dc = b.curve_zero().to_targets(); + // ZERO + let zero = b.zero(); + + // CURVE_ZERO + let curve_zero = b.curve_zero().to_targets(); // Register the public inputs. - PublicInputs::new(&h, &dc, &dc).register(b); + PublicInputs::new(&h, &curve_zero, &curve_zero, &zero, &zero).register(b); EmptyNodeWires } @@ -39,7 +42,7 @@ impl CircuitLogicWires for EmptyNodeWires { type Inputs = EmptyNodeCircuit; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CBuilder, @@ -59,6 +62,7 @@ mod tests { use super::*; use mp2_common::C; use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::field::types::Field; use plonky2_ecgfp5::curve::curve::WeierstrassPoint; impl UserCircuit for EmptyNodeCircuit { @@ -81,9 +85,19 @@ mod tests { let empty_hash = empty_poseidon_hash(); assert_eq!(pi.h, empty_hash.elements); } - // Check the cells digest - { - assert_eq!(pi.individual_digest_point(), WeierstrassPoint::NEUTRAL); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + WeierstrassPoint::NEUTRAL + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + WeierstrassPoint::NEUTRAL + ); + // Check individual counter + assert_eq!(pi.individual_counter(), F::ZERO); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), F::ZERO); } } diff --git a/verifiable-db/src/cells_tree/full_node.rs b/verifiable-db/src/cells_tree/full_node.rs index 2f523ba72..7751fde01 100644 --- a/verifiable-db/src/cells_tree/full_node.rs +++ b/verifiable-db/src/cells_tree/full_node.rs @@ -4,7 +4,7 @@ use super::{public_inputs::PublicInputs, Cell, CellWire}; use anyhow::Result; use derive_more::{From, Into}; use mp2_common::{ - public_inputs::PublicInputCommon, types::CBuilder, utils::ToTargets, CHasher, D, F, + poseidon::H, public_inputs::PublicInputCommon, types::CBuilder, utils::ToTargets, D, F, }; use plonky2::{ iop::{target::Target, witness::PartialWitness}, @@ -12,7 +12,7 @@ use plonky2::{ }; use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; -use std::{array, iter}; +use std::{array, iter::once}; #[derive(Clone, Debug, Serialize, Deserialize, Into, From)] pub struct FullNodeWires(CellWire); @@ -22,30 +22,42 @@ pub struct FullNodeCircuit(Cell); impl FullNodeCircuit { pub fn build(b: &mut CBuilder, child_proofs: [PublicInputs; 2]) -> FullNodeWires { - let cell = CellWire::new(b); + let [p1, p2] = child_proofs; - // h = Poseidon(p1.H || p2.H || identifier || value) - let [p1_hash, p2_hash] = [0, 1].map(|i| child_proofs[i].node_hash()); - let inputs: Vec<_> = p1_hash - .elements - .iter() - .cloned() - .chain(p2_hash.elements) - .chain(iter::once(cell.identifier)) + let cell = CellWire::new(b); + let values_digests = + cell.split_and_accumulate_values_digest(b, &p1.split_values_digest_target()); + let values_digests = values_digests.accumulate(b, &p2.split_values_digest_target()); + + let is_individual = cell.is_individual(b); + let individual_cnt = b.add_many([ + is_individual.target, + p1.individual_counter_target(), + p2.individual_counter_target(), + ]); + let multiplier_cnt = b.add_many([ + cell.is_multiplier().target, + p1.multiplier_counter_target(), + p2.multiplier_counter_target(), + ]); + + // H(p1.H || p2.H || identifier || value) + let inputs = p1 + .node_hash_target() + .into_iter() + .chain(p2.node_hash_target()) + .chain(once(cell.identifier)) .chain(cell.value.to_targets()) .collect(); - let h = b.hash_n_to_hash_no_pad::(inputs).elements; - - // digest_cell = p1.digest_cell + p2.digest_cell + D(identifier || value) - let split_digest = cell.split_digest(b); - let split_digest = split_digest.accumulate(b, &child_proofs[0].split_digest_target()); - let split_digest = split_digest.accumulate(b, &child_proofs[1].split_digest_target()); + let h = b.hash_n_to_hash_no_pad::(inputs); // Register the public inputs. PublicInputs::new( - &h, - &split_digest.individual.to_targets(), - &split_digest.multiplier.to_targets(), + &h.to_targets(), + &values_digests.individual.to_targets(), + &values_digests.multiplier.to_targets(), + &individual_cnt, + &multiplier_cnt, ) .register(b); @@ -54,7 +66,7 @@ impl FullNodeCircuit { /// Assign the wires. fn assign(&self, pw: &mut PartialWitness, wires: &FullNodeWires) { - self.0.assign_wires(pw, &wires.0); + self.0.assign(pw, &wires.0); } } @@ -64,7 +76,7 @@ impl CircuitLogicWires for FullNodeWires { type Inputs = FullNodeCircuit; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CBuilder, @@ -85,23 +97,10 @@ impl CircuitLogicWires for FullNodeWires { #[cfg(test)] mod tests { use super::*; - use alloy::primitives::U256; - use mp2_common::{ - group_hashing::{add_curve_point, map_to_curve_point}, - poseidon::H, - utils::{Fieldable, ToFields}, - C, - }; - use mp2_test::{ - circuit::{run_circuit, UserCircuit}, - utils::random_vector, - }; - use plonky2::{ - field::types::Sample, hash::hash_types::NUM_HASH_OUT_ELTS, iop::witness::WitnessWrite, - plonk::config::Hasher, - }; - use plonky2_ecgfp5::curve::curve::Point; - use rand::{thread_rng, Rng}; + use itertools::Itertools; + use mp2_common::{poseidon::H, utils::ToFields, C}; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{field::types::Field, iop::witness::WitnessWrite, plonk::config::Hasher}; #[derive(Clone, Debug)] struct TestFullNodeCircuit<'a> { @@ -115,7 +114,7 @@ mod tests { fn build(b: &mut CBuilder) -> Self::Wires { let child_pis = - [0; 2].map(|_| b.add_virtual_targets(PublicInputs::::TOTAL_LEN)); + [0; 2].map(|_| b.add_virtual_targets(PublicInputs::::total_len())); let wires = FullNodeCircuit::build( b, @@ -136,61 +135,101 @@ mod tests { } #[test] - fn test_cells_tree_full_node_circuit() { - let mut rng = thread_rng(); - - let identifier = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let value_fields = value.to_fields(); - - // Create the child public inputs. - let child_hashs = [0; 2].map(|_| random_vector::(NUM_HASH_OUT_ELTS).to_fields()); - let child_digests = [0; 2].map(|_| Point::sample(&mut rng)); - let child_pis = &array::from_fn(|i| { - let h = &child_hashs[i]; - let ind = &child_digests[i].to_weierstrass().to_fields(); - let neutral = Point::NEUTRAL.to_fields(); - - PublicInputs { - h, - ind, - mul: &neutral, - } - .to_vec() - }); + fn test_cells_tree_full_node_individual() { + [true, false] + .into_iter() + .cartesian_product([true, false]) + .for_each(|(is_left_child_multiplier, is_right_child_multiplier)| { + test_cells_tree_full_multiplier( + false, + is_left_child_multiplier, + is_right_child_multiplier, + ); + }); + } + + #[test] + fn test_cells_tree_full_node_multiplier() { + [true, false] + .into_iter() + .cartesian_product([true, false]) + .for_each(|(is_left_child_multiplier, is_right_child_multiplier)| { + test_cells_tree_full_multiplier( + true, + is_left_child_multiplier, + is_right_child_multiplier, + ); + }); + } + + fn test_cells_tree_full_multiplier( + is_multiplier: bool, + is_left_child_multiplier: bool, + is_right_child_multiplier: bool, + ) { + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + let values_digests = cell.split_values_digest(); + + let child_pis = &[ + PublicInputs::::sample(is_left_child_multiplier), + PublicInputs::::sample(is_right_child_multiplier), + ]; let test_circuit = TestFullNodeCircuit { - c: Cell { - identifier, - value, - is_multiplier: false, - } - .into(), + c: cell.into(), child_pis, }; let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // Check the node Poseidon hash + + let child_pis = child_pis + .iter() + .map(|pi| PublicInputs::from_slice(pi)) + .collect_vec(); + + let values_digests = child_pis.iter().fold(values_digests, |acc, pi| { + acc.accumulate(&pi.split_values_digest_point()) + }); + + // Check the node hash { - let inputs: Vec<_> = child_hashs[0] - .clone() + let inputs = child_pis[0] + .node_hash() + .to_fields() .into_iter() - .chain(child_hashs[1].clone()) - .chain(iter::once(identifier)) - .chain(value_fields.clone()) - .collect(); + .chain(child_pis[1].node_hash().to_fields()) + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - // Check the cells digest - { - let inputs: Vec<_> = iter::once(identifier).chain(value_fields).collect(); - let exp_digest = map_to_curve_point(&inputs); - let exp_digest = - add_curve_point(&[exp_digest, child_digests[0], child_digests[1]]).to_weierstrass(); - - assert_eq!(pi.individual_digest_point(), exp_digest); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!( + pi.individual_counter(), + child_pis.iter().fold(F::ONE - multiplier_cnt, |acc, pi| acc + + pi.individual_counter()), + ); + // Check multiplier counter + assert_eq!( + pi.multiplier_counter(), + child_pis + .iter() + .fold(multiplier_cnt, |acc, pi| acc + pi.multiplier_counter()), + ); } } diff --git a/verifiable-db/src/cells_tree/leaf.rs b/verifiable-db/src/cells_tree/leaf.rs index 72fefca14..5d97f69c8 100644 --- a/verifiable-db/src/cells_tree/leaf.rs +++ b/verifiable-db/src/cells_tree/leaf.rs @@ -3,8 +3,11 @@ use super::{public_inputs::PublicInputs, Cell, CellWire}; use derive_more::{From, Into}; use mp2_common::{ - poseidon::empty_poseidon_hash, public_inputs::PublicInputCommon, types::CBuilder, - utils::ToTargets, CHasher, D, F, + poseidon::{empty_poseidon_hash, H}, + public_inputs::PublicInputCommon, + types::CBuilder, + utils::ToTargets, + D, F, }; use plonky2::{ iop::witness::PartialWitness, @@ -12,7 +15,7 @@ use plonky2::{ }; use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; -use std::iter; +use std::iter::once; #[derive(Clone, Debug, Serialize, Deserialize, From, Into)] pub struct LeafWires(CellWire); @@ -23,28 +26,28 @@ pub struct LeafCircuit(Cell); impl LeafCircuit { fn build(b: &mut CBuilder) -> LeafWires { let cell = CellWire::new(b); - - // h = Poseidon(Poseidon("") || Poseidon("") || identifier || value) - let empty_hash = empty_poseidon_hash(); - let empty_hash = b.constant_hash(*empty_hash); - let inputs: Vec<_> = empty_hash - .elements - .iter() - .cloned() - .chain(empty_hash.elements) - .chain(iter::once(cell.identifier)) + let values_digests = cell.split_values_digest(b); + let individual_cnt = cell.is_individual(b).target; + let multiplier_cnt = cell.is_multiplier().target; + + // H(H("") || H("") || identifier || pack_u32(value)) + let empty_hash = b.constant_hash(*empty_poseidon_hash()).to_targets(); + let inputs = empty_hash + .clone() + .into_iter() + .chain(empty_hash) + .chain(once(cell.identifier)) .chain(cell.value.to_targets()) .collect(); - let h = b.hash_n_to_hash_no_pad::(inputs).elements; - - // digest_cell = D(identifier || value) - let split_digest = cell.split_digest(b); + let h = b.hash_n_to_hash_no_pad::(inputs); // Register the public inputs. PublicInputs::new( - &h, - &split_digest.individual.to_targets(), - &split_digest.multiplier.to_targets(), + &h.to_targets(), + &values_digests.individual.to_targets(), + &values_digests.multiplier.to_targets(), + &individual_cnt, + &multiplier_cnt, ) .register(b); @@ -53,7 +56,7 @@ impl LeafCircuit { /// Assign the wires. fn assign(&self, pw: &mut PartialWitness, wires: &LeafWires) { - self.0.assign_wires(pw, &wires.0); + self.0.assign(pw, &wires.0); } } @@ -63,7 +66,7 @@ impl CircuitLogicWires for LeafWires { type Inputs = LeafCircuit; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CircuitBuilder, @@ -82,16 +85,10 @@ impl CircuitLogicWires for LeafWires { #[cfg(test)] mod tests { use super::*; - use alloy::primitives::U256; - use mp2_common::{ - group_hashing::map_to_curve_point, - poseidon::H, - utils::{Fieldable, ToFields}, - C, - }; + use itertools::Itertools; + use mp2_common::{poseidon::H, utils::ToFields, C}; use mp2_test::circuit::{run_circuit, UserCircuit}; - use plonky2::plonk::config::Hasher; - use rand::{thread_rng, Rng}; + use plonky2::{field::types::Field, plonk::config::Hasher}; impl UserCircuit for LeafCircuit { type Wires = LeafWires; @@ -112,44 +109,44 @@ mod tests { } fn test_cells_tree_leaf_multiplier(is_multiplier: bool) { - let mut rng = thread_rng(); - - let identifier = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let value_fields = value.to_fields(); - - let test_circuit: LeafCircuit = Cell { - identifier, - value, - is_multiplier, - } - .into(); + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + let values_digests = cell.split_values_digest(); + let test_circuit: LeafCircuit = cell.into(); let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // Check the node Poseidon hash + + // Check the node hash { let empty_hash = empty_poseidon_hash(); - let inputs: Vec<_> = empty_hash + let inputs = empty_hash .elements .iter() .cloned() .chain(empty_hash.elements) - .chain(iter::once(identifier)) - .chain(value_fields.clone()) - .collect(); + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - // Check the cells digest - { - let inputs: Vec<_> = iter::once(identifier).chain(value_fields).collect(); - let exp_digest = map_to_curve_point(&inputs).to_weierstrass(); - match is_multiplier { - true => assert_eq!(pi.multiplier_digest_point(), exp_digest), - false => assert_eq!(pi.individual_digest_point(), exp_digest), - } - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!(pi.individual_counter(), F::ONE - multiplier_cnt); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), multiplier_cnt); } } diff --git a/verifiable-db/src/cells_tree/mod.rs b/verifiable-db/src/cells_tree/mod.rs index 42e8d064e..bdb83311e 100644 --- a/verifiable-db/src/cells_tree/mod.rs +++ b/verifiable-db/src/cells_tree/mod.rs @@ -5,11 +5,10 @@ mod leaf; mod partial_node; mod public_inputs; -use serde::{Deserialize, Serialize}; - use alloy::primitives::U256; pub use api::{build_circuits_params, extract_hash_from_proof, CircuitInput, PublicParameters}; use derive_more::Constructor; +use itertools::Itertools; use mp2_common::{ digest::{Digest, SplitDigestPoint, SplitDigestTarget}, group_hashing::{map_to_curve_point, CircuitBuilderGroupHashing}, @@ -17,15 +16,14 @@ use mp2_common::{ types::CBuilder, u256::{CircuitBuilderU256, UInt256Target, WitnessWriteU256}, utils::{ToFields, ToTargets}, - D, F, + F, }; +use serde::{Deserialize, Serialize}; +use std::iter::once; -use plonky2::{ - iop::{ - target::{BoolTarget, Target}, - witness::{PartialWitness, WitnessWrite}, - }, - plonk::circuit_builder::CircuitBuilder, +use plonky2::iop::{ + target::{BoolTarget, Target}, + witness::{PartialWitness, WitnessWrite}, }; use plonky2_ecgfp5::gadgets::curve::CurveTarget; pub use public_inputs::PublicInputs; @@ -43,36 +41,41 @@ pub struct Cell { } impl Cell { - pub(crate) fn assign_wires(&self, pw: &mut PartialWitness, wires: &CellWire) { + pub(crate) fn assign(&self, pw: &mut PartialWitness, wires: &CellWire) { pw.set_u256_target(&wires.value, self.value); pw.set_target(wires.identifier, self.identifier); pw.set_bool_target(wires.is_multiplier, self.is_multiplier); } - pub fn digest(&self) -> Digest { - map_to_curve_point(&self.to_fields()) + pub fn is_multiplier(&self) -> bool { + self.is_multiplier } - pub fn split_digest(&self) -> SplitDigestPoint { - let digest = self.digest(); + pub fn is_individual(&self) -> bool { + !self.is_multiplier + } + pub fn split_values_digest(&self) -> SplitDigestPoint { + let digest = self.values_digest(); SplitDigestPoint::from_single_digest_point(digest, self.is_multiplier) } - pub fn split_and_accumulate_digest(&self, child_digest: SplitDigestPoint) -> SplitDigestPoint { - let sd = self.split_digest(); - sd.accumulate(&child_digest) + pub fn split_and_accumulate_values_digest( + &self, + child_digest: SplitDigestPoint, + ) -> SplitDigestPoint { + let split_digest = self.split_values_digest(); + split_digest.accumulate(&child_digest) } -} - -impl ToFields for Cell { - fn to_fields(&self) -> Vec { - [self.identifier] - .into_iter() + fn values_digest(&self) -> Digest { + // D(identifier || pack_u32(value)) + let inputs = once(self.identifier) .chain(self.value.to_fields()) - .collect() + .collect_vec(); + + map_to_curve_point(&inputs) } } /// The basic wires generated for each circuit of the row tree #[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct CellWire { +pub struct CellWire { pub(crate) value: UInt256Target, pub(crate) identifier: Target, #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] @@ -80,41 +83,138 @@ pub(crate) struct CellWire { } impl CellWire { - pub(crate) fn new(b: &mut CircuitBuilder) -> Self { + pub fn new(b: &mut CBuilder) -> Self { Self { value: b.add_virtual_u256(), identifier: b.add_virtual_target(), is_multiplier: b.add_virtual_bool_target_safe(), } } - /// Returns the digest of the cell - pub(crate) fn digest(&self, b: &mut CircuitBuilder) -> CurveTarget { - b.map_to_curve_point(&self.to_targets()) + pub fn is_multiplier(&self) -> BoolTarget { + self.is_multiplier } - /// Returns the different digest, multiplier or individual - pub(crate) fn split_digest(&self, c: &mut CBuilder) -> SplitDigestTarget { - let d = self.digest(c); - SplitDigestTarget::from_single_digest_target(c, d, self.is_multiplier) + pub fn is_individual(&self, b: &mut CBuilder) -> BoolTarget { + b.not(self.is_multiplier) } - /// Returns the split digest from this cell added with the one from the proof. - /// NOTE: it calls agains split_digest, so call that first if you need the individual - /// SplitDigestTarget - pub(crate) fn split_and_accumulate_digest( + pub fn split_values_digest(&self, b: &mut CBuilder) -> SplitDigestTarget { + let digest = self.values_digest(b); + SplitDigestTarget::from_single_digest_target(b, digest, self.is_multiplier) + } + pub fn split_and_accumulate_values_digest( &self, - c: &mut CBuilder, - child_digest: SplitDigestTarget, + b: &mut CBuilder, + child_digest: &SplitDigestTarget, ) -> SplitDigestTarget { - let sd = self.split_digest(c); - sd.accumulate(c, &child_digest) + let split_digest = self.split_values_digest(b); + split_digest.accumulate(b, child_digest) + } + fn values_digest(&self, b: &mut CBuilder) -> CurveTarget { + // D(identifier || pack_u32(value)) + let inputs = once(self.identifier) + .chain(self.value.to_targets()) + .collect_vec(); + + b.map_to_curve_point(&inputs) } } -impl ToTargets for CellWire { - fn to_targets(&self) -> Vec { - self.identifier - .to_targets() - .into_iter() - .chain(self.value.to_targets()) - .collect::>() +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use mp2_common::{ + types::CURVE_TARGET_LEN, + utils::{Fieldable, FromFields}, + C, D, F, + }; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::field::types::Sample; + use plonky2_ecgfp5::{ + curve::curve::Point, + gadgets::curve::{CircuitBuilderEcGFp5, PartialWitnessCurve}, + }; + use rand::{thread_rng, Rng}; + use std::array; + + impl Cell { + pub(crate) fn sample(is_multiplier: bool) -> Self { + let rng = &mut thread_rng(); + + let identifier = rng.gen::().to_field(); + let value = U256::from_limbs(rng.gen()); + + Cell::new(identifier, value, is_multiplier) + } + } + + #[derive(Clone, Debug)] + struct TestCellCircuit<'a> { + cell: &'a Cell, + child_values_digest: &'a SplitDigestPoint, + } + + impl UserCircuit for TestCellCircuit<'_> { + // Cell wire + child values digest + child metadata digest + type Wires = (CellWire, SplitDigestTarget); + + fn build(b: &mut CBuilder) -> Self::Wires { + let [values_individual, values_multiplier] = + array::from_fn(|_| b.add_virtual_curve_target()); + + let child_values_digest = SplitDigestTarget { + individual: values_individual, + multiplier: values_multiplier, + }; + + let cell = CellWire::new(b); + let values_digest = cell.split_and_accumulate_values_digest(b, &child_values_digest); + + b.register_curve_public_input(values_digest.individual); + b.register_curve_public_input(values_digest.multiplier); + + (cell, child_values_digest) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.cell.assign(pw, &wires.0); + pw.set_curve_target( + wires.1.individual, + self.child_values_digest.individual.to_weierstrass(), + ); + pw.set_curve_target( + wires.1.multiplier, + self.child_values_digest.multiplier.to_weierstrass(), + ); + } + } + + #[test] + fn test_cells_tree_cell_circuit() { + let rng = &mut thread_rng(); + + let [values_individual, values_multiplier] = array::from_fn(|_| Point::sample(rng)); + let child_values_digest = &SplitDigestPoint { + individual: values_individual, + multiplier: values_multiplier, + }; + + let cell = &Cell::sample(rng.gen()); + let values_digests = cell.split_values_digest(); + let exp_values_digests = values_digests.accumulate(child_values_digest); + + let test_circuit = TestCellCircuit { + cell, + child_values_digest, + }; + + let proof = run_circuit::(test_circuit); + + let [values_individual, values_multiplier] = array::from_fn(|i| { + Point::from_fields( + &proof.public_inputs[i * CURVE_TARGET_LEN..(i + 1) * CURVE_TARGET_LEN], + ) + }); + + assert_eq!(values_individual, exp_values_digests.individual); + assert_eq!(values_multiplier, exp_values_digests.multiplier); } } diff --git a/verifiable-db/src/cells_tree/partial_node.rs b/verifiable-db/src/cells_tree/partial_node.rs index 32497b889..b865eab86 100644 --- a/verifiable-db/src/cells_tree/partial_node.rs +++ b/verifiable-db/src/cells_tree/partial_node.rs @@ -4,8 +4,11 @@ use super::{public_inputs::PublicInputs, Cell, CellWire}; use anyhow::Result; use derive_more::{From, Into}; use mp2_common::{ - poseidon::empty_poseidon_hash, public_inputs::PublicInputCommon, types::CBuilder, - utils::ToTargets, CHasher, D, F, + poseidon::{empty_poseidon_hash, H}, + public_inputs::PublicInputCommon, + types::CBuilder, + utils::ToTargets, + D, F, }; use plonky2::{ iop::{target::Target, witness::PartialWitness}, @@ -13,7 +16,7 @@ use plonky2::{ }; use recursion_framework::circuit_builder::CircuitLogicWires; use serde::{Deserialize, Serialize}; -use std::iter; +use std::iter::once; #[derive(Clone, Debug, Serialize, Deserialize, From, Into)] pub struct PartialNodeWires(CellWire); @@ -22,32 +25,39 @@ pub struct PartialNodeWires(CellWire); pub struct PartialNodeCircuit(Cell); impl PartialNodeCircuit { - pub fn build(b: &mut CBuilder, child_proof: PublicInputs) -> PartialNodeWires { + pub fn build(b: &mut CBuilder, p: PublicInputs) -> PartialNodeWires { let cell = CellWire::new(b); - - // h = Poseidon(p.H || Poseidon("") || identifier || value) - let child_hash = child_proof.node_hash(); - let empty_hash = empty_poseidon_hash(); - let empty_hash = b.constant_hash(*empty_hash); - let inputs: Vec<_> = child_hash - .elements - .iter() - .cloned() - .chain(empty_hash.elements) - .chain(iter::once(cell.identifier)) + let values_digests = + cell.split_and_accumulate_values_digest(b, &p.split_values_digest_target()); + + let is_individual = cell.is_individual(b); + let individual_cnt = b.add(is_individual.target, p.individual_counter_target()); + let multiplier_cnt = b.add(cell.is_multiplier().target, p.multiplier_counter_target()); + + /* + # since there is no sorting constraint among the nodes of this tree, to simplify + # the circuits, when we build a node with only one child, we can always place + # it as the left child + # NOTE: this is true only if we the "block" tree + h = H(p.H || H("") || identifier || value) + */ + let empty_hash = b.constant_hash(*empty_poseidon_hash()).to_targets(); + let inputs = p + .node_hash_target() + .into_iter() + .chain(empty_hash) + .chain(once(cell.identifier)) .chain(cell.value.to_targets()) .collect(); - let h = b.hash_n_to_hash_no_pad::(inputs).elements; - - // aggregate the digest of the child proof in the right digest - // digest_cell = p.digest_cell + D(identifier || value) - let split_digest = cell.split_and_accumulate_digest(b, child_proof.split_digest_target()); + let h = b.hash_n_to_hash_no_pad::(inputs); // Register the public inputs. PublicInputs::new( - &h, - &split_digest.individual.to_targets(), - &split_digest.multiplier.to_targets(), + &h.to_targets(), + &values_digests.individual.to_targets(), + &values_digests.multiplier.to_targets(), + &individual_cnt, + &multiplier_cnt, ) .register(b); @@ -56,7 +66,7 @@ impl PartialNodeCircuit { /// Assign the wires. fn assign(&self, pw: &mut PartialWitness, wires: &PartialNodeWires) { - self.0.assign_wires(pw, &wires.0); + self.0.assign(pw, &wires.0); } } @@ -66,7 +76,7 @@ impl CircuitLogicWires for PartialNodeWires { type Inputs = PartialNodeCircuit; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CBuilder, @@ -86,23 +96,10 @@ impl CircuitLogicWires for PartialNodeWires { #[cfg(test)] mod tests { use super::*; - use alloy::primitives::U256; - use mp2_common::{ - group_hashing::{add_curve_point, map_to_curve_point}, - poseidon::H, - utils::{Fieldable, ToFields}, - C, - }; - use mp2_test::{ - circuit::{run_circuit, UserCircuit}, - utils::random_vector, - }; - use plonky2::{ - field::types::Sample, hash::hash_types::NUM_HASH_OUT_ELTS, iop::witness::WitnessWrite, - plonk::config::Hasher, - }; - use plonky2_ecgfp5::curve::curve::Point; - use rand::{thread_rng, Rng}; + use itertools::Itertools; + use mp2_common::{poseidon::H, utils::ToFields, C}; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{field::types::Field, iop::witness::WitnessWrite, plonk::config::Hasher}; #[derive(Clone, Debug)] struct TestPartialNodeCircuit<'a> { @@ -115,8 +112,7 @@ mod tests { type Wires = (PartialNodeWires, Vec); fn build(b: &mut CBuilder) -> Self::Wires { - let child_pi = b.add_virtual_targets(PublicInputs::::TOTAL_LEN); - + let child_pi = b.add_virtual_targets(PublicInputs::::total_len()); let wires = PartialNodeCircuit::build(b, PublicInputs::from_slice(&child_pi)); (wires, child_pi) @@ -129,56 +125,71 @@ mod tests { } #[test] - fn test_cells_tree_partial_node_circuit() { - let mut rng = thread_rng(); - - let identifier = rng.gen::().to_field(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let value_fields = value.to_fields(); - - // Create the child public inputs. - let child_hash = random_vector::(NUM_HASH_OUT_ELTS).to_fields(); - let child_digest = Point::sample(&mut rng); - let dc = &child_digest.to_weierstrass().to_fields(); - let neutral = Point::NEUTRAL.to_fields(); - let child_pi = &PublicInputs { - h: &child_hash, - ind: dc, - mul: &neutral, - } - .to_vec(); + fn test_cells_tree_partial_node_individual() { + test_cells_tree_partial_multiplier(false, true); + test_cells_tree_partial_multiplier(false, false); + } + + #[test] + fn test_cells_tree_partial_node_multiplier() { + test_cells_tree_partial_multiplier(true, true); + test_cells_tree_partial_multiplier(true, false); + } + + fn test_cells_tree_partial_multiplier(is_multiplier: bool, is_child_multiplier: bool) { + let cell = Cell::sample(is_multiplier); + let id = cell.identifier; + let value = cell.value; + + let child_proof = &PublicInputs::::sample(is_child_multiplier); + let child_pi = PublicInputs::from_slice(child_proof); + + let values_digests = + cell.split_and_accumulate_values_digest(child_pi.split_values_digest_point()); let test_circuit = TestPartialNodeCircuit { - c: Cell { - identifier, - value, - is_multiplier: false, - } - .into(), - child_pi, + c: cell.into(), + child_pi: child_proof, }; + let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // Check the node Poseidon hash + + // Check the node hash { let empty_hash = empty_poseidon_hash(); - let inputs: Vec<_> = child_hash + let inputs = child_pi + .node_hash() + .to_fields() .into_iter() .chain(empty_hash.elements) - .chain(iter::once(identifier)) - .chain(value_fields.clone()) - .collect(); + .chain(once(id)) + .chain(value.to_fields()) + .collect_vec(); let exp_hash = H::hash_no_pad(&inputs); assert_eq!(pi.h, exp_hash.elements); } - // Check the cells digest - { - let inputs: Vec<_> = iter::once(identifier).chain(value_fields).collect(); - let exp_digest = map_to_curve_point(&inputs); - let exp_digest = add_curve_point(&[exp_digest, child_digest]).to_weierstrass(); - - assert_eq!(pi.individual_digest_point(), exp_digest); - } + // Check individual values digest + assert_eq!( + pi.individual_values_digest_point(), + values_digests.individual.to_weierstrass(), + ); + // Check multiplier values digest + assert_eq!( + pi.multiplier_values_digest_point(), + values_digests.multiplier.to_weierstrass(), + ); + // Check individual counter + let multiplier_cnt = F::from_bool(is_multiplier); + assert_eq!( + pi.individual_counter(), + F::ONE - multiplier_cnt + child_pi.individual_counter(), + ); + // Check multiplier counter + assert_eq!( + pi.multiplier_counter(), + multiplier_cnt + child_pi.multiplier_counter(), + ); } } diff --git a/verifiable-db/src/cells_tree/public_inputs.rs b/verifiable-db/src/cells_tree/public_inputs.rs index 608116ef5..189cf6e2b 100644 --- a/verifiable-db/src/cells_tree/public_inputs.rs +++ b/verifiable-db/src/cells_tree/public_inputs.rs @@ -1,128 +1,218 @@ //! Public inputs for Cells Tree Construction circuits + use mp2_common::{ digest::{SplitDigestPoint, SplitDigestTarget}, group_hashing::weierstrass_to_point, public_inputs::{PublicInputCommon, PublicInputRange}, - types::{CBuilder, GFp, CURVE_TARGET_LEN}, + types::{CBuilder, CURVE_TARGET_LEN}, utils::{FromFields, FromTargets}, F, }; use plonky2::{ - hash::hash_types::{HashOut, HashOutTarget, NUM_HASH_OUT_ELTS}, + hash::hash_types::{HashOut, NUM_HASH_OUT_ELTS}, iop::target::Target, }; use plonky2_ecgfp5::{curve::curve::WeierstrassPoint, gadgets::curve::CurveTarget}; -use std::{array, fmt::Debug}; +use std::iter::once; -// Cells Tree Construction public inputs: -// - `H : [4]F` : Poseidon hash of the subtree at this node -// - `DI : Digest[F]` : Cells digests accumulated up so far for INDIVIDUAL digest -// - `DM: Digest[F]` : Cells digests accumulated up so far for MULTIPLIER digest -const H_RANGE: PublicInputRange = 0..NUM_HASH_OUT_ELTS; -const DI_RANGE: PublicInputRange = H_RANGE.end..H_RANGE.end + CURVE_TARGET_LEN; -const DM_RANGE: PublicInputRange = DI_RANGE.end..DI_RANGE.end + CURVE_TARGET_LEN; +pub enum CellsTreePublicInputs { + // `H : F[4]` - Poseidon hash of the subtree at this node + NodeHash, + // - `individual_vd : Digest` - Cumulative digest of values of cells accumulated as individual + IndividualValuesDigest, + // - `multiplier_vd : Digest` - Cumulative digest of values of cells accumulated as multiplier + MultiplierValuesDigest, + // - `individual_counter : F` - Counter of the number of cells accumulated so far as individual + IndividualCounter, + // - `multiplier_counter : F` - Counter of the number of cells accumulated so far as multiplier + MultiplierCounter, +} /// Public inputs for Cells Tree Construction #[derive(Clone, Debug)] pub struct PublicInputs<'a, T> { pub(crate) h: &'a [T], - pub(crate) ind: &'a [T], - pub(crate) mul: &'a [T], + pub(crate) individual_vd: &'a [T], + pub(crate) multiplier_vd: &'a [T], + pub(crate) individual_cnt: &'a T, + pub(crate) multiplier_cnt: &'a T, } -impl PublicInputCommon for PublicInputs<'_, Target> { - const RANGES: &'static [PublicInputRange] = &[H_RANGE, DI_RANGE, DM_RANGE]; +const NUM_PUBLIC_INPUTS: usize = CellsTreePublicInputs::MultiplierCounter as usize + 1; - fn register_args(&self, cb: &mut CBuilder) { - cb.register_public_inputs(self.h); - cb.register_public_inputs(self.ind); - cb.register_public_inputs(self.mul); - } -} +impl<'a, T: Clone> PublicInputs<'a, T> { + const PI_RANGES: [PublicInputRange; NUM_PUBLIC_INPUTS] = [ + Self::to_range(CellsTreePublicInputs::NodeHash), + Self::to_range(CellsTreePublicInputs::IndividualValuesDigest), + Self::to_range(CellsTreePublicInputs::MultiplierValuesDigest), + Self::to_range(CellsTreePublicInputs::IndividualCounter), + Self::to_range(CellsTreePublicInputs::MultiplierCounter), + ]; -impl PublicInputs<'_, GFp> { - /// Get the cells digest point. - pub fn individual_digest_point(&self) -> WeierstrassPoint { - WeierstrassPoint::from_fields(self.ind) + const SIZES: [usize; NUM_PUBLIC_INPUTS] = [ + // Poseidon hash of the subtree at this node + NUM_HASH_OUT_ELTS, + // Cumulative digest of values of cells accumulated as individual + CURVE_TARGET_LEN, + // Cumulative digest of values of cells accumulated as multiplier + CURVE_TARGET_LEN, + // Counter of the number of cells accumulated so far as individual + 1, + // Counter of the number of cells accumulated so far as multiplier + 1, + ]; + + pub(crate) const fn to_range(pi: CellsTreePublicInputs) -> PublicInputRange { + let mut i = 0; + let mut offset = 0; + let pi_pos = pi as usize; + while i < pi_pos { + offset += Self::SIZES[i]; + i += 1; + } + offset..offset + Self::SIZES[pi_pos] } - pub fn multiplier_digest_point(&self) -> WeierstrassPoint { - WeierstrassPoint::from_fields(self.mul) + + pub const fn total_len() -> usize { + Self::to_range(CellsTreePublicInputs::MultiplierCounter).end } - pub fn split_digest_point(&self) -> SplitDigestPoint { - SplitDigestPoint { - individual: weierstrass_to_point(&self.individual_digest_point()), - multiplier: weierstrass_to_point(&self.multiplier_digest_point()), - } + + pub fn to_node_hash_raw(&self) -> &[T] { + self.h } -} -impl PublicInputs<'_, Target> { - /// Get the Poseidon hash of the subtree at this node. - pub fn node_hash(&self) -> HashOutTarget { - self.h.try_into().unwrap() + pub fn to_individual_values_digest_raw(&self) -> &[T] { + self.individual_vd } - /// Get the individual digest target. - pub fn individual_digest_target(&self) -> CurveTarget { - CurveTarget::from_targets(self.ind) + pub fn to_multiplier_values_digest_raw(&self) -> &[T] { + self.multiplier_vd } - /// Get the cells multiplier digest - pub fn multiplier_digest_target(&self) -> CurveTarget { - CurveTarget::from_targets(self.mul) + pub fn to_individual_counter_raw(&self) -> &T { + self.individual_cnt } - pub fn split_digest_target(&self) -> SplitDigestTarget { - SplitDigestTarget { - individual: self.individual_digest_target(), - multiplier: self.multiplier_digest_target(), - } + + pub fn to_multiplier_counter_raw(&self) -> &T { + self.multiplier_cnt } -} -impl<'a, T: Copy> PublicInputs<'a, T> { - /// Total length of the public inputs - pub(crate) const TOTAL_LEN: usize = DM_RANGE.end; + pub fn from_slice(input: &'a [T]) -> Self { + assert!( + input.len() >= Self::total_len(), + "Input slice too short to build cells tree public inputs, must be at least {} elements", + Self::total_len(), + ); - /// Create a new public inputs. - pub fn new(h: &'a [T], ind: &'a [T], mul: &'a [T]) -> Self { - Self { h, ind, mul } + Self { + h: &input[Self::PI_RANGES[0].clone()], + individual_vd: &input[Self::PI_RANGES[1].clone()], + multiplier_vd: &input[Self::PI_RANGES[2].clone()], + individual_cnt: &input[Self::PI_RANGES[3].clone()][0], + multiplier_cnt: &input[Self::PI_RANGES[4].clone()][0], + } } - /// Create from a slice. - pub fn from_slice(pi: &'a [T]) -> Self { - assert!(pi.len() >= Self::TOTAL_LEN); + pub fn new( + h: &'a [T], + individual_vd: &'a [T], + multiplier_vd: &'a [T], + individual_cnt: &'a T, + multiplier_cnt: &'a T, + ) -> Self { Self { - h: &pi[H_RANGE], - ind: &pi[DI_RANGE], - mul: &pi[DM_RANGE], + h, + individual_vd, + multiplier_vd, + individual_cnt, + multiplier_cnt, } } - /// Combine to a vector. pub fn to_vec(&self) -> Vec { self.h .iter() - .chain(self.ind) - .chain(self.mul) + .chain(self.individual_vd) + .chain(self.multiplier_vd) + .chain(once(self.individual_cnt)) + .chain(once(self.multiplier_cnt)) .cloned() .collect() } +} - pub fn h_raw(&self) -> &'a [T] { - self.h +impl PublicInputCommon for PublicInputs<'_, Target> { + const RANGES: &'static [PublicInputRange] = &Self::PI_RANGES; + + fn register_args(&self, cb: &mut CBuilder) { + cb.register_public_inputs(self.h); + cb.register_public_inputs(self.individual_vd); + cb.register_public_inputs(self.multiplier_vd); + cb.register_public_input(*self.individual_cnt); + cb.register_public_input(*self.multiplier_cnt); + } +} + +impl PublicInputs<'_, Target> { + pub fn node_hash_target(&self) -> [Target; NUM_HASH_OUT_ELTS] { + self.to_node_hash_raw().try_into().unwrap() + } + + pub fn individual_values_digest_target(&self) -> CurveTarget { + CurveTarget::from_targets(self.individual_vd) + } + + pub fn multiplier_values_digest_target(&self) -> CurveTarget { + CurveTarget::from_targets(self.multiplier_vd) + } + + pub fn split_values_digest_target(&self) -> SplitDigestTarget { + SplitDigestTarget { + individual: self.individual_values_digest_target(), + multiplier: self.multiplier_values_digest_target(), + } + } + + pub fn individual_counter_target(&self) -> Target { + *self.to_individual_counter_raw() + } + + pub fn multiplier_counter_target(&self) -> Target { + *self.to_multiplier_counter_raw() } } impl PublicInputs<'_, F> { - pub fn root_hash_hashout(&self) -> HashOut { - HashOut { - elements: array::from_fn(|i| self.h[i]), + pub fn node_hash(&self) -> HashOut { + HashOut::from_partial(self.to_node_hash_raw()) + } + + pub fn individual_values_digest_point(&self) -> WeierstrassPoint { + WeierstrassPoint::from_fields(self.individual_vd) + } + + pub fn multiplier_values_digest_point(&self) -> WeierstrassPoint { + WeierstrassPoint::from_fields(self.multiplier_vd) + } + + pub fn split_values_digest_point(&self) -> SplitDigestPoint { + SplitDigestPoint { + individual: weierstrass_to_point(&self.individual_values_digest_point()), + multiplier: weierstrass_to_point(&self.multiplier_values_digest_point()), } } + + pub fn individual_counter(&self) -> F { + *self.to_individual_counter_raw() + } + + pub fn multiplier_counter(&self) -> F { + *self.to_multiplier_counter_raw() + } } #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; use mp2_common::{utils::ToFields, C, D, F}; use mp2_test::{ @@ -137,21 +227,48 @@ mod tests { }, }; use plonky2_ecgfp5::curve::curve::Point; - use rand::thread_rng; + use rand::{thread_rng, Rng}; + use std::slice; + + impl PublicInputs<'_, F> { + pub(crate) fn sample(is_multiplier: bool) -> Vec { + let rng = &mut thread_rng(); + + let h = random_vector::(NUM_HASH_OUT_ELTS).to_fields(); + + let point_zero = WeierstrassPoint::NEUTRAL.to_fields(); + let values_digest = Point::sample(rng).to_weierstrass().to_fields(); + let [individual_vd, multiplier_vd] = if is_multiplier { + [point_zero.clone(), values_digest] + } else { + [values_digest, point_zero] + }; + let [individual_cnt, multiplier_cnt] = F::rand_array(); + + PublicInputs::new( + &h, + &individual_vd, + &multiplier_vd, + &individual_cnt, + &multiplier_cnt, + ) + .to_vec() + } + } #[derive(Clone, Debug)] - struct TestPICircuit<'a> { + struct TestPublicInputs<'a> { exp_pi: &'a [F], } - impl UserCircuit for TestPICircuit<'_> { + impl UserCircuit for TestPublicInputs<'_> { type Wires = Vec; fn build(b: &mut CBuilder) -> Self::Wires { - let pi = b.add_virtual_targets(PublicInputs::::TOTAL_LEN); - PublicInputs::from_slice(&pi).register(b); + let exp_pi = b.add_virtual_targets(PublicInputs::::total_len()); + PublicInputs::from_slice(&exp_pi).register(b); - pi + exp_pi } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { @@ -161,21 +278,35 @@ mod tests { #[test] fn test_cells_tree_public_inputs() { - let mut rng = thread_rng(); + let rng = &mut thread_rng(); + let is_multiplier = rng.gen(); - // Prepare the public inputs. - let h = &random_vector::(NUM_HASH_OUT_ELTS).to_fields(); - let dc = &Point::sample(&mut rng).to_weierstrass().to_fields(); - let exp_pi = PublicInputs { - h, - ind: dc, - mul: dc, - }; - let exp_pi = &exp_pi.to_vec(); - - let test_circuit = TestPICircuit { exp_pi }; + let exp_pi = &PublicInputs::sample(is_multiplier); + let test_circuit = TestPublicInputs { exp_pi }; let proof = run_circuit::(test_circuit); - assert_eq!(&proof.public_inputs, exp_pi); + + // Check if the public inputs are constructed correctly. + let pi = PublicInputs::from_slice(&proof.public_inputs); + assert_eq!( + &exp_pi[PublicInputs::::to_range(CellsTreePublicInputs::NodeHash)], + pi.to_node_hash_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(CellsTreePublicInputs::IndividualValuesDigest)], + pi.to_individual_values_digest_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(CellsTreePublicInputs::MultiplierValuesDigest)], + pi.to_multiplier_values_digest_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(CellsTreePublicInputs::IndividualCounter)], + slice::from_ref(pi.to_individual_counter_raw()), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(CellsTreePublicInputs::MultiplierCounter)], + slice::from_ref(pi.to_multiplier_counter_raw()), + ); } } diff --git a/verifiable-db/src/query/circuits/non_existence.rs b/verifiable-db/src/query/circuits/non_existence.rs index 18cc90303..ebdd876b1 100644 --- a/verifiable-db/src/query/circuits/non_existence.rs +++ b/verifiable-db/src/query/circuits/non_existence.rs @@ -139,7 +139,7 @@ where // since they are exposed as public inputs let index_path = MerklePathWithNeighborsGadget::build( b, - index_node_value.clone(), + index_node_value, index_node_subtree_hash, primary_index, ); diff --git a/verifiable-db/src/query/merkle_path.rs b/verifiable-db/src/query/merkle_path.rs index 571cc8bb2..693088692 100644 --- a/verifiable-db/src/query/merkle_path.rs +++ b/verifiable-db/src/query/merkle_path.rs @@ -540,8 +540,8 @@ where embedded_tree_hash: end_node_tree_hash, child_hashes: [left_child_hash, right_child_hash], value: end_node_value, - min: end_node_info.node_min.clone(), - max: end_node_info.node_max.clone(), + min: end_node_info.node_min, + max: end_node_info.node_max, }; let end_node_hash = end_node.compute_node_hash(b, index_id); let (inputs, path) = MerklePathGadget::build_path(b, end_node_hash, index_id); @@ -665,8 +665,8 @@ where self.path_gadget.assign(pw, &wires.path_inputs); pw.set_u256_target_arr( &[ - wires.end_node_inputs.node_min.clone(), - wires.end_node_inputs.node_max.clone(), + wires.end_node_inputs.node_min, + wires.end_node_inputs.node_max, ], &[self.end_node_min, self.end_node_max], ); @@ -869,7 +869,7 @@ pub(crate) mod tests { let end_node_value = c.add_virtual_u256_unsafe(); let merkle_path_wires = MerklePathWithNeighborsGadget::build( c, - end_node_value.clone(), + end_node_value, end_node_tree_hash, index_id, ); diff --git a/verifiable-db/src/query/output_computation.rs b/verifiable-db/src/query/output_computation.rs index 70b5f232c..a58d7506b 100644 --- a/verifiable-db/src/query/output_computation.rs +++ b/verifiable-db/src/query/output_computation.rs @@ -96,8 +96,8 @@ where // which each field may be out of range of an Uint32 (to combine an Uint256). sum_value = b.select_u256(is_op_id, &u256_zero, &sum_value); } - let mut min_value = sum_value.clone(); - let mut max_value = sum_value.clone(); + let mut min_value = sum_value; + let mut max_value = sum_value; for p in outputs[1..].iter() { // Get the current proof value. let mut value = p.value_target_at_index(i); diff --git a/verifiable-db/src/query/row_chunk_gadgets/aggregate_chunks.rs b/verifiable-db/src/query/row_chunk_gadgets/aggregate_chunks.rs index 8cd1d1ec4..c38913687 100644 --- a/verifiable-db/src/query/row_chunk_gadgets/aggregate_chunks.rs +++ b/verifiable-db/src/query/row_chunk_gadgets/aggregate_chunks.rs @@ -196,25 +196,25 @@ mod tests { array::from_fn(|_| b.add_virtual_hash()); let left_boundary_row_path = MerklePathWithNeighborsGadget::build( b, - left_boundary_row_value.clone(), + left_boundary_row_value, left_boundary_row_subtree_hash, secondary_index_id, ); let left_boundary_index_path = MerklePathWithNeighborsGadget::build( b, - left_boundary_index_value.clone(), + left_boundary_index_value, left_boundary_row_path.root, primary_index_id, ); let right_boundary_row_path = MerklePathWithNeighborsGadget::build( b, - right_boundary_row_value.clone(), + right_boundary_row_value, right_boundary_row_subtree_hash, secondary_index_id, ); let right_boundary_index_path = MerklePathWithNeighborsGadget::build( b, - right_boundary_index_value.clone(), + right_boundary_index_value, right_boundary_row_path.root, primary_index_id, ); diff --git a/verifiable-db/src/query/row_chunk_gadgets/consecutive_rows.rs b/verifiable-db/src/query/row_chunk_gadgets/consecutive_rows.rs index 984f6828d..b1e545036 100644 --- a/verifiable-db/src/query/row_chunk_gadgets/consecutive_rows.rs +++ b/verifiable-db/src/query/row_chunk_gadgets/consecutive_rows.rs @@ -294,13 +294,13 @@ mod tests { let index_id = c.add_virtual_target(); let first_node_path = MerklePathWithNeighborsGadget::build( c, - first_node_value.clone(), + first_node_value, first_node_tree_hash, index_id, ); let second_node_path = MerklePathWithNeighborsGadget::build( c, - second_node_value.clone(), + second_node_value, second_node_tree_hash, index_id, ); diff --git a/verifiable-db/src/query/row_chunk_gadgets/row_process_gadget.rs b/verifiable-db/src/query/row_chunk_gadgets/row_process_gadget.rs index 30b9c84b6..d9d9bcb80 100644 --- a/verifiable-db/src/query/row_chunk_gadgets/row_process_gadget.rs +++ b/verifiable-db/src/query/row_chunk_gadgets/row_process_gadget.rs @@ -236,7 +236,7 @@ where let [primary_index_id, secondary_index_id] = array::from_fn(|i| hash_input_wires.column_extraction_wires.column_ids[i]); let [primary_index_value, secondary_index_value] = - array::from_fn(|i| value_wires.input_wires.column_values[i].clone()); + array::from_fn(|i| value_wires.input_wires.column_values[i]); let row_path = MerklePathWithNeighborsGadget::build( b, secondary_index_value, diff --git a/verifiable-db/src/query/universal_circuit/basic_operation.rs b/verifiable-db/src/query/universal_circuit/basic_operation.rs index 793a062cd..cd3a38afa 100644 --- a/verifiable-db/src/query/universal_circuit/basic_operation.rs +++ b/verifiable-db/src/query/universal_circuit/basic_operation.rs @@ -234,7 +234,7 @@ impl BasicOperationInputs { let op_selector = b.add_virtual_target(); let input_wires = BasicOperationInputWires { - constant_operand: constant_operand.clone(), + constant_operand: *constant_operand, placeholder_values: placeholder_values.to_vec().try_into().unwrap(), placeholder_ids, first_input_selector, diff --git a/verifiable-db/src/query/universal_circuit/cells.rs b/verifiable-db/src/query/universal_circuit/cells.rs index f57e5b04f..23ac0db56 100644 --- a/verifiable-db/src/query/universal_circuit/cells.rs +++ b/verifiable-db/src/query/universal_circuit/cells.rs @@ -16,7 +16,7 @@ use ryhope::tree::{ TreeTopology, }; use std::iter::once; -type CellTree = sbbst::Tree; +type CellTree = sbbst::IncrementalTree; type CellTreeKey = ::Key; /// Re-compute the root hash of the cells tree by the column identifiers and values @@ -47,7 +47,7 @@ pub(crate) fn build_cells_tree( assert_eq!(input_len, input_values.len()); assert_eq!(input_len, is_real_value.len()); - let sbbst_state = sbbst::Tree::with_capacity(input_len); + let sbbst_state = sbbst::IncrementalTree::with_capacity(input_len); let root_key = sbbst_state.root(); build_cells_subtree_at_key( @@ -140,7 +140,7 @@ mod tests { // Compute the root hash of cells tree. let (input_ids, input_values): (Vec<_>, Vec<_>) = - input_cells.iter().map(|c| (c.id, c.value.clone())).unzip(); + input_cells.iter().map(|c| (c.id, c.value)).unzip(); let real_root_hash = build_cells_tree(b, &input_values, &input_ids, &is_real_cell); // Check the output root hash. diff --git a/verifiable-db/src/query/universal_circuit/output_with_aggregation.rs b/verifiable-db/src/query/universal_circuit/output_with_aggregation.rs index cee481838..3b99a1069 100644 --- a/verifiable-db/src/query/universal_circuit/output_with_aggregation.rs +++ b/verifiable-db/src/query/universal_circuit/output_with_aggregation.rs @@ -87,7 +87,7 @@ impl OutputComponentValueWires for ValueWires Self::FirstT { - self.output_values[0].clone() + self.output_values[0] } fn other_output_values(&self) -> &[UInt256Target] { diff --git a/verifiable-db/src/query/universal_circuit/universal_query_gadget.rs b/verifiable-db/src/query/universal_circuit/universal_query_gadget.rs index 77991622b..08df6a889 100644 --- a/verifiable-db/src/query/universal_circuit/universal_query_gadget.rs +++ b/verifiable-db/src/query/universal_circuit/universal_query_gadget.rs @@ -763,8 +763,8 @@ where &output_component_wires.computational_hash(), ); - let min_secondary = min_query_secondary.get_bound_value().clone(); - let max_secondary = max_query_secondary.get_bound_value().clone(); + let min_secondary = *min_query_secondary.get_bound_value(); + let max_secondary = *max_query_secondary.get_bound_value(); let num_bound_overflows = QueryBoundTarget::num_overflows_for_query_bound_operations( b, &min_query_secondary, @@ -1043,7 +1043,7 @@ where if i == 0 { self.first_output.as_u256_target() } else { - self.other_outputs[i - 1].clone() + self.other_outputs[i - 1] } } diff --git a/verifiable-db/src/query/utils.rs b/verifiable-db/src/query/utils.rs index 1ee5e70db..873e9fd58 100644 --- a/verifiable-db/src/query/utils.rs +++ b/verifiable-db/src/query/utils.rs @@ -321,7 +321,7 @@ impl NodeInfoTarget { .into_iter() .for_each(|(target, value)| pw.set_hash_target(target, value)); pw.set_u256_target_arr( - &[self.min.clone(), self.max.clone(), self.value.clone()], + &[self.min, self.max, self.value], &[inputs.min, inputs.max, inputs.value], ); } diff --git a/verifiable-db/src/revelation/placeholders_check.rs b/verifiable-db/src/revelation/placeholders_check.rs index cd53ce875..49b0bdc7f 100644 --- a/verifiable-db/src/revelation/placeholders_check.rs +++ b/verifiable-db/src/revelation/placeholders_check.rs @@ -380,7 +380,7 @@ pub(crate) fn check_placeholders( "random_access function cannot handle more than 64 elements" ); padded_placeholder_ids.resize(pad_len, placeholder_ids[0]); - padded_placeholder_values.resize(pad_len, placeholder_values[0].clone()); + padded_placeholder_values.resize(pad_len, placeholder_values[0]); let mut check_placeholder_pair = |id: &Target, value, pos| { // Check that the pair (id, value) is same as: diff --git a/verifiable-db/src/row_tree/api.rs b/verifiable-db/src/row_tree/api.rs index e8fa9b90c..932152aea 100644 --- a/verifiable-db/src/row_tree/api.rs +++ b/verifiable-db/src/row_tree/api.rs @@ -1,6 +1,6 @@ use alloy::primitives::U256; use anyhow::Result; -use mp2_common::{default_config, proof::ProofWithVK, C, D, F}; +use mp2_common::{default_config, proof::ProofWithVK, types::HashOutput, C, D, F}; use plonky2::{field::types::Field, hash::hash_types::HashOut}; use recursion_framework::{ circuit_builder::{CircuitWithUniversalVerifier, CircuitWithUniversalVerifierBuilder}, @@ -14,6 +14,7 @@ use super::{ full_node::{self, FullNodeCircuit}, leaf::{self, LeafCircuit}, partial_node::{self, PartialNodeCircuit}, + secondary_index_cell::SecondaryIndexCell, PublicInputs, }; @@ -38,7 +39,7 @@ pub struct PublicParameters { row_set: RecursiveCircuits, } -const ROW_IO_LEN: usize = super::public_inputs::TOTAL_LEN; +const ROW_IO_LEN: usize = super::PublicInputs::::total_len(); impl PublicParameters { pub fn build(cells_set: &RecursiveCircuits) -> Self { @@ -184,11 +185,13 @@ impl CircuitInput { identifier: u64, value: U256, is_multiplier: bool, + row_unique_data: HashOutput, cells_proof: Vec, ) -> Result { - let circuit = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); + let cell = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); + let secondary_index_cell = SecondaryIndexCell::new(cell, row_unique_data.into()); Ok(CircuitInput::Leaf { - witness: circuit.into(), + witness: secondary_index_cell.into(), cells_proof, }) } @@ -197,28 +200,33 @@ impl CircuitInput { identifier: u64, value: U256, is_multiplier: bool, + row_unique_data: HashOutput, left_proof: Vec, right_proof: Vec, cells_proof: Vec, ) -> Result { - let circuit = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); + let cell = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); + let secondary_index_cell = SecondaryIndexCell::new(cell, row_unique_data.into()); Ok(CircuitInput::Full { - witness: circuit.into(), + witness: secondary_index_cell.into(), left_proof, right_proof, cells_proof, }) } + pub fn partial( identifier: u64, value: U256, is_multiplier: bool, is_child_left: bool, + row_unique_data: HashOutput, child_proof: Vec, cells_proof: Vec, ) -> Result { - let tuple = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); - let witness = PartialNodeCircuit::new(tuple, is_child_left); + let cell = Cell::new(F::from_canonical_u64(identifier), value, is_multiplier); + let secondary_index_cell = SecondaryIndexCell::new(cell, row_unique_data.into()); + let witness = PartialNodeCircuit::new(secondary_index_cell, is_child_left); Ok(CircuitInput::Partial { witness, child_proof, @@ -229,20 +237,21 @@ impl CircuitInput { pub fn extract_hash_from_proof(proof: &[u8]) -> Result> { let p = ProofWithVK::deserialize(proof)?; - Ok(PublicInputs::from_slice(&p.proof.public_inputs).root_hash_hashout()) + Ok(PublicInputs::from_slice(&p.proof.public_inputs).root_hash()) } #[cfg(test)] mod test { - use crate::{cells_tree, row_tree::public_inputs::PublicInputs}; - use super::*; + use crate::cells_tree; + use itertools::Itertools; use mp2_common::{ + group_hashing::weierstrass_to_point, poseidon::{empty_poseidon_hash, H}, utils::ToFields, F, }; - use mp2_test::{log::init_logging, utils::weierstrass_to_point}; + use mp2_test::log::init_logging; use partial_node::test::partial_safety_check; use plonky2::{ field::types::{PrimeField64, Sample}, @@ -251,10 +260,10 @@ mod test { circuit_data::VerifierOnlyCircuitData, config::Hasher, proof::ProofWithPublicInputs, }, }; - use plonky2_ecgfp5::curve::curve::Point; use recursion_framework::framework_testing::TestingRecursiveCircuits; + use std::iter::once; - const CELL_IO_LEN: usize = cells_tree::PublicInputs::::TOTAL_LEN; + const CELL_IO_LEN: usize = cells_tree::PublicInputs::::total_len(); struct TestParams { cells_test: TestingRecursiveCircuits, @@ -263,17 +272,17 @@ mod test { // to save on test time cells_proof: ProofWithPublicInputs, cells_vk: VerifierOnlyCircuitData, - leaf1: Cell, - leaf2: Cell, - full: Cell, - partial: Cell, + leaf1: SecondaryIndexCell, + leaf2: SecondaryIndexCell, + full: SecondaryIndexCell, + partial: SecondaryIndexCell, } impl TestParams { fn build() -> Result { let cells_test = TestingRecursiveCircuits::::default(); let params = PublicParameters::build(cells_test.get_recursive_circuit_set()); - let cells_pi = Self::rand_cells_pi(); + let cells_pi = cells_tree::PublicInputs::sample(false); let cells_proof = cells_test.generate_input_proofs::<1>([cells_pi.clone().try_into().unwrap()])?; let cells_vk = cells_test.verifier_data_for_input_proofs::<1>()[0].clone(); @@ -295,10 +304,16 @@ mod test { params, cells_proof: cells_proof[0].clone(), cells_vk, - leaf1: Cell::new(identifier, v1, false), - leaf2: Cell::new(identifier, v2, false), - full: Cell::new(identifier, v_full, false), - partial: Cell::new(identifier, v_partial, false), + leaf1: SecondaryIndexCell::new(Cell::new(identifier, v1, false), HashOut::rand()), + leaf2: SecondaryIndexCell::new(Cell::new(identifier, v2, false), HashOut::rand()), + full: SecondaryIndexCell::new( + Cell::new(identifier, v_full, false), + HashOut::rand(), + ), + partial: SecondaryIndexCell::new( + Cell::new(identifier, v_partial, false), + HashOut::rand(), + ), }) } @@ -308,19 +323,6 @@ mod test { fn cells_proof_vk(&self) -> ProofWithVK { ProofWithVK::new(self.cells_proof.clone(), self.cells_vk.clone()) } - - fn rand_cells_pi() -> Vec { - // generate cells tree input and fake proof - let cells_hash = HashOut::rand().to_fields(); - let cells_digest = Point::rand().to_weierstrass().to_fields(); - let cells_pi = cells_tree::PublicInputs::new( - &cells_hash, - &cells_digest, - &Point::NEUTRAL.to_fields(), - ) - .to_vec(); - cells_pi - } } #[test] @@ -336,29 +338,35 @@ mod test { log::info!("Generating full proof (from leaf 1 and leaf 2)"); let full_proof = generate_full_proof(¶ms, children_proof)?; log::info!("Generating partial proof (from full proof)"); - let _ = generate_partial_proof(¶ms, params.partial.clone(), true, full_proof)?; + let _ = generate_partial_proof(¶ms, true, full_proof)?; log::info!("Test done"); Ok(()) } fn generate_partial_proof( p: &TestParams, - tuple: Cell, is_left: bool, child_proof_buff: Vec, ) -> Result> { + let secondary_index_cell = &p.partial; + let id = secondary_index_cell.cell.identifier; + let value = secondary_index_cell.cell.value; + let row_unique_data = secondary_index_cell.row_unique_data.into(); + let row_digest = secondary_index_cell.digest(&p.cells_pi()); + let child_proof = ProofWithVK::deserialize(&child_proof_buff)?; let child_pi = PublicInputs::from_slice(&child_proof.proof.public_inputs); - let child_min = child_pi.min_value_u256(); - let child_max = child_pi.max_value_u256(); + let child_min = child_pi.min_value(); + let child_max = child_pi.max_value(); - partial_safety_check(child_min, child_max, tuple.value, is_left); + partial_safety_check(child_min, child_max, value, is_left); let input = CircuitInput::partial( - tuple.identifier.to_canonical_u64(), - tuple.value, + id.to_canonical_u64(), + value, false, is_left, + row_unique_data, child_proof_buff.clone(), p.cells_proof_vk().serialize()?, )?; @@ -367,47 +375,66 @@ mod test { .generate_proof(input, p.cells_test.get_recursive_circuit_set().clone())?; let pi = ProofWithVK::deserialize(&proof)?.proof.public_inputs; let pi = PublicInputs::from_slice(&pi); + + // Check root hash { // node_min = left ? child_proof.min : index_value // node_max = left ? index_value : child_proof.max let (node_min, node_max) = match is_left { - true => (pi.min_value_u256(), tuple.value), - false => (tuple.value, pi.max_value_u256()), + true => (pi.min_value(), value), + false => (value, pi.max_value()), }; - - let child_hash = child_pi.root_hash_hashout(); - let empty_hash = empty_poseidon_hash(); + // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H + let child_hash = child_pi.root_hash().to_fields(); + let empty_hash = empty_poseidon_hash().to_fields(); let input_hash = match is_left { - true => [child_hash.to_fields(), empty_hash.to_fields()].concat(), - false => [empty_hash.to_fields(), child_hash.to_fields()].concat(), + true => [child_hash, empty_hash].concat(), + false => [empty_hash, child_hash].concat(), }; let inputs = input_hash - .iter() - .chain(node_min.to_fields().iter()) - .chain(node_max.to_fields().iter()) - .chain(tuple.to_fields().iter()) - .chain(p.cells_pi().h_raw().iter()) - .cloned() - .collect::>(); - let hash = H::hash_no_pad(&inputs); - assert_eq!(hash, pi.root_hash_hashout()); - - // final_digest = HashToInt(mul_digest) * D(ind_digest) + row_proof.digest() - let split_digest = tuple.split_and_accumulate_digest(p.cells_pi().split_digest_point()); - let res = split_digest.cond_combine_to_row_digest(); - // then adding with the rest of the rows digest, the other nodes - let res = res + weierstrass_to_point(&child_pi.rows_digest_field()); - assert_eq!(res.to_weierstrass(), pi.rows_digest_field()); + .into_iter() + .chain(node_min.to_fields()) + .chain(node_max.to_fields()) + .chain(once(id)) + .chain(value.to_fields()) + .chain(p.cells_pi().node_hash().to_fields()) + .collect_vec(); + let exp_root_hash = H::hash_no_pad(&inputs); + assert_eq!(pi.root_hash(), exp_root_hash); } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + (row_digest.individual_vd + weierstrass_to_point(&child_pi.individual_digest_point())) + .to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check minimum value + assert_eq!(pi.min_value(), value.min(child_min)); + // Check maximum value + assert_eq!(pi.max_value(), value.max(child_max)); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); + Ok(vec![]) } fn generate_full_proof(p: &TestParams, child_proof: [Vec; 2]) -> Result> { - let tuple = p.full.clone(); + let secondary_index_cell = &p.full; + let id = secondary_index_cell.cell.identifier; + let value = secondary_index_cell.cell.value; + let row_unique_data = secondary_index_cell.row_unique_data.into(); + let row_digest = secondary_index_cell.digest(&p.cells_pi()); + let input = CircuitInput::full( - tuple.identifier.to_canonical_u64(), - tuple.value, + id.to_canonical_u64(), + value, false, + row_unique_data, child_proof[0].to_vec(), child_proof[1].to_vec(), p.cells_proof_vk().serialize()?, @@ -416,53 +443,62 @@ mod test { let left_pi = PublicInputs::from_slice(&left_proof.proof.public_inputs); let right_proof = ProofWithVK::deserialize(&child_proof[1])?; let right_pi = PublicInputs::from_slice(&right_proof.proof.public_inputs); - assert!(left_pi.max_value_u256() < tuple.value); - assert!(tuple.value < right_pi.min_value_u256()); + assert!(left_pi.max_value() < value); + assert!(value < right_pi.min_value()); let proof = p .params .generate_proof(input, p.cells_test.get_recursive_circuit_set().clone())?; let pi = ProofWithVK::deserialize(&proof)?.proof.public_inputs; let pi = PublicInputs::from_slice(&pi); + + // Check root hash { - // H(left_child_hash,right_child_hash,min,max,index_identifier,index_value,cells_tree_hash) - // min coming from left - // max coming from right - let inputs: Vec<_> = left_pi - .root_hash_hashout() + // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H + let inputs = left_pi + .root_hash() .to_fields() - .iter() - .chain(right_pi.root_hash_hashout().to_fields().iter()) - .chain(left_pi.min_value_u256().to_fields().iter()) - .chain(right_pi.max_value_u256().to_fields().iter()) - .chain(tuple.to_fields().iter()) - .chain(p.cells_pi().h_raw().iter()) - .cloned() - .collect(); - let exp_hash = H::hash_no_pad(&inputs); - assert_eq!(pi.root_hash_hashout(), exp_hash); - - { - // final_digest = HashToInt(mul_digest) * D(ind_digest) + p1.digest() + p2.digest() - let split_digest = - tuple.split_and_accumulate_digest(p.cells_pi().split_digest_point()); - let row_digest = split_digest.cond_combine_to_row_digest(); - - let p1dr = weierstrass_to_point(&left_pi.rows_digest_field()); - let p2dr = weierstrass_to_point(&right_pi.rows_digest_field()); - let result_digest = p1dr + p2dr + row_digest; - assert_eq!(result_digest.to_weierstrass(), pi.rows_digest_field()); - } + .into_iter() + .chain(right_pi.root_hash().to_fields()) + .chain(left_pi.min_value().to_fields()) + .chain(right_pi.max_value().to_fields()) + .chain(once(id)) + .chain(value.to_fields()) + .chain(p.cells_pi().node_hash().to_fields()) + .collect_vec(); + let hash = H::hash_no_pad(&inputs); + assert_eq!(hash, pi.root_hash()); } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + (row_digest.individual_vd + + weierstrass_to_point(&left_pi.individual_digest_point()) + + weierstrass_to_point(&right_pi.individual_digest_point())) + .to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); + Ok(proof) } - fn generate_leaf_proof(p: &TestParams, tuple: &Cell) -> Result> { - let cells_pi = p.cells_pi(); + fn generate_leaf_proof(p: &TestParams, row: &SecondaryIndexCell) -> Result> { + let id = row.cell.identifier; + let value = row.cell.value; + let row_unique_data = row.row_unique_data.into(); + let row_digest = row.digest(&p.cells_pi()); + // generate row leaf proof let input = CircuitInput::leaf( - tuple.identifier.to_canonical_u64(), - tuple.value, + id.to_canonical_u64(), + value, false, + row_unique_data, p.cells_proof_vk().serialize()?, )?; @@ -474,29 +510,41 @@ mod test { .proof .public_inputs; let pi = PublicInputs::from_slice(&pi); - let tuple = tuple.clone(); + + // Check root hash { - let empty_hash = empty_poseidon_hash(); - // H(left_child_hash,right_child_hash,min,max,index_identifier,index_value,cells_tree_hash) - let inputs: Vec<_> = empty_hash - .to_fields() + let value = value.to_fields(); + let empty_hash = empty_poseidon_hash().to_fields(); + let inputs = empty_hash .iter() - .chain(empty_hash.to_fields().iter()) - .chain(tuple.value.to_fields().iter()) - .chain(tuple.value.to_fields().iter()) - .chain(tuple.to_fields().iter()) - .chain(cells_pi.h_raw().iter()) + .chain(empty_hash.iter()) + .chain(value.iter()) + .chain(value.iter()) + .chain(once(&id)) + .chain(value.iter()) + .chain(p.cells_pi().to_node_hash_raw()) .cloned() - .collect(); - let exp_hash = H::hash_no_pad(&inputs); - assert_eq!(pi.root_hash_hashout(), exp_hash); - } - { - // final_digest = HashToInt(mul_digest) * D(ind_digest) - let split_digest = tuple.split_and_accumulate_digest(cells_pi.split_digest_point()); - let result = split_digest.cond_combine_to_row_digest(); - assert_eq!(result.to_weierstrass(), pi.rows_digest_field()); + .collect_vec(); + let exp_root_hash = H::hash_no_pad(&inputs); + assert_eq!(pi.root_hash(), exp_root_hash); } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + row_digest.individual_vd.to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check minimum value + assert_eq!(pi.min_value(), value); + // Check maximum value + assert_eq!(pi.max_value(), value); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); + Ok(proof) } } diff --git a/verifiable-db/src/row_tree/full_node.rs b/verifiable-db/src/row_tree/full_node.rs index b01e996b9..f1da5b0d3 100644 --- a/verifiable-db/src/row_tree/full_node.rs +++ b/verifiable-db/src/row_tree/full_node.rs @@ -1,13 +1,14 @@ +use super::secondary_index_cell::{SecondaryIndexCell, SecondaryIndexCellWire}; +use crate::cells_tree; use derive_more::{From, Into}; use mp2_common::{ - default_config, poseidon::H, proof::ProofWithVK, public_inputs::PublicInputCommon, - u256::CircuitBuilderU256, utils::ToTargets, C, D, F, + default_config, group_hashing::CircuitBuilderGroupHashing, poseidon::H, proof::ProofWithVK, + public_inputs::PublicInputCommon, u256::CircuitBuilderU256, utils::ToTargets, C, D, F, }; use plonky2::{ iop::{target::Target, witness::PartialWitness}, plonk::{circuit_builder::CircuitBuilder, proof::ProofWithPublicInputsTarget}, }; -use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; use recursion_framework::{ circuit_builder::CircuitLogicWires, framework::{ @@ -15,19 +16,17 @@ use recursion_framework::{ }, }; use serde::{Deserialize, Serialize}; -use std::array::from_fn as create_array; - -use crate::cells_tree::{self, Cell, CellWire}; +use std::{array::from_fn as create_array, iter::once}; use super::public_inputs::PublicInputs; // Arity not strictly needed now but may be an easy way to increase performance // easily down the line with less recursion. Best to provide code which is easily // amenable to a different arity rather than hardcoding binary tree only #[derive(Clone, Debug, From, Into)] -pub struct FullNodeCircuit(Cell); +pub struct FullNodeCircuit(SecondaryIndexCell); #[derive(Clone, Serialize, Deserialize, From, Into)] -pub(crate) struct FullNodeWires(CellWire); +pub(crate) struct FullNodeWires(SecondaryIndexCellWire); impl FullNodeCircuit { pub(crate) fn build( @@ -36,55 +35,64 @@ impl FullNodeCircuit { right_pi: &[Target], cells_pi: &[Target], ) -> FullNodeWires { - let cells_pi = cells_tree::PublicInputs::from_slice(cells_pi); let min_child = PublicInputs::from_slice(left_pi); let max_child = PublicInputs::from_slice(right_pi); - let tuple = CellWire::new(b); - let node_min = min_child.min_value(); - let node_max = max_child.max_value(); + let cells_pi = cells_tree::PublicInputs::from_slice(cells_pi); + let secondary_index_cell = SecondaryIndexCellWire::new(b); + let id = secondary_index_cell.identifier(); + let value = secondary_index_cell.value(); + let digest = secondary_index_cell.digest(b, &cells_pi); + + // Check multiplier_vd and multiplier_counter are the same as children proofs. + // assert multiplier_vd == p1.multiplier_vd == p2.multiplier_vd + b.connect_curve_points(digest.multiplier_vd, min_child.multiplier_digest_target()); + b.connect_curve_points(digest.multiplier_vd, max_child.multiplier_digest_target()); + // assert multiplier_counter == p1.multiplier_counter == p2.multiplier_counter + b.connect(digest.multiplier_cnt, min_child.multiplier_counter_target()); + b.connect(digest.multiplier_cnt, max_child.multiplier_counter_target()); + + let node_min = min_child.min_value_target(); + let node_max = max_child.max_value_target(); // enforcing BST property let _true = b._true(); - let left_comparison = b.is_less_or_equal_than_u256(&min_child.max_value(), &tuple.value); - let right_comparison = b.is_less_or_equal_than_u256(&tuple.value, &max_child.min_value()); + let left_comparison = b.is_less_or_equal_than_u256(&min_child.max_value_target(), value); + let right_comparison = b.is_less_or_equal_than_u256(value, &max_child.min_value_target()); b.connect(left_comparison.target, _true.target); b.connect(right_comparison.target, _true.target); // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H let inputs = min_child - .root_hash() - .to_targets() + .root_hash_target() .iter() - .chain(max_child.root_hash().to_targets().iter()) + .chain(max_child.root_hash_target().iter()) .chain(node_min.to_targets().iter()) .chain(node_max.to_targets().iter()) - .chain(tuple.to_targets().iter()) - .chain(cells_pi.node_hash().to_targets().iter()) + .chain(once(&id)) + .chain(value.to_targets().iter()) + .chain(cells_pi.node_hash_target().iter()) .cloned() .collect::>(); let hash = b.hash_n_to_hash_no_pad::(inputs); - // final_digest = HashToInt(mul_digest) * D(ind_digest) + left.digest() + right.digest() - let split_digest = tuple.split_and_accumulate_digest(b, cells_pi.split_digest_target()); - let (row_digest, is_merge) = split_digest.cond_combine_to_row_digest(b); + let individual_vd = b.add_curve_point(&[ + digest.individual_vd, + min_child.individual_digest_target(), + max_child.individual_digest_target(), + ]); - // add this row digest with the rest - let final_digest = b.curve_add(min_child.rows_digest(), max_child.rows_digest()); - let final_digest = b.curve_add(final_digest, row_digest); - // assert `is_merge` is the same as the flags in children pis - b.connect(min_child.is_merge_case().target, is_merge.target); - b.connect(max_child.is_merge_case().target, is_merge.target); PublicInputs::new( &hash.to_targets(), - &final_digest.to_targets(), + &individual_vd.to_targets(), + &digest.multiplier_vd.to_targets(), &node_min.to_targets(), &node_max.to_targets(), - &[is_merge.target], + &digest.multiplier_cnt, ) .register(b); - FullNodeWires(tuple) + FullNodeWires(secondary_index_cell) } fn assign(&self, pw: &mut PartialWitness, wires: &FullNodeWires) { - self.0.assign_wires(pw, &wires.0); + self.0.assign(pw, &wires.0); } } @@ -107,14 +115,14 @@ impl CircuitLogicWires for RecursiveFullWires { type Inputs = RecursiveFullInput; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CircuitBuilder, verified_proofs: [&ProofWithPublicInputsTarget; NUM_CHILDREN], builder_parameters: Self::CircuitBuilderParams, ) -> Self { - const CELLS_IO: usize = cells_tree::PublicInputs::::TOTAL_LEN; + const CELLS_IO: usize = cells_tree::PublicInputs::::total_len(); let verifier_gadget = RecursiveCircuitsVerifierGagdet::::new( default_config(), &builder_parameters, @@ -140,27 +148,12 @@ impl CircuitLogicWires for RecursiveFullWires { #[cfg(test)] pub(crate) mod test { - + use super::*; use alloy::primitives::U256; - use mp2_common::{poseidon::H, utils::ToFields, C, D, F}; - use mp2_test::{ - circuit::{run_circuit, UserCircuit}, - utils::weierstrass_to_point, - }; - use plonky2::{ - field::types::{Field, Sample}, - hash::hash_types::HashOut, - iop::{ - target::Target, - witness::{PartialWitness, WitnessWrite}, - }, - plonk::{circuit_builder::CircuitBuilder, config::Hasher}, - }; - use plonky2_ecgfp5::curve::curve::Point; - - use crate::{cells_tree, row_tree::public_inputs::PublicInputs}; - - use super::{FullNodeCircuit, FullNodeWires, *}; + use itertools::Itertools; + use mp2_common::{group_hashing::weierstrass_to_point, utils::ToFields, C, D, F}; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{field::types::PrimeField64, iop::witness::WitnessWrite, plonk::config::Hasher}; #[derive(Clone, Debug)] struct TestFullNodeCircuit { @@ -174,9 +167,9 @@ pub(crate) mod test { type Wires = (FullNodeWires, Vec, Vec, Vec); fn build(c: &mut CircuitBuilder) -> Self::Wires { - let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::TOTAL_LEN); - let left_pi = c.add_virtual_targets(PublicInputs::::TOTAL_LEN); - let right_pi = c.add_virtual_targets(PublicInputs::::TOTAL_LEN); + let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::total_len()); + let left_pi = c.add_virtual_targets(PublicInputs::::total_len()); + let right_pi = c.add_virtual_targets(PublicInputs::::total_len()); ( FullNodeCircuit::build(c, &left_pi, &right_pi, &cells_pi), left_pi, @@ -193,82 +186,75 @@ pub(crate) mod test { } } - pub(crate) fn generate_random_pi(min: usize, max: usize, is_merge: bool) -> Vec { - let hash = HashOut::rand(); - let digest = Point::rand(); - let min = U256::from(min); - let max = U256::from(max); - let merge = F::from_canonical_usize(is_merge as usize); - PublicInputs::new( - &hash.to_fields(), - &digest.to_weierstrass().to_fields(), - &min.to_fields(), - &max.to_fields(), - &[merge], - ) - .to_vec() - } - fn test_row_tree_full_circuit(is_multiplier: bool, cells_multiplier: bool) { - let cells_point = Point::rand(); - let ind_cell_digest = cells_point.to_weierstrass().to_fields(); - let mul_cell_digest = if cells_multiplier { - cells_point.to_weierstrass().to_fields() - } else { - Point::NEUTRAL.to_fields() - }; - let cells_hash = HashOut::rand().to_fields(); - let cells_pi_struct = - cells_tree::PublicInputs::new(&cells_hash, &ind_cell_digest, &mul_cell_digest); - let cells_pi = cells_pi_struct.to_vec(); - + let mut row = SecondaryIndexCell::sample(is_multiplier); + row.cell.value = U256::from(18); + let id = row.cell.identifier; + let value = row.cell.value; + let cells_pi = cells_tree::PublicInputs::sample(cells_multiplier); + // Compute the row digest. + let row_digest = row.digest(&cells_tree::PublicInputs::from_slice(&cells_pi)); + let node_circuit = FullNodeCircuit::from(row.clone()); let (left_min, left_max) = (10, 15); // this should work since we allow multipleicities of indexes in the row tree let (right_min, right_max) = (18, 30); - let value = U256::from(18); // 15 < 18 < 23 - let identifier = F::rand(); - let tuple = Cell::new(identifier, value, is_multiplier); - let node_circuit = FullNodeCircuit::from(tuple.clone()); - let left_pi = generate_random_pi(left_min, left_max, is_multiplier || cells_multiplier); - let right_pi = generate_random_pi(right_min, right_max, is_multiplier || cells_multiplier); + let multiplier_cnt = row_digest.multiplier_cnt.to_canonical_u64(); + let left_pi = + PublicInputs::sample(row_digest.multiplier_vd, left_min, left_max, multiplier_cnt); + let right_pi = PublicInputs::sample( + row_digest.multiplier_vd, + right_min, + right_max, + multiplier_cnt, + ); let test_circuit = TestFullNodeCircuit { circuit: node_circuit, left_pi: left_pi.clone(), right_pi: right_pi.clone(), - cells_pi, + cells_pi: cells_pi.clone(), }; let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - let left_pis = PublicInputs::from_slice(&left_pi); - let right_pis = PublicInputs::from_slice(&right_pi); - - assert_eq!(U256::from(left_min), pi.min_value_u256()); - assert_eq!(U256::from(right_max), pi.max_value_u256()); - // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H - let left_hash = PublicInputs::from_slice(&left_pi).root_hash_hashout(); - let right_hash = PublicInputs::from_slice(&right_pi).root_hash_hashout(); - let inputs = left_hash - .to_fields() - .iter() - .chain(right_hash.to_fields().iter()) - .chain(left_pis.min_value_u256().to_fields().iter()) - .chain(right_pis.max_value_u256().to_fields().iter()) - .chain(Cell::new(identifier, value, false).to_fields().iter()) - .chain(cells_hash.iter()) - .cloned() - .collect::>(); - let hash = H::hash_no_pad(&inputs); - assert_eq!(hash, pi.root_hash_hashout()); - - // final_digest = HashToInt(mul_digest) * D(ind_digest) + p1.digest() + p2.digest() - let split_digest = tuple.split_and_accumulate_digest(cells_pi_struct.split_digest_point()); - let row_digest = split_digest.cond_combine_to_row_digest(); - - let p1dr = weierstrass_to_point(&PublicInputs::from_slice(&left_pi).rows_digest_field()); - let p2dr = weierstrass_to_point(&PublicInputs::from_slice(&right_pi).rows_digest_field()); - let result_digest = p1dr + p2dr + row_digest; - assert_eq!(result_digest.to_weierstrass(), pi.rows_digest_field()); - assert_eq!(split_digest.is_merge_case(), pi.is_merge_flag()); + let left_pi = PublicInputs::from_slice(&left_pi); + let right_pi = PublicInputs::from_slice(&right_pi); + let cells_pi = cells_tree::PublicInputs::from_slice(&cells_pi); + + // Check root hash + { + // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H + let inputs = left_pi + .root_hash() + .to_fields() + .into_iter() + .chain(right_pi.root_hash().to_fields()) + .chain(left_pi.min_value().to_fields()) + .chain(right_pi.max_value().to_fields()) + .chain(once(id)) + .chain(value.to_fields()) + .chain(cells_pi.node_hash().to_fields()) + .collect_vec(); + let hash = H::hash_no_pad(&inputs); + assert_eq!(hash, pi.root_hash()); + } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + (row_digest.individual_vd + + weierstrass_to_point(&left_pi.individual_digest_point()) + + weierstrass_to_point(&right_pi.individual_digest_point())) + .to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check minimum value + assert_eq!(pi.min_value(), U256::from(left_min)); + // Check maximum value + assert_eq!(pi.max_value(), U256::from(right_max)); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); } #[test] diff --git a/verifiable-db/src/row_tree/leaf.rs b/verifiable-db/src/row_tree/leaf.rs index fe03ded58..bd873f23a 100644 --- a/verifiable-db/src/row_tree/leaf.rs +++ b/verifiable-db/src/row_tree/leaf.rs @@ -1,3 +1,8 @@ +use super::{ + public_inputs::PublicInputs, + secondary_index_cell::{SecondaryIndexCell, SecondaryIndexCellWire}, +}; +use crate::cells_tree; use derive_more::{From, Into}; use mp2_common::{ default_config, @@ -18,61 +23,54 @@ use recursion_framework::{ }, }; use serde::{Deserialize, Serialize}; - -use crate::cells_tree::{self, Cell, CellWire}; - -use super::public_inputs::PublicInputs; +use std::iter::once; // new type to implement the circuit logic on each differently // deref to access directly the same members - read only so it's ok #[derive(Clone, Debug, From, Into)] -pub struct LeafCircuit(Cell); +pub struct LeafCircuit(SecondaryIndexCell); #[derive(Clone, Serialize, Deserialize, From, Into)] -pub(crate) struct LeafWires(CellWire); +pub(crate) struct LeafWires(SecondaryIndexCellWire); impl LeafCircuit { pub(crate) fn build(b: &mut CircuitBuilder, cells_pis: &[Target]) -> LeafWires { let cells_pis = cells_tree::PublicInputs::from_slice(cells_pis); - // D(index_id||pack_u32(index_value) - let tuple = CellWire::new(b); - // set the right digest depending on the multiplier and accumulate the ones from the public - // inputs of the cell root proof - let split_digest = tuple.split_and_accumulate_digest(b, cells_pis.split_digest_target()); - // final_digest = HashToInt(D(mul_digest)) * D(ind_digest) - // NOTE This additional digest is necessary since the individual digest is supposed to be a - // full row, that is how it is extracted from MPT - let (final_digest, is_merge) = split_digest.cond_combine_to_row_digest(b); + let secondary_index_cell = SecondaryIndexCellWire::new(b); + let id = secondary_index_cell.identifier(); + let value = secondary_index_cell.value().to_targets(); + let digest = secondary_index_cell.digest(b, &cells_pis); // H(left_child_hash,right_child_hash,min,max,index_identifier,index_value,cells_tree_hash) // in our case, min == max == index_value // left_child_hash == right_child_hash == empty_hash since there is not children - let empty_hash = b.constant_hash(*empty_poseidon_hash()); + let empty_hash = b.constant_hash(*empty_poseidon_hash()).to_targets(); let inputs = empty_hash - .to_targets() - .iter() - .chain(empty_hash.to_targets().iter()) - .chain(tuple.value.to_targets().iter()) - .chain(tuple.value.to_targets().iter()) - .chain(tuple.to_targets().iter()) - .chain(cells_pis.node_hash().to_targets().iter()) - .cloned() + .clone() + .into_iter() + .chain(empty_hash) + .chain(value.clone()) + .chain(value.clone()) + .chain(once(id)) + .chain(value.clone()) + .chain(cells_pis.node_hash_target()) .collect::>(); let row_hash = b.hash_n_to_hash_no_pad::(inputs); - let value_fields = tuple.value.to_targets(); PublicInputs::new( &row_hash.elements, - &final_digest.to_targets(), - &value_fields, - &value_fields, - &[is_merge.target], + &digest.individual_vd.to_targets(), + &digest.multiplier_vd.to_targets(), + &value, + &value, + &digest.multiplier_cnt, ) .register(b); - LeafWires(tuple) + + LeafWires(secondary_index_cell) } fn assign(&self, pw: &mut PartialWitness, wires: &LeafWires) { - self.0.assign_wires(pw, &wires.0); + self.0.assign(pw, &wires.0); } } @@ -98,14 +96,14 @@ impl CircuitLogicWires for RecursiveLeafWires { type Inputs = RecursiveLeafInput; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CircuitBuilder, _verified_proofs: [&ProofWithPublicInputsTarget; 0], builder_parameters: Self::CircuitBuilderParams, ) -> Self { - const CELLS_IO: usize = cells_tree::PublicInputs::::TOTAL_LEN; + const CELLS_IO: usize = cells_tree::PublicInputs::::total_len(); let verifier_gadget = RecursiveCircuitsVerifierGagdet::::new( default_config(), &builder_parameters, @@ -129,25 +127,18 @@ impl CircuitLogicWires for RecursiveLeafWires { #[cfg(test)] mod test { - - use alloy::primitives::U256; - use mp2_common::{poseidon::empty_poseidon_hash, utils::ToFields, CHasher, C, D, F}; + use super::*; + use crate::{ + cells_tree::PublicInputs as CellsPublicInputs, row_tree::public_inputs::PublicInputs, + }; + use itertools::Itertools; + use mp2_common::{poseidon::empty_poseidon_hash, utils::ToFields, C, D, F}; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::{ - field::types::Sample, - hash::{hash_types::HashOut, hashing::hash_n_to_hash_no_pad}, iop::{target::Target, witness::WitnessWrite}, plonk::{circuit_builder::CircuitBuilder, config::Hasher}, }; - use plonky2_ecgfp5::curve::curve::Point; - use rand::{thread_rng, Rng}; - - use crate::{ - cells_tree::{self, Cell}, - row_tree::public_inputs::PublicInputs, - }; - - use super::{LeafCircuit, LeafWires}; + use std::iter::once; #[derive(Debug, Clone)] struct TestLeafCircuit { @@ -159,7 +150,7 @@ mod test { type Wires = (LeafWires, Vec); fn build(c: &mut CircuitBuilder) -> Self::Wires { - let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::TOTAL_LEN); + let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::total_len()); (LeafCircuit::build(c, &cells_pi), cells_pi) } @@ -170,48 +161,56 @@ mod test { } fn test_row_tree_leaf_circuit(is_multiplier: bool, cells_multiplier: bool) { - let mut rng = thread_rng(); - let value = U256::from_limbs(rng.gen::<[u64; 4]>()); - let identifier = F::rand(); - let row_cell = Cell::new(identifier, value, is_multiplier); - let circuit = LeafCircuit::from(row_cell.clone()); - let tuple = row_cell.clone(); - - let ind_cells_digest = Point::rand().to_fields(); - // TODO: test with other than neutral - let mul_cells_digest = if cells_multiplier { - Point::rand().to_fields() - } else { - Point::NEUTRAL.to_fields() + let cells_pi = CellsPublicInputs::sample(cells_multiplier); + + let secondary_index_cell = SecondaryIndexCell::sample(is_multiplier); + let id = secondary_index_cell.cell.identifier; + let value = secondary_index_cell.cell.value; + let row_digest = secondary_index_cell.digest(&CellsPublicInputs::from_slice(&cells_pi)); + + let circuit = LeafCircuit::from(secondary_index_cell); + let test_circuit = TestLeafCircuit { + circuit, + cells_pi: cells_pi.clone(), }; - let cells_hash = HashOut::rand().to_fields(); - let cells_pi_struct = - cells_tree::PublicInputs::new(&cells_hash, &ind_cells_digest, &mul_cells_digest); - let cells_pi = cells_pi_struct.to_vec(); - let test_circuit = TestLeafCircuit { circuit, cells_pi }; + let cells_pi = CellsPublicInputs::from_slice(&cells_pi); + let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - assert_eq!(value, pi.max_value_u256()); - assert_eq!(value, pi.min_value_u256()); - let empty_hash = empty_poseidon_hash(); - let inputs = empty_hash - .to_fields() - .iter() - .chain(empty_hash.to_fields().iter()) - .chain(tuple.value.to_fields().iter()) - .chain(tuple.value.to_fields().iter()) - .chain(tuple.to_fields().iter()) - .chain(cells_hash.iter()) - .cloned() - .collect::>(); - let row_hash = hash_n_to_hash_no_pad::>::Permutation>(&inputs); - assert_eq!(row_hash, pi.root_hash_hashout()); - // final_digest = HashToInt(mul_digest) * D(ind_digest) - let split_digest = - row_cell.split_and_accumulate_digest(cells_pi_struct.split_digest_point()); - let result = split_digest.cond_combine_to_row_digest(); - assert_eq!(result.to_weierstrass(), pi.rows_digest_field()); - assert_eq!(split_digest.is_merge_case(), pi.is_merge_flag()); + + // Check root hash + { + let value = value.to_fields(); + let empty_hash = empty_poseidon_hash().to_fields(); + let inputs = empty_hash + .iter() + .chain(empty_hash.iter()) + .chain(value.iter()) + .chain(value.iter()) + .chain(once(&id)) + .chain(value.iter()) + .chain(cells_pi.to_node_hash_raw()) + .cloned() + .collect_vec(); + let exp_root_hash = H::hash_no_pad(&inputs); + assert_eq!(pi.root_hash(), exp_root_hash); + } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + row_digest.individual_vd.to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check row ID multiplier + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); + // Check minimum value + assert_eq!(pi.min_value(), value); + // Check maximum value + assert_eq!(pi.max_value(), value); } #[test] diff --git a/verifiable-db/src/row_tree/mod.rs b/verifiable-db/src/row_tree/mod.rs index c76daa172..f3c73ce12 100644 --- a/verifiable-db/src/row_tree/mod.rs +++ b/verifiable-db/src/row_tree/mod.rs @@ -3,6 +3,7 @@ mod full_node; mod leaf; mod partial_node; mod public_inputs; +mod secondary_index_cell; pub use api::{extract_hash_from_proof, CircuitInput, PublicParameters}; pub use public_inputs::PublicInputs; diff --git a/verifiable-db/src/row_tree/partial_node.rs b/verifiable-db/src/row_tree/partial_node.rs index b78bd47f2..b33d741db 100644 --- a/verifiable-db/src/row_tree/partial_node.rs +++ b/verifiable-db/src/row_tree/partial_node.rs @@ -1,7 +1,8 @@ -use plonky2::plonk::proof::ProofWithPublicInputsTarget; - +use super::secondary_index_cell::{SecondaryIndexCell, SecondaryIndexCellWire}; +use crate::cells_tree; use mp2_common::{ default_config, + group_hashing::CircuitBuilderGroupHashing, hash::hash_maybe_first, poseidon::empty_poseidon_hash, proof::ProofWithVK, @@ -17,9 +18,8 @@ use plonky2::{ target::{BoolTarget, Target}, witness::{PartialWitness, WitnessWrite}, }, - plonk::circuit_builder::CircuitBuilder, + plonk::{circuit_builder::CircuitBuilder, proof::ProofWithPublicInputsTarget}, }; -use plonky2_ecgfp5::gadgets::curve::CircuitBuilderEcGFp5; use recursion_framework::{ circuit_builder::CircuitLogicWires, framework::{ @@ -27,28 +27,27 @@ use recursion_framework::{ }, }; use serde::{Deserialize, Serialize}; - -use crate::cells_tree::{self, Cell, CellWire}; +use std::iter::once; use super::public_inputs::PublicInputs; #[derive(Clone, Debug)] pub struct PartialNodeCircuit { - pub(crate) tuple: Cell, + pub(crate) row: SecondaryIndexCell, pub(crate) is_child_at_left: bool, } #[derive(Clone, Debug, Serialize, Deserialize)] struct PartialNodeWires { - tuple: CellWire, + row: SecondaryIndexCellWire, #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] is_child_at_left: BoolTarget, } impl PartialNodeCircuit { - pub(crate) fn new(tuple: Cell, is_child_at_left: bool) -> Self { + pub(crate) fn new(row: SecondaryIndexCell, is_child_at_left: bool) -> Self { Self { - tuple, + row, is_child_at_left, } } @@ -57,22 +56,32 @@ impl PartialNodeCircuit { child_pi: &[Target], cells_pi: &[Target], ) -> PartialNodeWires { + let child_pi = PublicInputs::from_slice(child_pi); let cells_pi = cells_tree::PublicInputs::from_slice(cells_pi); - let tuple = CellWire::new(b); + let secondary_index_cell = SecondaryIndexCellWire::new(b); + let id = secondary_index_cell.identifier(); + let value = secondary_index_cell.value(); + let digest = secondary_index_cell.digest(b, &cells_pi); + + // Check multiplier_vd and multiplier_counter are the same as children proof. + // assert multiplier_vd == p.multiplier_vd + b.connect_curve_points(digest.multiplier_vd, child_pi.multiplier_digest_target()); + // assert multiplier_counter == p.multiplier_counter + b.connect(digest.multiplier_cnt, child_pi.multiplier_counter_target()); + // bool target range checked in poseidon gate let is_child_at_left = b.add_virtual_bool_target_unsafe(); - let child_pi = PublicInputs::from_slice(child_pi); // max_left = left ? child_proof.max : index_value // min_right = left ? index_value : child_proof.min - let max_left = b.select_u256(is_child_at_left, &child_pi.max_value(), &tuple.value); - let min_right = b.select_u256(is_child_at_left, &tuple.value, &child_pi.min_value()); + let max_left = b.select_u256(is_child_at_left, &child_pi.max_value_target(), value); + let min_right = b.select_u256(is_child_at_left, value, &child_pi.min_value_target()); let bst_enforced = b.is_less_or_equal_than_u256(&max_left, &min_right); let _true = b._true(); b.connect(bst_enforced.target, _true.target); // node_min = left ? child_proof.min : index_value // node_max = left ? index_value : child_proof.max - let node_min = b.select_u256(is_child_at_left, &child_pi.min_value(), &tuple.value); - let node_max = b.select_u256(is_child_at_left, &tuple.value, &child_pi.max_value()); + let node_min = b.select_u256(is_child_at_left, &child_pi.min_value_target(), value); + let node_max = b.select_u256(is_child_at_left, value, &child_pi.max_value_target()); let empty_hash = b.constant_hash(*empty_poseidon_hash()); // left_hash = left ? child_proof.H : H("") @@ -84,8 +93,9 @@ impl PartialNodeCircuit { .to_targets() .iter() .chain(node_max.to_targets().iter()) - .chain(tuple.to_targets().iter()) - .chain(cells_pi.node_hash().to_targets().iter()) + .chain(once(&id)) + .chain(value.to_targets().iter()) + .chain(cells_pi.node_hash_target().iter()) .cloned() .collect::>(); // if child at left, then hash should be child_proof.H || H("") || rest @@ -94,34 +104,30 @@ impl PartialNodeCircuit { b, is_child_at_left, empty_hash.elements, - child_pi.root_hash().elements, + child_pi.root_hash_target(), &rest, ); - // final_digest = HashToInt(mul_digest) * D(ind_digest) - let split_digest = tuple.split_and_accumulate_digest(b, cells_pi.split_digest_target()); - let (row_digest, is_merge) = split_digest.cond_combine_to_row_digest(b); + let individual_vd = + b.add_curve_point(&[digest.individual_vd, child_pi.individual_digest_target()]); - // and add the digest of the row other rows - let final_digest = b.curve_add(child_pi.rows_digest(), row_digest); - // assert is_merge is the same between this row and `child_pi` - b.connect(is_merge.target, child_pi.is_merge_case().target); PublicInputs::new( &node_hash, - &final_digest.to_targets(), + &individual_vd.to_targets(), + &digest.multiplier_vd.to_targets(), &node_min.to_targets(), &node_max.to_targets(), - &[is_merge.target], + &digest.multiplier_cnt, ) .register(b); PartialNodeWires { - tuple, + row: secondary_index_cell, is_child_at_left, } } fn assign(&self, pw: &mut PartialWitness, wires: &PartialNodeWires) { - self.tuple.assign_wires(pw, &wires.tuple); + self.row.assign(pw, &wires.row); pw.set_bool_target(wires.is_child_at_left, self.is_child_at_left); } } @@ -144,14 +150,14 @@ impl CircuitLogicWires for RecursivePartialWires { type Inputs = RecursivePartialInput; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::TOTAL_LEN; + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CircuitBuilder, verified_proofs: [&ProofWithPublicInputsTarget; NUM_CHILDREN], builder_parameters: Self::CircuitBuilderParams, ) -> Self { - const CELLS_IO: usize = cells_tree::PublicInputs::::TOTAL_LEN; + const CELLS_IO: usize = cells_tree::PublicInputs::::total_len(); let verifier_gadget = RecursiveCircuitsVerifierGagdet::::new( default_config(), &builder_parameters, @@ -176,32 +182,19 @@ impl CircuitLogicWires for RecursivePartialWires { #[cfg(test)] pub mod test { - use mp2_common::{poseidon::empty_poseidon_hash, utils::ToFields, CHasher}; - use plonky2::{hash::hash_types::HashOut, plonk::config::Hasher}; - use plonky2_ecgfp5::curve::curve::Point; - + use super::*; use alloy::primitives::U256; - use mp2_common::{C, D, F}; - use mp2_test::{ - circuit::{run_circuit, UserCircuit}, - utils::weierstrass_to_point, - }; - use plonky2::{ - field::types::Sample, - hash::hashing::hash_n_to_hash_no_pad, - iop::{target::Target, witness::WitnessWrite}, - plonk::circuit_builder::CircuitBuilder, - }; - - use crate::{ - cells_tree::{self, Cell}, - row_tree::{ - full_node::test::generate_random_pi, partial_node::PartialNodeCircuit, - public_inputs::PublicInputs, - }, + use itertools::Itertools; + use mp2_common::{ + group_hashing::weierstrass_to_point, + poseidon::{empty_poseidon_hash, H}, + types::CBuilder, + utils::ToFields, + C, D, F, }; - - use super::PartialNodeWires; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{field::types::PrimeField64, plonk::config::Hasher}; + use std::iter::once; #[derive(Clone, Debug)] struct TestPartialNodeCircuit { @@ -213,10 +206,11 @@ pub mod test { impl UserCircuit for TestPartialNodeCircuit { type Wires = (PartialNodeWires, Vec, Vec); - fn build(c: &mut CircuitBuilder) -> Self::Wires { - let child_pi = c.add_virtual_targets(PublicInputs::::TOTAL_LEN); - let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::TOTAL_LEN); + fn build(c: &mut CBuilder) -> Self::Wires { + let child_pi = c.add_virtual_targets(PublicInputs::::total_len()); + let cells_pi = c.add_virtual_targets(cells_tree::PublicInputs::::total_len()); let wires = PartialNodeCircuit::build(c, &child_pi, &cells_pi); + (wires, child_pi, cells_pi) } @@ -283,29 +277,25 @@ pub mod test { } fn partial_node_circuit(child_at_left: bool, is_multiplier: bool, is_cell_multiplier: bool) { - let tuple = Cell::new(F::rand(), U256::from(18), is_multiplier); + let mut row = SecondaryIndexCell::sample(is_multiplier); + row.cell.value = U256::from(18); + let id = row.cell.identifier; + let value = row.cell.value; + let cells_pi = cells_tree::PublicInputs::sample(is_cell_multiplier); + // Compute the row digest. + let row_digest = row.digest(&cells_tree::PublicInputs::from_slice(&cells_pi)); let (child_min, child_max) = match child_at_left { true => (U256::from(10), U256::from(15)), false => (U256::from(20), U256::from(25)), }; - partial_safety_check(child_min, child_max, tuple.value, child_at_left); - let node_circuit = PartialNodeCircuit::new(tuple.clone(), child_at_left); - let child_pi = generate_random_pi( + partial_safety_check(child_min, child_max, value, child_at_left); + let node_circuit = PartialNodeCircuit::new(row.clone(), child_at_left); + let child_pi = PublicInputs::sample( + row_digest.multiplier_vd, child_min.to(), child_max.to(), - is_cell_multiplier || is_multiplier, + row_digest.multiplier_cnt.to_canonical_u64(), ); - let cells_point = Point::rand(); - let ind_cell_digest = cells_point.to_weierstrass().to_fields(); - let cells_hash = HashOut::rand().to_fields(); - let mul_cell_digest = if is_cell_multiplier { - cells_point.to_weierstrass().to_fields() - } else { - Point::NEUTRAL.to_fields() - }; - let cells_pi_struct = - cells_tree::PublicInputs::new(&cells_hash, &ind_cell_digest, &mul_cell_digest); - let cells_pi = cells_pi_struct.to_vec(); let test_circuit = TestPartialNodeCircuit { circuit: node_circuit, cells_pi: cells_pi.clone(), @@ -313,36 +303,52 @@ pub mod test { }; let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); - // node_min = left ? child_proof.min : index_value - // node_max = left ? index_value : child_proof.max - let (node_min, node_max) = match child_at_left { - true => (pi.min_value_u256(), tuple.value), - false => (tuple.value, pi.max_value_u256()), - }; - // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H - let child_hash = PublicInputs::from_slice(&child_pi).root_hash_hashout(); - let empty_hash = empty_poseidon_hash(); - let input_hash = match child_at_left { - true => [child_hash.to_fields(), empty_hash.to_fields()].concat(), - false => [empty_hash.to_fields(), child_hash.to_fields()].concat(), - }; - let inputs = input_hash - .iter() - .chain(node_min.to_fields().iter()) - .chain(node_max.to_fields().iter()) - .chain(tuple.to_fields().iter()) - .chain(cells_hash.iter()) - .cloned() - .collect::>(); - let hash = hash_n_to_hash_no_pad::>::Permutation>(&inputs); - assert_eq!(hash, pi.root_hash_hashout()); - // final_digest = HashToInt(mul_digest) * D(ind_digest) + row_proof.digest() - let split_digest = tuple.split_and_accumulate_digest(cells_pi_struct.split_digest_point()); - let res = split_digest.cond_combine_to_row_digest(); - // then adding with the rest of the rows digest, the other nodes - let res = - res + weierstrass_to_point(&PublicInputs::from_slice(&child_pi).rows_digest_field()); - assert_eq!(res.to_weierstrass(), pi.rows_digest_field()); - assert_eq!(split_digest.is_merge_case(), pi.is_merge_flag()); + + let child_pi = PublicInputs::from_slice(&child_pi); + let cells_pi = cells_tree::PublicInputs::from_slice(&cells_pi); + + // Check root hash + { + // node_min = left ? child_proof.min : index_value + // node_max = left ? index_value : child_proof.max + let (node_min, node_max) = match child_at_left { + true => (pi.min_value(), value), + false => (value, pi.max_value()), + }; + // Poseidon(p1.H || p2.H || node_min || node_max || index_id || index_value ||p.H)) as H + let child_hash = child_pi.root_hash().to_fields(); + let empty_hash = empty_poseidon_hash().to_fields(); + let input_hash = match child_at_left { + true => [child_hash, empty_hash].concat(), + false => [empty_hash, child_hash].concat(), + }; + let inputs = input_hash + .into_iter() + .chain(node_min.to_fields()) + .chain(node_max.to_fields()) + .chain(once(id)) + .chain(value.to_fields()) + .chain(cells_pi.node_hash().to_fields()) + .collect_vec(); + let exp_root_hash = H::hash_no_pad(&inputs); + assert_eq!(pi.root_hash(), exp_root_hash); + } + // Check individual digest + assert_eq!( + pi.individual_digest_point(), + (row_digest.individual_vd + weierstrass_to_point(&child_pi.individual_digest_point())) + .to_weierstrass() + ); + // Check multiplier digest + assert_eq!( + pi.multiplier_digest_point(), + row_digest.multiplier_vd.to_weierstrass() + ); + // Check minimum value + assert_eq!(pi.min_value(), value.min(child_min)); + // Check maximum value + assert_eq!(pi.max_value(), value.max(child_max)); + // Check multiplier counter + assert_eq!(pi.multiplier_counter(), row_digest.multiplier_cnt); } } diff --git a/verifiable-db/src/row_tree/public_inputs.rs b/verifiable-db/src/row_tree/public_inputs.rs index 2dac37010..27cfe938b 100644 --- a/verifiable-db/src/row_tree/public_inputs.rs +++ b/verifiable-db/src/row_tree/public_inputs.rs @@ -1,158 +1,229 @@ //! Public inputs for rows trees creation circuits -//! + use alloy::primitives::U256; use mp2_common::{ public_inputs::{PublicInputCommon, PublicInputRange}, - types::CURVE_TARGET_LEN, + types::{CBuilder, CURVE_TARGET_LEN}, u256::{self, UInt256Target}, - utils::{FromFields, FromTargets, TryIntoBool}, - D, F, + utils::{FromFields, FromTargets}, + F, }; use plonky2::{ - hash::hash_types::{HashOut, HashOutTarget, NUM_HASH_OUT_ELTS}, - iop::target::{BoolTarget, Target}, - plonk::circuit_builder::CircuitBuilder, + hash::hash_types::{HashOut, NUM_HASH_OUT_ELTS}, + iop::target::Target, }; use plonky2_ecgfp5::{curve::curve::WeierstrassPoint, gadgets::curve::CurveTarget}; -use std::array::from_fn as create_array; - -// Contract extraction public Inputs: -// - `H : [4]F` : Poseidon hash of the leaf -// - `DR : Digest[F]` : accumulated digest of all the rows up to this node -// - `min : Uint256` : min value of the secondary index stored up to this node -// - `max : Uint256` : max value of the secondary index stored up to this node -// - `merge : bool` : Flag specifying whether we are building rows for a merge table or not -const H_RANGE: PublicInputRange = 0..NUM_HASH_OUT_ELTS; -const DR_RANGE: PublicInputRange = H_RANGE.end..H_RANGE.end + CURVE_TARGET_LEN; -const MIN_RANGE: PublicInputRange = DR_RANGE.end..DR_RANGE.end + u256::NUM_LIMBS; -const MAX_RANGE: PublicInputRange = MIN_RANGE.end..MIN_RANGE.end + u256::NUM_LIMBS; -const MERGE_RANGE: PublicInputRange = MAX_RANGE.end..MAX_RANGE.end + 1; - -/// Public inputs for contract extraction +use std::iter::once; + +pub enum RowsTreePublicInputs { + // `H : F[4]` - Poseidon hash of the leaf + RootHash, + // `individual_digest : Digest` - Cumulative digest of the values of the cells which are accumulated in individual digest + IndividualDigest, + // `multiplier_digest : Digest` - Cumulative digest of the values of the cells which are accumulated in multiplier digest + MultiplierDigest, + // `min : Uint256` - Minimum alue of the secondary index stored up to this node + MinValue, + // `max : Uint256` - Maximum value of the secondary index stored up to this node + MaxValue, + // `multiplier_counter : F` - Number of cells accumulated as multiplier + MultiplierCounter, +} + +/// Public inputs for Rows Tree Construction #[derive(Clone, Debug)] pub struct PublicInputs<'a, T> { pub(crate) h: &'a [T], - pub(crate) dr: &'a [T], + pub(crate) individual_digest: &'a [T], + pub(crate) multiplier_digest: &'a [T], pub(crate) min: &'a [T], pub(crate) max: &'a [T], - pub(crate) merge: &'a [T], + pub(crate) multiplier_cnt: &'a T, } -impl PublicInputCommon for PublicInputs<'_, Target> { - const RANGES: &'static [PublicInputRange] = - &[H_RANGE, DR_RANGE, MIN_RANGE, MAX_RANGE, MERGE_RANGE]; +const NUM_PUBLIC_INPUTS: usize = RowsTreePublicInputs::MultiplierCounter as usize + 1; - fn register_args(&self, cb: &mut CircuitBuilder) { - cb.register_public_inputs(self.h); - cb.register_public_inputs(self.dr); - cb.register_public_inputs(self.min); - cb.register_public_inputs(self.max); - cb.register_public_input(self.merge[0]); - } -} +impl<'a, T: Clone> PublicInputs<'a, T> { + const PI_RANGES: [PublicInputRange; NUM_PUBLIC_INPUTS] = [ + Self::to_range(RowsTreePublicInputs::RootHash), + Self::to_range(RowsTreePublicInputs::IndividualDigest), + Self::to_range(RowsTreePublicInputs::MultiplierDigest), + Self::to_range(RowsTreePublicInputs::MinValue), + Self::to_range(RowsTreePublicInputs::MaxValue), + Self::to_range(RowsTreePublicInputs::MultiplierCounter), + ]; -// mostly used for testing -impl PublicInputs<'_, F> { - /// Get the metadata point. - pub fn rows_digest_field(&self) -> WeierstrassPoint { - WeierstrassPoint::from_fields(self.dr) - } - /// minimum index value - pub fn min_value_u256(&self) -> U256 { - U256::from_fields(self.min) - } - /// maximum index value - pub fn max_value_u256(&self) -> U256 { - U256::from_fields(self.max) - } - /// hash of the subtree at this node - pub fn root_hash_hashout(&self) -> HashOut { - HashOut { - elements: create_array(|i| self.h[i]), + const SIZES: [usize; NUM_PUBLIC_INPUTS] = [ + // Poseidon hash of the leaf + NUM_HASH_OUT_ELTS, + // Cumulative digest of the values of the cells which are accumulated in individual digest + CURVE_TARGET_LEN, + // Cumulative digest of the values of the cells which are accumulated in multiplier digest + CURVE_TARGET_LEN, + // Minimum value of the secondary index stored up to this node + u256::NUM_LIMBS, + // Maximum value of the secondary index stored up to this node + u256::NUM_LIMBS, + // Counter of the number of cells accumulated so far as multiplier + 1, + ]; + + pub(crate) const fn to_range(pi: RowsTreePublicInputs) -> PublicInputRange { + let mut i = 0; + let mut offset = 0; + let pi_pos = pi as usize; + while i < pi_pos { + offset += Self::SIZES[i]; + i += 1; } + offset..offset + Self::SIZES[pi_pos] } - pub fn is_merge_flag(&self) -> bool { - self.merge[0].try_into_bool().unwrap() + pub const fn total_len() -> usize { + Self::to_range(RowsTreePublicInputs::MultiplierCounter).end } -} -impl PublicInputs<'_, Target> { - /// Get the hash corresponding to the root of the subtree of this node - pub fn root_hash(&self) -> HashOutTarget { - HashOutTarget::from_targets(self.h) + pub fn to_root_hash_raw(&self) -> &[T] { + self.h } - pub fn rows_digest(&self) -> CurveTarget { - let dv = self.dr; - CurveTarget::from_targets(dv) + pub fn to_individual_digest_raw(&self) -> &[T] { + self.individual_digest } - pub fn min_value(&self) -> UInt256Target { - UInt256Target::from_targets(self.min) - } - pub fn max_value(&self) -> UInt256Target { - UInt256Target::from_targets(self.max) + pub fn to_multiplier_digest_raw(&self) -> &[T] { + self.multiplier_digest } - pub fn is_merge_case(&self) -> BoolTarget { - BoolTarget::new_unsafe(self.merge[0]) + pub fn to_min_value_raw(&self) -> &[T] { + self.min } -} -pub const TOTAL_LEN: usize = PublicInputs::::TOTAL_LEN; + pub fn to_max_value_raw(&self) -> &[T] { + self.max + } -impl<'a, T: Copy> PublicInputs<'a, T> { - /// Total length of the public inputs - pub(crate) const TOTAL_LEN: usize = MERGE_RANGE.end; + pub fn to_multiplier_counter_raw(&self) -> &T { + self.multiplier_cnt + } - /// Create from a slice. - pub fn from_slice(pi: &'a [T]) -> Self { - assert!(pi.len() >= Self::TOTAL_LEN); + pub fn from_slice(input: &'a [T]) -> Self { + assert!( + input.len() >= Self::total_len(), + "Input slice too short to build rows tree public inputs, must be at least {} elements", + Self::total_len(), + ); Self { - h: &pi[H_RANGE], - dr: &pi[DR_RANGE], - min: &pi[MIN_RANGE], - max: &pi[MAX_RANGE], - merge: &pi[MERGE_RANGE], + h: &input[Self::PI_RANGES[0].clone()], + individual_digest: &input[Self::PI_RANGES[1].clone()], + multiplier_digest: &input[Self::PI_RANGES[2].clone()], + min: &input[Self::PI_RANGES[3].clone()], + max: &input[Self::PI_RANGES[4].clone()], + multiplier_cnt: &input[Self::PI_RANGES[5].clone()][0], } } - /// Create a new public inputs. - pub fn new(h: &'a [T], dr: &'a [T], min: &'a [T], max: &'a [T], merge: &'a [T]) -> Self { - assert_eq!(h.len(), NUM_HASH_OUT_ELTS); - assert_eq!(dr.len(), CURVE_TARGET_LEN); - assert_eq!(min.len(), u256::NUM_LIMBS); - assert_eq!(max.len(), u256::NUM_LIMBS); - assert_eq!(merge.len(), 1); + pub fn new( + h: &'a [T], + individual_digest: &'a [T], + multiplier_digest: &'a [T], + min: &'a [T], + max: &'a [T], + multiplier_cnt: &'a T, + ) -> Self { Self { h, - dr, + individual_digest, + multiplier_digest, min, max, - merge, + multiplier_cnt, } } - /// Combine to a vector. pub fn to_vec(&self) -> Vec { self.h .iter() - .chain(self.dr) + .chain(self.individual_digest) + .chain(self.multiplier_digest) .chain(self.min) .chain(self.max) - .chain(self.merge) + .chain(once(self.multiplier_cnt)) .cloned() .collect() } } +impl PublicInputCommon for PublicInputs<'_, Target> { + const RANGES: &'static [PublicInputRange] = &Self::PI_RANGES; + + fn register_args(&self, cb: &mut CBuilder) { + cb.register_public_inputs(self.h); + cb.register_public_inputs(self.individual_digest); + cb.register_public_inputs(self.multiplier_digest); + cb.register_public_inputs(self.min); + cb.register_public_inputs(self.max); + cb.register_public_input(*self.multiplier_cnt); + } +} + +impl PublicInputs<'_, Target> { + pub fn root_hash_target(&self) -> [Target; NUM_HASH_OUT_ELTS] { + self.to_root_hash_raw().try_into().unwrap() + } + + pub fn individual_digest_target(&self) -> CurveTarget { + CurveTarget::from_targets(self.individual_digest) + } + + pub fn multiplier_digest_target(&self) -> CurveTarget { + CurveTarget::from_targets(self.multiplier_digest) + } + + pub fn min_value_target(&self) -> UInt256Target { + UInt256Target::from_targets(self.min) + } + + pub fn max_value_target(&self) -> UInt256Target { + UInt256Target::from_targets(self.max) + } + + pub fn multiplier_counter_target(&self) -> Target { + *self.to_multiplier_counter_raw() + } +} + +impl PublicInputs<'_, F> { + pub fn root_hash(&self) -> HashOut { + HashOut::from_partial(self.h) + } + + pub fn individual_digest_point(&self) -> WeierstrassPoint { + WeierstrassPoint::from_fields(self.individual_digest) + } + + pub fn multiplier_digest_point(&self) -> WeierstrassPoint { + WeierstrassPoint::from_fields(self.multiplier_digest) + } + + pub fn min_value(&self) -> U256 { + U256::from_fields(self.min) + } + + pub fn max_value(&self) -> U256 { + U256::from_fields(self.max) + } + + pub fn multiplier_counter(&self) -> F { + *self.to_multiplier_counter_raw() + } +} + #[cfg(test)] -mod tests { +pub(crate) mod tests { use super::*; - use alloy::primitives::U256; - use mp2_common::{public_inputs::PublicInputCommon, utils::ToFields, C, D, F}; + use mp2_common::{utils::ToFields, C, D, F}; use mp2_test::circuit::{run_circuit, UserCircuit}; use plonky2::{ field::types::{Field, Sample}, @@ -160,24 +231,49 @@ mod tests { target::Target, witness::{PartialWitness, WitnessWrite}, }, - plonk::config::GenericHashOut, }; use plonky2_ecgfp5::curve::curve::Point; use rand::{thread_rng, Rng}; + use std::{array, slice}; + + impl PublicInputs<'_, F> { + pub(crate) fn sample( + multiplier_digest: Point, + min: usize, + max: usize, + multiplier_cnt: u64, + ) -> Vec { + let h = HashOut::rand().to_fields(); + let individual_digest = Point::rand(); + let [individual_digest, multiplier_digest] = + [individual_digest, multiplier_digest].map(|p| p.to_weierstrass().to_fields()); + let [min, max] = [min, max].map(|v| U256::from(v).to_fields()); + let multiplier_cnt = F::from_canonical_u64(multiplier_cnt); + PublicInputs::new( + &h, + &individual_digest, + &multiplier_digest, + &min, + &max, + &multiplier_cnt, + ) + .to_vec() + } + } #[derive(Clone, Debug)] - struct TestPICircuit<'a> { + struct TestPublicInputs<'a> { exp_pi: &'a [F], } - impl UserCircuit for TestPICircuit<'_> { + impl UserCircuit for TestPublicInputs<'_> { type Wires = Vec; - fn build(b: &mut CircuitBuilder) -> Self::Wires { - let pi = b.add_virtual_targets(PublicInputs::::TOTAL_LEN); - let pi = PublicInputs::from_slice(&pi); - pi.register(b); - pi.to_vec() + fn build(b: &mut CBuilder) -> Self::Wires { + let exp_pi = b.add_virtual_targets(PublicInputs::::total_len()); + PublicInputs::from_slice(&exp_pi).register(b); + + exp_pi } fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { @@ -187,21 +283,44 @@ mod tests { #[test] fn test_rows_tree_public_inputs() { - let mut rng = thread_rng(); + let rng = &mut thread_rng(); // Prepare the public inputs. - let h = HashOut::rand().to_vec(); - let dr = Point::sample(&mut rng); - let drw = dr.to_weierstrass().to_fields(); - let min = U256::from_limbs(rng.gen::<[u64; 4]>()).to_fields(); - let max = U256::from_limbs(rng.gen::<[u64; 4]>()).to_fields(); - let merge = [F::from_canonical_usize(rng.gen_bool(0.5) as usize)]; - let exp_pi = PublicInputs::new(&h, &drw, &min, &max, &merge); + let multiplier_digest = Point::sample(rng); + let [min, max] = array::from_fn(|_| rng.gen()); + let multiplier_cnt = rng.gen(); + let exp_pi = PublicInputs::sample(multiplier_digest, min, max, multiplier_cnt); let exp_pi = &exp_pi.to_vec(); - assert_eq!(exp_pi.len(), PublicInputs::::TOTAL_LEN); - let test_circuit = TestPICircuit { exp_pi }; - let proof = run_circuit::(test_circuit); + let test_circuit = TestPublicInputs { exp_pi }; + let proof = run_circuit::(test_circuit); assert_eq!(&proof.public_inputs, exp_pi); + + // Check if the public inputs are constructed correctly. + let pi = PublicInputs::from_slice(&proof.public_inputs); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::RootHash)], + pi.to_root_hash_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::IndividualDigest)], + pi.to_individual_digest_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::MultiplierDigest)], + pi.to_multiplier_digest_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::MinValue)], + pi.to_min_value_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::MaxValue)], + pi.to_max_value_raw(), + ); + assert_eq!( + &exp_pi[PublicInputs::::to_range(RowsTreePublicInputs::MultiplierCounter)], + slice::from_ref(pi.to_multiplier_counter_raw()), + ); } } diff --git a/verifiable-db/src/row_tree/secondary_index_cell.rs b/verifiable-db/src/row_tree/secondary_index_cell.rs new file mode 100644 index 000000000..67739911c --- /dev/null +++ b/verifiable-db/src/row_tree/secondary_index_cell.rs @@ -0,0 +1,253 @@ +//! Row information for the rows tree + +use crate::cells_tree::{Cell, CellWire, PublicInputs as CellsPublicInputs}; +use derive_more::Constructor; +use mp2_common::{ + poseidon::{hash_to_int_target, H}, + serialization::{deserialize, serialize}, + types::{CBuilder, CURVE_TARGET_LEN}, + u256::UInt256Target, + utils::{FromFields, ToTargets}, + F, +}; +use plonky2::{ + hash::hash_types::{HashOut, HashOutTarget}, + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, +}; +use plonky2_ecdsa::gadgets::nonnative::CircuitBuilderNonNative; +use plonky2_ecgfp5::{ + curve::curve::Point, + gadgets::curve::{CircuitBuilderEcGFp5, CurveTarget}, +}; +use serde::{Deserialize, Serialize}; +use std::iter::once; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub(crate) struct RowDigest { + pub(crate) multiplier_cnt: F, + pub(crate) individual_vd: Point, + pub(crate) multiplier_vd: Point, +} + +impl FromFields for RowDigest { + fn from_fields(t: &[F]) -> Self { + let mut pos = 0; + + let multiplier_cnt = t[0]; + pos += 1; + + let individual_vd = Point::from_fields(&t[pos..pos + CURVE_TARGET_LEN]); + pos += CURVE_TARGET_LEN; + + let multiplier_vd = Point::from_fields(&t[pos..pos + CURVE_TARGET_LEN]); + + Self { + multiplier_cnt, + individual_vd, + multiplier_vd, + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct RowDigestTarget { + pub(crate) multiplier_cnt: Target, + pub(crate) individual_vd: CurveTarget, + pub(crate) multiplier_vd: CurveTarget, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Constructor)] +pub(crate) struct SecondaryIndexCell { + pub(crate) cell: Cell, + pub(crate) row_unique_data: HashOut, +} + +impl SecondaryIndexCell { + pub(crate) fn assign(&self, pw: &mut PartialWitness, wires: &SecondaryIndexCellWire) { + self.cell.assign(pw, &wires.cell); + pw.set_hash_target(wires.row_unique_data, self.row_unique_data); + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct SecondaryIndexCellWire { + pub(crate) cell: CellWire, + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + pub(crate) row_unique_data: HashOutTarget, +} + +impl SecondaryIndexCellWire { + pub(crate) fn new(b: &mut CBuilder) -> Self { + Self { + cell: CellWire::new(b), + row_unique_data: b.add_virtual_hash(), + } + } + + pub(crate) fn identifier(&self) -> Target { + self.cell.identifier + } + + pub(crate) fn value(&self) -> &UInt256Target { + &self.cell.value + } + + pub(crate) fn digest( + &self, + b: &mut CBuilder, + cells_pi: &CellsPublicInputs, + ) -> RowDigestTarget { + let values_digests = self + .cell + .split_and_accumulate_values_digest(b, &cells_pi.split_values_digest_target()); + + // individual_counter = p.individual_counter + is_individual + let is_individual = self.cell.is_individual(b); + let individual_cnt = b.add(cells_pi.individual_counter_target(), is_individual.target); + + // multiplier_counter = p.multiplier_counter + not is_individual + let is_multiplier = self.cell.is_multiplier(); + let multiplier_cnt = b.add(cells_pi.multiplier_counter_target(), is_multiplier.target); + + // Compute row ID for individual cells: + // row_id_individual = H2Int(row_unique_data || individual_counter) + let inputs = self + .row_unique_data + .to_targets() + .into_iter() + .chain(once(individual_cnt)) + .collect(); + let hash = b.hash_n_to_hash_no_pad::(inputs); + let row_id_individual = hash_to_int_target(b, hash); + + // Multiply row ID to individual value digest: + // individual_vd = row_id_individual * individual_vd + let row_id_individual = b.biguint_to_nonnative(&row_id_individual); + let individual_vd = b.curve_scalar_mul(values_digests.individual, &row_id_individual); + + let multiplier_vd = values_digests.multiplier; + + RowDigestTarget { + multiplier_cnt, + individual_vd, + multiplier_vd, + } + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use itertools::Itertools; + use mp2_common::{ + poseidon::hash_to_int_value, + utils::{FromFields, ToFields}, + C, D, F, + }; + use mp2_test::circuit::{run_circuit, UserCircuit}; + use plonky2::{ + field::types::{Field, Sample}, + plonk::config::Hasher, + }; + use plonky2_ecgfp5::curve::scalar_field::Scalar; + use rand::{thread_rng, Rng}; + + impl SecondaryIndexCell { + pub(crate) fn sample(is_multiplier: bool) -> Self { + let cell = Cell::sample(is_multiplier); + let row_unique_data = HashOut::rand(); + + SecondaryIndexCell::new(cell, row_unique_data) + } + + pub(crate) fn digest(&self, cells_pi: &CellsPublicInputs) -> RowDigest { + let values_digests = self + .cell + .split_and_accumulate_values_digest(cells_pi.split_values_digest_point()); + + // individual_counter = p.individual_counter + is_individual + let individual_cnt = + cells_pi.individual_counter() + F::from_bool(self.cell.is_individual()); + + // multiplier_counter = p.multiplier_counter + not is_individual + let multiplier_cnt = + cells_pi.multiplier_counter() + F::from_bool(self.cell.is_multiplier()); + + // Compute row ID for individual cells: + // row_id_individual = H2Int(row_unique_data || individual_counter) + let inputs = self + .row_unique_data + .to_fields() + .into_iter() + .chain(once(individual_cnt)) + .collect_vec(); + let hash = H::hash_no_pad(&inputs); + let row_id_individual = hash_to_int_value(hash); + let row_id_individual = Scalar::from_noncanonical_biguint(row_id_individual); + + // Multiply row ID to individual value digest: + // individual_vd = row_id_individual * individual_vd + let individual_vd = values_digests.individual * row_id_individual; + + let multiplier_vd = values_digests.multiplier; + + RowDigest { + multiplier_cnt, + individual_vd, + multiplier_vd, + } + } + } + + #[derive(Clone, Debug)] + struct TestRowCircuit<'a> { + row: &'a SecondaryIndexCell, + cells_pi: &'a [F], + } + + impl UserCircuit for TestRowCircuit<'_> { + // Row wire + cells PI + type Wires = (SecondaryIndexCellWire, Vec); + + fn build(b: &mut CBuilder) -> Self::Wires { + let secondary_index_cell = SecondaryIndexCellWire::new(b); + let cells_proof = b.add_virtual_targets(CellsPublicInputs::::total_len()); + let cells_pi = CellsPublicInputs::from_slice(&cells_proof); + + let digest = secondary_index_cell.digest(b, &cells_pi); + + b.register_public_inputs(&digest.multiplier_cnt.to_targets()); + b.register_public_inputs(&digest.individual_vd.to_targets()); + b.register_public_inputs(&digest.multiplier_vd.to_targets()); + + (secondary_index_cell, cells_proof) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.row.assign(pw, &wires.0); + pw.set_target_arr(&wires.1, self.cells_pi); + } + } + + #[test] + fn test_rows_tree_row_circuit() { + let rng = &mut thread_rng(); + + let cells_pi = &CellsPublicInputs::sample(rng.gen()); + let secondary_index_cell = &SecondaryIndexCell::sample(rng.gen()); + let exp_row_digest = secondary_index_cell.digest(&CellsPublicInputs::from_slice(cells_pi)); + + let test_circuit = TestRowCircuit { + row: secondary_index_cell, + cells_pi, + }; + + let proof = run_circuit::(test_circuit); + let row_digest = RowDigest::from_fields(&proof.public_inputs); + + assert_eq!(row_digest, exp_row_digest); + } +}