diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf95f1c8..dabad9072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * Standardised CI and Makefile across Miden repositories (#367) * Removed client dependency from faucet (#368). * Changed sync endpoint to return a list of committed transactions (#377). +* Added support for unauthenticated transaction notes (#390). ## 0.3.0 (2024-05-15) diff --git a/Cargo.lock b/Cargo.lock index d0241ae62..3a99e3713 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-sink", @@ -44,7 +44,7 @@ dependencies = [ "actix-service", "actix-utils", "actix-web", - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "derive_more", "futures-core", @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.7.0" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" +checksum = "3ae682f693a9cd7b058f2b0b5d9a6d7728a8555779bedbbc35dd88528611d020" dependencies = [ "actix-codec", "actix-rt", @@ -69,7 +69,7 @@ dependencies = [ "actix-utils", "ahash", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "brotli", "bytes", "bytestring", @@ -103,7 +103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -171,9 +171,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.7.0" +version = "4.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6316df3fa569627c98b12557a8b6ff0674e5be4bb9b5e4ae2550ddb4964ed6" +checksum = "1988c02af8d2b718c05bc4aeb6a66395b7cdf32858c2c71131e5637a8c05a9ff" dependencies = [ "actix-codec", "actix-http", @@ -219,7 +219,7 @@ dependencies = [ "actix-router", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -390,7 +390,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -401,7 +401,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -512,7 +512,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "cexpr", "clang-sys", "itertools", @@ -523,7 +523,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.66", + "syn", ] [[package]] @@ -549,9 +549,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "blake3" @@ -604,9 +604,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "bytes" @@ -625,9 +625,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" dependencies = [ "jobserver", "libc", @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -686,9 +686,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.7" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", @@ -698,14 +698,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.5" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -854,15 +854,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn", ] [[package]] @@ -896,22 +896,11 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "encoding_rs" @@ -1180,9 +1169,9 @@ checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -1249,134 +1238,14 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -1467,9 +1336,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "lazycell" @@ -1485,9 +1354,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if", "windows-targets 0.52.5", @@ -1505,7 +1374,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", ] @@ -1527,12 +1396,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "local-channel" version = "0.1.5" @@ -1562,9 +1425,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" @@ -1587,7 +1450,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.8.4", - "syn 2.0.66", + "syn", ] [[package]] @@ -1698,7 +1561,7 @@ dependencies = [ [[package]] name = "miden-lib" version = "0.4.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#be1cc6ef501313fb8a4f29fb755980b883a15cec" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#861837b2f5ce4771af3a627e7e06f8cdcb672e80" dependencies = [ "miden-assembly", "miden-objects", @@ -1822,7 +1685,7 @@ name = "miden-node-test-macro" version = "0.1.0" dependencies = [ "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -1844,7 +1707,7 @@ dependencies = [ [[package]] name = "miden-objects" version = "0.4.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#be1cc6ef501313fb8a4f29fb755980b883a15cec" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#861837b2f5ce4771af3a627e7e06f8cdcb672e80" dependencies = [ "miden-assembly", "miden-core", @@ -1895,7 +1758,7 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.4.0" -source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#be1cc6ef501313fb8a4f29fb755980b883a15cec" +source = "git+https://github.com/0xPolygonMiden/miden-base.git?branch=next#861837b2f5ce4771af3a627e7e06f8cdcb672e80" dependencies = [ "miden-lib", "miden-objects", @@ -1946,7 +1809,7 @@ checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -1973,9 +1836,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] @@ -2034,9 +1897,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -2126,7 +1989,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2211,7 +2074,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2247,7 +2110,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2287,7 +2150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.66", + "syn", ] [[package]] @@ -2301,9 +2164,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -2316,20 +2179,20 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", "version_check", "yansi", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.5.0", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", @@ -2368,7 +2231,7 @@ dependencies = [ "prost", "prost-types", "regex", - "syn 2.0.66", + "syn", "tempfile", ] @@ -2382,7 +2245,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2514,7 +2377,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] @@ -2584,7 +2447,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -2629,7 +2492,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2689,14 +2552,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -2800,12 +2663,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "strsim" version = "0.11.1" @@ -2835,20 +2692,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.66" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -2861,17 +2707,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "tempfile" version = "3.10.1" @@ -2922,7 +2757,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -2967,15 +2802,20 @@ dependencies = [ ] [[package]] -name = "tinystr" -version = "0.7.6" +name = "tinyvec" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ - "displaydoc", - "zerovec", + "tinyvec_macros", ] +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.38.0" @@ -3013,7 +2853,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3122,7 +2962,7 @@ dependencies = [ "proc-macro2", "prost-build", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3171,7 +3011,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "bytes", "futures-core", "futures-util", @@ -3215,7 +3055,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3319,6 +3159,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" @@ -3331,6 +3177,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-width" version = "0.1.13" @@ -3339,27 +3194,15 @@ checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.2" @@ -3435,7 +3278,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn", "wasm-bindgen-shared", ] @@ -3457,7 +3300,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3708,7 +3551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77eafc7944188b941c90759b938b8377247ee6e440952a0a7c4dbde7edc4ecbb" dependencies = [ "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3737,9 +3580,9 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6efccf6efa6fd0a80784f3894bc372ada67cc30d9c017fc907d4c0cdce86e7" +checksum = "ef7d7195967f35140fc2542b44813572e0907cde8a818507177ba8db666e7f17" dependencies = [ "rayon", ] @@ -3767,48 +3610,12 @@ dependencies = [ "winter-verifier", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zerocopy" version = "0.7.34" @@ -3826,50 +3633,7 @@ checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "syn", ] [[package]] @@ -3892,9 +3656,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", diff --git a/crates/block-producer/src/batch_builder/batch.rs b/crates/block-producer/src/batch_builder/batch.rs index dabcdc0e5..14e181f81 100644 --- a/crates/block-producer/src/batch_builder/batch.rs +++ b/crates/block-producer/src/batch_builder/batch.rs @@ -1,9 +1,11 @@ +use std::collections::BTreeSet; + use miden_objects::{ accounts::AccountId, batches::BatchNoteTree, block::BlockAccountUpdate, crypto::hash::blake::{Blake3Digest, Blake3_256}, - notes::Nullifier, + notes::{NoteId, Nullifier}, transaction::{OutputNote, TransactionId, TxAccountUpdate}, Digest, MAX_NOTES_PER_BATCH, }; @@ -24,9 +26,10 @@ pub type BatchId = Blake3Digest<32>; pub struct TransactionBatch { id: BatchId, updated_accounts: Vec<(TransactionId, TxAccountUpdate)>, + unauthenticated_input_notes: BTreeSet, produced_nullifiers: Vec, - created_notes_smt: BatchNoteTree, - created_notes: Vec, + output_notes_smt: BatchNoteTree, + output_notes: Vec, } impl TransactionBatch { @@ -44,38 +47,61 @@ impl TransactionBatch { pub fn new(txs: Vec) -> Result { let id = Self::compute_id(&txs); - // TODO: we need to handle a possibility that a batch contains multiple transactions against - // the same account (e.g., transaction `x` takes account from state `A` to `B` and - // transaction `y` takes account from state `B` to `C`). These will need to be merged - // into a single "update" `A` to `C`. - let updated_accounts = - txs.iter().map(|tx| (tx.id(), tx.account_update().clone())).collect(); + let mut updated_accounts = vec![]; + let mut produced_nullifiers = vec![]; + let mut unauthenticated_input_notes = BTreeSet::new(); + for tx in &txs { + // TODO: we need to handle a possibility that a batch contains multiple transactions against + // the same account (e.g., transaction `x` takes account from state `A` to `B` and + // transaction `y` takes account from state `B` to `C`). These will need to be merged + // into a single "update" `A` to `C`. + updated_accounts.push((tx.id(), tx.account_update().clone())); + + for note in tx.input_notes() { + produced_nullifiers.push(note.nullifier()); + if let Some(header) = note.header() { + if !unauthenticated_input_notes.insert(header.id()) { + return Err(BuildBatchError::DuplicateUnauthenticatedNote( + header.id(), + txs, + )); + } + } + } + } - let produced_nullifiers = txs + // Populate batch output notes, filtering out unauthenticated notes consumed in the same batch. + // Consumed notes are also removed from the unauthenticated input notes set in order to avoid + // consumption of notes with the same ID by one single input. + // + // One thing to note: + // This still allows transaction `A` to consume an unauthenticated note `x` and output note `y` + // and for transaction `B` to consume an unauthenticated note `y` and output note `x` + // (i.e., have a circular dependency between transactions), but this is not a problem. + let output_notes: Vec<_> = txs .iter() - .flat_map(|tx| tx.input_notes().iter()) - .map(|note| note.nullifier()) + .flat_map(|tx| tx.output_notes().iter()) + .filter(|¬e| !unauthenticated_input_notes.remove(¬e.id())) + .cloned() .collect(); - let created_notes: Vec<_> = - txs.iter().flat_map(|tx| tx.output_notes().iter()).cloned().collect(); - - if created_notes.len() > MAX_NOTES_PER_BATCH { - return Err(BuildBatchError::TooManyNotesCreated(created_notes.len(), txs)); + if output_notes.len() > MAX_NOTES_PER_BATCH { + return Err(BuildBatchError::TooManyNotesCreated(output_notes.len(), txs)); } // TODO: document under what circumstances SMT creating can fail - let created_notes_smt = BatchNoteTree::with_contiguous_leaves( - created_notes.iter().map(|note| (note.id(), note.metadata())), + let output_notes_smt = BatchNoteTree::with_contiguous_leaves( + output_notes.iter().map(|note| (note.id(), note.metadata())), ) .map_err(|e| BuildBatchError::NotesSmtError(e, txs))?; Ok(Self { id, updated_accounts, + unauthenticated_input_notes, produced_nullifiers, - created_notes_smt, - created_notes, + output_notes_smt, + output_notes, }) } @@ -108,19 +134,24 @@ impl TransactionBatch { }) } + /// Returns unauthenticated input notes set consumed by the transactions in this batch. + pub fn unauthenticated_input_notes(&self) -> &BTreeSet { + &self.unauthenticated_input_notes + } + /// Returns an iterator over produced nullifiers for all consumed notes. pub fn produced_nullifiers(&self) -> impl Iterator + '_ { self.produced_nullifiers.iter().cloned() } - /// Returns the root hash of the created notes SMT. - pub fn created_notes_root(&self) -> Digest { - self.created_notes_smt.root() + /// Returns the root hash of the output notes SMT. + pub fn output_notes_root(&self) -> Digest { + self.output_notes_smt.root() } - /// Returns created notes list. - pub fn created_notes(&self) -> &Vec { - &self.created_notes + /// Returns output notes list. + pub fn output_notes(&self) -> &Vec { + &self.output_notes } // HELPER FUNCTIONS diff --git a/crates/block-producer/src/batch_builder/mod.rs b/crates/block-producer/src/batch_builder/mod.rs index 76a89e76a..702ee7e6c 100644 --- a/crates/block-producer/src/batch_builder/mod.rs +++ b/crates/block-producer/src/batch_builder/mod.rs @@ -1,7 +1,8 @@ -use std::{cmp::min, sync::Arc, time::Duration}; +use std::{cmp::min, collections::BTreeSet, sync::Arc, time::Duration}; use async_trait::async_trait; -use tokio::{sync::RwLock, time}; +use miden_objects::{notes::NoteId, transaction::OutputNote}; +use tokio::time; use tracing::{debug, info, instrument, Span}; use crate::{block_builder::BlockBuilder, ProvenTransaction, SharedRwVec, COMPONENT}; @@ -13,7 +14,7 @@ pub mod batch; pub use batch::TransactionBatch; use miden_node_utils::formatting::{format_array, format_blake3_digest}; -use crate::errors::BuildBatchError; +use crate::{errors::BuildBatchError, store::Store}; // BATCH BUILDER // ================================================================================================ @@ -45,31 +46,35 @@ pub struct DefaultBatchBuilderOptions { pub max_batches_per_block: usize, } -pub struct DefaultBatchBuilder { - /// Batches ready to be included in a block - ready_batches: SharedRwVec, +pub struct DefaultBatchBuilder { + store: Arc, block_builder: Arc, options: DefaultBatchBuilderOptions, + + /// Batches ready to be included in a block + ready_batches: SharedRwVec, } // FIXME: remove the allow when the upstream clippy issue is fixed: // https://github.com/rust-lang/rust-clippy/issues/12281 #[allow(clippy::blocks_in_conditions)] -impl DefaultBatchBuilder +impl DefaultBatchBuilder where + S: Store, BB: BlockBuilder, { // CONSTRUCTOR // -------------------------------------------------------------------------------------------- /// Returns an new [BatchBuilder] instantiated with the provided [BlockBuilder] and the /// specified options. - pub fn new(block_builder: Arc, options: DefaultBatchBuilderOptions) -> Self { + pub fn new(store: Arc, block_builder: Arc, options: DefaultBatchBuilderOptions) -> Self { Self { - ready_batches: Arc::new(RwLock::new(Vec::new())), + store, block_builder, options, + ready_batches: Default::default(), } } @@ -112,14 +117,42 @@ where }, } } + + /// Returns a list of IDs for unauthenticated notes which are not output notes of any ready + /// transaction batch or the candidate batch itself. + async fn find_dangling_notes(&self, txs: &[ProvenTransaction]) -> Vec { + // TODO: We can optimize this by looking at the notes created in the previous batches + + // build a set of output notes from all ready batches and the candidate batch + let mut all_output_notes: BTreeSet = txs + .iter() + .flat_map(|tx| tx.output_notes().iter().map(OutputNote::id)) + .chain( + self.ready_batches + .read() + .await + .iter() + .flat_map(|batch| batch.output_notes().iter().map(OutputNote::id)), + ) + .collect(); + + // from the list of unauthenticated notes in the candidate batch, filter out any note + // which is also an output note either in any of the ready batches or in the candidate + // batch itself + txs.iter() + .flat_map(|tx| tx.get_unauthenticated_notes().map(|note| note.id())) + .filter(|note_id| !all_output_notes.remove(note_id)) + .collect() + } } // FIXME: remove the allow when the upstream clippy issue is fixed: // https://github.com/rust-lang/rust-clippy/issues/12281 #[allow(clippy::blocks_in_conditions)] #[async_trait] -impl BatchBuilder for DefaultBatchBuilder +impl BatchBuilder for DefaultBatchBuilder where + S: Store, BB: BlockBuilder, { #[instrument(target = "miden-block-producer", skip_all, err, fields(batch_id))] @@ -129,6 +162,29 @@ where info!(target: COMPONENT, num_txs, "Building a transaction batch"); debug!(target: COMPONENT, txs = %format_array(txs.iter().map(|tx| tx.id().to_hex()))); + // make sure that all unauthenticated notes in the transactions of the proposed batch + // have been either created in any of the ready batches (or the batch itself) or are + // already in the store + // + // TODO: this can be optimized by first computing dangling notes of the batch itself, + // and only then checking against the other ready batches + let dangling_notes = self.find_dangling_notes(&txs).await; + if !dangling_notes.is_empty() { + let stored_notes = + match self.store.get_note_authentication_info(dangling_notes.iter()).await { + Ok(stored_notes) => stored_notes, + Err(err) => return Err(BuildBatchError::NotePathsError(err, txs)), + }; + let missing_notes: Vec<_> = dangling_notes + .into_iter() + .filter(|note_id| !stored_notes.contains_key(note_id)) + .collect(); + + if !missing_notes.is_empty() { + return Err(BuildBatchError::UnauthenticatedNotesNotFound(missing_notes, txs)); + } + } + let batch = TransactionBatch::new(txs)?; info!(target: COMPONENT, "Transaction batch built"); diff --git a/crates/block-producer/src/batch_builder/tests/mod.rs b/crates/block-producer/src/batch_builder/tests/mod.rs index 49ef1a5a7..07e7344c4 100644 --- a/crates/block-producer/src/batch_builder/tests/mod.rs +++ b/crates/block-producer/src/batch_builder/tests/mod.rs @@ -1,5 +1,12 @@ +use std::iter; + +use tokio::sync::RwLock; + use super::*; -use crate::{errors::BuildBlockError, test_utils::MockProvenTxBuilder}; +use crate::{ + errors::BuildBlockError, + test_utils::{MockProvenTxBuilder, MockStoreSuccessBuilder}, +}; // STRUCTS // ================================================================================================ @@ -43,9 +50,11 @@ async fn test_block_size_doesnt_exceed_limit() { let block_frequency = Duration::from_millis(20); let max_batches_per_block = 2; + let store = Arc::new(MockStoreSuccessBuilder::from_accounts(iter::empty()).build()); let block_builder = Arc::new(BlockBuilderSuccess::default()); let batch_builder = Arc::new(DefaultBatchBuilder::new( + store, block_builder.clone(), DefaultBatchBuilderOptions { block_frequency, max_batches_per_block }, )); @@ -81,9 +90,11 @@ async fn test_build_block_called_when_no_batches() { let block_frequency = Duration::from_millis(20); let max_batches_per_block = 2; + let store = Arc::new(MockStoreSuccessBuilder::from_accounts(iter::empty()).build()); let block_builder = Arc::new(BlockBuilderSuccess::default()); let batch_builder = Arc::new(DefaultBatchBuilder::new( + store, block_builder.clone(), DefaultBatchBuilderOptions { block_frequency, max_batches_per_block }, )); @@ -106,9 +117,11 @@ async fn test_batches_added_back_to_queue_on_block_build_failure() { let block_frequency = Duration::from_millis(20); let max_batches_per_block = 2; + let store = Arc::new(MockStoreSuccessBuilder::from_accounts(iter::empty()).build()); let block_builder = Arc::new(BlockBuilderFailure); let batch_builder = Arc::new(DefaultBatchBuilder::new( + store, block_builder.clone(), DefaultBatchBuilderOptions { block_frequency, max_batches_per_block }, )); diff --git a/crates/block-producer/src/block.rs b/crates/block-producer/src/block.rs index d0f4fa17f..cf2b886a8 100644 --- a/crates/block-producer/src/block.rs +++ b/crates/block-producer/src/block.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use miden_node_proto::{ errors::{ConversionError, MissingFieldHelper}, @@ -7,8 +7,11 @@ use miden_node_proto::{ }; use miden_objects::{ accounts::AccountId, - crypto::merkle::{MerklePath, MmrPeaks, SmtProof}, - notes::Nullifier, + crypto::{ + hash::rpo::RpoDigest, + merkle::{MerklePath, MmrPeaks, SmtProof}, + }, + notes::{NoteId, Nullifier}, BlockHeader, Digest, }; @@ -31,6 +34,9 @@ pub struct BlockInputs { /// The requested nullifiers and their authentication paths pub nullifiers: BTreeMap, + + /// List of unauthenticated notes found in the store + pub found_unauthenticated_notes: BTreeSet, } #[derive(Clone, Debug, Default)] @@ -42,8 +48,8 @@ pub struct AccountWitness { impl TryFrom for BlockInputs { type Error = BlockInputsError; - fn try_from(get_block_inputs: GetBlockInputsResponse) -> Result { - let block_header: BlockHeader = get_block_inputs + fn try_from(response: GetBlockInputsResponse) -> Result { + let block_header: BlockHeader = response .block_header .ok_or(GetBlockInputsResponse::missing_field(stringify!(block_header)))? .try_into()?; @@ -57,7 +63,7 @@ impl TryFrom for BlockInputs { MmrPeaks::new( num_leaves, - get_block_inputs + response .mmr_peaks .into_iter() .map(TryInto::try_into) @@ -65,7 +71,7 @@ impl TryFrom for BlockInputs { )? }; - let accounts = get_block_inputs + let accounts = response .account_states .into_iter() .map(|entry| { @@ -78,7 +84,7 @@ impl TryFrom for BlockInputs { }) .collect::, ConversionError>>()?; - let nullifiers = get_block_inputs + let nullifiers = response .nullifiers .into_iter() .map(|entry| { @@ -87,11 +93,18 @@ impl TryFrom for BlockInputs { }) .collect::, ConversionError>>()?; + let found_unauthenticated_notes = response + .found_unauthenticated_notes + .into_iter() + .map(|digest| Ok(RpoDigest::try_from(digest)?.into())) + .collect::>()?; + Ok(Self { block_header, chain_peaks, accounts, nullifiers, + found_unauthenticated_notes, }) } } diff --git a/crates/block-producer/src/block_builder/mod.rs b/crates/block-producer/src/block_builder/mod.rs index 27bfbf33f..63258f451 100644 --- a/crates/block-producer/src/block_builder/mod.rs +++ b/crates/block-producer/src/block_builder/mod.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::BTreeSet, sync::Arc}; use async_trait::async_trait; use miden_node_utils::formatting::{format_array, format_blake3_digest}; @@ -76,19 +76,42 @@ where let updated_accounts: Vec<_> = batches.iter().flat_map(TransactionBatch::updated_accounts).collect(); - let created_notes = batches.iter().map(TransactionBatch::created_notes).cloned().collect(); + let created_notes: Vec<_> = + batches.iter().map(TransactionBatch::output_notes).cloned().collect(); let produced_nullifiers: Vec = batches.iter().flat_map(TransactionBatch::produced_nullifiers).collect(); + let created_notes_set: BTreeSet<_> = created_notes + .iter() + .flat_map(|batch| batch.iter().map(|note| note.id())) + .collect(); + + let dangling_notes: BTreeSet<_> = batches + .iter() + .flat_map(TransactionBatch::unauthenticated_input_notes) + .filter(|¬e_id| !created_notes_set.contains(note_id)) + .copied() + .collect(); + let block_inputs = self .store .get_block_inputs( updated_accounts.iter().map(BlockAccountUpdate::account_id), produced_nullifiers.iter(), + dangling_notes.iter(), ) .await?; + if block_inputs.found_unauthenticated_notes.len() < dangling_notes.len() { + return Err(BuildBlockError::UnauthenticatedNotesNotFound( + dangling_notes + .difference(&block_inputs.found_unauthenticated_notes) + .copied() + .collect(), + )); + } + let block_header_witness = BlockWitness::new(block_inputs, batches)?; let new_block_header = self.block_kernel.prove(block_header_witness)?; diff --git a/crates/block-producer/src/block_builder/prover/block_witness.rs b/crates/block-producer/src/block_builder/prover/block_witness.rs index 0e73603eb..e2f02401c 100644 --- a/crates/block-producer/src/block_builder/prover/block_witness.rs +++ b/crates/block-producer/src/block_builder/prover/block_witness.rs @@ -73,8 +73,8 @@ impl BlockWitness { let batch_created_notes_roots = batches .iter() .enumerate() - .filter(|(_, batch)| !batch.created_notes().is_empty()) - .map(|(batch_index, batch)| (batch_index, batch.created_notes_root())) + .filter(|(_, batch)| !batch.output_notes().is_empty()) + .map(|(batch_index, batch)| (batch_index, batch.output_notes_root())) .collect(); Ok(Self { diff --git a/crates/block-producer/src/block_builder/prover/tests.rs b/crates/block-producer/src/block_builder/prover/tests.rs index 0a6f23092..d65941c16 100644 --- a/crates/block-producer/src/block_builder/prover/tests.rs +++ b/crates/block-producer/src/block_builder/prover/tests.rs @@ -56,6 +56,7 @@ fn test_block_witness_validation_inconsistent_account_ids() { chain_peaks, accounts, nullifiers: Default::default(), + found_unauthenticated_notes: Default::default(), } }; @@ -128,6 +129,7 @@ fn test_block_witness_validation_inconsistent_account_hashes() { chain_peaks, accounts, nullifiers: Default::default(), + found_unauthenticated_notes: Default::default(), } }; @@ -209,7 +211,7 @@ async fn test_compute_account_root_success() { // Block inputs is initialized with all the accounts and their initial state let block_inputs_from_store: BlockInputs = store - .get_block_inputs(account_ids.into_iter(), std::iter::empty()) + .get_block_inputs(account_ids.into_iter(), std::iter::empty(), std::iter::empty()) .await .unwrap(); @@ -300,8 +302,10 @@ async fn test_compute_account_root_empty_batches() { // --------------------------------------------------------------------------------------------- // Block inputs is initialized with all the accounts and their initial state - let block_inputs_from_store: BlockInputs = - store.get_block_inputs(std::iter::empty(), std::iter::empty()).await.unwrap(); + let block_inputs_from_store: BlockInputs = store + .get_block_inputs(std::iter::empty(), std::iter::empty(), std::iter::empty()) + .await + .unwrap(); let batches = Vec::new(); let block_witness = BlockWitness::new(block_inputs_from_store, &batches).unwrap(); @@ -331,8 +335,10 @@ async fn test_compute_note_root_empty_batches_success() { // --------------------------------------------------------------------------------------------- // Block inputs is initialized with all the accounts and their initial state - let block_inputs_from_store: BlockInputs = - store.get_block_inputs(std::iter::empty(), std::iter::empty()).await.unwrap(); + let block_inputs_from_store: BlockInputs = store + .get_block_inputs(std::iter::empty(), std::iter::empty(), std::iter::empty()) + .await + .unwrap(); let batches: Vec = Vec::new(); @@ -361,8 +367,10 @@ async fn test_compute_note_root_empty_notes_success() { // --------------------------------------------------------------------------------------------- // Block inputs is initialized with all the accounts and their initial state - let block_inputs_from_store: BlockInputs = - store.get_block_inputs(std::iter::empty(), std::iter::empty()).await.unwrap(); + let block_inputs_from_store: BlockInputs = store + .get_block_inputs(std::iter::empty(), std::iter::empty(), std::iter::empty()) + .await + .unwrap(); let batches: Vec = { let batch = TransactionBatch::new(Vec::new()).unwrap(); @@ -422,7 +430,7 @@ async fn test_compute_note_root_success() { // Block inputs is initialized with all the accounts and their initial state let block_inputs_from_store: BlockInputs = store - .get_block_inputs(account_ids.into_iter(), std::iter::empty()) + .get_block_inputs(account_ids.into_iter(), std::iter::empty(), std::iter::empty()) .await .unwrap(); @@ -544,6 +552,7 @@ fn test_block_witness_validation_inconsistent_nullifiers() { chain_peaks, accounts, nullifiers, + found_unauthenticated_notes: Default::default(), } }; @@ -590,7 +599,7 @@ async fn test_compute_nullifier_root_empty_success() { // Block inputs is initialized with all the accounts and their initial state let block_inputs_from_store: BlockInputs = store - .get_block_inputs(account_ids.into_iter(), std::iter::empty()) + .get_block_inputs(account_ids.into_iter(), std::iter::empty(), std::iter::empty()) .await .unwrap(); @@ -651,7 +660,7 @@ async fn test_compute_nullifier_root_success() { // Block inputs is initialized with all the accounts and their initial state let block_inputs_from_store: BlockInputs = store - .get_block_inputs(account_ids.into_iter(), nullifiers.iter()) + .get_block_inputs(account_ids.into_iter(), nullifiers.iter(), std::iter::empty()) .await .unwrap(); diff --git a/crates/block-producer/src/errors.rs b/crates/block-producer/src/errors.rs index 0f66213c2..6093be290 100644 --- a/crates/block-producer/src/errors.rs +++ b/crates/block-producer/src/errors.rs @@ -3,7 +3,7 @@ use miden_node_utils::formatting::format_opt; use miden_objects::{ accounts::AccountId, crypto::merkle::{MerkleError, MmrError}, - notes::Nullifier, + notes::{NoteId, Nullifier}, transaction::{ProvenTransaction, TransactionId}, Digest, TransactionInputError, BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH, MAX_NOTES_PER_BATCH, }; @@ -24,6 +24,12 @@ pub enum VerifyTxError { #[error("Input notes with given nullifiers were already consumed by another transaction")] InputNotesAlreadyConsumed(Vec), + /// Unauthenticated transaction notes were not found in the store or in outputs of in-flight transactions + #[error( + "Unauthenticated transaction notes were not found in the store or in outputs of in-flight transactions: {0:?}" + )] + UnauthenticatedNotesNotFound(Vec), + /// The account's initial hash did not match the current account's hash #[error("Incorrect account's initial hash ({tx_initial_account_hash}, stored: {})", format_opt(.store_account_hash.as_ref()))] IncorrectAccountInitialHash { @@ -68,8 +74,17 @@ pub enum BuildBatchError { #[error("Too many notes in the batch. Got: {0}, max: {}", MAX_NOTES_PER_BATCH)] TooManyNotesCreated(usize, Vec), - #[error("failed to create notes SMT: {0}")] + #[error("Failed to create notes SMT: {0}")] NotesSmtError(MerkleError, Vec), + + #[error("Failed to get note paths: {0}")] + NotePathsError(NotePathsError, Vec), + + #[error("Duplicated note ID in the batch: {0}")] + DuplicateUnauthenticatedNote(NoteId, Vec), + + #[error("Unauthenticated transaction notes not found in the store: {0:?}")] + UnauthenticatedNotesNotFound(Vec, Vec), } impl BuildBatchError { @@ -77,6 +92,9 @@ impl BuildBatchError { match self { BuildBatchError::TooManyNotesCreated(_, txs) => txs, BuildBatchError::NotesSmtError(_, txs) => txs, + BuildBatchError::NotePathsError(_, txs) => txs, + BuildBatchError::DuplicateUnauthenticatedNote(_, txs) => txs, + BuildBatchError::UnauthenticatedNotesNotFound(_, txs) => txs, } } } @@ -108,6 +126,18 @@ pub enum BlockInputsError { GrpcClientError(String), } +// Note paths errors +// ================================================================================================= + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, PartialEq, Eq, Error)] +pub enum NotePathsError { + #[error("failed to parse protobuf message: {0}")] + ConversionError(#[from] ConversionError), + #[error("gRPC client failed with error: {0}")] + GrpcClientError(String), +} + // Block applying errors // ================================================================================================= @@ -134,6 +164,8 @@ pub enum BuildBlockError { InconsistentAccountStates(Vec), #[error("transaction batches and store don't produce the same nullifiers. Offending nullifiers: {0:?}")] InconsistentNullifiers(Vec), + #[error("unauthenticated transaction notes not found in the store or in outputs of other transactions in the block: {0:?}")] + UnauthenticatedNotesNotFound(Vec), #[error( "too many batches in block. Got: {0}, max: 2^{}", BLOCK_OUTPUT_NOTES_BATCH_TREE_DEPTH diff --git a/crates/block-producer/src/lib.rs b/crates/block-producer/src/lib.rs index 0b0630048..dbbdd9803 100644 --- a/crates/block-producer/src/lib.rs +++ b/crates/block-producer/src/lib.rs @@ -21,7 +21,7 @@ pub mod server; // TYPE ALIASES // ================================================================================================= -/// A proven transaction that can be shared across threads +/// A vector that can be shared across threads pub(crate) type SharedRwVec = Arc>>; // CONSTANTS diff --git a/crates/block-producer/src/server/mod.rs b/crates/block-producer/src/server/mod.rs index 66a2589f1..0e31b9d63 100644 --- a/crates/block-producer/src/server/mod.rs +++ b/crates/block-producer/src/server/mod.rs @@ -29,15 +29,18 @@ pub async fn serve(config: BlockProducerConfig) -> Result<(), ApiError> { .await .map_err(|err| ApiError::DatabaseConnectionFailed(err.to_string()))?, )); - let state_view = Arc::new(DefaultStateView::new(store.clone(), config.verify_tx_proofs)); + let state_view = Arc::new(DefaultStateView::new(Arc::clone(&store), config.verify_tx_proofs)); - let block_builder = DefaultBlockBuilder::new(store.clone(), state_view.clone()); + let block_builder = DefaultBlockBuilder::new(Arc::clone(&store), Arc::clone(&state_view)); let batch_builder_options = DefaultBatchBuilderOptions { block_frequency: SERVER_BLOCK_FREQUENCY, max_batches_per_block: SERVER_MAX_BATCHES_PER_BLOCK, }; - let batch_builder = - Arc::new(DefaultBatchBuilder::new(Arc::new(block_builder), batch_builder_options)); + let batch_builder = Arc::new(DefaultBatchBuilder::new( + Arc::clone(&store), + Arc::new(block_builder), + batch_builder_options, + )); let transaction_queue_options = TransactionQueueOptions { build_batch_frequency: SERVER_BUILD_BATCH_FREQUENCY, @@ -45,11 +48,11 @@ pub async fn serve(config: BlockProducerConfig) -> Result<(), ApiError> { }; let queue = Arc::new(TransactionQueue::new( state_view, - batch_builder.clone(), + Arc::clone(&batch_builder), transaction_queue_options, )); - let block_producer = api_server::ApiServer::new(api::BlockProducerApi::new(queue.clone())); + let block_producer = api_server::ApiServer::new(api::BlockProducerApi::new(Arc::clone(&queue))); tokio::spawn(async move { queue.run().await }); tokio::spawn(async move { batch_builder.run().await }); diff --git a/crates/block-producer/src/state_view/mod.rs b/crates/block-producer/src/state_view/mod.rs index d0ea5fde7..192892cef 100644 --- a/crates/block-producer/src/state_view/mod.rs +++ b/crates/block-producer/src/state_view/mod.rs @@ -3,7 +3,11 @@ use std::{collections::BTreeSet, sync::Arc}; use async_trait::async_trait; use miden_node_utils::formatting::format_array; use miden_objects::{ - accounts::AccountId, block::Block, notes::Nullifier, Digest, MIN_PROOF_SECURITY_LEVEL, + accounts::AccountId, + block::Block, + notes::{NoteId, Nullifier}, + transaction::OutputNote, + Digest, MIN_PROOF_SECURITY_LEVEL, }; use miden_tx::TransactionVerifier; use tokio::sync::RwLock; @@ -31,6 +35,9 @@ pub struct DefaultStateView { /// The nullifiers of notes consumed by transactions currently in the block production pipeline. nullifiers_in_flight: Arc>>, + + /// The output notes of transactions currently in the block production pipeline. + notes_in_flight: Arc>>, } impl DefaultStateView @@ -41,8 +48,9 @@ where Self { store, verify_tx_proofs, - accounts_in_flight: Arc::new(RwLock::new(BTreeSet::new())), - nullifiers_in_flight: Arc::new(RwLock::new(BTreeSet::new())), + accounts_in_flight: Default::default(), + nullifiers_in_flight: Default::default(), + notes_in_flight: Default::default(), } } } @@ -69,15 +77,23 @@ where // // This is a "soft" check, because we'll need to redo it at the end. We do this soft check // to quickly reject clearly infracting transactions before hitting the store (slow). + // + // At this stage we don't provide missing notes, they will be available on the second check + // after getting the transaction inputs. ensure_in_flight_constraints( candidate_tx, &*self.accounts_in_flight.read().await, &*self.nullifiers_in_flight.read().await, + &*self.notes_in_flight.read().await, + &[], )?; - // Fetch the transaction inputs from the store, and check tx input constraints + // Fetch the transaction inputs from the store, and check tx input constraints; this will + // identify a set of unauthenticated input notes which are not in the store yet; we'll use + // this set to verify that these notes are currently in flight (i.e., they are output notes + // of one of the inflight transactions) let tx_inputs = self.store.get_tx_inputs(candidate_tx).await?; - ensure_tx_inputs_constraints(candidate_tx, tx_inputs)?; + let missing_notes = ensure_tx_inputs_constraints(candidate_tx, tx_inputs)?; // Re-check in-flight transaction constraints, and if verification passes, register // transaction @@ -87,19 +103,20 @@ where { let mut locked_accounts_in_flight = self.accounts_in_flight.write().await; let mut locked_nullifiers_in_flight = self.nullifiers_in_flight.write().await; + let mut locked_notes_in_flight = self.notes_in_flight.write().await; ensure_in_flight_constraints( candidate_tx, &locked_accounts_in_flight, &locked_nullifiers_in_flight, + &locked_notes_in_flight, + &missing_notes, )?; // Success! Register transaction as successfully verified locked_accounts_in_flight.insert(candidate_tx.account_id()); - - let mut nullifiers_in_tx: BTreeSet<_> = - candidate_tx.input_notes().iter().map(|note| note.nullifier()).collect(); - locked_nullifiers_in_flight.append(&mut nullifiers_in_tx); + locked_nullifiers_in_flight.extend(&mut candidate_tx.get_nullifiers()); + locked_notes_in_flight.extend(candidate_tx.output_notes().iter().map(OutputNote::id)); } Ok(()) @@ -120,6 +137,7 @@ where let mut locked_accounts_in_flight = self.accounts_in_flight.write().await; let mut locked_nullifiers_in_flight = self.nullifiers_in_flight.write().await; + let mut locked_notes_in_flight = self.notes_in_flight.write().await; // Remove account ids of transactions in block for update in block.updated_accounts() { @@ -133,6 +151,14 @@ where debug_assert!(was_in_flight); } + // Remove new notes of transactions in block + for batch in block.created_notes() { + for note in batch.iter() { + let was_in_flight = locked_notes_in_flight.remove(¬e.id()); + debug_assert!(was_in_flight); + } + } + Ok(()) } } @@ -143,13 +169,16 @@ where /// Ensures the constraints related to in-flight transactions: /// - the candidate transaction doesn't modify the same account as an existing in-flight /// transaction (issue: #186) -/// - no consumed note's nullifier in candidate tx's consumed notes is already contained in +/// - no note's nullifier in candidate tx's consumed notes is already contained in /// `already_consumed_nullifiers` +/// - all notes in `tx_notes_not_in_store` are currently in flight #[instrument(target = "miden-block-producer", skip_all, err)] fn ensure_in_flight_constraints( candidate_tx: &ProvenTransaction, accounts_in_flight: &BTreeSet, already_consumed_nullifiers: &BTreeSet, + notes_in_flight: &BTreeSet, + tx_notes_not_in_store: &[NoteId], ) -> Result<(), VerifyTxError> { debug!(target: COMPONENT, accounts_in_flight = %format_array(accounts_in_flight), already_consumed_nullifiers = %format_array(already_consumed_nullifiers)); @@ -161,10 +190,8 @@ fn ensure_in_flight_constraints( // Check no consumed notes were already consumed let infracting_nullifiers: Vec = { candidate_tx - .input_notes() - .iter() - .filter(|&commitment| already_consumed_nullifiers.contains(&commitment.nullifier())) - .map(|commitment| commitment.nullifier()) + .get_nullifiers() + .filter(|nullifier| already_consumed_nullifiers.contains(nullifier)) .collect() }; @@ -172,14 +199,30 @@ fn ensure_in_flight_constraints( return Err(VerifyTxError::InputNotesAlreadyConsumed(infracting_nullifiers)); } + // Check all notes not found in store are in in-flight notes, return list of missing notes + let missing_notes: Vec = tx_notes_not_in_store + .iter() + .filter(|note_id| !notes_in_flight.contains(note_id)) + .copied() + .collect(); + if !missing_notes.is_empty() { + return Err(VerifyTxError::UnauthenticatedNotesNotFound(missing_notes)); + } + Ok(()) } +/// Ensures the constraints related to transaction inputs: +/// - the candidate transaction's initial account state hash must be the same as the one +/// in the Store or empty for new accounts +/// - input notes must not be already consumed +/// +/// Returns a list of unauthenticated input notes that were not found in the store. #[instrument(target = "miden-block-producer", skip_all, err)] fn ensure_tx_inputs_constraints( candidate_tx: &ProvenTransaction, tx_inputs: TransactionInputs, -) -> Result<(), VerifyTxError> { +) -> Result, VerifyTxError> { debug!(target: COMPONENT, %tx_inputs); match tx_inputs.account_hash { @@ -209,13 +252,12 @@ fn ensure_tx_inputs_constraints( let infracting_nullifiers: Vec = tx_inputs .nullifiers .into_iter() - .filter(|&(_, block_num)| block_num != 0) - .map(|(nullifier_in_tx, _)| nullifier_in_tx) + .filter_map(|(nullifier_in_tx, block_num)| (block_num != 0).then_some(nullifier_in_tx)) .collect(); if !infracting_nullifiers.is_empty() { return Err(VerifyTxError::InputNotesAlreadyConsumed(infracting_nullifiers)); } - Ok(()) + Ok(tx_inputs.missing_unauthenticated_notes) } diff --git a/crates/block-producer/src/state_view/tests/apply_block.rs b/crates/block-producer/src/state_view/tests/apply_block.rs index 2d806a4e6..9d01970f4 100644 --- a/crates/block-producer/src/state_view/tests/apply_block.rs +++ b/crates/block-producer/src/state_view/tests/apply_block.rs @@ -6,10 +6,7 @@ use std::iter; -use miden_objects::{ - accounts::delta::AccountUpdateDetails, block::BlockAccountUpdate, - transaction::InputNoteCommitment, -}; +use miden_objects::{accounts::delta::AccountUpdateDetails, block::BlockAccountUpdate}; use super::*; use crate::test_utils::{block::MockBlockBuilder, MockStoreSuccessBuilder}; @@ -161,14 +158,12 @@ async fn test_apply_block_ab3() { accounts[0].states[1], accounts[0].states[2], ) - .nullifiers(txs[0].input_notes().iter().map(InputNoteCommitment::nullifier).collect()) + .nullifiers(txs[0].get_nullifiers().collect()) .build(); let verify_tx_res = state_view.verify_tx(&tx_new).await; assert_eq!( verify_tx_res, - Err(VerifyTxError::InputNotesAlreadyConsumed( - txs[0].input_notes().iter().map(InputNoteCommitment::nullifier).collect() - )) + Err(VerifyTxError::InputNotesAlreadyConsumed(txs[0].get_nullifiers().collect())) ); } diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index fe727072b..b7bbc633f 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -7,8 +7,11 @@ use async_trait::async_trait; use miden_node_proto::{ errors::{ConversionError, MissingFieldHelper}, generated::{ - digest, - requests::{ApplyBlockRequest, GetBlockInputsRequest, GetTransactionInputsRequest}, + digest, note, + requests::{ + ApplyBlockRequest, GetBlockInputsRequest, GetNotesByIdRequest, + GetTransactionInputsRequest, + }, responses::{GetTransactionInputsResponse, NullifierTransactionInputRecord}, store::api_client as store_client, }, @@ -16,31 +19,50 @@ use miden_node_proto::{ }; use miden_node_utils::formatting::{format_map, format_opt}; use miden_objects::{ - accounts::AccountId, block::Block, notes::Nullifier, utils::Serializable, Digest, + accounts::AccountId, + block::Block, + crypto::merkle::MerklePath, + notes::{NoteId, Nullifier}, + utils::Serializable, + Digest, }; +use miden_processor::crypto::RpoDigest; use tonic::transport::Channel; use tracing::{debug, info, instrument}; pub use crate::errors::{ApplyBlockError, BlockInputsError, TxInputsError}; -use crate::{block::BlockInputs, ProvenTransaction, COMPONENT}; +use crate::{block::BlockInputs, errors::NotePathsError, ProvenTransaction, COMPONENT}; // STORE TRAIT // ================================================================================================ #[async_trait] pub trait Store: ApplyBlock { - /// TODO: add comments + /// Returns information needed from the store to verify a given proven transaction. async fn get_tx_inputs( &self, proven_tx: &ProvenTransaction, ) -> Result; - /// TODO: add comments + /// Returns information needed from the store to build a block. async fn get_block_inputs( &self, updated_accounts: impl Iterator + Send, produced_nullifiers: impl Iterator + Send, + notes: impl Iterator + Send, ) -> Result; + + /// Returns note authentication information for the set of specified notes. + /// + /// If authentication info could for a note does not exist in the store, the note is omitted + /// from the returned set of notes. + /// + /// TODO: right now this return only Merkle paths per note, but this will need to be updated to + /// return full authentication info. + async fn get_note_authentication_info( + &self, + notes: impl Iterator + Send, + ) -> Result, NotePathsError>; } #[async_trait] @@ -61,6 +83,8 @@ pub struct TransactionInputs { /// Maps each consumed notes' nullifier to block number, where the note is consumed /// (`zero` means, that note isn't consumed yet) pub nullifiers: BTreeMap, + /// List of unauthenticated notes that were not found in the store + pub missing_unauthenticated_notes: Vec, } impl Display for TransactionInputs { @@ -93,7 +117,18 @@ impl TryFrom for TransactionInputs { nullifiers.insert(nullifier, nullifier_record.block_num); } - Ok(Self { account_id, account_hash, nullifiers }) + let missing_unauthenticated_notes = response + .missing_unauthenticated_notes + .into_iter() + .map(|digest| Ok(RpoDigest::try_from(digest)?.into())) + .collect::, ConversionError>>()?; + + Ok(Self { + account_id, + account_hash, + nullifiers, + missing_unauthenticated_notes, + }) } } @@ -143,10 +178,10 @@ impl Store for DefaultStore { ) -> Result { let message = GetTransactionInputsRequest { account_id: Some(proven_tx.account_id().into()), - nullifiers: proven_tx - .input_notes() - .iter() - .map(|note| note.nullifier().into()) + nullifiers: proven_tx.get_nullifiers().map(Into::into).collect(), + unauthenticated_notes: proven_tx + .get_unauthenticated_notes() + .map(|note| note.id().into()) .collect(), }; @@ -183,10 +218,12 @@ impl Store for DefaultStore { &self, updated_accounts: impl Iterator + Send, produced_nullifiers: impl Iterator + Send, + notes: impl Iterator + Send, ) -> Result { let request = tonic::Request::new(GetBlockInputsRequest { account_ids: updated_accounts.map(Into::into).collect(), nullifiers: produced_nullifiers.map(digest::Digest::from).collect(), + unauthenticated_notes: notes.map(digest::Digest::from).collect(), }); let store_response = self @@ -199,4 +236,37 @@ impl Store for DefaultStore { Ok(store_response.try_into()?) } + + async fn get_note_authentication_info( + &self, + notes: impl Iterator + Send, + ) -> Result, NotePathsError> { + let request = tonic::Request::new(GetNotesByIdRequest { + note_ids: notes.map(digest::Digest::from).collect(), + }); + + let store_response = self + .store + .clone() + .get_notes_by_id(request) + .await + .map_err(|err| NotePathsError::GrpcClientError(err.message().to_string()))? + .into_inner(); + + Ok(store_response + .notes + .into_iter() + .map(|note| { + Ok(( + RpoDigest::try_from( + note.note_id.ok_or(note::Note::missing_field(stringify!(note_id)))?, + )? + .into(), + note.merkle_path + .ok_or(note::Note::missing_field(stringify!(merkle_path)))? + .try_into()?, + )) + }) + .collect::, ConversionError>>()?) + } } diff --git a/crates/block-producer/src/test_utils/block.rs b/crates/block-producer/src/test_utils/block.rs index 02644d70e..9bba6e5bf 100644 --- a/crates/block-producer/src/test_utils/block.rs +++ b/crates/block-producer/src/test_utils/block.rs @@ -1,3 +1,5 @@ +use std::iter; + use miden_objects::{ block::{Block, BlockAccountUpdate, BlockNoteIndex, BlockNoteTree, NoteBatch}, crypto::merkle::{Mmr, SimpleSmt}, @@ -74,6 +76,7 @@ pub async fn build_actual_block_header( .get_block_inputs( updated_accounts.iter().map(|update| update.account_id()), produced_nullifiers.iter(), + iter::empty(), ) .await .unwrap(); @@ -167,5 +170,5 @@ pub(crate) fn note_created_smt_from_note_batches<'a>( } pub(crate) fn note_created_smt_from_batches(batches: &[TransactionBatch]) -> BlockNoteTree { - note_created_smt_from_note_batches(batches.iter().map(TransactionBatch::created_notes)) + note_created_smt_from_note_batches(batches.iter().map(TransactionBatch::output_notes)) } diff --git a/crates/block-producer/src/test_utils/proven_tx.rs b/crates/block-producer/src/test_utils/proven_tx.rs index 58c80d880..92ffa5369 100644 --- a/crates/block-producer/src/test_utils/proven_tx.rs +++ b/crates/block-producer/src/test_utils/proven_tx.rs @@ -3,10 +3,8 @@ use std::ops::Range; use miden_air::HashFunction; use miden_objects::{ accounts::AccountId, - notes::{NoteHeader, NoteId, NoteMetadata, NoteType, Nullifier}, - transaction::{ - OutputNote, ProvenTransaction, ProvenTransactionBuilder, ToInputNoteCommitments, - }, + notes::{NoteHeader, NoteMetadata, NoteType, Nullifier}, + transaction::{OutputNote, ProvenTransaction, ProvenTransactionBuilder}, vm::ExecutionProof, Digest, Felt, Hasher, ONE, }; @@ -89,29 +87,9 @@ impl MockProvenTxBuilder { Digest::default(), ExecutionProof::new(StarkProof::new_dummy(), HashFunction::Blake3_192), ) - .add_input_notes( - self.nullifiers - .unwrap_or_default() - .iter() - .copied() - .map(NullifierToInputNoteCommitmentsWrapper), - ) + .add_input_notes(self.nullifiers.unwrap_or_default().iter().copied()) .add_output_notes(self.notes_created.unwrap_or_default()) .build() .unwrap() } } - -// TODO: This is a dirty workaround for the faster migration to the latest `miden-objects`. -// We need to find a better way to do this. -struct NullifierToInputNoteCommitmentsWrapper(Nullifier); - -impl ToInputNoteCommitments for NullifierToInputNoteCommitmentsWrapper { - fn nullifier(&self) -> Nullifier { - self.0 - } - - fn note_id(&self) -> Option { - None - } -} diff --git a/crates/block-producer/src/test_utils/store.rs b/crates/block-producer/src/test_utils/store.rs index b63da2119..3280e515f 100644 --- a/crates/block-producer/src/test_utils/store.rs +++ b/crates/block-producer/src/test_utils/store.rs @@ -1,10 +1,10 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use async_trait::async_trait; use miden_objects::{ block::{Block, BlockNoteTree}, - crypto::merkle::{Mmr, SimpleSmt, Smt, ValuePath}, - notes::Nullifier, + crypto::merkle::{MerklePath, Mmr, SimpleSmt, Smt, ValuePath}, + notes::{NoteId, Nullifier}, transaction::OutputNote, BlockHeader, ACCOUNT_TREE_DEPTH, EMPTY_WORD, ZERO, }; @@ -13,6 +13,7 @@ use super::*; use crate::{ batch_builder::TransactionBatch, block::{AccountWitness, BlockInputs}, + errors::NotePathsError, store::{ ApplyBlock, ApplyBlockError, BlockInputsError, Store, TransactionInputs, TxInputsError, }, @@ -231,6 +232,7 @@ impl Store for MockStoreSuccess { account_id: proven_tx.account_id(), account_hash, nullifiers, + missing_unauthenticated_notes: Default::default(), }) } @@ -238,6 +240,7 @@ impl Store for MockStoreSuccess { &self, updated_accounts: impl Iterator + Send, produced_nullifiers: impl Iterator + Send, + notes: impl Iterator + Send, ) -> Result { let locked_accounts = self.accounts.read().await; let locked_produced_nullifiers = self.produced_nullifiers.read().await; @@ -262,13 +265,23 @@ impl Store for MockStoreSuccess { .map(|nullifier| (*nullifier, locked_produced_nullifiers.open(&nullifier.inner()))) .collect(); + let found_unauthenticated_notes = notes.copied().collect(); + Ok(BlockInputs { block_header: *self.last_block_header.read().await, chain_peaks, accounts, nullifiers, + found_unauthenticated_notes, }) } + + async fn get_note_authentication_info( + &self, + _notes: impl Iterator + Send, + ) -> Result, NotePathsError> { + todo!() + } } #[derive(Default)] @@ -294,7 +307,15 @@ impl Store for MockStoreFailure { &self, _updated_accounts: impl Iterator + Send, _produced_nullifiers: impl Iterator + Send, + _notes: impl Iterator + Send, ) -> Result { Err(BlockInputsError::GrpcClientError(String::new())) } + + async fn get_note_authentication_info( + &self, + _notes: impl Iterator + Send, + ) -> Result, NotePathsError> { + Err(NotePathsError::GrpcClientError(String::new())) + } } diff --git a/crates/block-producer/src/txqueue/mod.rs b/crates/block-producer/src/txqueue/mod.rs index c37975ae5..41c04f430 100644 --- a/crates/block-producer/src/txqueue/mod.rs +++ b/crates/block-producer/src/txqueue/mod.rs @@ -94,7 +94,7 @@ where return; } - locked_ready_queue.drain(..).collect() + locked_ready_queue.drain(..).rev().collect() }; while !txs.is_empty() { @@ -114,10 +114,10 @@ where // to the list of available transactions and forward the current batch. txs.push(tx); break; - } else { - // The tx fits in the current batch - batch.push(tx) } + + // The tx fits in the current batch + batch.push(tx) } let ready_queue = self.ready_queue.clone(); @@ -130,8 +130,12 @@ where // batch was successfully built, do nothing }, Err(e) => { - // batch building failed, add txs back at the end of the queue - ready_queue.write().await.append(&mut e.into_transactions()); + // batch building failed, add txs back to the beginning of the queue + let mut locked_ready_queue = ready_queue.write().await; + e.into_transactions() + .into_iter() + .enumerate() + .for_each(|(i, tx)| locked_ready_queue.insert(i, tx)); }, } } diff --git a/crates/proto/src/generated/requests.rs b/crates/proto/src/generated/requests.rs index 8d5c5d129..7ce1e69ac 100644 --- a/crates/proto/src/generated/requests.rs +++ b/crates/proto/src/generated/requests.rs @@ -70,6 +70,9 @@ pub struct GetBlockInputsRequest { /// Array of nullifiers for all notes consumed by a transaction. #[prost(message, repeated, tag = "2")] pub nullifiers: ::prost::alloc::vec::Vec, + /// Array of note IDs to be checked for existence in the database. + #[prost(message, repeated, tag = "3")] + pub unauthenticated_notes: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] @@ -78,6 +81,8 @@ pub struct GetTransactionInputsRequest { pub account_id: ::core::option::Option, #[prost(message, repeated, tag = "2")] pub nullifiers: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "3")] + pub unauthenticated_notes: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/proto/src/generated/responses.rs b/crates/proto/src/generated/responses.rs index 79d581890..fce40c797 100644 --- a/crates/proto/src/generated/responses.rs +++ b/crates/proto/src/generated/responses.rs @@ -82,7 +82,7 @@ pub struct GetBlockInputsResponse { /// The latest block header #[prost(message, optional, tag = "1")] pub block_header: ::core::option::Option, - /// Peaks of the above block's mmr, The `forest` value is equal to the block number. + /// Peaks of the above block's mmr, The `forest` value is equal to the block number #[prost(message, repeated, tag = "2")] pub mmr_peaks: ::prost::alloc::vec::Vec, /// The hashes of the requested accounts and their authentication paths @@ -91,6 +91,9 @@ pub struct GetBlockInputsResponse { /// The requested nullifiers and their authentication paths #[prost(message, repeated, tag = "4")] pub nullifiers: ::prost::alloc::vec::Vec, + /// The list of requested notes which were found in the database + #[prost(message, repeated, tag = "5")] + pub found_unauthenticated_notes: ::prost::alloc::vec::Vec, } /// An account returned as a response to the GetTransactionInputs #[allow(clippy::derive_partial_eq_without_eq)] @@ -119,6 +122,8 @@ pub struct GetTransactionInputsResponse { pub account_state: ::core::option::Option, #[prost(message, repeated, tag = "2")] pub nullifiers: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag = "3")] + pub missing_unauthenticated_notes: ::prost::alloc::vec::Vec, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 1bd1173bd..934ea33d1 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -1,4 +1,5 @@ use std::{ + collections::BTreeSet, fs::{self, create_dir_all}, sync::Arc, }; @@ -170,6 +171,14 @@ impl Db { })? } + /// Loads all the note IDs from the DB. + #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] + pub async fn select_note_ids(&self) -> Result> { + self.pool.get().await?.interact(sql::select_note_ids).await.map_err(|err| { + DatabaseError::InteractError(format!("Select note IDs task failed: {err}")) + })? + } + /// Loads all the accounts from the DB. #[instrument(target = "miden-store", skip_all, ret(level = "debug"), err)] pub async fn select_accounts(&self) -> Result> { diff --git a/crates/store/src/db/sql.rs b/crates/store/src/db/sql.rs index 018c2bb31..06090e635 100644 --- a/crates/store/src/db/sql.rs +++ b/crates/store/src/db/sql.rs @@ -1,6 +1,6 @@ //! Wrapper functions for SQL statements. -use std::{borrow::Cow, rc::Rc}; +use std::{borrow::Cow, collections::BTreeSet, rc::Rc}; use miden_node_proto::domain::accounts::{AccountInfo, AccountSummary}; use miden_objects::{ @@ -369,6 +369,25 @@ pub fn select_notes(conn: &mut Connection) -> Result> { Ok(notes) } +/// Select all note IDs from the DB using the given [Connection]. +/// +/// # Returns +/// +/// A set with note IDs, or an error. +pub fn select_note_ids(conn: &mut Connection) -> Result> { + let mut stmt = conn.prepare("SELECT note_id FROM notes ORDER BY note_id")?; + let mut rows = stmt.query([])?; + + let mut notes = BTreeSet::new(); + while let Some(row) = rows.next()? { + let note_id_data = row.get_ref(0)?.as_blob()?; + let note_id = RpoDigest::read_from_bytes(note_id_data)?.into(); + + notes.insert(note_id); + } + Ok(notes) +} + /// Insert notes to the DB using the given [Transaction]. /// /// # Returns diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index 6809fa6da..9f4c90b09 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -36,7 +36,11 @@ use miden_objects::{ use tonic::{Response, Status}; use tracing::{debug, info, instrument}; -use crate::{state::State, types::AccountId, COMPONENT}; +use crate::{ + state::{BlockInputs, State}, + types::AccountId, + COMPONENT, +}; // STORE API // ================================================================================================ @@ -300,18 +304,26 @@ impl api_server::Api for StoreApi { let nullifiers = validate_nullifiers(&request.nullifiers)?; let account_ids: Vec = request.account_ids.iter().map(|e| e.id).collect(); + let unauthenticated_notes = validate_notes(&request.unauthenticated_notes)?; - let (latest, accumulator, account_states, nullifier_records) = self + let BlockInputs { + block_header, + chain_peaks, + account_states, + nullifiers, + found_unauthenticated_notes, + } = self .state - .get_block_inputs(&account_ids, &nullifiers) + .get_block_inputs(&account_ids, &nullifiers, &unauthenticated_notes) .await .map_err(internal_error)?; Ok(Response::new(GetBlockInputsResponse { - block_header: Some(latest.into()), - mmr_peaks: convert(accumulator.peaks()), + block_header: Some(block_header.into()), + mmr_peaks: convert(chain_peaks.peaks()), account_states: convert(account_states), - nullifiers: convert(nullifier_records), + nullifiers: convert(nullifiers), + found_unauthenticated_notes: convert(found_unauthenticated_notes), })) } @@ -330,10 +342,14 @@ impl api_server::Api for StoreApi { debug!(target: COMPONENT, ?request); - let nullifiers = validate_nullifiers(&request.nullifiers)?; let account_id = request.account_id.ok_or(invalid_argument("Account_id missing"))?.id; + let nullifiers = validate_nullifiers(&request.nullifiers)?; + let unauthenticated_notes = validate_notes(&request.unauthenticated_notes)?; - let tx_inputs = self.state.get_transaction_inputs(account_id, &nullifiers).await; + let tx_inputs = self + .state + .get_transaction_inputs(account_id, &nullifiers, &unauthenticated_notes) + .await; Ok(Response::new(GetTransactionInputsResponse { account_state: Some(AccountTransactionInputRecord { @@ -348,6 +364,11 @@ impl api_server::Api for StoreApi { block_num: nullifier.block_num, }) .collect(), + missing_unauthenticated_notes: tx_inputs + .missing_unauthenticated_notes + .into_iter() + .map(Into::into) + .collect(), })) } @@ -465,3 +486,12 @@ fn validate_nullifiers(nullifiers: &[generated::digest::Digest]) -> Result>() .map_err(|_| invalid_argument("Digest field is not in the modulus range")) } + +#[instrument(target = "miden-store", skip_all, err)] +fn validate_notes(notes: &[generated::digest::Digest]) -> Result, Status> { + notes + .iter() + .map(|digest| Ok(RpoDigest::try_from(digest.clone())?.into())) + .collect::>() + .map_err(|_| invalid_argument("Digest field is not in the modulus range")) +} diff --git a/crates/store/src/state.rs b/crates/store/src/state.rs index b43335c4d..a25932790 100644 --- a/crates/store/src/state.rs +++ b/crates/store/src/state.rs @@ -2,7 +2,7 @@ //! //! The [State] provides data access and modifications methods, its main purpose is to ensure that //! data is atomically written, and that reads are consistent. -use std::sync::Arc; +use std::{collections::BTreeSet, sync::Arc}; use miden_node_proto::{domain::accounts::AccountInfo, AccountInputRecord, NullifierWitness}; use miden_node_utils::formatting::{format_account_id, format_array}; @@ -38,10 +38,30 @@ use crate::{ // STRUCTURES // ================================================================================================ +/// Information needed from the store to validate and build a block +#[derive(Debug)] +pub struct BlockInputs { + /// Previous block header + pub block_header: BlockHeader, + + /// MMR peaks for the current chain state + pub chain_peaks: MmrPeaks, + + /// The hashes of the requested accounts and their authentication paths + pub account_states: Vec, + + /// The requested nullifiers and their authentication paths + pub nullifiers: Vec, + + /// List of notes found in the store + pub found_unauthenticated_notes: Vec, +} + #[derive(Debug)] pub struct TransactionInputs { pub account_hash: RpoDigest, pub nullifiers: Vec, + pub missing_unauthenticated_notes: Vec, } /// Container for state that needs to be updated atomically. @@ -49,6 +69,7 @@ struct InnerState { nullifier_tree: NullifierTree, chain_mmr: Mmr, account_tree: SimpleSmt, + notes: BTreeSet, } /// The rollup state @@ -79,11 +100,18 @@ impl State { let nullifier_tree = load_nullifier_tree(&mut db).await?; let chain_mmr = load_mmr(&mut db).await?; let account_tree = load_accounts(&mut db).await?; + let notes = load_notes(&mut db).await?; - let inner = RwLock::new(InnerState { nullifier_tree, chain_mmr, account_tree }); + let inner = RwLock::new(InnerState { + nullifier_tree, + chain_mmr, + account_tree, + notes, + }); let writer = Mutex::new(()); let db = Arc::new(db); + Ok(Self { db, block_store, inner, writer }) } @@ -153,7 +181,7 @@ impl State { tokio::spawn(async move { store.save_block(block_num, &block_data).await }); // scope to read in-memory data, validate the request, and compute intermediary values - let (account_tree, chain_mmr, nullifier_tree, notes) = { + let (account_tree, chain_mmr, nullifier_tree, notes, note_set) = { let inner = self.inner.read().await; let span = info_span!(target: COMPONENT, "update_in_memory_structs").entered(); @@ -227,7 +255,7 @@ impl State { drop(span); - let notes = block + let (notes, note_set) = block .notes() .map(|(note_index, note)| { let details = match note { @@ -240,18 +268,23 @@ impl State { .get_note_path(note_index) .map_err(ApplyBlockError::UnableToCreateProofForNote)?; - Ok(NoteRecord { - block_num, - note_index, - note_id: note.id().into(), - metadata: *note.metadata(), - details, - merkle_path, - }) + Ok(( + NoteRecord { + block_num, + note_index, + note_id: note.id().into(), + metadata: *note.metadata(), + details, + merkle_path, + }, + note.id(), + )) }) - .collect::, ApplyBlockError>>()?; + .collect::, ApplyBlockError>>()? + .into_iter() + .unzip(); - (account_tree, chain_mmr, nullifier_tree, notes) + (account_tree, chain_mmr, nullifier_tree, notes, note_set) }; // Signals the transaction is ready to be committed, and the write lock can be acquired @@ -298,6 +331,7 @@ impl State { inner.chain_mmr = chain_mmr; inner.nullifier_tree = nullifier_tree; inner.account_tree = account_tree; + inner.notes = note_set; } info!(%block_hash, block_num, COMPONENT, "apply_block successful"); @@ -411,10 +445,8 @@ impl State { &self, account_ids: &[AccountId], nullifiers: &[Nullifier], - ) -> Result< - (BlockHeader, MmrPeaks, Vec, Vec), - GetBlockInputsError, - > { + unauthenticated_notes: &[NoteId], + ) -> Result { let inner = self.inner.read().await; let latest = self @@ -433,7 +465,7 @@ impl State { // using current block number gets us the peaks of the chain MMR as of one block ago; // this is done so that latest.chain_root matches the returned peaks - let peaks = inner.chain_mmr.peaks(latest.block_num() as usize).map_err(|error| { + let chain_peaks = inner.chain_mmr.peaks(latest.block_num() as usize).map_err(|error| { GetBlockInputsError::FailedToGetMmrPeaksForForest { forest: latest.block_num() as usize, error, @@ -453,7 +485,7 @@ impl State { }) .collect::>()?; - let nullifier_input_records: Vec = nullifiers + let nullifiers: Vec = nullifiers .iter() .map(|nullifier| { let proof = inner.nullifier_tree.open(nullifier); @@ -462,7 +494,15 @@ impl State { }) .collect(); - Ok((latest, peaks, account_states, nullifier_input_records)) + let found_unauthenticated_notes = filter_found_notes(unauthenticated_notes, &inner.notes); + + Ok(BlockInputs { + block_header: latest, + chain_peaks, + account_states, + nullifiers, + found_unauthenticated_notes, + }) } /// Returns data needed by the block producer to verify transactions validity. @@ -471,6 +511,7 @@ impl State { &self, account_id: AccountId, nullifiers: &[Nullifier], + unauthenticated_notes: &[NoteId], ) -> TransactionInputs { info!(target: COMPONENT, account_id = %format_account_id(account_id), nullifiers = %format_array(nullifiers)); @@ -486,7 +527,13 @@ impl State { }) .collect(); - TransactionInputs { account_hash, nullifiers } + let missing_unauthenticated_notes = filter_found_notes(unauthenticated_notes, &inner.notes); + + TransactionInputs { + account_hash, + nullifiers, + missing_unauthenticated_notes, + } } /// Lists all known nullifiers with their inclusion blocks, intended for testing. @@ -571,3 +618,13 @@ async fn load_accounts( SimpleSmt::with_leaves(account_data) .map_err(StateInitializationError::FailedToCreateAccountsTree) } + +#[instrument(target = "miden-store", skip_all)] +async fn load_notes(db: &mut Db) -> Result, StateInitializationError> { + Ok(db.select_note_ids().await?) +} + +#[instrument(target = "miden-store", skip_all)] +fn filter_found_notes(notes: &[NoteId], in_memory: &BTreeSet) -> Vec { + notes.iter().filter(|¬e_id| in_memory.contains(note_id)).copied().collect() +} diff --git a/crates/utils/src/formatting.rs b/crates/utils/src/formatting.rs index 4cdd6dcfe..642eaf2ab 100644 --- a/crates/utils/src/formatting.rs +++ b/crates/utils/src/formatting.rs @@ -18,8 +18,8 @@ pub fn format_opt(opt: Option<&T>) -> String { } pub fn format_input_notes(notes: &InputNotes) -> String { - format_array(notes.iter().map(|c| match c.note_id() { - Some(note_id) => format!("({}, {})", c.nullifier().to_hex(), note_id.to_hex()), + format_array(notes.iter().map(|c| match c.header() { + Some(header) => format!("({}, {})", c.nullifier().to_hex(), header.id().to_hex()), None => format!("({})", c.nullifier().to_hex()), })) } diff --git a/proto/requests.proto b/proto/requests.proto index 50c850775..79dc3004f 100644 --- a/proto/requests.proto +++ b/proto/requests.proto @@ -64,11 +64,14 @@ message GetBlockInputsRequest { repeated account.AccountId account_ids = 1; // Array of nullifiers for all notes consumed by a transaction. repeated digest.Digest nullifiers = 2; + // Array of note IDs to be checked for existence in the database. + repeated digest.Digest unauthenticated_notes = 3; } message GetTransactionInputsRequest { account.AccountId account_id = 1; repeated digest.Digest nullifiers = 2; + repeated digest.Digest unauthenticated_notes = 3; } message SubmitProvenTransactionRequest { diff --git a/proto/responses.proto b/proto/responses.proto index 945a28a8e..25a4100cc 100644 --- a/proto/responses.proto +++ b/proto/responses.proto @@ -74,7 +74,7 @@ message GetBlockInputsResponse { // The latest block header block_header.BlockHeader block_header = 1; - // Peaks of the above block's mmr, The `forest` value is equal to the block number. + // Peaks of the above block's mmr, The `forest` value is equal to the block number repeated digest.Digest mmr_peaks = 2; // The hashes of the requested accounts and their authentication paths @@ -82,6 +82,9 @@ message GetBlockInputsResponse { // The requested nullifiers and their authentication paths repeated NullifierBlockInputRecord nullifiers = 4; + + // The list of requested notes which were found in the database + repeated digest.Digest found_unauthenticated_notes = 5; } // An account returned as a response to the GetTransactionInputs @@ -101,6 +104,7 @@ message NullifierTransactionInputRecord { message GetTransactionInputsResponse { AccountTransactionInputRecord account_state = 1; repeated NullifierTransactionInputRecord nullifiers = 2; + repeated digest.Digest missing_unauthenticated_notes = 3; } message SubmitProvenTransactionResponse {}