diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d4214ab99..5492c7726e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,11 @@ - [BREAKING] Changed `SyncChainMmr` endpoint: the upper end of the block range we're syncing is now the chain tip with the requested finality level. Validator signature is also returned ([#2075](https://github.com/0xMiden/node/pull/2075)). - [BREAKING] Renamed `SubmitProvenTransaction` RPC endpoint to `SubmitProvenTx` ([#2094](https://github.com/0xMiden/node/pull/2094)). - [BREAKING] Renamed `SubmitProvenBatch` RPC endpoint to `SubmitProvenTxBatch` ([#2094](https://github.com/0xMiden/node/pull/2094)). +- Updated `miden-protocol` and bumped `miden-crypto` to `v0.25`. `AccountId::is_network()` was removed upstream, so `SubmitProvenTx` and `SubmitProvenTxBatch` now consult the store to classify post-deployment public-account transactions as network accounts. +- [BREAKING] Removed `Network` variant from genesis config `StorageMode`. The implicit default for wallets and fungible faucets is now `Private` (previously `Network`, which mapped to `Public` storage) ([#2095](https://github.com/0xMiden/node/pull/2095)). +- [BREAKING] Updated `miden-protocol` family of crates to the published `v0.15.0` on crates.io (previously tracked the `next` branch). The published release removes the multi-variant `AccountType` (`RegularAccount*`, `FungibleFaucet`, `NonFungibleFaucet`) and renames the former `AccountStorageMode` to `AccountType` with only `Public`/`Private`. Faucet-vs-wallet distinction is now component-based. +- [BREAKING] Removed the `has_updatable_code` field from genesis `[[wallet]]` config entries. Updatable/immutable code is no longer modeled by the protocol, so any genesis config that explicitly sets this field will fail to parse — remove the field. + ## v0.14.11 (TBD) - Replaced blocking-in-async operations in the validator, remote prover, and ntx-builder with `spawn_blocking` to avoid starving the Tokio runtime ([#2041](https://github.com/0xMiden/node/pull/2041)). diff --git a/Cargo.lock b/Cargo.lock index e980114ea4..400dd3791b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1236,6 +1236,12 @@ dependencies = [ "itertools 0.13.0", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -2925,10 +2931,22 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "miden-ace-codegen" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c5e3d08008b5f9dd3e6145e55e4d3c1a4e47a74383dd8d0d64003455ee94510" +dependencies = [ + "miden-core", + "miden-crypto", + "thiserror 2.0.18", +] + [[package]] name = "miden-agglayer" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3e46490926f6084d6216067ef9c0e606e547361ba492b1ca311dcfd780b185" dependencies = [ "alloy-sol-types", "fs-err", @@ -2948,22 +2966,25 @@ dependencies = [ [[package]] name = "miden-air" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15646ebc95906b2a7cb66711d1e184f53fd6edc2605730bbcf0c2a129f792cf" +checksum = "d980fa23010e53c43077cf0182e6e88acd1a099abbf8a98cd298aa69b86688dc" dependencies = [ + "miden-ace-codegen", "miden-core", "miden-crypto", + "miden-lifted-stark", "miden-utils-indexing", + "proptest", "thiserror 2.0.18", "tracing", ] [[package]] name = "miden-assembly" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae6013b3a390e0dcb29242f4480a7727965887bbf0903466c88f362b4cb20c0e" +checksum = "24eb53abd723b7dd8ff1210b7d126f0d9e1d9127ea0e7f6a46471845d060cc43" dependencies = [ "env_logger", "log", @@ -2972,15 +2993,16 @@ dependencies = [ "miden-mast-package", "miden-package-registry", "miden-project", + "proptest", "smallvec", "thiserror 2.0.18", ] [[package]] name = "miden-assembly-syntax" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "996156b8f7c5fe6be17dea71089c6d7985c2dec1e3a4fec068b1dfc690e25df5" +checksum = "02ead435c8dccfd3b9bd97779cfa0d74cc14c767a9740f162bf7cd49a0da26bd" dependencies = [ "aho-corasick", "env_logger", @@ -3004,7 +3026,8 @@ dependencies = [ [[package]] name = "miden-block-prover" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1829e10ccf220c6052cdcc4181c820307f177612e070623213c8670766de64" dependencies = [ "miden-protocol", "thiserror 2.0.18", @@ -3012,12 +3035,12 @@ dependencies = [ [[package]] name = "miden-core" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdec54a321cdf3d23e9ef615e91cb858038c6b4d4202507bdec048fc6d7763e4" +checksum = "9a7c7eecda385bcc66aea99ee3279ae7c78f38e3541f4c1d67d309ac32314ff4" dependencies = [ "derive_more", - "itertools 0.14.0", + "log", "miden-crypto", "miden-debug-types", "miden-formatting", @@ -3034,9 +3057,9 @@ dependencies = [ [[package]] name = "miden-core-lib" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "621e8fa911a790bcf3cd3aedce80bc10922a19d6181f08ff3ca078f955cff70b" +checksum = "99bc06c94dcb75e3e44220fe7623ab99585e7828f19b80534c48a9971d31bfd8" dependencies = [ "env_logger", "fs-err", @@ -3051,24 +3074,26 @@ dependencies = [ [[package]] name = "miden-crypto" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0a034a460e27723dcfdf25effffab84331c3b46b13e7a1bd674197cc71bfe" +checksum = "72ae084a6d15d5d862760bcbdbb5caad41415ed455cd7ab2b53a012d21a431ed" dependencies = [ "blake3", "cc", "chacha20poly1305", "curve25519-dalek", + "der", "ed25519-dalek", "flume", - "glob", "hkdf", "k256", "miden-crypto-derive", "miden-field", + "miden-lifted-stark", "miden-serde-utils", "num", "num-complex", + "once_cell", "p3-blake3", "p3-challenger", "p3-dft", @@ -3076,7 +3101,6 @@ dependencies = [ "p3-keccak", "p3-matrix", "p3-maybe-rayon", - "p3-miden-lifted-stark", "p3-symmetric", "p3-util", "rand 0.9.2", @@ -3095,9 +3119,9 @@ dependencies = [ [[package]] name = "miden-crypto-derive" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8bf6ebde028e79bcc61a3632d2f375a5cc64caa17d014459f75015238cb1e08" +checksum = "e8523f6ac9b28782ca759920e536164e7eab7dbfaf9132721ca3e325c060df73" dependencies = [ "quote", "syn 2.0.117", @@ -3105,9 +3129,9 @@ dependencies = [ [[package]] name = "miden-debug-types" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6e50274d11c80b901cf6c90362de8c98c8c8ad6030c80624d683b63d899a0fb" +checksum = "206f5346bef744da1ffae9874a22170a2d0c27dd20947d89182d16f5d86348f7" dependencies = [ "memchr", "miden-crypto", @@ -3116,6 +3140,7 @@ dependencies = [ "miden-utils-indexing", "miden-utils-sync", "paste", + "proptest", "serde", "serde_spanned", "thiserror 2.0.18", @@ -3123,15 +3148,16 @@ dependencies = [ [[package]] name = "miden-field" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38011348f4fb4c9e5ce1f471203d024721c00e3b60a91aa91aaefe6738d8b5ea" +checksum = "7b8a48ba526c353bf583bba18ddf62ad4846945e85fcaf44614633276416b5d9" dependencies = [ "miden-serde-utils", "num-bigint", "p3-challenger", "p3-field", "p3-goldilocks", + "p3-util", "paste", "rand 0.10.0", "serde", @@ -3177,13 +3203,48 @@ dependencies = [ "rocksdb", ] +[[package]] +name = "miden-lifted-air" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00d97ada3bfd70d6cc4f1a13ced8cb509f72fb16b1b28c292366aee846d3220" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", + "p3-util", + "thiserror 2.0.18", +] + +[[package]] +name = "miden-lifted-stark" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3601a30d39e08a31ecc5b5215c87b11623942d9610aebccda89a4a5bf757c4" +dependencies = [ + "miden-lifted-air", + "miden-stark-transcript", + "miden-stateful-hasher", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-goldilocks", + "p3-matrix", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "rand 0.10.0", + "serde", + "thiserror 2.0.18", + "tracing", +] + [[package]] name = "miden-mast-package" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8b2e3447fcde1f0e6b76e5219f129517639772cb02ca543177f0584e315288" +checksum = "3ae69cd4dc25b0993eb5fafcc99987abd3502d8a7a902248af4a597537dce600" dependencies = [ - "derive_more", "miden-assembly-syntax", "miden-core", "miden-debug-types", @@ -3548,13 +3609,14 @@ dependencies = [ [[package]] name = "miden-package-registry" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969ba3942052e52b3968e34dbd1c52c707e75777ee42ebdae2c8f57af56cf6cf" +checksum = "8c1d77c6f5942f3111fc56a19d638076ea77df3628efa97c7955259c609ca8dd" dependencies = [ "miden-assembly-syntax", "miden-core", "miden-mast-package", + "proptest", "pubgrub", "serde", "smallvec", @@ -3563,9 +3625,9 @@ dependencies = [ [[package]] name = "miden-processor" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ec6cecbf22bd92b73a931ee80b424e46b8b7cdf4f2f3c364c25c5c15d2840da" +checksum = "70508a4a75989a47f03a0681df412b80040c76230c3d850f8f2b80f7a986d4aa" dependencies = [ "itertools 0.14.0", "miden-air", @@ -3576,20 +3638,20 @@ dependencies = [ "paste", "rayon", "thiserror 2.0.18", - "tokio", "tracing", ] [[package]] name = "miden-project" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3840520c01881534fbbceb6b3687ec1c407fbaf310a35ce415fd3510abc52fdb" +checksum = "6338d92713845f57b137e4305db11ee8614b0cec9b1516470233ce64d53f6376" dependencies = [ "miden-assembly-syntax", "miden-core", "miden-mast-package", "miden-package-registry", + "proptest", "serde", "serde-untagged", "thiserror 2.0.18", @@ -3599,7 +3661,8 @@ dependencies = [ [[package]] name = "miden-protocol" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b70acd2c57f7f9185c5174966dd47080389271410f25f6a3539970a4474fd440" dependencies = [ "bech32", "fs-err", @@ -3627,19 +3690,16 @@ dependencies = [ [[package]] name = "miden-prover" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb2c94e36f57684d7fa0cd382adeedc1728d502dbbe69ad1c12f4a931f45511" +checksum = "c59b5dbdac3a8596fc201de028c17ed5060ad554901259222420afd186f2b467" dependencies = [ "bincode", "miden-air", "miden-core", "miden-crypto", - "miden-debug-types", "miden-processor", "serde", - "thiserror 2.0.18", - "tokio", "tracing", ] @@ -3694,9 +3754,9 @@ dependencies = [ [[package]] name = "miden-serde-utils" -version = "0.23.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff78082e9b4ca89863e68da01b35f8a4029ee6fd912e39fa41fde4273a7debab" +checksum = "d899afcfbf85c851522f2dff73db1064116486fb8e0ebce0ade44ce72dadc075" dependencies = [ "p3-field", "p3-goldilocks", @@ -3705,13 +3765,13 @@ dependencies = [ [[package]] name = "miden-standards" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc788da5d0dc43f93d6489a302d453cc6c67a12923208c9c8963e43073db0b3b" dependencies = [ "bon", "fs-err", "miden-assembly", "miden-core-lib", - "miden-processor", "miden-protocol", "rand 0.9.2", "regex", @@ -3719,10 +3779,33 @@ dependencies = [ "walkdir", ] +[[package]] +name = "miden-stark-transcript" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c0e1f55bdff89f12ec6fc3881660a0d51d4e77f66026ab0e6ddcbd098a16f2f" +dependencies = [ + "p3-challenger", + "p3-field", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "miden-stateful-hasher" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0069bab01139a5660c7189589c95dfedf8449514d03641fd3f9d049e1b031f9" +dependencies = [ + "p3-field", + "p3-symmetric", +] + [[package]] name = "miden-testing" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f8cdaa93307907537ae95f3c30efbba94ed9d393bbc75c057c763cceff8599" dependencies = [ "anyhow", "itertools 0.14.0", @@ -3742,7 +3825,8 @@ dependencies = [ [[package]] name = "miden-tx" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3cfee228a8b02efa9130bbe6ea086d1c8ffc1b4c93df10f880c9a9a3dfe634" dependencies = [ "miden-processor", "miden-protocol", @@ -3755,7 +3839,8 @@ dependencies = [ [[package]] name = "miden-tx-batch-prover" version = "0.15.0" -source = "git+https://github.com/0xMiden/protocol?branch=next#9160eee3eea8b79bbff9ff1113c535743d6c693c" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff829d060dc0cf38816fbe179fe22f0916d2a141974b2518606d58fe0bd1b37" dependencies = [ "miden-protocol", "miden-tx", @@ -3763,9 +3848,9 @@ dependencies = [ [[package]] name = "miden-utils-core-derive" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3846c8674ccec0c37005f99c1a599a24790ba2a5e5f4e1c7aec5f456821df835" +checksum = "9f74ef56bd44d23c805f662cd4f4639b04df6e0204d78806ede0e6f93d9695a5" dependencies = [ "proc-macro2", "quote", @@ -3774,33 +3859,33 @@ dependencies = [ [[package]] name = "miden-utils-diagnostics" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397f5d1e8679cf17cf7713ffd9654840791a6ed5818b025bbc2fbfdce846579a" +checksum = "c7b3aa61e0ee0f8a5b3f44250b73879437f9e10b6061c3ae743fd1cdb1f56321" dependencies = [ "miden-crypto", "miden-debug-types", "miden-miette", - "paste", "tracing", ] [[package]] name = "miden-utils-indexing" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8834e76299686bcce3de1685158aa4cff49b7fa5e0e00a6cc811e8f2cf5775f" +checksum = "57419a0557e580e04125542f4383910fb997660633e15ad4570ce1ff2ae80557" dependencies = [ "miden-crypto", + "proptest", "serde", "thiserror 2.0.18", ] [[package]] name = "miden-utils-sync" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9e9747e9664c1a0997bb040ae291306ea0a1c74a572141ec66cec855c1b0e8" +checksum = "5a8a579c0c7cf44c123129045339f53b1f26f0aa2dc508494e97360d3ccab80e" dependencies = [ "lock_api", "loom", @@ -3839,9 +3924,9 @@ dependencies = [ [[package]] name = "miden-verifier" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4580df640d889c9f3c349cd2268968e44a99a8cf0df6c36ae5b1fb273712b00" +checksum = "49cd4ca9a8b90c48d5fc6f5707c46cc44b17285f2f705cbe44e5b56c430a85c8" dependencies = [ "bincode", "miden-air", @@ -3854,9 +3939,9 @@ dependencies = [ [[package]] name = "midenc-hir-type" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb29d7c049fb69373c7e775e3d4411e63e4ee608bc43826282ba62c6ec9f891" +checksum = "1ff0511aa2201f7098995e38a3c97a319d379c3b2d26fb83677b21b71f61a7b4" dependencies = [ "miden-formatting", "miden-serde-utils", @@ -4095,6 +4180,10 @@ name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] [[package]] name = "once_cell_polyfill" @@ -4228,19 +4317,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "p3-commit" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916ae7989d5c3b49f887f5c55b2f9826bdbb81aaebf834503c4145d8b267c829" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-matrix", - "p3-util", - "serde", -] - [[package]] name = "p3-dft" version = "0.5.2" @@ -4340,102 +4416,6 @@ dependencies = [ "rand 0.10.0", ] -[[package]] -name = "p3-miden-lifted-air" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5c31c65fdc88952d7b301546add9670676e5b878aa0066dd929f107c203b006" -dependencies = [ - "p3-air", - "p3-field", - "p3-matrix", - "p3-util", - "thiserror 2.0.18", -] - -[[package]] -name = "p3-miden-lifted-fri" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab9932f1b0a16609a45cd4ee10a4d35412728bc4b38837c7979d7c85d8dcc9fc" -dependencies = [ - "p3-challenger", - "p3-commit", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-miden-lmcs", - "p3-miden-transcript", - "p3-util", - "rand 0.10.0", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "p3-miden-lifted-stark" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3956ab7270c3cdd53ca9796d39ae1821984eb977415b0672110f9666bff5d8" -dependencies = [ - "p3-challenger", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-miden-lifted-air", - "p3-miden-lifted-fri", - "p3-miden-lmcs", - "p3-miden-stateful-hasher", - "p3-miden-transcript", - "p3-util", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "p3-miden-lmcs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c46791c983e772136db3d48f102431457451447abb9087deb6c8ce3c1efc86" -dependencies = [ - "p3-commit", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-miden-stateful-hasher", - "p3-miden-transcript", - "p3-symmetric", - "p3-util", - "rand 0.10.0", - "serde", - "thiserror 2.0.18", - "tracing", -] - -[[package]] -name = "p3-miden-stateful-hasher" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec47a9d9615eb3d9d2a59b00d19751d9ad85384b55886827913d680d912eac6a" -dependencies = [ - "p3-field", - "p3-symmetric", -] - -[[package]] -name = "p3-miden-transcript" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c565647487e4a949f67e6f115b0391d6cb82ac8e561165789939bab23d0ae7" -dependencies = [ - "p3-challenger", - "p3-field", - "serde", - "thiserror 2.0.18", -] - [[package]] name = "p3-monty-31" version = "0.5.2" diff --git a/Cargo.toml b/Cargo.toml index 6ae414d7bb..ca7dfd1d2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,16 +61,16 @@ miden-validator = { path = "bin/validator", version = "0.15" } miden-node-rocksdb-cxx-linkage-fix = { path = "crates/rocksdb-cxx-linkage-fix", version = "0.15" } # miden-protocol dependencies. These should be updated in sync. -miden-agglayer = { branch = "next", git = "https://github.com/0xMiden/protocol" } -miden-block-prover = { branch = "next", git = "https://github.com/0xMiden/protocol" } -miden-protocol = { branch = "next", default-features = false, git = "https://github.com/0xMiden/protocol" } -miden-standards = { branch = "next", git = "https://github.com/0xMiden/protocol" } -miden-testing = { branch = "next", git = "https://github.com/0xMiden/protocol" } -miden-tx = { branch = "next", default-features = false, git = "https://github.com/0xMiden/protocol" } -miden-tx-batch-prover = { branch = "next", git = "https://github.com/0xMiden/protocol" } +miden-agglayer = { version = "0.15" } +miden-block-prover = { version = "0.15" } +miden-protocol = { default-features = false, version = "0.15" } +miden-standards = { version = "0.15" } +miden-testing = { version = "0.15" } +miden-tx = { default-features = false, version = "0.15" } +miden-tx-batch-prover = { version = "0.15" } # Other miden dependencies. These should align with those expected by miden-protocol. -miden-crypto = { version = "0.23" } +miden-crypto = { version = "0.25" } # External dependencies anyhow = { version = "1.0" } diff --git a/bin/genesis/src/main.rs b/bin/genesis/src/main.rs index 9ce02cf5f9..3155a726c7 100644 --- a/bin/genesis/src/main.rs +++ b/bin/genesis/src/main.rs @@ -6,13 +6,7 @@ use clap::Parser; use miden_agglayer::create_bridge_account; use miden_protocol::account::auth::{AuthScheme, AuthSecretKey}; use miden_protocol::account::delta::{AccountStorageDelta, AccountVaultDelta}; -use miden_protocol::account::{ - Account, - AccountDelta, - AccountFile, - AccountStorageMode, - AccountType, -}; +use miden_protocol::account::{Account, AccountDelta, AccountFile, AccountType}; use miden_protocol::crypto::dsa::falcon512_poseidon2::{self, SecretKey as FalconSecretKey}; use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::utils::serde::Deserializable; @@ -73,8 +67,7 @@ fn run( AuthMethod::SingleSig { approver: (bridge_admin_pub.into(), AuthScheme::Falcon512Poseidon2), }, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, + AccountType::Public, ) .context("failed to create bridge admin account")?; let bridge_admin_id = bridge_admin.id(); @@ -85,8 +78,7 @@ fn run( AuthMethod::SingleSig { approver: (ger_manager_pub.into(), AuthScheme::Falcon512Poseidon2), }, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, + AccountType::Public, ) .context("failed to create GER manager account")?; let ger_manager_id = ger_manager.id(); @@ -94,7 +86,7 @@ fn run( // Create bridge account (NoAuth, nonce=0), then bump nonce to 1 for genesis. let mut rng = ChaCha20Rng::from_seed(rand::random()); let bridge_seed: [u64; 4] = rng.random(); - let bridge_seed = Word::from(bridge_seed.map(Felt::new)); + let bridge_seed = Word::from(bridge_seed.map(Felt::new_unchecked)); let bridge = create_bridge_account(bridge_seed, bridge_admin_id, ger_manager_id); // Bump bridge nonce to 1 (required for genesis accounts). File-loaded accounts via [[account]] @@ -158,7 +150,7 @@ path = "bridge.mac" fn generate_falcon_keypair() -> (falcon512_poseidon2::PublicKey, FalconSecretKey) { let mut rng = ChaCha20Rng::from_seed(rand::random()); let auth_seed: [u64; 4] = rng.random(); - let mut coin = RandomCoin::new(Word::from(auth_seed.map(Felt::new))); + let mut coin = RandomCoin::new(Word::from(auth_seed.map(Felt::new_unchecked))); let secret_key = FalconSecretKey::with_rng(&mut coin); let public_key = secret_key.public_key(); (public_key, secret_key) @@ -197,7 +189,7 @@ fn bump_nonce_to_one(mut account: Account) -> anyhow::Result { #[cfg(test)] mod tests { use miden_node_store::genesis::config::GenesisConfig; - use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; + use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::utils::serde::Serializable; use super::*; @@ -208,7 +200,7 @@ mod tests { let bridge_id = AccountFile::read(dir.join("bridge.mac")).unwrap().account.id(); let config = GenesisConfig::read_toml_file(&dir.join("genesis.toml")).unwrap(); - let signer = SecretKey::read_from_bytes(&[0x01; 32]).unwrap(); + let signer = SigningKey::read_from_bytes(&[0x01; 32]).unwrap(); let (state, _) = config.into_state(signer.public_key()).unwrap(); let bridge = state.accounts.iter().find(|a| a.id() == bridge_id).unwrap(); diff --git a/bin/network-monitor/src/counter.rs b/bin/network-monitor/src/counter.rs index 2d829235ba..58b18ce2df 100644 --- a/bin/network-monitor/src/counter.rs +++ b/bin/network-monitor/src/counter.rs @@ -834,7 +834,7 @@ async fn fetch_wallet_account( let storage_details = details.storage_details.context("missing storage details")?; let storage = build_account_storage(storage_details)?; - let account = Account::new(account_id, vault, storage, code, Felt::new(nonce), None) + let account = Account::new(account_id, vault, storage, code, Felt::new_unchecked(nonce), None) .context("failed to create account")?; // Sanity check: verify reconstructed account matches header commitments @@ -952,10 +952,10 @@ fn create_network_note( let partial_metadata = PartialNoteMetadata::new(wallet_account.id(), NoteType::Public); let serial_num = Word::new([ - Felt::new(rng.random()), - Felt::new(rng.random()), - Felt::new(rng.random()), - Felt::new(rng.random()), + Felt::new_unchecked(rng.random()), + Felt::new_unchecked(rng.random()), + Felt::new_unchecked(rng.random()), + Felt::new_unchecked(rng.random()), ]); let recipient = NoteRecipient::new(serial_num, script, NoteStorage::new(vec![])?); diff --git a/bin/network-monitor/src/deploy/counter.rs b/bin/network-monitor/src/deploy/counter.rs index 5479f15898..b127476a36 100644 --- a/bin/network-monitor/src/deploy/counter.rs +++ b/bin/network-monitor/src/deploy/counter.rs @@ -10,7 +10,6 @@ use miden_protocol::account::{ AccountComponent, AccountFile, AccountId, - AccountStorageMode, AccountType, StorageSlot, StorageSlotName, @@ -54,7 +53,7 @@ pub fn create_counter_account(owner_account_id: AccountId) -> Result { let component_code = CodeBuilder::default().compile_component_code("counter::program", script)?; - let metadata = AccountComponentMetadata::new("counter::program", AccountType::all()); + let metadata = AccountComponentMetadata::new("counter::program"); let account_code = AccountComponent::new(component_code, vec![counter_slot, owner_id_slot], metadata)?; @@ -63,8 +62,7 @@ pub fn create_counter_account(owner_account_id: AccountId) -> Result { // Create the counter program account let init_seed: [u8; 32] = rand::random(); let counter_account = AccountBuilder::new(init_seed) - .account_type(AccountType::RegularAccountUpdatableCode) - .storage_mode(AccountStorageMode::Network) + .account_type(AccountType::Public) .with_component(account_code) .with_auth_component(incr_nonce_auth) .build()?; diff --git a/bin/network-monitor/src/deploy/wallet.rs b/bin/network-monitor/src/deploy/wallet.rs index 9b9274440d..981cde45d9 100644 --- a/bin/network-monitor/src/deploy/wallet.rs +++ b/bin/network-monitor/src/deploy/wallet.rs @@ -5,7 +5,7 @@ use std::path::Path; use anyhow::Result; use miden_node_utils::crypto::get_rpo_random_coin; use miden_protocol::account::auth::{AuthScheme, AuthSecretKey}; -use miden_protocol::account::{Account, AccountFile, AccountStorageMode, AccountType}; +use miden_protocol::account::{Account, AccountFile, AccountType}; use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey; use miden_standards::AuthMethod; use miden_standards::account::wallets::create_basic_wallet; @@ -27,12 +27,7 @@ pub fn create_wallet_account() -> Result<(Account, SecretKey)> { }; let init_seed: [u8; 32] = rng.random(); - let wallet_account = create_basic_wallet( - init_seed, - auth, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - )?; + let wallet_account = create_basic_wallet(init_seed, auth, AccountType::Public)?; Ok((wallet_account, secret_key)) } diff --git a/bin/ntx-builder/src/actor/mod.rs b/bin/ntx-builder/src/actor/mod.rs index cefb9bdb65..335c559310 100644 --- a/bin/ntx-builder/src/actor/mod.rs +++ b/bin/ntx-builder/src/actor/mod.rs @@ -118,7 +118,9 @@ impl AccountActorContext { let url = Url::parse("http://127.0.0.1:1").unwrap(); let block_header = mock_block_header(0_u32.into()); - let chain_mmr = PartialMmr::from_peaks(MmrPeaks::new(Forest::new(0), vec![]).unwrap()); + let chain_mmr = PartialMmr::from_peaks( + MmrPeaks::new(Forest::new(0).expect("forest 0 is valid"), vec![]).unwrap(), + ); let chain_state = Arc::new(SharedChainState::new(block_header, chain_mmr)); let (request_tx, _request_rx) = mpsc::channel(1); diff --git a/bin/ntx-builder/src/builder.rs b/bin/ntx-builder/src/builder.rs index a051249601..03f759b2e7 100644 --- a/bin/ntx-builder/src/builder.rs +++ b/bin/ntx-builder/src/builder.rs @@ -5,8 +5,10 @@ use anyhow::Context; use futures::Stream; use miden_node_proto::domain::account::NetworkAccountId; use miden_node_proto::domain::mempool::MempoolEvent; +use miden_protocol::account::Account; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::block::BlockHeader; +use miden_standards::account::auth::NetworkAccount; use tokio::net::TcpListener; use tokio::sync::mpsc; use tokio::task::JoinSet; @@ -223,13 +225,17 @@ impl NetworkTransactionBuilder { .await .context("failed to write TransactionAdded to DB")?; - // Spawn new actors for newly created network accounts. - if let Some(AccountUpdateDetails::Delta(delta)) = account_delta { - if delta.is_full_state() { - if let Ok(network_id) = NetworkAccountId::try_from(delta.id()) { - self.coordinator.spawn_actor(network_id, &self.actor_context); - } - } + // Spawn new actors for newly created network accounts. A delta carrying full state + // lets us reconstruct the Account and look for the standardized + // `NetworkAccountNoteAllowlist` slot in storage; that is the protocol-level + // definition of a network account. + if let Some(AccountUpdateDetails::Delta(delta)) = account_delta + && delta.is_full_state() + && let Ok(account) = Account::try_from(delta) + && let Ok(network_account) = NetworkAccount::new(account) + { + let network_id = NetworkAccountId::new_unchecked(network_account.id()); + self.coordinator.spawn_actor(network_id, &self.actor_context); } let inactive_targets = self.coordinator.send_targeted(&event); for account_id in inactive_targets { diff --git a/bin/ntx-builder/src/clients/store.rs b/bin/ntx-builder/src/clients/store.rs index c901fc69ab..86b7dbba94 100644 --- a/bin/ntx-builder/src/clients/store.rs +++ b/bin/ntx-builder/src/clients/store.rs @@ -109,12 +109,14 @@ impl StoreClient { let header = BlockHeader::try_from(block).map_err(StoreError::DeserializationError)?; - let peaks = MmrPeaks::new(Forest::new(header.block_num().as_usize()), peaks) - .map_err(|_| { - StoreError::MalformedResponse( - "returned peaks are not valid for the sent request".into(), - ) - })?; + let forest = Forest::new(header.block_num().as_usize()).map_err(|err| { + StoreError::DeserializationError(ConversionError::from(err).context("forest")) + })?; + let peaks = MmrPeaks::new(forest, peaks).map_err(|_| { + StoreError::MalformedResponse( + "returned peaks are not valid for the sent request".into(), + ) + })?; let partial_mmr = PartialMmr::from_peaks(peaks); @@ -327,11 +329,7 @@ impl StoreClient { ConversionError::from(err).context("account_id"), ) })?; - NetworkAccountId::try_from(account_id).map_err(|_| { - StoreError::MalformedResponse( - "account id is not a valid network account".into(), - ) - }) + Ok(NetworkAccountId::new_unchecked(account_id)) }) .collect::, StoreError>>()?; diff --git a/bin/ntx-builder/src/coordinator.rs b/bin/ntx-builder/src/coordinator.rs index d4243cc979..8705c2fb36 100644 --- a/bin/ntx-builder/src/coordinator.rs +++ b/bin/ntx-builder/src/coordinator.rs @@ -274,21 +274,19 @@ impl Coordinator { // transaction has been applied, and in the future also resolves race conditions with // external network transactions (once these are allowed). if let Some(AccountUpdateDetails::Delta(delta)) = account_delta { - let account_id = delta.id(); - if account_id.is_network() { - let network_account_id = - account_id.try_into().expect("account is network account"); - if self.actor_registry.contains_key(&network_account_id) { - target_account_ids.insert(network_account_id); - } + // The actor registry only contains accounts the builder has already classified as + // network. Wrap the id unconditionally and let the registry lookup filter for us; + // unknown accounts simply won't match. + let network_account_id = NetworkAccountId::new_unchecked(delta.id()); + if self.actor_registry.contains_key(&network_account_id) { + target_account_ids.insert(network_account_id); } } // Determine target actors for each note. for note in network_notes { let account = note.target_account_id(); - let account = NetworkAccountId::try_from(account) - .expect("network note target account should be a network account"); + let account = NetworkAccountId::new_unchecked(account); if self.actor_registry.contains_key(&account) { target_account_ids.insert(account); diff --git a/bin/ntx-builder/src/db/models/account_effect.rs b/bin/ntx-builder/src/db/models/account_effect.rs index 7a6acf0058..9a86a452aa 100644 --- a/bin/ntx-builder/src/db/models/account_effect.rs +++ b/bin/ntx-builder/src/db/models/account_effect.rs @@ -1,6 +1,7 @@ use miden_node_proto::domain::account::NetworkAccountId; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{Account, AccountDelta, AccountId}; +use miden_standards::account::auth::NetworkAccount; // NETWORK ACCOUNT EFFECT // ================================================================================================ @@ -14,23 +15,30 @@ pub enum NetworkAccountEffect { impl NetworkAccountEffect { pub fn from_protocol(update: &AccountUpdateDetails) -> Option { - let update = match update { - AccountUpdateDetails::Private => return None, + match update { + AccountUpdateDetails::Private => None, AccountUpdateDetails::Delta(update) if update.is_full_state() => { - NetworkAccountEffect::Created( - Account::try_from(update) - .expect("Account should be derivable by full state AccountDelta"), - ) + // Only treat full-state creations as network if the storage carries the + // standardized `NetworkAccountNoteAllowlist` slot. + let account = Account::try_from(update) + .expect("Account should be derivable by full state AccountDelta"); + NetworkAccount::new(account) + .ok() + .map(|na| NetworkAccountEffect::Created(na.into_account())) }, - AccountUpdateDetails::Delta(update) => NetworkAccountEffect::Updated(update.clone()), - }; - - update.protocol_account_id().is_network().then_some(update) + AccountUpdateDetails::Delta(update) => { + // Partial updates carry no storage we can inspect here. Forward them as updates and + // let the coordinator's actor registry filter to known network accounts. + Some(NetworkAccountEffect::Updated(update.clone())) + }, + } } pub fn network_account_id(&self) -> NetworkAccountId { - // SAFETY: This is a network account by construction. - self.protocol_account_id().try_into().unwrap() + // Trusted: constructors only produce this enum for accounts already classified as network + // (via the allowlist check above) or for updates that the caller filters through the actor + // registry. + NetworkAccountId::new_unchecked(self.protocol_account_id()) } fn protocol_account_id(&self) -> AccountId { diff --git a/bin/ntx-builder/src/db/models/conv.rs b/bin/ntx-builder/src/db/models/conv.rs index 0fed2b9593..ca21393ae3 100644 --- a/bin/ntx-builder/src/db/models/conv.rs +++ b/bin/ntx-builder/src/db/models/conv.rs @@ -58,8 +58,7 @@ pub fn account_id_from_bytes(bytes: &[u8]) -> Result { pub fn network_account_id_from_bytes(bytes: &[u8]) -> Result { let account_id = account_id_from_bytes(bytes)?; - NetworkAccountId::try_from(account_id) - .map_err(|e| DatabaseError::deserialization("network account id", e)) + Ok(NetworkAccountId::new_unchecked(account_id)) } pub fn word_to_bytes(word: &Word) -> Vec { diff --git a/bin/ntx-builder/src/db/models/queries/mod.rs b/bin/ntx-builder/src/db/models/queries/mod.rs index f2f97eaf3e..cd90fb1f12 100644 --- a/bin/ntx-builder/src/db/models/queries/mod.rs +++ b/bin/ntx-builder/src/db/models/queries/mod.rs @@ -115,19 +115,19 @@ pub fn add_transaction( match update { NetworkAccountEffect::Updated(ref account_delta) => { // Query latest_account, apply delta, insert inflight row. - let current_account = - get_account(conn, account_id)?.expect("account must exist to apply delta"); - let mut updated = current_account; - updated.apply_delta(account_delta).expect( - "network account delta should apply since it was accepted by the mempool", - ); - - let insert = AccountInsert { - account_id: conversions::network_account_id_to_bytes(account_id), - transaction_id: Some(tx_id_bytes.clone()), - account_data: conversions::account_to_bytes(&updated), - }; - diesel::insert_into(schema::accounts::table).values(&insert).execute(conn)?; + if let Some(current_account) = get_account(conn, account_id)? { + let mut updated = current_account; + updated.apply_delta(account_delta).expect( + "network account delta should apply since it was accepted by the mempool", + ); + + let insert = AccountInsert { + account_id: conversions::network_account_id_to_bytes(account_id), + transaction_id: Some(tx_id_bytes.clone()), + account_data: conversions::account_to_bytes(&updated), + }; + diesel::insert_into(schema::accounts::table).values(&insert).execute(conn)?; + } }, NetworkAccountEffect::Created(ref account) => { let insert = AccountInsert { @@ -146,11 +146,9 @@ pub fn add_transaction( for note in notes { let insert = NoteInsert { nullifier: conversions::nullifier_to_bytes(¬e.as_note().nullifier()), - account_id: conversions::network_account_id_to_bytes( - note.target_account_id() - .try_into() - .expect("network note's target account must be a network account"), - ), + account_id: conversions::network_account_id_to_bytes(NetworkAccountId::new_unchecked( + note.target_account_id(), + )), note_data: note.as_note().to_bytes(), note_id: Some(conversions::note_id_to_bytes(¬e.as_note().id())), attempt_count: 0, diff --git a/bin/ntx-builder/src/db/models/queries/notes.rs b/bin/ntx-builder/src/db/models/queries/notes.rs index bb846d7a5e..1f933dbf23 100644 --- a/bin/ntx-builder/src/db/models/queries/notes.rs +++ b/bin/ntx-builder/src/db/models/queries/notes.rs @@ -77,10 +77,9 @@ pub fn insert_committed_notes( for note in notes { let row = NoteInsert { nullifier: conversions::nullifier_to_bytes(¬e.as_note().nullifier()), - account_id: conversions::network_account_id_to_bytes( - NetworkAccountId::try_from(note.target_account_id()) - .expect("account ID of a network note should be a network account"), - ), + account_id: conversions::network_account_id_to_bytes(NetworkAccountId::new_unchecked( + note.target_account_id(), + )), note_data: note.as_note().to_bytes(), note_id: Some(conversions::note_id_to_bytes(¬e.as_note().id())), attempt_count: 0, diff --git a/bin/ntx-builder/src/test_utils.rs b/bin/ntx-builder/src/test_utils.rs index 1c2c743106..347a43ccaf 100644 --- a/bin/ntx-builder/src/test_utils.rs +++ b/bin/ntx-builder/src/test_utils.rs @@ -2,10 +2,10 @@ use miden_node_proto::domain::account::NetworkAccountId; use miden_protocol::Word; -use miden_protocol::account::{AccountId, AccountStorageMode, AccountType}; +use miden_protocol::account::{AccountId, AccountType}; use miden_protocol::block::BlockNumber; use miden_protocol::testing::account_id::{ - ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE, + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE, AccountIdBuilder, }; use miden_protocol::transaction::TransactionId; @@ -17,17 +17,16 @@ use rand_chacha::rand_core::SeedableRng; /// Creates a network account ID from a test constant. pub fn mock_network_account_id() -> NetworkAccountId { let account_id: AccountId = - ACCOUNT_ID_REGULAR_NETWORK_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); - NetworkAccountId::try_from(account_id).unwrap() + ACCOUNT_ID_REGULAR_PUBLIC_ACCOUNT_IMMUTABLE_CODE.try_into().unwrap(); + NetworkAccountId::new_unchecked(account_id) } /// Creates a distinct network account ID using a seeded RNG. pub fn mock_network_account_id_seeded(seed: u8) -> NetworkAccountId { let account_id = AccountIdBuilder::new() - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Network) + .account_type(AccountType::Public) .build_with_seed([seed; 32]); - NetworkAccountId::try_from(account_id).unwrap() + NetworkAccountId::new_unchecked(account_id) } /// Creates a unique `TransactionId` from a seed value. @@ -47,8 +46,7 @@ pub fn mock_single_target_note( ) -> AccountTargetNetworkNote { let mut rng = ChaCha20Rng::from_seed([seed; 32]); let sender = AccountIdBuilder::new() - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Private) + .account_type(AccountType::Private) .build_with_rng(&mut rng); let target = NetworkAccountTarget::new(network_account_id.inner(), NoteExecutionHint::Always) @@ -68,8 +66,7 @@ pub fn mock_account(_account_id: NetworkAccountId) -> miden_protocol::account::A use miden_standards::testing::account_component::MockAccountComponent; AccountBuilder::new([0u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Network) + .account_type(AccountType::Public) .with_component(MockAccountComponent::with_slots(vec![])) .with_auth_component(NoopAuthComponent) .build_existing() diff --git a/bin/stress-test/src/seeding/mod.rs b/bin/stress-test/src/seeding/mod.rs index bed3d5d34c..1bcb3bdc72 100644 --- a/bin/stress-test/src/seeding/mod.rs +++ b/bin/stress-test/src/seeding/mod.rs @@ -20,7 +20,6 @@ use miden_protocol::account::{ AccountDelta, AccountId, AccountStorageDelta, - AccountStorageMode, AccountType, AccountVaultDelta, StorageMap, @@ -28,7 +27,7 @@ use miden_protocol::account::{ StorageSlot, StorageSlotName, }; -use miden_protocol::asset::{Asset, AssetAmount, FungibleAsset, TokenSymbol}; +use miden_protocol::asset::{Asset, FungibleAsset, TokenSymbol}; use miden_protocol::batch::{BatchAccountUpdate, BatchId, ProvenBatch}; use miden_protocol::block::{ BlockHeader, @@ -38,7 +37,7 @@ use miden_protocol::block::{ ProposedBlock, SignedBlock, }; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey as EcdsaSecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey as EcdsaSecretKey; use miden_protocol::crypto::dsa::falcon512_poseidon2::{PublicKey, SecretKey}; use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::errors::AssetError; @@ -62,7 +61,7 @@ use miden_standards::account::faucets::{FungibleFaucet, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, - PolicyAuthority, + PolicyRegistration, TokenPolicyManager, }; use miden_standards::account::wallets::BasicWallet; @@ -122,7 +121,7 @@ pub async fn seed_store( let benchmark_faucets = create_benchmark_faucets(vault_entries); let faucet = benchmark_faucets[0].clone(); let asset_faucet_ids = benchmark_faucets.iter().map(Account::id).collect::>(); - let fee_params = FeeParameters::new(faucet.id(), 0).unwrap(); + let fee_params = FeeParameters::new(faucet.id(), 0); let signer = EcdsaSecretKey::new(); let genesis_state = GenesisState::new(benchmark_faucets, fee_params, 1, 1, signer.public_key()); let genesis_block = genesis_state @@ -205,7 +204,7 @@ async fn generate_blocks( // share random coin seed and key pair for all accounts to avoid key generation overhead let coin_seed: [u64; 4] = rand::rng().random(); - let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.map(Felt::new).into()))); + let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()))); let key_pair = { let mut rng = rng.lock().unwrap(); SecretKey::with_rng(&mut *rng) @@ -220,7 +219,7 @@ async fn generate_blocks( // create public accounts and notes that mint assets for these accounts let (pub_accounts, pub_notes) = create_accounts_and_notes( num_public_accounts, - AccountStorageMode::Public, + AccountType::Public, &key_pair, &rng, &asset_faucet_ids, @@ -232,7 +231,7 @@ async fn generate_blocks( // create private accounts and notes that mint assets for these accounts let (priv_accounts, priv_notes) = create_accounts_and_notes( num_private_accounts, - AccountStorageMode::Private, + AccountType::Private, &key_pair, &rng, &asset_faucet_ids, @@ -404,7 +403,7 @@ fn fee_from_block(block_ref: &BlockHeader) -> Result #[expect(clippy::too_many_arguments)] fn create_accounts_and_notes( num_accounts: usize, - storage_mode: AccountStorageMode, + account_type: AccountType, key_pair: &SecretKey, rng: &Arc>, asset_faucet_ids: &[AccountId], @@ -416,11 +415,9 @@ fn create_accounts_and_notes( !asset_faucet_ids.is_empty(), "at least one faucet id is required to create benchmark notes" ); - let note_faucet_ids = match storage_mode { - AccountStorageMode::Public => { - asset_faucet_ids.iter().take(vault_entries).copied().collect() - }, - AccountStorageMode::Private | AccountStorageMode::Network => vec![asset_faucet_ids[0]], + let note_faucet_ids = match account_type { + AccountType::Public => asset_faucet_ids.iter().take(vault_entries).copied().collect(), + AccountType::Private => vec![asset_faucet_ids[0]], }; (0..num_accounts) @@ -429,7 +426,7 @@ fn create_accounts_and_notes( let account = create_account( key_pair.public_key(), ((block_num * num_accounts) + account_index) as u64, - storage_mode, + account_type, storage_map_entries, ); let note = { @@ -524,17 +521,16 @@ pub fn benchmark_storage_map_slot() -> StorageSlotName { fn create_account( public_key: PublicKey, index: u64, - storage_mode: AccountStorageMode, + account_type: AccountType, storage_map_entries: usize, ) -> Account { let init_seed: Vec<_> = index.to_be_bytes().into_iter().chain([0u8; 24]).collect(); let mut builder = AccountBuilder::new(init_seed.try_into().unwrap()) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(storage_mode) + .account_type(account_type) .with_auth_component(AuthSingleSig::new(public_key.into(), AuthScheme::Falcon512Poseidon2)) .with_component(BasicWallet); - if storage_mode == AccountStorageMode::Public && storage_map_entries > 0 { + if account_type == AccountType::Public && storage_map_entries > 0 { let entries = (1..=storage_map_entries) .map(|i| { let i = u32::try_from(i).expect("storage map entry index fits into u32"); @@ -553,10 +549,7 @@ fn create_account( let component = AccountComponent::new( component_code, component_storage, - AccountComponentMetadata::new( - "benchmark_storage_map", - [AccountType::RegularAccountImmutableCode], - ), + AccountComponentMetadata::new("benchmark_storage_map"), ) .unwrap(); builder = builder.with_component(component); @@ -573,7 +566,7 @@ fn create_benchmark_faucets(vault_entries: usize) -> Vec { fn create_faucet_with_seed(index: u64) -> Account { let coin_seed: [u64; 4] = rand::rng().random(); - let mut rng = RandomCoin::new(coin_seed.map(Felt::new).into()); + let mut rng = RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()); let key_pair = SecretKey::with_rng(&mut rng); let init_seed: Vec<_> = index.to_be_bytes().into_iter().chain([0u8; 24]).collect(); @@ -582,19 +575,20 @@ fn create_faucet_with_seed(index: u64) -> Account { .name(TokenName::new("TEST").unwrap()) .symbol(token_symbol) .decimals(2) - .max_supply(AssetAmount::new(FungibleAsset::MAX_AMOUNT).unwrap()) + .max_supply(FungibleAsset::MAX_AMOUNT) .build() .unwrap(); AccountBuilder::new(init_seed.try_into().unwrap()) - .account_type(AccountType::FungibleFaucet) - .storage_mode(AccountStorageMode::Private) + .account_type(AccountType::Private) .with_component(faucet) - .with_components(TokenPolicyManager::new( - PolicyAuthority::AuthControlled, - MintPolicyConfig::AllowAll, - BurnPolicyConfig::AllowAll, - )) + .with_components( + TokenPolicyManager::new() + .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active) + .unwrap() + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active) + .unwrap(), + ) .with_auth_component(AuthSingleSig::new( key_pair.public_key().into(), AuthScheme::Falcon512Poseidon2, @@ -757,7 +751,10 @@ fn create_emit_note_tx( let slot = faucet.storage().get_item(token_config_slot).unwrap(); faucet .storage_mut() - .set_item(token_config_slot, [slot[0] + Felt::new(10), slot[1], slot[2], slot[3]].into()) + .set_item( + token_config_slot, + [slot[0] + Felt::new_unchecked(10), slot[1], slot[2], slot[3]].into(), + ) .unwrap(); faucet.increment_nonce(ONE).unwrap(); @@ -803,7 +800,7 @@ async fn get_batch_inputs( let batch_inputs = store_client .get_batch_inputs( vec![(block_ref.block_num(), block_ref.commitment())].into_iter(), - notes.iter().map(Note::commitment), + notes.iter().map(Note::id), ) .await .unwrap(); @@ -826,7 +823,7 @@ async fn get_block_inputs( batch .input_notes() .into_iter() - .filter_map(|note| note.header().map(NoteHeader::to_commitment)) + .filter_map(|note| note.header().map(NoteHeader::id)) }), batches.iter().map(ProvenBatch::reference_block_num), ) diff --git a/bin/stress-test/src/seeding/tests.rs b/bin/stress-test/src/seeding/tests.rs index 3084d2a8be..3bb6935c67 100644 --- a/bin/stress-test/src/seeding/tests.rs +++ b/bin/stress-test/src/seeding/tests.rs @@ -11,11 +11,11 @@ fn benchmark_fungible_faucet_ids(vault_entries: usize) -> Vec { #[test] fn public_account_can_be_created_with_large_storage_map() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let mut rng = RandomCoin::new(coin_seed.into()); let key_pair = SecretKey::with_rng(&mut rng); - let account = create_account(key_pair.public_key(), 42, AccountStorageMode::Public, 128); + let account = create_account(key_pair.public_key(), 42, AccountType::Public, 128); let map_slot = account .storage() @@ -33,11 +33,11 @@ fn public_account_can_be_created_with_large_storage_map() { #[test] fn private_account_ignores_large_storage_map_entries() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let mut rng = RandomCoin::new(coin_seed.into()); let key_pair = SecretKey::with_rng(&mut rng); - let account = create_account(key_pair.public_key(), 42, AccountStorageMode::Private, 128); + let account = create_account(key_pair.public_key(), 42, AccountType::Private, 128); assert!( account @@ -50,23 +50,15 @@ fn private_account_ignores_large_storage_map_entries() { #[test] fn public_account_note_contains_requested_distinct_vault_assets() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.into()))); let mut key_rng = rng.lock().unwrap(); let key_pair = SecretKey::with_rng(&mut *key_rng); drop(key_rng); let faucet_ids = benchmark_fungible_faucet_ids(5); - let (_, notes) = create_accounts_and_notes( - 1, - AccountStorageMode::Public, - &key_pair, - &rng, - &faucet_ids, - 0, - 0, - 5, - ); + let (_, notes) = + create_accounts_and_notes(1, AccountType::Public, &key_pair, &rng, &faucet_ids, 0, 0, 5); let assets = notes[0].assets(); assert_eq!(assets.num_assets(), 5); @@ -78,33 +70,25 @@ fn public_account_note_contains_requested_distinct_vault_assets() { #[test] fn private_account_note_keeps_single_vault_asset() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.into()))); let mut key_rng = rng.lock().unwrap(); let key_pair = SecretKey::with_rng(&mut *key_rng); drop(key_rng); let faucet_ids = benchmark_fungible_faucet_ids(5); - let (_, notes) = create_accounts_and_notes( - 1, - AccountStorageMode::Private, - &key_pair, - &rng, - &faucet_ids, - 0, - 0, - 5, - ); + let (_, notes) = + create_accounts_and_notes(1, AccountType::Private, &key_pair, &rng, &faucet_ids, 0, 0, 5); assert_eq!(notes[0].assets().num_assets(), 1); } #[test] fn public_account_storage_map_entry_can_be_updated_for_benchmark_blocks() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let mut rng = RandomCoin::new(coin_seed.into()); let key_pair = SecretKey::with_rng(&mut rng); - let mut account = create_account(key_pair.public_key(), 42, AccountStorageMode::Public, 4); + let mut account = create_account(key_pair.public_key(), 42, AccountType::Public, 4); let key = StorageMapKey::from_index(2); let old_value = account @@ -125,10 +109,10 @@ fn public_account_storage_map_entry_can_be_updated_for_benchmark_blocks() { #[test] fn private_account_storage_map_update_is_skipped() { - let coin_seed = [1, 2, 3, 4].map(Felt::new); + let coin_seed = [1, 2, 3, 4].map(Felt::new_unchecked); let mut rng = RandomCoin::new(coin_seed.into()); let key_pair = SecretKey::with_rng(&mut rng); - let mut account = create_account(key_pair.public_key(), 42, AccountStorageMode::Private, 4); + let mut account = create_account(key_pair.public_key(), 42, AccountType::Private, 4); let updated = update_benchmark_storage_map_entry(&mut account, 3, 9, 4); diff --git a/bin/stress-test/src/store/mod.rs b/bin/stress-test/src/store/mod.rs index 7dfef90b35..5cee642613 100644 --- a/bin/stress-test/src/store/mod.rs +++ b/bin/stress-test/src/store/mod.rs @@ -55,7 +55,7 @@ pub async fn bench_get_account( let mut account_ids: Vec = accounts .lines() .map(|a| AccountId::from_hex(a).expect("invalid account id")) - .filter(AccountId::has_public_state) + .filter(AccountId::is_public) .collect(); assert!( @@ -360,9 +360,15 @@ pub async fn bench_sync_nullifiers( .notes; nullifier_prefixes.extend(notes.iter().filter_map(|n| { - let details_bytes = n.note.as_ref()?.details.as_ref()?; + let proto_note = n.note.as_ref()?; + let details_bytes = proto_note.details.as_ref()?; let details = NoteDetails::read_from_bytes(details_bytes).unwrap(); - Some(u32::from(details.nullifier().prefix())) + let metadata = + miden_protocol::note::NoteMetadata::try_from(proto_note.metadata.clone()?) + .ok()?; + let nullifier = + miden_protocol::note::Nullifier::from_details_and_metadata(&details, &metadata); + Some(u32::from(nullifier.prefix())) })); } diff --git a/bin/validator/src/commands/mod.rs b/bin/validator/src/commands/mod.rs index 8ad6073ac1..96fda9abfa 100644 --- a/bin/validator/src/commands/mod.rs +++ b/bin/validator/src/commands/mod.rs @@ -6,7 +6,7 @@ use std::path::PathBuf; use clap::Parser; use miden_node_utils::clap::GrpcOptionsInternal; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::utils::serde::Deserializable; use miden_validator::ValidatorSigner; @@ -158,7 +158,7 @@ impl ValidatorCommand { ) .await } else { - let signer = SecretKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; + let signer = SigningKey::read_from_bytes(hex::decode(validator_key)?.as_ref())?; let signer = ValidatorSigner::new_local(signer); start::start( address, @@ -216,7 +216,7 @@ impl ValidatorKey { if let Some(kms_key_id) = self.validator_kms_key_id { Ok(ValidatorSigner::new_kms(kms_key_id).await?) } else { - let signer = SecretKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; + let signer = SigningKey::read_from_bytes(hex::decode(self.validator_key)?.as_ref())?; Ok(ValidatorSigner::new_local(signer)) } } diff --git a/bin/validator/src/db/models.rs b/bin/validator/src/db/models.rs index fd10445338..85f1e7354d 100644 --- a/bin/validator/src/db/models.rs +++ b/bin/validator/src/db/models.rs @@ -39,7 +39,7 @@ impl ValidatedTransactionRowInsert { output_notes: tx.output_notes().to_bytes(), initial_account_hash: tx.initial_account_hash().to_bytes(), final_account_hash: tx.final_account_hash().to_bytes(), - fee: tx.fee().amount().to_le_bytes().to_vec(), + fee: tx.fee().amount().as_u64().to_le_bytes().to_vec(), } } } diff --git a/bin/validator/src/signers/mod.rs b/bin/validator/src/signers/mod.rs index 2c50b2dbb2..59999339d7 100644 --- a/bin/validator/src/signers/mod.rs +++ b/bin/validator/src/signers/mod.rs @@ -2,7 +2,7 @@ mod kms; pub use kms::KmsSigner; use miden_node_utils::spawn::spawn_blocking_in_current_span; use miden_protocol::block::BlockHeader; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature, SigningKey}; // VALIDATOR SIGNER // ================================================================================================= @@ -10,7 +10,7 @@ use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signa /// Signer that the Validator uses to sign blocks. pub enum ValidatorSigner { Kms(KmsSigner), - Local(SecretKey), + Local(SigningKey), } impl ValidatorSigner { @@ -24,7 +24,7 @@ impl ValidatorSigner { } /// Constructs a signer which uses a local secret key for signing. - pub fn new_local(secret_key: SecretKey) -> Self { + pub fn new_local(secret_key: SigningKey) -> Self { Self::Local(secret_key) } diff --git a/crates/block-producer/src/batch_builder/mod.rs b/crates/block-producer/src/batch_builder/mod.rs index a18024cfdb..1294e027bb 100644 --- a/crates/block-producer/src/batch_builder/mod.rs +++ b/crates/block-producer/src/batch_builder/mod.rs @@ -9,6 +9,7 @@ use miden_node_utils::spawn::spawn_blocking_in_current_span; use miden_node_utils::tracing::OpenTelemetrySpanExt; use miden_protocol::MIN_PROOF_SECURITY_LEVEL; use miden_protocol::batch::{BatchId, ProposedBatch, ProvenBatch}; +use miden_protocol::note::NoteId; use miden_remote_prover_client::RemoteBatchProver; use miden_tx_batch_prover::LocalBatchProver; use rand::Rng; @@ -221,7 +222,8 @@ impl BatchJob { .transactions() .iter() .map(Deref::deref) - .flat_map(AuthenticatedTransaction::unauthenticated_note_commitments); + .flat_map(AuthenticatedTransaction::unauthenticated_note_ids) + .map(NoteId::from_raw); self.store .get_batch_inputs(block_references, unauthenticated_notes) @@ -363,7 +365,7 @@ impl TelemetryInjectorExt for SelectedBatch { tx_ids.push(tx.id()); input_notes_count += tx.input_note_count(); output_notes_count += tx.output_note_count(); - unauth_notes_count += tx.unauthenticated_note_commitments().count(); + unauth_notes_count += tx.unauthenticated_note_ids().count(); (tx_ids, input_notes_count, output_notes_count, unauth_notes_count) }, ); diff --git a/crates/block-producer/src/block_builder/mod.rs b/crates/block-producer/src/block_builder/mod.rs index 00611ebcd2..b004315fdf 100644 --- a/crates/block-producer/src/block_builder/mod.rs +++ b/crates/block-producer/src/block_builder/mod.rs @@ -172,7 +172,7 @@ impl BlockBuilder { .input_notes() .iter() .cloned() - .filter_map(|note| note.header().map(NoteHeader::to_commitment)) + .filter_map(|note| note.header().map(NoteHeader::id)) }); let block_references_iter = batch_iter.clone().map(Deref::deref).map(ProvenBatch::reference_block_num); diff --git a/crates/block-producer/src/domain/batch.rs b/crates/block-producer/src/domain/batch.rs index ca739b796b..dc02d8e8b4 100644 --- a/crates/block-producer/src/domain/batch.rs +++ b/crates/block-producer/src/domain/batch.rs @@ -122,9 +122,9 @@ not match the current commitment {}", let id = BatchId::from_ids(txs.iter().map(|tx| (tx.id(), tx.account_id()))); let mut unauthenticated_notes: HashSet<_> = - txs.iter().flat_map(|tx| tx.unauthenticated_note_commitments()).collect(); + txs.iter().flat_map(|tx| tx.unauthenticated_note_ids()).collect(); - for output_note in txs.iter().flat_map(|tx| tx.output_note_commitments()) { + for output_note in txs.iter().flat_map(|tx| tx.output_note_ids()) { unauthenticated_notes.remove(&output_note); } diff --git a/crates/block-producer/src/domain/transaction.rs b/crates/block-producer/src/domain/transaction.rs index 4292db804c..550a4c20f1 100644 --- a/crates/block-producer/src/domain/transaction.rs +++ b/crates/block-producer/src/domain/transaction.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::block::BlockNumber; -use miden_protocol::note::{NoteHeader, Nullifier}; +use miden_protocol::note::Nullifier; use miden_protocol::transaction::{OutputNote, ProvenTransaction, TransactionId, TxAccountUpdate}; use crate::errors::StateConflict; @@ -89,11 +89,8 @@ impl AuthenticatedTransaction { self.inner.nullifiers() } - pub fn output_note_commitments(&self) -> impl Iterator + '_ { - self.inner - .output_notes() - .iter() - .map(miden_protocol::transaction::OutputNote::to_commitment) + pub fn output_note_ids(&self) -> impl Iterator + '_ { + self.inner.output_notes().iter().map(|n| n.id().as_word()) } pub fn output_notes(&self) -> impl Iterator + '_ { @@ -112,12 +109,12 @@ impl AuthenticatedTransaction { (self.inner.ref_block_num(), self.inner.ref_block_commitment()) } - /// Note commitments which were unauthenticated in the transaction __and__ which were not - /// authenticated by the store inputs. - pub fn unauthenticated_note_commitments(&self) -> impl Iterator + '_ { + /// Note IDs which were unauthenticated in the transaction __and__ which were not authenticated + /// by the store inputs. + pub fn unauthenticated_note_ids(&self) -> impl Iterator + '_ { self.inner .unauthenticated_notes() - .map(NoteHeader::to_commitment) + .map(|h| h.id().as_word()) .filter(|commitment| !self.notes_authenticated_by_store.contains(commitment)) } diff --git a/crates/block-producer/src/mempool/graph/batch.rs b/crates/block-producer/src/mempool/graph/batch.rs index c42257c8ca..9a551f8de5 100644 --- a/crates/block-producer/src/mempool/graph/batch.rs +++ b/crates/block-producer/src/mempool/graph/batch.rs @@ -25,7 +25,7 @@ impl GraphNode for SelectedBatch { } fn output_notes(&self) -> Box + '_> { - Box::new(self.transactions().iter().flat_map(|tx| tx.output_note_commitments())) + Box::new(self.transactions().iter().flat_map(|tx| tx.output_note_ids())) } fn unauthenticated_notes(&self) -> Box + '_> { diff --git a/crates/block-producer/src/mempool/graph/transaction.rs b/crates/block-producer/src/mempool/graph/transaction.rs index d9583b03c5..71bbefead7 100644 --- a/crates/block-producer/src/mempool/graph/transaction.rs +++ b/crates/block-producer/src/mempool/graph/transaction.rs @@ -27,11 +27,11 @@ impl GraphNode for Arc { } fn output_notes(&self) -> Box + '_> { - Box::new(self.output_note_commitments()) + Box::new(self.output_note_ids()) } fn unauthenticated_notes(&self) -> Box + '_> { - Box::new(self.unauthenticated_note_commitments()) + Box::new(self.unauthenticated_note_ids()) } fn account_updates( diff --git a/crates/block-producer/src/mempool/subscription.rs b/crates/block-producer/src/mempool/subscription.rs index d38ea0fe0f..7b42245ea9 100644 --- a/crates/block-producer/src/mempool/subscription.rs +++ b/crates/block-producer/src/mempool/subscription.rs @@ -87,8 +87,16 @@ impl SubscriptionProvider { OutputNote::Private(_) => None, }) .collect(); - let account_delta = - tx.account_id().is_network().then(|| tx.account_update().details().clone()); + // The classifier `is_network()` is gone from the protocol; network-ness now lives in + // account storage and cannot be determined from an AccountId alone. We send the delta for + // every non-private update and let the subscriber (which keeps its own list of network + // accounts) filter. Private accounts carry no payload, so the extra envelopes are cheap. + let account_delta = match tx.account_update().details() { + miden_protocol::account::delta::AccountUpdateDetails::Private => None, + details @ miden_protocol::account::delta::AccountUpdateDetails::Delta(_) => { + Some(details.clone()) + }, + }; let event = MempoolEvent::TransactionAdded { id, nullifiers, diff --git a/crates/block-producer/src/store/mod.rs b/crates/block-producer/src/store/mod.rs index 92c78cfe4d..cf314a10a0 100644 --- a/crates/block-producer/src/store/mod.rs +++ b/crates/block-producer/src/store/mod.rs @@ -13,7 +13,7 @@ use miden_protocol::Word; use miden_protocol::account::AccountId; use miden_protocol::batch::OrderedBatches; use miden_protocol::block::{BlockHeader, BlockInputs, BlockNumber, SignedBlock}; -use miden_protocol::note::Nullifier; +use miden_protocol::note::{NoteId, Nullifier}; use miden_protocol::transaction::ProvenTransaction; use miden_protocol::utils::serde::Serializable; use tracing::{debug, info, instrument}; @@ -162,7 +162,7 @@ impl StoreClient { nullifiers: proven_tx.nullifiers().map(Into::into).collect(), unauthenticated_notes: proven_tx .unauthenticated_notes() - .map(|note| note.to_commitment().into()) + .map(|note| note.id().into()) .collect(), }; @@ -202,7 +202,7 @@ impl StoreClient { &self, updated_accounts: impl Iterator + Send, created_nullifiers: impl Iterator + Send, - unauthenticated_notes: impl Iterator + Send, + unauthenticated_notes: impl Iterator + Send, reference_blocks: impl Iterator + Send, ) -> Result { let request = tonic::Request::new(proto::store::BlockInputsRequest { @@ -223,11 +223,11 @@ impl StoreClient { pub async fn get_batch_inputs( &self, block_references: impl Iterator + Send, - note_commitments: impl Iterator + Send, + note_ids: impl Iterator + Send, ) -> Result { let request = tonic::Request::new(proto::store::BatchInputsRequest { reference_blocks: block_references.map(|(block_num, _)| block_num.as_u32()).collect(), - note_commitments: note_commitments.map(proto::primitives::Digest::from).collect(), + note_commitments: note_ids.map(proto::primitives::Digest::from).collect(), }); let store_response = self.client.clone().get_batch_inputs(request).await?.into_inner(); diff --git a/crates/block-producer/src/test_utils/account.rs b/crates/block-producer/src/test_utils/account.rs index dcd6da86a7..6276f9b6ad 100644 --- a/crates/block-producer/src/test_utils/account.rs +++ b/crates/block-producer/src/test_utils/account.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::LazyLock; -use miden_protocol::account::{AccountId, AccountIdVersion, AccountStorageMode, AccountType}; +use miden_protocol::account::{AccountId, AccountIdVersion, AccountType}; use miden_protocol::{Hasher, Word}; pub static MOCK_ACCOUNTS: LazyLock>> = @@ -33,8 +33,7 @@ impl MockPrivateAccount { fn generate(init_seed: [u8; 32], new_account: bool) -> Self { let account_seed = AccountId::compute_account_seed( init_seed, - AccountType::RegularAccountUpdatableCode, - AccountStorageMode::Private, + AccountType::Private, AccountIdVersion::Version1, Word::empty(), Word::empty(), diff --git a/crates/block-producer/src/test_utils/proven_tx.rs b/crates/block-producer/src/test_utils/proven_tx.rs index e434dd52c9..aa7eccf16d 100644 --- a/crates/block-producer/src/test_utils/proven_tx.rs +++ b/crates/block-producer/src/test_utils/proven_tx.rs @@ -110,7 +110,7 @@ impl MockProvenTxBuilder { pub fn nullifiers_range(self, range: Range) -> Self { let nullifiers = range .map(|index| { - let nullifier = Word::from([ONE, ONE, ONE, Felt::new(index)]); + let nullifier = Word::from([ONE, ONE, ONE, Felt::new_unchecked(index)]); Nullifier::from_raw(nullifier) }) diff --git a/crates/db/src/conv.rs b/crates/db/src/conv.rs index 8da9b50d38..5f1b4d0903 100644 --- a/crates/db/src/conv.rs +++ b/crates/db/src/conv.rs @@ -147,7 +147,7 @@ pub(crate) fn nullifier_prefix_to_raw_sql(prefix: u16) -> i32 { #[inline(always)] pub(crate) fn raw_sql_to_nonce(raw: i64) -> Felt { debug_assert!(raw >= 0); - Felt::new(raw as u64) + Felt::new_unchecked(raw as u64) } #[inline(always)] pub(crate) fn nonce_to_raw_sql(nonce: Felt) -> i64 { diff --git a/crates/large-smt-backend-rocksdb/src/helpers.rs b/crates/large-smt-backend-rocksdb/src/helpers.rs index 23f3c8d88f..bcc1611e92 100644 --- a/crates/large-smt-backend-rocksdb/src/helpers.rs +++ b/crates/large-smt-backend-rocksdb/src/helpers.rs @@ -1,5 +1,4 @@ use miden_crypto::merkle::smt::{MAX_LEAF_ENTRIES, SmtLeaf, SmtLeafError}; -use miden_crypto::word::LexicographicWord; use rocksdb::Error as RocksDbError; use crate::{StorageError, Word}; @@ -25,30 +24,28 @@ pub(crate) fn insert_into_leaf( Ok(Some(old_value)) } else { let mut pairs = vec![*kv_pair, (key, value)]; - pairs.sort_by(|(key_1, _), (key_2, _)| { - LexicographicWord::from(*key_1).cmp(&LexicographicWord::from(*key_2)) - }); + pairs.sort_by(|(key_1, _), (key_2, _)| key_1.cmp(key_2)); *leaf = SmtLeaf::Multiple(pairs); Ok(None) } }, - SmtLeaf::Multiple(kv_pairs) => match kv_pairs.binary_search_by(|kv_pair| { - LexicographicWord::from(kv_pair.0).cmp(&LexicographicWord::from(key)) - }) { - Ok(pos) => { - let old_value = kv_pairs[pos].1; - kv_pairs[pos].1 = value; - Ok(Some(old_value)) - }, - Err(pos) => { - if kv_pairs.len() >= MAX_LEAF_ENTRIES { - return Err(StorageError::Leaf(SmtLeafError::TooManyLeafEntries { - actual: kv_pairs.len() + 1, - })); - } - kv_pairs.insert(pos, (key, value)); - Ok(None) - }, + SmtLeaf::Multiple(kv_pairs) => { + match kv_pairs.binary_search_by(|kv_pair| kv_pair.0.cmp(&key)) { + Ok(pos) => { + let old_value = kv_pairs[pos].1; + kv_pairs[pos].1 = value; + Ok(Some(old_value)) + }, + Err(pos) => { + if kv_pairs.len() >= MAX_LEAF_ENTRIES { + return Err(StorageError::Leaf(SmtLeafError::TooManyLeafEntries { + actual: kv_pairs.len() + 1, + })); + } + kv_pairs.insert(pos, (key, value)); + Ok(None) + }, + } }, } } @@ -65,19 +62,19 @@ pub(crate) fn remove_from_leaf(leaf: &mut SmtLeaf, key: Word) -> (Option, (None, false) } }, - SmtLeaf::Multiple(kv_pairs) => match kv_pairs.binary_search_by(|kv_pair| { - LexicographicWord::from(kv_pair.0).cmp(&LexicographicWord::from(key)) - }) { - Ok(pos) => { - let old_value = kv_pairs[pos].1; - kv_pairs.remove(pos); - debug_assert!(!kv_pairs.is_empty()); - if kv_pairs.len() == 1 { - *leaf = SmtLeaf::Single(kv_pairs[0]); - } - (Some(old_value), false) - }, - Err(_) => (None, false), + SmtLeaf::Multiple(kv_pairs) => { + match kv_pairs.binary_search_by(|kv_pair| kv_pair.0.cmp(&key)) { + Ok(pos) => { + let old_value = kv_pairs[pos].1; + kv_pairs.remove(pos); + debug_assert!(!kv_pairs.is_empty()); + if kv_pairs.len() == 1 { + *leaf = SmtLeaf::Single(kv_pairs[0]); + } + (Some(old_value), false) + }, + Err(_) => (None, false), + } }, } } diff --git a/crates/large-smt-backend-rocksdb/src/lib.rs b/crates/large-smt-backend-rocksdb/src/lib.rs index eaca341fdc..97ccceff99 100644 --- a/crates/large-smt-backend-rocksdb/src/lib.rs +++ b/crates/large-smt-backend-rocksdb/src/lib.rs @@ -39,6 +39,7 @@ pub use miden_protocol::crypto::merkle::smt::{ SmtLeafError, SmtProof, SmtStorage, + SmtStorageReader, StorageError, StorageUpdateParts, StorageUpdates, @@ -61,6 +62,7 @@ pub use rocksdb::{ RocksDbConfig, RocksDbDurabilityMode, RocksDbMemoryBudget, + RocksDbSnapshotStorage, RocksDbStorage, RocksDbTuningOptions, RocksDbWriteBufferManagerBudget, diff --git a/crates/large-smt-backend-rocksdb/src/rocksdb.rs b/crates/large-smt-backend-rocksdb/src/rocksdb.rs index 1310ae6dff..048116ef4e 100644 --- a/crates/large-smt-backend-rocksdb/src/rocksdb.rs +++ b/crates/large-smt-backend-rocksdb/src/rocksdb.rs @@ -1,5 +1,6 @@ use alloc::boxed::Box; use alloc::vec::Vec; +use std::mem::ManuallyDrop; use std::path::PathBuf; use std::sync::Arc; @@ -24,7 +25,14 @@ use rocksdb::{ WriteOptions, }; -use super::{SmtStorage, StorageError, StorageUpdateParts, StorageUpdates, SubtreeUpdate}; +use super::{ + SmtStorage, + SmtStorageReader, + StorageError, + StorageUpdateParts, + StorageUpdates, + SubtreeUpdate, +}; use crate::helpers::{insert_into_leaf, map_rocksdb_err, remove_from_leaf}; use crate::{EMPTY_WORD, Word}; @@ -317,71 +325,297 @@ impl RocksDbStorage { }; KeyBytes::new(index.position(), keep) } +} + +// READ-SOURCE TRAIT +// -------------------------------------------------------------------------------------------- + +/// Internal abstraction over reading from either the live `DB` ([`RocksDbStorage`]) or a +/// point-in-time `Snapshot` ([`RocksDbSnapshotStorage`]). +/// +/// The two storage types each implement this trait by providing the few low-level operations +/// (`db`, `get_cf_bytes`, `multi_get_cf_bytes`, optional `apply_snapshot`); the shared +/// `SmtStorageReader` logic lives in the default methods below so it is written once. +trait RocksReadSource: Send + Sync + 'static { + fn db(&self) -> &DB; + + fn get_cf_bytes( + &self, + cf: &rocksdb::ColumnFamily, + key: &[u8], + ) -> Result>, rocksdb::Error>; + + fn multi_get_cf_bytes<'b, K, I, W>( + &self, + keys_cf: I, + ) -> Vec>, rocksdb::Error>> + where + K: AsRef<[u8]>, + I: IntoIterator, + W: rocksdb::AsColumnFamilyRef + 'b; + + fn apply_snapshot(&self, _read_opts: &mut ReadOptions) {} - /// Retrieves a handle to a `RocksDB` column family by its name. - /// - /// # Errors - /// Returns `StorageError::Backend` if the column family with the given `name` does not - /// exist. fn cf_handle(&self, name: &str) -> Result<&rocksdb::ColumnFamily, StorageError> { - self.db + self.db() .cf_handle(name) .ok_or_else(|| StorageError::Unsupported(format!("unknown column family `{name}`"))) } - /* helper: CF handle from NodeIndex ------------------------------------- */ #[inline(always)] fn subtree_cf(&self, index: NodeIndex) -> &rocksdb::ColumnFamily { - let name = cf_for_depth(index.depth()); - self.cf_handle(name).expect("CF handle missing") + self.cf_handle(cf_for_depth(index.depth())).expect("CF handle missing") } -} -impl SmtStorage for RocksDbStorage { - /// Retrieves the total count of non-empty leaves from the `METADATA_CF` column family. - /// Returns 0 if the count is not found. - /// - /// # Errors - /// - `StorageError::Backend`: If the metadata column family is missing or a RocksDB error - /// occurs. - /// - `StorageError::BadValueLen`: If the retrieved count bytes are invalid. - fn leaf_count(&self) -> Result { + fn read_count_impl(&self, what: &'static str, key: &[u8]) -> Result { let cf = self.cf_handle(METADATA_CF)?; - self.db - .get_cf(cf, LEAF_COUNT_KEY) - .map_err(map_rocksdb_err)? - .map_or(Ok(0), |bytes| { - let arr: [u8; 8] = - bytes.as_slice().try_into().map_err(|_| StorageError::BadValueLen { - what: "leaf count", - expected: 8, - found: bytes.len(), - })?; - Ok(usize::from_be_bytes(arr)) + self.get_cf_bytes(cf, key).map_err(map_rocksdb_err)?.map_or(Ok(0), |bytes| { + let arr: [u8; 8] = bytes + .as_slice() + .try_into() + .map_err(|_| StorageError::BadValueLen { what, expected: 8, found: bytes.len() })?; + Ok(usize::from_be_bytes(arr)) + }) + } + + fn leaf_count_impl(&self) -> Result { + self.read_count_impl("leaf count", LEAF_COUNT_KEY) + } + + fn entry_count_impl(&self) -> Result { + self.read_count_impl("entry count", ENTRY_COUNT_KEY) + } + + fn get_leaf_impl(&self, index: u64) -> Result, StorageError> { + let cf = self.cf_handle(LEAVES_CF)?; + let key = RocksDbStorage::index_db_key(index); + match self.get_cf_bytes(cf, &key).map_err(map_rocksdb_err)? { + Some(bytes) => Ok(Some(SmtLeaf::read_from_bytes_with_budget(&bytes, bytes.len())?)), + None => Ok(None), + } + } + + fn get_leaves_impl(&self, indices: &[u64]) -> Result>, StorageError> { + let cf = self.cf_handle(LEAVES_CF)?; + let db_keys: Vec<[u8; 8]> = + indices.iter().map(|&idx| RocksDbStorage::index_db_key(idx)).collect(); + let results = self.multi_get_cf_bytes(db_keys.iter().map(|k| (cf, k.as_ref()))); + results + .into_iter() + .map(|result| match result { + Ok(Some(bytes)) => { + Ok(Some(SmtLeaf::read_from_bytes_with_budget(&bytes, bytes.len())?)) + }, + Ok(None) => Ok(None), + Err(e) => Err(map_rocksdb_err(e)), }) + .collect() } - /// Retrieves the total count of key-value entries from the `METADATA_CF` column family. - /// Returns 0 if the count is not found. + fn has_leaves_impl(&self) -> Result { + Ok(self.leaf_count_impl()? > 0) + } + + fn get_subtree_impl(&self, index: NodeIndex) -> Result, StorageError> { + let cf = self.subtree_cf(index); + let key = RocksDbStorage::subtree_db_key(index); + match self.get_cf_bytes(cf, key.as_ref()).map_err(map_rocksdb_err)? { + Some(bytes) => Ok(Some(Subtree::from_vec(index, &bytes)?)), + None => Ok(None), + } + } + + /// Batch-retrieves subtrees grouped by depth via parallel `multi_get` calls, one per depth + /// bucket. /// - /// # Errors - /// - `StorageError::Backend`: If the metadata column family is missing or a RocksDB error - /// occurs. - /// - `StorageError::BadValueLen`: If the retrieved count bytes are invalid. + /// Note: when multiple buckets error, only the first one observed by `collect` is propagated. + fn get_subtrees_impl( + &self, + indices: &[NodeIndex], + ) -> Result>, StorageError> { + use rayon::prelude::*; + + let mut depth_buckets: [Vec<(usize, NodeIndex)>; 5] = Default::default(); + + for (original_index, &node_index) in indices.iter().enumerate() { + let depth = node_index.depth(); + let bucket_index = match depth { + 56 => 0, + 48 => 1, + 40 => 2, + 32 => 3, + 24 => 4, + _ => { + return Err(StorageError::Unsupported(format!( + "unsupported subtree depth {depth}" + ))); + }, + }; + depth_buckets[bucket_index].push((original_index, node_index)); + } + let mut results = vec![None; indices.len()]; + + let bucket_results: Result, StorageError> = depth_buckets + .into_par_iter() + .enumerate() + .filter(|(_, bucket)| !bucket.is_empty()) + .map( + |(bucket_index, bucket)| -> Result)>, StorageError> { + let depth = SUBTREE_DEPTHS[bucket_index]; + let cf = self.cf_handle(cf_for_depth(depth))?; + let keys: Vec<_> = bucket + .iter() + .map(|(_, idx)| RocksDbStorage::subtree_db_key(*idx)) + .collect(); + + let db_results = self.multi_get_cf_bytes(keys.iter().map(|k| (cf, k.as_ref()))); + + bucket + .into_iter() + .zip(db_results) + .map(|((original_index, node_index), db_result)| { + let subtree = match db_result { + Ok(Some(bytes)) => Some(Subtree::from_vec(node_index, &bytes)?), + Ok(None) => None, + Err(e) => return Err(map_rocksdb_err(e)), + }; + Ok((original_index, subtree)) + }) + .collect() + }, + ) + .collect(); + + for bucket_result in bucket_results? { + for (original_index, subtree) in bucket_result { + results[original_index] = subtree; + } + } + + Ok(results) + } + + fn get_inner_node_impl(&self, index: NodeIndex) -> Result, StorageError> { + if index.depth() < IN_MEMORY_DEPTH { + return Err(StorageError::Unsupported( + "Cannot get inner node from upper part of the tree".into(), + )); + } + let subtree_root_index = Subtree::find_subtree_root(index); + Ok(self + .get_subtree_impl(subtree_root_index)? + .and_then(|subtree| subtree.get_inner_node(index))) + } + + fn iter_leaves_impl( + &self, + ) -> Result + '_>, StorageError> { + let cf = self.cf_handle(LEAVES_CF)?; + let mut read_opts = ReadOptions::default(); + read_opts.set_total_order_seek(true); + self.apply_snapshot(&mut read_opts); + let db_iter = self.db().iterator_cf_opt(cf, read_opts, IteratorMode::Start); + Ok(Box::new(RocksDbDirectLeafIterator { iter: db_iter })) + } + + fn iter_subtrees_impl(&self) -> Result + '_>, StorageError> { + const SUBTREE_CFS: [&str; 5] = + [SUBTREE_24_CF, SUBTREE_32_CF, SUBTREE_40_CF, SUBTREE_48_CF, SUBTREE_56_CF]; + + let mut cf_handles = Vec::new(); + for cf_name in SUBTREE_CFS { + cf_handles.push(self.cf_handle(cf_name)?); + } + let configure = move |opts: &mut ReadOptions| self.apply_snapshot(opts); + Ok(Box::new(RocksDbSubtreeIterator::new(self.db(), cf_handles, configure))) + } + + fn get_depth24_impl(&self) -> Result, StorageError> { + let cf = self.cf_handle(DEPTH_24_CF)?; + let mut read_opts = ReadOptions::default(); + self.apply_snapshot(&mut read_opts); + let iter = self.db().iterator_cf_opt(cf, read_opts, IteratorMode::Start); + let mut hashes = Vec::new(); + for item in iter { + let (key_bytes, value_bytes) = item.map_err(map_rocksdb_err)?; + let index = index_from_key_bytes(&key_bytes)?; + let hash = Word::read_from_bytes_with_budget(&value_bytes, value_bytes.len())?; + hashes.push((index, hash)); + } + Ok(hashes) + } +} + +impl RocksReadSource for RocksDbStorage { + fn db(&self) -> &DB { + &self.db + } + + fn get_cf_bytes( + &self, + cf: &rocksdb::ColumnFamily, + key: &[u8], + ) -> Result>, rocksdb::Error> { + self.db.get_cf(cf, key) + } + + fn multi_get_cf_bytes<'b, K, I, W>( + &self, + keys_cf: I, + ) -> Vec>, rocksdb::Error>> + where + K: AsRef<[u8]>, + I: IntoIterator, + W: rocksdb::AsColumnFamilyRef + 'b, + { + self.db.multi_get_cf(keys_cf) + } +} + +impl SmtStorageReader for RocksDbStorage { + fn leaf_count(&self) -> Result { + self.leaf_count_impl() + } fn entry_count(&self) -> Result { - let cf = self.cf_handle(METADATA_CF)?; - self.db - .get_cf(cf, ENTRY_COUNT_KEY) - .map_err(map_rocksdb_err)? - .map_or(Ok(0), |bytes| { - let arr: [u8; 8] = - bytes.as_slice().try_into().map_err(|_| StorageError::BadValueLen { - what: "entry count", - expected: 8, - found: bytes.len(), - })?; - Ok(usize::from_be_bytes(arr)) - }) + self.entry_count_impl() + } + fn get_leaf(&self, index: u64) -> Result, StorageError> { + self.get_leaf_impl(index) + } + fn get_leaves(&self, indices: &[u64]) -> Result>, StorageError> { + self.get_leaves_impl(indices) + } + fn has_leaves(&self) -> Result { + self.has_leaves_impl() + } + fn get_subtree(&self, index: NodeIndex) -> Result, StorageError> { + self.get_subtree_impl(index) + } + fn get_subtrees(&self, indices: &[NodeIndex]) -> Result>, StorageError> { + self.get_subtrees_impl(indices) + } + fn get_inner_node(&self, index: NodeIndex) -> Result, StorageError> { + self.get_inner_node_impl(index) + } + fn iter_leaves(&self) -> Result + '_>, StorageError> { + self.iter_leaves_impl() + } + fn iter_subtrees(&self) -> Result + '_>, StorageError> { + self.iter_subtrees_impl() + } + fn get_depth24(&self) -> Result, StorageError> { + self.get_depth24_impl() + } +} + +impl SmtStorage for RocksDbStorage { + type Reader = RocksDbSnapshotStorage; + + /// Returns a read-only point-in-time snapshot of the underlying RocksDB. + /// + /// Subsequent writes through `self` do not affect the returned reader. + fn reader(&self) -> Result { + Ok(RocksDbSnapshotStorage::new(self.db.clone())) } /// Inserts a key-value pair into the SMT leaf at the specified logical `index`. @@ -503,23 +737,6 @@ impl SmtStorage for RocksDbStorage { Ok(current_value) } - /// Retrieves a single SMT leaf node by its logical `index` from the `LEAVES_CF` column family. - /// - /// # Errors - /// - `StorageError::Backend`: If the leaves column family is missing or a RocksDB error occurs. - /// - `StorageError::DeserializationError`: If the retrieved leaf data is corrupt. - fn get_leaf(&self, index: u64) -> Result, StorageError> { - let cf = self.cf_handle(LEAVES_CF)?; - let key = Self::index_db_key(index); - match self.db.get_cf(cf, key).map_err(map_rocksdb_err)? { - Some(bytes) => { - let leaf = SmtLeaf::read_from_bytes_with_budget(&bytes, bytes.len())?; - Ok(Some(leaf)) - }, - None => Ok(None), - } - } - /// Sets or updates multiple SMT leaf nodes in the `LEAVES_CF` column family. /// /// This method performs a batch write to RocksDB. It also updates the global @@ -575,144 +792,6 @@ impl SmtStorage for RocksDbStorage { })) } - /// Retrieves multiple SMT leaf nodes by their logical `indices` using RocksDB's `multi_get_cf`. - /// - /// # Errors - /// - `StorageError::Backend`: If the leaves column family is missing or a RocksDB error occurs. - /// - `StorageError::DeserializationError`: If any retrieved leaf data is corrupt. - fn get_leaves(&self, indices: &[u64]) -> Result>, StorageError> { - let cf = self.cf_handle(LEAVES_CF)?; - let db_keys: Vec<[u8; 8]> = indices.iter().map(|&idx| Self::index_db_key(idx)).collect(); - let results = self.db.multi_get_cf(db_keys.iter().map(|k| (cf, k.as_ref()))); - - results - .into_iter() - .map(|result| match result { - Ok(Some(bytes)) => { - Ok(Some(SmtLeaf::read_from_bytes_with_budget(&bytes, bytes.len())?)) - }, - Ok(None) => Ok(None), - Err(e) => Err(map_rocksdb_err(e)), - }) - .collect() - } - - /// Returns true if the storage has any leaves. - /// - /// # Errors - /// Returns `StorageError` if the storage read operation fails. - fn has_leaves(&self) -> Result { - Ok(self.leaf_count()? > 0) - } - - /// Batch-retrieves multiple subtrees from RocksDB by their node indices. - /// - /// This method groups requests by subtree depth into column family buckets, - /// then performs parallel `multi_get` operations to efficiently retrieve - /// all subtrees. Results are deserialized and placed in the same order as - /// the input indices. - /// - /// Note: Retrieval is performed in parallel. If multiple errors occur (e.g., - /// deserialization or backend errors), only the first one encountered is returned. - /// Other errors will be discarded. - /// - /// # Parameters - /// - `indices`: A slice of subtree root indices to retrieve. - /// - /// # Returns - /// - A `Vec>` where each index corresponds to the original input. - /// - `Ok(...)` if all fetches succeed. - /// - `Err(StorageError)` if any RocksDB access or deserialization fails. - fn get_subtree(&self, index: NodeIndex) -> Result, StorageError> { - let cf = self.subtree_cf(index); - let key = Self::subtree_db_key(index); - match self.db.get_cf(cf, key).map_err(map_rocksdb_err)? { - Some(bytes) => { - let subtree = Subtree::from_vec(index, &bytes)?; - Ok(Some(subtree)) - }, - None => Ok(None), - } - } - - /// Batch-retrieves multiple subtrees from RocksDB by their node indices. - /// - /// This method groups requests by subtree depth into column family buckets, - /// then performs parallel `multi_get` operations to efficiently retrieve - /// all subtrees. Results are deserialized and placed in the same order as - /// the input indices. - /// - /// # Parameters - /// - `indices`: A slice of subtree root indices to retrieve. - /// - /// # Returns - /// - A `Vec>` where each index corresponds to the original input. - /// - `Ok(...)` if all fetches succeed. - /// - `Err(StorageError)` if any RocksDB access or deserialization fails. - fn get_subtrees(&self, indices: &[NodeIndex]) -> Result>, StorageError> { - use rayon::prelude::*; - - let mut depth_buckets: [Vec<(usize, NodeIndex)>; 5] = Default::default(); - - for (original_index, &node_index) in indices.iter().enumerate() { - let depth = node_index.depth(); - let bucket_index = match depth { - 56 => 0, - 48 => 1, - 40 => 2, - 32 => 3, - 24 => 4, - _ => { - return Err(StorageError::Unsupported(format!( - "unsupported subtree depth {depth}" - ))); - }, - }; - depth_buckets[bucket_index].push((original_index, node_index)); - } - let mut results = vec![None; indices.len()]; - - // Process depth buckets in parallel - let bucket_results: Result, StorageError> = depth_buckets - .into_par_iter() - .enumerate() - .filter(|(_, bucket)| !bucket.is_empty()) - .map( - |(bucket_index, bucket)| -> Result)>, StorageError> { - let depth = SUBTREE_DEPTHS[bucket_index]; - let cf = self.cf_handle(cf_for_depth(depth))?; - let keys: Vec<_> = - bucket.iter().map(|(_, idx)| Self::subtree_db_key(*idx)).collect(); - - let db_results = self.db.multi_get_cf(keys.iter().map(|k| (cf, k.as_ref()))); - - // Process results for this bucket - bucket - .into_iter() - .zip(db_results) - .map(|((original_index, node_index), db_result)| { - let subtree = match db_result { - Ok(Some(bytes)) => Some(Subtree::from_vec(node_index, &bytes)?), - Ok(None) => None, - Err(e) => return Err(map_rocksdb_err(e)), - }; - Ok((original_index, subtree)) - }) - .collect() - }, - ) - .collect(); - - // Flatten results and place them in correct positions - for bucket_result in bucket_results? { - for (original_index, subtree) in bucket_result { - results[original_index] = subtree; - } - } - - Ok(results) - } - /// Stores a single subtree in RocksDB and optionally updates the depth-24 root cache. /// /// The subtree is serialized and written to its corresponding column family. @@ -808,27 +887,6 @@ impl SmtStorage for RocksDbStorage { Ok(()) } - /// Retrieves a single inner node (non-leaf node) from within a Subtree. - /// - /// This method is intended for accessing nodes at depths greater than or equal to - /// `IN_MEMORY_DEPTH`. It first finds the appropriate Subtree containing the `index`, then - /// delegates to `Subtree::get_inner_node()`. - /// - /// # Errors - /// - `StorageError::Backend`: If `index.depth() < IN_MEMORY_DEPTH`, or if RocksDB errors occur. - /// - `StorageError::Value`: If the containing Subtree data is corrupt. - fn get_inner_node(&self, index: NodeIndex) -> Result, StorageError> { - if index.depth() < IN_MEMORY_DEPTH { - return Err(StorageError::Unsupported( - "Cannot get inner node from upper part of the tree".into(), - )); - } - let subtree_root_index = Subtree::find_subtree_root(index); - Ok(self - .get_subtree(subtree_root_index)? - .and_then(|subtree| subtree.get_inner_node(index))) - } - /// Sets or updates a single inner node (non-leaf node) within a Subtree. /// /// This method is intended for `index.depth() >= IN_MEMORY_DEPTH`. @@ -995,70 +1053,6 @@ impl SmtStorage for RocksDbStorage { Ok(()) } - - /// Returns an iterator over all (logical u64 index, `SmtLeaf`) pairs in the `LEAVES_CF`. - /// - /// The iterator uses a RocksDB snapshot for consistency and iterates in lexicographical - /// order of the keys (leaf indices). Errors during iteration (e.g., deserialization issues) - /// cause the iterator to skip the problematic item and attempt to continue. - /// - /// # Errors - /// - `StorageError::Backend`: If the leaves column family is missing or a RocksDB error occurs - /// during iterator creation. - fn iter_leaves(&self) -> Result + '_>, StorageError> { - let cf = self.cf_handle(LEAVES_CF)?; - let mut read_opts = ReadOptions::default(); - read_opts.set_total_order_seek(true); - let db_iter = self.db.iterator_cf_opt(cf, read_opts, IteratorMode::Start); - - Ok(Box::new(RocksDbDirectLeafIterator { iter: db_iter })) - } - - /// Returns an iterator over all `Subtree` instances across all subtree column families. - /// - /// The iterator uses a RocksDB snapshot and iterates in lexicographical order of keys - /// (subtree root NodeIndex) across all depth column families (24, 32, 40, 48, 56). - /// Errors during iteration (e.g., deserialization issues) cause the iterator to skip - /// the problematic item and attempt to continue. - /// - /// # Errors - /// - `StorageError::Backend`: If any subtree column family is missing or a RocksDB error occurs - /// during iterator creation. - fn iter_subtrees(&self) -> Result + '_>, StorageError> { - // All subtree column family names in order - const SUBTREE_CFS: [&str; 5] = - [SUBTREE_24_CF, SUBTREE_32_CF, SUBTREE_40_CF, SUBTREE_48_CF, SUBTREE_56_CF]; - - let mut cf_handles = Vec::new(); - for cf_name in SUBTREE_CFS { - cf_handles.push(self.cf_handle(cf_name)?); - } - - Ok(Box::new(RocksDbSubtreeIterator::new(&self.db, cf_handles))) - } - - /// Retrieves all depth 24 hashes for fast tree rebuilding. - /// - /// # Errors - /// - `StorageError::Backend`: If the depth24 column family is missing or a RocksDB error - /// occurs. - /// - `StorageError::Value`: If any hash bytes are corrupt. - fn get_depth24(&self) -> Result, StorageError> { - let cf = self.cf_handle(DEPTH_24_CF)?; - let iter = self.db.iterator_cf(cf, IteratorMode::Start); - let mut hashes = Vec::new(); - - for item in iter { - let (key_bytes, value_bytes) = item.map_err(map_rocksdb_err)?; - - let index = index_from_key_bytes(&key_bytes)?; - let hash = Word::read_from_bytes_with_budget(&value_bytes, value_bytes.len())?; - - hashes.push((index, hash)); - } - - Ok(hashes) - } } /// Syncs the RocksDB database to disk before dropping the storage. @@ -1105,18 +1099,28 @@ impl Iterator for RocksDbDirectLeafIterator<'_> { /// /// Iterates through all subtree column families (24, 32, 40, 48, 56) sequentially. /// When one column family is exhausted, it moves to the next one. +/// +/// The `configure` hook is called whenever a new per-CF iterator is built; it is used by the +/// snapshot-backed source to attach its `Snapshot` to the per-CF `ReadOptions`, and is a no-op for +/// the live-DB source. struct RocksDbSubtreeIterator<'a> { db: &'a DB, cf_handles: Vec<&'a rocksdb::ColumnFamily>, + configure: Box, current_cf_index: usize, current_iter: Option>, } impl<'a> RocksDbSubtreeIterator<'a> { - fn new(db: &'a DB, cf_handles: Vec<&'a rocksdb::ColumnFamily>) -> Self { + fn new( + db: &'a DB, + cf_handles: Vec<&'a rocksdb::ColumnFamily>, + configure: F, + ) -> Self { let mut iterator = Self { db, cf_handles, + configure: Box::new(configure), current_cf_index: 0, current_iter: None, }; @@ -1129,6 +1133,7 @@ impl<'a> RocksDbSubtreeIterator<'a> { let cf = self.cf_handles[self.current_cf_index]; let mut read_opts = ReadOptions::default(); read_opts.set_total_order_seek(true); + (self.configure)(&mut read_opts); self.current_iter = Some(self.db.iterator_cf_opt(cf, read_opts, IteratorMode::Start)); } else { self.current_iter = None; @@ -1596,3 +1601,138 @@ fn cf_for_depth(depth: u8) -> &'static str { _ => panic!("unsupported subtree depth: {depth}"), } } + +// SNAPSHOT STORAGE +// -------------------------------------------------------------------------------------------- + +/// Read-only point-in-time view over [`RocksDbStorage`]. +/// +/// Wraps a `rocksdb::Snapshot` together with the `Arc` it borrows from so the snapshot +/// stays valid for the lifetime of this value. Used as [`SmtStorage::Reader`]. +pub struct RocksDbSnapshotStorage { + inner: Arc, +} + +impl std::fmt::Debug for RocksDbSnapshotStorage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RocksDbSnapshotStorage").finish_non_exhaustive() + } +} + +/// Owns a RocksDB snapshot alongside the database it borrows from. +/// +/// `rocksdb::Snapshot<'a>` borrows the DB used to create it, but `SmtStorage::Reader` must be +/// `'static`, so we store the `Arc` next to the snapshot and rely on field-drop ordering +/// (snapshot first via `ManuallyDrop`, then the `Arc`) to keep the borrow valid. +/// +/// This mirrors the `RocksDbSnapshotInner` defined upstream in +/// `miden_crypto::merkle::smt::large::storage::rocksdb` (≥0.25); see that module for the same +/// SAFETY rationale. +struct RocksDbSnapshotInner { + snapshot: ManuallyDrop>, + db: Arc, +} + +impl RocksDbSnapshotInner { + fn new(db: Arc) -> Self { + let snapshot = db.snapshot(); + // SAFETY: The snapshot internally borrows the `DB` allocation owned by `db`. This struct + // keeps that `Arc` alive and its `Drop` impl releases the snapshot before the `Arc` is + // dropped by Rust's normal field cleanup, so the borrow stays valid for the snapshot's + // entire lifetime. Mirrors the identical pattern used upstream in + // `miden_crypto::merkle::smt::large::storage::rocksdb::RocksDbSnapshotInner`. + let snapshot = unsafe { + core::mem::transmute::, rocksdb::Snapshot<'static>>(snapshot) + }; + Self { + snapshot: ManuallyDrop::new(snapshot), + db, + } + } +} + +impl Drop for RocksDbSnapshotInner { + fn drop(&mut self) { + // SAFETY: `snapshot` is only wrapped in `ManuallyDrop` to control field-drop order. We drop + // it exactly once here, before `db` is dropped by Rust's normal field cleanup. Mirrors the + // upstream `RocksDbSnapshotInner::drop` in `miden_crypto`. + unsafe { + ManuallyDrop::drop(&mut self.snapshot); + } + } +} + +impl RocksDbSnapshotStorage { + /// Builds a snapshot-backed reader from a shared RocksDB handle. + pub fn new(db: Arc) -> Self { + Self { + inner: Arc::new(RocksDbSnapshotInner::new(db)), + } + } +} + +impl RocksReadSource for RocksDbSnapshotStorage { + fn db(&self) -> &DB { + &self.inner.db + } + + fn get_cf_bytes( + &self, + cf: &rocksdb::ColumnFamily, + key: &[u8], + ) -> Result>, rocksdb::Error> { + self.inner.snapshot.get_cf(cf, key) + } + + fn multi_get_cf_bytes<'b, K, I, W>( + &self, + keys_cf: I, + ) -> Vec>, rocksdb::Error>> + where + K: AsRef<[u8]>, + I: IntoIterator, + W: rocksdb::AsColumnFamilyRef + 'b, + { + self.inner.snapshot.multi_get_cf(keys_cf) + } + + fn apply_snapshot(&self, read_opts: &mut ReadOptions) { + read_opts.set_snapshot(&self.inner.snapshot); + } +} + +impl SmtStorageReader for RocksDbSnapshotStorage { + fn leaf_count(&self) -> Result { + self.leaf_count_impl() + } + fn entry_count(&self) -> Result { + self.entry_count_impl() + } + fn get_leaf(&self, index: u64) -> Result, StorageError> { + self.get_leaf_impl(index) + } + fn get_leaves(&self, indices: &[u64]) -> Result>, StorageError> { + self.get_leaves_impl(indices) + } + fn has_leaves(&self) -> Result { + self.has_leaves_impl() + } + fn get_subtree(&self, index: NodeIndex) -> Result, StorageError> { + self.get_subtree_impl(index) + } + fn get_subtrees(&self, indices: &[NodeIndex]) -> Result>, StorageError> { + self.get_subtrees_impl(indices) + } + fn get_inner_node(&self, index: NodeIndex) -> Result, StorageError> { + self.get_inner_node_impl(index) + } + fn iter_leaves(&self) -> Result + '_>, StorageError> { + self.iter_leaves_impl() + } + fn iter_subtrees(&self) -> Result + '_>, StorageError> { + self.iter_subtrees_impl() + } + fn get_depth24(&self) -> Result, StorageError> { + self.get_depth24_impl() + } +} diff --git a/crates/proto/src/domain/account.rs b/crates/proto/src/domain/account.rs index 33e31b8526..fe59b189ae 100644 --- a/crates/proto/src/domain/account.rs +++ b/crates/proto/src/domain/account.rs @@ -992,6 +992,26 @@ impl std::fmt::Display for NetworkAccountId { } impl NetworkAccountId { + /// Wraps an `AccountId` known by the caller to belong to a network account. + /// + /// # Preconditions + /// + /// Callers must ensure that: + /// - The account's storage contains a valid `NetworkAccountNoteAllowlist` slot + /// (verified e.g. via `miden_standards::account::auth::network_account::NetworkAccount` + /// or the store's `network_account_type` column). + /// - The account ID has public storage mode (network accounts cannot be private). + /// + /// The public-mode precondition is asserted in debug builds. + pub fn new_unchecked(id: AccountId) -> Self { + debug_assert!( + id.is_public(), + "NetworkAccountId requires a public AccountId, got account type {:?}", + id.account_type() + ); + Self(id) + } + /// Returns the inner `AccountId`. pub fn inner(&self) -> AccountId { self.0 @@ -1003,17 +1023,6 @@ impl NetworkAccountId { } } -impl TryFrom for NetworkAccountId { - type Error = NetworkAccountError; - - fn try_from(id: AccountId) -> Result { - if !id.is_network() { - return Err(NetworkAccountError::NotNetworkAccount(id)); - } - Ok(NetworkAccountId(id)) - } -} - impl TryFrom<&NoteAttachment> for NetworkAccountId { type Error = NetworkAccountError; diff --git a/crates/proto/src/domain/block.rs b/crates/proto/src/domain/block.rs index 36d37cbb01..a6b6e364e8 100644 --- a/crates/proto/src/domain/block.rs +++ b/crates/proto/src/domain/block.rs @@ -312,7 +312,7 @@ impl TryFrom for FeeParameters { "native_asset_id", ))? .context("native_asset_id")?; - let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee)?; + let fee_params = FeeParameters::new(native_asset_id, fee_params.verification_base_fee); Ok(fee_params) } } diff --git a/crates/proto/src/domain/digest.rs b/crates/proto/src/domain/digest.rs index fd7640ae5b..043d458f61 100644 --- a/crates/proto/src/domain/digest.rs +++ b/crates/proto/src/domain/digest.rs @@ -179,10 +179,10 @@ impl TryFrom for [Felt; 4] { } Ok([ - Felt::new(value.d0), - Felt::new(value.d1), - Felt::new(value.d2), - Felt::new(value.d3), + Felt::new_unchecked(value.d0), + Felt::new_unchecked(value.d1), + Felt::new_unchecked(value.d2), + Felt::new_unchecked(value.d3), ]) } } diff --git a/crates/proto/src/domain/merkle.rs b/crates/proto/src/domain/merkle.rs index a4d3119769..1dbbd6e9ff 100644 --- a/crates/proto/src/domain/merkle.rs +++ b/crates/proto/src/domain/merkle.rs @@ -94,7 +94,7 @@ impl TryFrom for MmrDelta { .context("data")?; Ok(MmrDelta { - forest: Forest::new(value.forest as usize), + forest: Forest::new(value.forest as usize).context("forest")?, data, }) } diff --git a/crates/proto/src/domain/note.rs b/crates/proto/src/domain/note.rs index 5d8a51f0cf..5d970de1d0 100644 --- a/crates/proto/src/domain/note.rs +++ b/crates/proto/src/domain/note.rs @@ -7,6 +7,7 @@ use miden_protocol::note::{ NoteAttachmentScheme, NoteAttachments, NoteDetails, + NoteDetailsCommitment, NoteHeader, NoteId, NoteInclusionProof, @@ -269,10 +270,13 @@ impl TryFrom for NoteHeader { fn try_from(value: proto::note::NoteHeader) -> Result { let decoder = value.decoder(); - let note_id_word: Word = decode!(decoder, value.note_id)?; + let note_details_commitment: Word = decode!(decoder, value.note_id)?; let metadata: NoteMetadata = decode!(decoder, value.metadata)?; - Ok(NoteHeader::new(NoteId::from_raw(note_id_word), metadata)) + Ok(NoteHeader::new( + NoteDetailsCommitment::from_raw(note_details_commitment), + metadata, + )) } } diff --git a/crates/proto/src/errors/mod.rs b/crates/proto/src/errors/mod.rs index 9bf21e9810..5eb37713a0 100644 --- a/crates/proto/src/errors/mod.rs +++ b/crates/proto/src/errors/mod.rs @@ -193,7 +193,6 @@ impl_from_for_conversion_error!( miden_protocol::errors::AccountError, miden_protocol::errors::AssetError, miden_protocol::errors::AssetVaultError, - miden_protocol::errors::FeeError, miden_protocol::errors::NoteError, miden_protocol::errors::StorageSlotNameError, miden_protocol::crypto::merkle::MerkleError, diff --git a/crates/rpc/src/server/api.rs b/crates/rpc/src/server/api.rs index ed2d5b7b2f..65fb4fb66a 100644 --- a/crates/rpc/src/server/api.rs +++ b/crates/rpc/src/server/api.rs @@ -490,13 +490,29 @@ impl api_server::Api for RpcService { let mut request = request; request.transaction = rebuilt_tx.to_bytes(); - // Only allow deployment transactions for new network accounts - if tx.account_id().is_network() - && !tx.account_update().initial_state_commitment().is_empty() + // Block post-deployment network-account transactions from user RPC. First-deployment txs + // are allowed because the protocol-level allowlist only kicks in once the account exists. + // For non-deployment txs, ask the store whether the account is classified as a network + // account; the store is the source of truth because network-ness now lives in account + // storage and isn't derivable from an AccountId alone. Network accounts must be public, so + // private-account txs short-circuit and skip the store roundtrip. + if !tx.account_update().initial_state_commitment().is_empty() && tx.account_id().is_public() { - return Err(Status::invalid_argument( - "Network transactions may not be submitted by users yet", - )); + let response = self + .store + .clone() + .are_network_accounts(tonic::Request::new(proto::account::AccountIdList { + account_ids: vec![tx.account_id().into()], + })) + .await + .map_err(|err| { + Status::internal(format!("network-account classification failed: {err}")) + })?; + if !response.into_inner().network_account_ids.is_empty() { + return Err(Status::invalid_argument( + "Network transactions may not be submitted by users yet", + )); + } } let tx_verifier = TransactionVerifier::new(MIN_PROOF_SECURITY_LEVEL); @@ -571,11 +587,32 @@ impl api_server::Api for RpcService { ))); } - // Only allow deployment transactions for new network accounts. - for tx in proposed_batch.transactions() { - if tx.account_id().is_network() - && !tx.account_update().initial_state_commitment().is_empty() - { + // Same gate as `submit_proven_transaction`, applied to every post-deployment tx in the + // batch. One store round-trip filters the non-deployment account ids; any match fails the + // entire batch (matches the original loop semantics). Network accounts must be public, so + // private-account txs are excluded up front to skip the store roundtrip when possible. + let non_deployment_ids: Vec<_> = proposed_batch + .transactions() + .iter() + .filter(|tx| { + !tx.account_update().initial_state_commitment().is_empty() + && tx.account_id().is_public() + }) + .map(|tx| proto::account::AccountId::from(tx.account_id())) + .collect(); + + if !non_deployment_ids.is_empty() { + let response = self + .store + .clone() + .are_network_accounts(tonic::Request::new(proto::account::AccountIdList { + account_ids: non_deployment_ids, + })) + .await + .map_err(|err| { + Status::internal(format!("network-account classification failed: {err}")) + })?; + if !response.into_inner().network_account_ids.is_empty() { return Err(Status::invalid_argument( "Network transactions may not be submitted by users yet", )); diff --git a/crates/rpc/src/tests.rs b/crates/rpc/src/tests.rs index cf47b5d356..f4e8b7e42e 100644 --- a/crates/rpc/src/tests.rs +++ b/crates/rpc/src/tests.rs @@ -27,10 +27,9 @@ use miden_protocol::account::{ AccountDelta, AccountId, AccountIdVersion, - AccountStorageMode, AccountType, }; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::testing::noop_auth_component::NoopAuthComponent; use miden_protocol::transaction::{ProvenTransaction, TxAccountUpdate}; use miden_protocol::utils::serde::Serializable; @@ -64,6 +63,10 @@ impl TestStore { self.store_addr } + fn data_directory_path(&self) -> &std::path::Path { + self.data_directory.as_ref().expect("data_directory should be set").path() + } + async fn shutdown(mut self) -> TempDir { if let Some(runtime) = self.runtime.take() { shutdown_store_runtime(runtime).await; @@ -79,7 +82,7 @@ impl TestStore { fn bootstrap(path: &std::path::Path) -> Word { let config = GenesisConfig::default(); - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (genesis_state, _) = config.into_state(signer.public_key()).unwrap(); let genesis_block = genesis_state .clone() @@ -172,8 +175,7 @@ const REQUEST_TIMEOUT: Duration = Duration::from_secs(5); /// Creates a minimal account and its delta for testing proven transaction building. fn build_test_account(seed: [u8; 32]) -> (Account, AccountDelta) { let account = AccountBuilder::new(seed) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_assets(vec![]) .with_component(BasicWallet) .with_auth_component(NoopAuthComponent) @@ -193,12 +195,7 @@ fn build_test_proven_tx( delta: &AccountDelta, genesis: Word, ) -> ProvenTransaction { - let account_id = AccountId::dummy( - [0; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - ); + let account_id = AccountId::dummy([0; 15], AccountIdVersion::Version1, AccountType::Public); let account_update = TxAccountUpdate::new( account_id, @@ -222,6 +219,36 @@ fn build_test_proven_tx( .unwrap() } +/// Same as `build_test_proven_tx` but lets the caller supply the `AccountId`. Uses a non-empty +/// `initial_state_commitment` so the result is a post-deployment tx. +fn build_test_proven_tx_with_id( + account_id: AccountId, + account: &Account, + delta: &AccountDelta, + genesis: Word, +) -> ProvenTransaction { + let account_update = TxAccountUpdate::new( + account_id, + [8; 32].try_into().unwrap(), + account.to_commitment(), + delta.to_commitment(), + AccountUpdateDetails::Delta(delta.clone()), + ) + .unwrap(); + + ProvenTransaction::new( + account_update, + Vec::::new(), + Vec::::new(), + 0.into(), + genesis, + test_fee(), + u32::MAX.into(), + ExecutionProof::new_dummy(), + ) + .unwrap() +} + #[tokio::test] async fn rpc_server_accepts_requests_without_accept_header() { // Start the RPC. @@ -493,6 +520,58 @@ async fn rpc_server_rejects_proven_transactions_with_invalid_reference_block() { ); } +#[tokio::test] +async fn rpc_rejects_post_deployment_network_account_tx() { + let (_, rpc_addr, store_listener) = start_rpc().await; + let store = TestStore::start(store_listener).await; + let genesis = store.genesis_commitment(); + + // Wait for the store to be ready before sending requests. + tokio::time::sleep(Duration::from_millis(100)).await; + + // Build a client that advertises the right `application/vnd.miden` content type so + // tx-submission requests reach the handler. + let mut rpc_client = + miden_node_proto::clients::Builder::new(Url::parse(&format!("http://{rpc_addr}")).unwrap()) + .without_tls() + .with_timeout(Duration::from_secs(5)) + .without_metadata_version() + .with_metadata_genesis(genesis.to_hex()) + .without_otel_context_injection() + .connect_lazy::(); + + // Seed a row marking a known AccountId as a network account directly in the store's SQLite DB. + // The store uses WAL mode so a secondary connection is safe. + let network_account_id = + AccountId::dummy([7u8; 15], AccountIdVersion::Version1, AccountType::Public); + miden_node_store::test_support::seed_network_account( + &store.data_directory_path().join("miden-store.sqlite3"), + network_account_id, + ); + + // Build a non-deployment tx for that account. + let (account, account_delta) = build_test_account([0; 32]); + let tx = build_test_proven_tx_with_id(network_account_id, &account, &account_delta, genesis); + let request = proto::transaction::ProvenTransaction { + transaction: tx.to_bytes(), + transaction_inputs: None, + }; + + let response = rpc_client.submit_proven_tx(request).await; + assert!(response.is_err()); + let err = response.as_ref().unwrap_err().message(); + assert!( + err.contains("Network transactions may not be submitted by users yet"), + "expected the network-tx gate error, got: {err}" + ); +} + +// Batch-path coverage for the network-account gate is provided manually. Building a valid +// `ProposedBatch` + `ProvenBatch` in this test harness would require duplicating LocalBatchProver +// setup. The query layer is covered by the unit test in store::db::tests, and the gRPC wiring is +// the same as `submit_proven_transaction` (covered by +// `rpc_rejects_post_deployment_network_account_tx`). + #[tokio::test] async fn rpc_server_rejects_tx_submissions_without_genesis() { // Start the RPC. @@ -717,25 +796,26 @@ fn sync_chain_mmr_block_header_matches_chain_commitment() { for i in 0..5u32 { let chain_commitment = server_mmr.peaks().hash_peaks(); let header = BlockHeader::mock(i, Some(chain_commitment), None, &[], Word::default()); - server_mmr.add(header.commitment()); + server_mmr.add(header.commitment()).unwrap(); headers.push(header); } // Client bootstraps with genesis. - let mut client_mmr = PartialMmr::from_peaks(MmrPeaks::new(Forest::new(0), vec![]).unwrap()); - client_mmr.add(headers[0].commitment(), false); + let mut client_mmr = + PartialMmr::from_peaks(MmrPeaks::new(Forest::new(0).unwrap(), vec![]).unwrap()); + client_mmr.add(headers[0].commitment(), false).unwrap(); // First delta: block_from=0, block_to=2, so from_forest=1, to_forest=2. - let delta = server_mmr.get_delta(Forest::new(1), Forest::new(2)).unwrap(); + let delta = server_mmr.get_delta(Forest::new(1).unwrap(), Forest::new(2).unwrap()).unwrap(); client_mmr.apply(delta).unwrap(); assert_eq!(client_mmr.peaks().hash_peaks(), headers[2].chain_commitment()); - client_mmr.add(headers[2].commitment(), false); + client_mmr.add(headers[2].commitment(), false).unwrap(); // Second delta: block_from=2, block_to=4, so from_forest=3, to_forest=4. - let delta = server_mmr.get_delta(Forest::new(3), Forest::new(4)).unwrap(); + let delta = server_mmr.get_delta(Forest::new(3).unwrap(), Forest::new(4).unwrap()).unwrap(); client_mmr.apply(delta).unwrap(); assert_eq!(client_mmr.peaks().hash_peaks(), headers[4].chain_commitment()); - client_mmr.add(headers[4].commitment(), false); + client_mmr.add(headers[4].commitment(), false).unwrap(); assert_eq!(client_mmr.peaks().hash_peaks(), server_mmr.peaks().hash_peaks()); } diff --git a/crates/store/Cargo.toml b/crates/store/Cargo.toml index 44edd5357f..0ab2168ce4 100644 --- a/crates/store/Cargo.toml +++ b/crates/store/Cargo.toml @@ -66,6 +66,7 @@ miden-standards = { workspace = true } assert_matches = { workspace = true } criterion = "0.8" fs-err = { workspace = true } +miden-agglayer = { features = ["testing"], workspace = true } miden-node-test-macro = { workspace = true } miden-node-utils = { features = ["testing", "tracing-forest"], workspace = true } miden-protocol = { default-features = true, features = ["testing"], workspace = true } diff --git a/crates/store/build.rs b/crates/store/build.rs index 5df52daa5b..95c87860be 100644 --- a/crates/store/build.rs +++ b/crates/store/build.rs @@ -1,14 +1,9 @@ use std::path::PathBuf; use std::sync::Arc; -use miden_agglayer::{ - EthAddress, - MetadataHash, - create_existing_agglayer_faucet, - create_existing_bridge_account, -}; +use miden_agglayer::{create_existing_agglayer_faucet, create_existing_bridge_account}; use miden_protocol::account::auth::AuthScheme; -use miden_protocol::account::{Account, AccountCode, AccountFile, AccountStorageMode, AccountType}; +use miden_protocol::account::{Account, AccountCode, AccountFile, AccountType}; use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey; use miden_protocol::crypto::rand::RandomCoin; use miden_protocol::{Felt, Word}; @@ -45,24 +40,23 @@ fn generate_agglayer_sample_accounts() { fs_err::create_dir_all(&samples_dir).expect("Failed to create samples directory"); // Use deterministic seeds for reproducible builds. WARNING: DO NOT USE THESE IN PRODUCTION - let bridge_seed: Word = Word::new([Felt::new(1u64); 4]); - let eth_faucet_seed: Word = Word::new([Felt::new(2u64); 4]); - let usdc_faucet_seed: Word = Word::new([Felt::new(3u64); 4]); + let bridge_seed: Word = Word::new([Felt::new_unchecked(1u64); 4]); + let eth_faucet_seed: Word = Word::new([Felt::new_unchecked(2u64); 4]); + let usdc_faucet_seed: Word = Word::new([Felt::new_unchecked(3u64); 4]); // Create bridge admin and GER manager as proper wallet accounts. WARNING: DO NOT USE THESE IN // PRODUCTION let bridge_admin_key = - SecretKey::with_rng(&mut RandomCoin::new(Word::new([Felt::new(4u64); 4]))); + SecretKey::with_rng(&mut RandomCoin::new(Word::new([Felt::new_unchecked(4u64); 4]))); let ger_manager_key = - SecretKey::with_rng(&mut RandomCoin::new(Word::new([Felt::new(5u64); 4]))); + SecretKey::with_rng(&mut RandomCoin::new(Word::new([Felt::new_unchecked(5u64); 4]))); let bridge_admin = create_basic_wallet( [4u8; 32], AuthMethod::SingleSig { approver: (bridge_admin_key.public_key().into(), AuthScheme::Falcon512Poseidon2), }, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, + AccountType::Public, ) .expect("bridge admin account should be valid"); @@ -71,8 +65,7 @@ fn generate_agglayer_sample_accounts() { AuthMethod::SingleSig { approver: (ger_manager_key.public_key().into(), AuthScheme::Falcon512Poseidon2), }, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, + AccountType::Public, ) .expect("GER manager account should be valid"); @@ -85,24 +78,15 @@ fn generate_agglayer_sample_accounts() { create_existing_bridge_account(bridge_seed, bridge_admin_id, ger_manager_id); let bridge_account_id = bridge_account.id(); - // Placeholder Ethereum addresses for sample faucets. WARNING: DO NOT USE THESE ADDRESSES IN - // PRODUCTION - let eth_origin_address = EthAddress::new([1u8; 20]); - let usdc_origin_address = EthAddress::new([2u8; 20]); - // Create AggLayer faucets using "existing" variant ETH: 8 decimals (protocol max is 12), max // supply of 1 billion tokens let eth_faucet = create_existing_agglayer_faucet( eth_faucet_seed, "ETH", 8, - Felt::new(1_000_000_000), - Felt::new(0), + Felt::new_unchecked(1_000_000_000), + Felt::new_unchecked(0), bridge_account_id, - ð_origin_address, - 0u32, - 10u8, - MetadataHash::from_token_info("Ether", "ETH", 8), ); // USDC: 6 decimals, max supply of 10 billion tokens @@ -110,13 +94,9 @@ fn generate_agglayer_sample_accounts() { usdc_faucet_seed, "USDC", 6, - Felt::new(10_000_000_000), - Felt::new(0), + Felt::new_unchecked(10_000_000_000), + Felt::new_unchecked(0), bridge_account_id, - &usdc_origin_address, - 0u32, - 10u8, - MetadataHash::from_token_info("USD Coin", "USDC", 6), ); // Strip source location decorators from account code to ensure deterministic output. diff --git a/crates/store/src/account_state_forest/mod.rs b/crates/store/src/account_state_forest/mod.rs index fdd63f682c..3d793fa71a 100644 --- a/crates/store/src/account_state_forest/mod.rs +++ b/crates/store/src/account_state_forest/mod.rs @@ -356,7 +356,10 @@ impl AccountStateForest { let entries = self.forest.entries(tree).map_err(Self::map_forest_error_to_witness)?; let assets = entries .take(AccountVaultDetails::MAX_RETURN_ENTRIES + 1) - .map(|entry| Asset::from_key_value_words(entry.key, entry.value)) + .map(|entry| { + let entry = entry.map_err(Self::map_forest_error_to_witness)?; + Asset::from_key_value_words(entry.key, entry.value).map_err(WitnessError::from) + }) .collect::, _>>()?; Ok(AccountVaultDetails::from_assets(assets)) @@ -402,12 +405,12 @@ impl AccountStateForest { let lineage = Self::storage_lineage_id(account_id, slot_name); let tree = self.get_tree_id(lineage, block_num)?; - Some( - self.forest - .entries(tree) - .map_err(Self::map_forest_error) - .map(|entries| entries.take(limit).map(|entry| (entry.key, entry.value)).collect()), - ) + Some(self.forest.entries(tree).map_err(Self::map_forest_error).and_then(|entries| { + entries + .take(limit) + .map(|entry| entry.map(|e| (e.key, e.value)).map_err(Self::map_forest_error)) + .collect() + })) } /// Returns all storage map entries when the forest and reverse-key cache contain enough data. @@ -725,7 +728,7 @@ impl AccountStateForest { asset.add(delta)? }; - let value = if updated.amount() == 0 { + let value = if updated.amount().as_u64() == 0 { EMPTY_WORD } else { updated.to_value_word() diff --git a/crates/store/src/account_state_forest/tests.rs b/crates/store/src/account_state_forest/tests.rs index eb63f56fdd..a1f84e2372 100644 --- a/crates/store/src/account_state_forest/tests.rs +++ b/crates/store/src/account_state_forest/tests.rs @@ -1,7 +1,7 @@ use assert_matches::assert_matches; use miden_node_proto::domain::account::{AccountVaultDetails, StorageMapEntries}; use miden_protocol::Felt; -use miden_protocol::account::{AccountCode, AccountStorageMode, AccountType, StorageMapKey}; +use miden_protocol::account::{AccountCode, AccountType, StorageMapKey}; use miden_protocol::asset::{ Asset, AssetVault, @@ -199,14 +199,12 @@ fn vault_details_limit_exceeded_for_large_vault() { let block_num = BlockNumber::GENESIS.child(); let faucet_id = AccountIdBuilder::new() - .account_type(AccountType::NonFungibleFaucet) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .build_with_seed([7; 32]); let assets = (0..=AccountVaultDetails::MAX_RETURN_ENTRIES) .map(|i| { - let details = - NonFungibleAssetDetails::new(faucet_id, vec![i as u8, (i >> 8) as u8]).unwrap(); - Asset::NonFungible(NonFungibleAsset::new(&details).unwrap()) + let details = NonFungibleAssetDetails::new(faucet_id, vec![i as u8, (i >> 8) as u8]); + Asset::NonFungible(NonFungibleAsset::new(&details)) }) .collect::>(); @@ -582,7 +580,6 @@ fn storage_map_empty_entries_query() { use miden_protocol::account::{ AccountBuilder, AccountComponent, - AccountStorageMode, AccountType, StorageMap, StorageSlot, @@ -603,13 +600,12 @@ fn storage_map_empty_entries_query() { let account_component = AccountComponent::new( component_code, component_storage, - AccountComponentMetadata::new("test", AccountType::all()), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([1u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), diff --git a/crates/store/src/db/migrations.rs b/crates/store/src/db/migrations.rs index c9ee04ebff..bd85c69a66 100644 --- a/crates/store/src/db/migrations.rs +++ b/crates/store/src/db/migrations.rs @@ -50,7 +50,7 @@ mod tests { use super::*; const EXPECTED_SCHEMA_HASHES: [SchemaHash; 1] = [SchemaHash::from_hex( - "b3bdd2e530fbb66c9146cda9f3bf79df49c6a6bf99f7432aae0a8927a15406ac", + "747f3ba3e26ca1e80b37b2e2e1f52c40a225adbcacd319679a6e99a3330e7935", )]; #[test] diff --git a/crates/store/src/db/migrations/001_initial.sql b/crates/store/src/db/migrations/001_initial.sql index f0b4a8fb89..9bf66735f0 100644 --- a/crates/store/src/db/migrations/001_initial.sql +++ b/crates/store/src/db/migrations/001_initial.sql @@ -55,8 +55,8 @@ CREATE TABLE notes ( committed_at BIGINT NOT NULL, -- Block number when the note was committed batch_index INTEGER NOT NULL, -- Index of batch in block, starting from 0 note_index INTEGER NOT NULL, -- Index of note in batch, starting from 0 + details_commitment BLOB NOT NULL, note_id BLOB NOT NULL, - note_commitment BLOB NOT NULL, note_type INTEGER NOT NULL, -- 0-Private, 1-Public sender BLOB NOT NULL, tag INTEGER NOT NULL, @@ -79,8 +79,8 @@ CREATE TABLE notes ( CONSTRAINT notes_note_index_is_u32 CHECK (note_index BETWEEN 0 AND 0xFFFFFFFF) ); +CREATE INDEX idx_notes_details_commitment ON notes(details_commitment); CREATE INDEX idx_notes_note_id ON notes(note_id); -CREATE INDEX idx_notes_note_commitment ON notes(note_commitment); CREATE INDEX idx_notes_sender ON notes(sender, committed_at); CREATE INDEX idx_notes_tag ON notes(tag, committed_at); CREATE INDEX idx_notes_nullifier ON notes(nullifier); diff --git a/crates/store/src/db/mod.rs b/crates/store/src/db/mod.rs index 306f4a0e16..ab36a1ef99 100644 --- a/crates/store/src/db/mod.rs +++ b/crates/store/src/db/mod.rs @@ -143,7 +143,7 @@ impl TransactionRecord { .output_note_proofs .into_iter() .map(|n| proto::note::NoteInclusionInBlockProof { - note_id: Some(n.note_id.into()), + note_id: Some((&n.note_id).into()), block_num: n.block_num.as_u32(), note_index_in_block: n.note_index.leaf_index_value().into(), inclusion_path: Some(n.inclusion_path.into()), @@ -170,8 +170,8 @@ impl TransactionRecord { pub struct NoteRecord { pub block_num: BlockNumber, pub note_index: BlockNoteIndex, + pub details_commitment: Word, pub note_id: Word, - pub note_commitment: Word, pub metadata: NoteMetadata, pub details: Option, pub attachments: NoteAttachments, @@ -205,7 +205,7 @@ pub struct NoteSyncUpdate { pub struct NoteSyncRecord { pub block_num: BlockNumber, pub note_index: BlockNoteIndex, - pub note_id: Word, + pub note_id: NoteId, pub metadata: NoteMetadata, pub inclusion_path: SparseMerklePath, } @@ -214,7 +214,7 @@ impl From for proto::note::NoteSyncRecord { fn from(note: NoteSyncRecord) -> Self { let metadata = Some(note.metadata.into()); let inclusion_proof = Some(proto::note::NoteInclusionInBlockProof { - note_id: Some(note.note_id.into()), + note_id: Some((¬e.note_id).into()), block_num: note.block_num.as_u32(), note_index_in_block: note.note_index.leaf_index_value().into(), inclusion_path: Some(note.inclusion_path.into()), @@ -228,7 +228,7 @@ impl From for NoteSyncRecord { Self { block_num: note.block_num, note_index: note.note_index, - note_id: note.note_id, + note_id: NoteId::from_raw(note.note_id), metadata: note.metadata, inclusion_path: note.inclusion_path, } @@ -452,6 +452,18 @@ impl Db { .await } + /// Returns the subset of the provided account IDs that currently classify as network accounts. + #[instrument(level = "debug", target = COMPONENT, skip_all, ret(level = "debug"), err)] + pub async fn select_network_accounts_subset( + &self, + account_ids: Vec, + ) -> Result> { + self.transact("Filter network accounts subset", move |conn| { + queries::select_network_accounts_subset(conn, &account_ids) + }) + .await + } + /// Returns network account IDs within the specified block range (based on account creation /// block). /// diff --git a/crates/store/src/db/models/conv.rs b/crates/store/src/db/models/conv.rs index 25b6047f9e..66524b307b 100644 --- a/crates/store/src/db/models/conv.rs +++ b/crates/store/src/db/models/conv.rs @@ -200,7 +200,7 @@ pub(crate) fn nullifier_prefix_to_raw_sql(prefix: u16) -> i32 { #[inline(always)] pub(crate) fn raw_sql_to_nonce(raw: i64) -> Felt { debug_assert!(raw >= 0); - Felt::new(raw as u64) + Felt::new_unchecked(raw as u64) } #[inline(always)] pub(crate) fn nonce_to_raw_sql(nonce: Felt) -> i64 { diff --git a/crates/store/src/db/models/queries/accounts.rs b/crates/store/src/db/models/queries/accounts.rs index c7b7464a1e..c433457c87 100644 --- a/crates/store/src/db/models/queries/accounts.rs +++ b/crates/store/src/db/models/queries/accounts.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::num::NonZeroUsize; use std::ops::RangeInclusive; @@ -20,7 +20,11 @@ use diesel::{ SqliteConnection, }; use miden_node_proto::domain::account::{AccountInfo, AccountSummary}; -use miden_node_utils::limiter::MAX_RESPONSE_PAYLOAD_BYTES; +use miden_node_utils::limiter::{ + MAX_RESPONSE_PAYLOAD_BYTES, + QueryParamAccountIdLimit, + QueryParamLimiter, +}; use miden_protocol::account::delta::AccountUpdateDetails; use miden_protocol::account::{ Account, @@ -40,6 +44,7 @@ use miden_protocol::asset::{Asset, AssetVault, AssetVaultKey, FungibleAsset}; use miden_protocol::block::{BlockAccountUpdate, BlockNumber}; use miden_protocol::utils::serde::{Deserializable, Serializable}; use miden_protocol::{Felt, Word}; +use miden_standards::account::auth::NetworkAccount; use crate::COMPONENT; use crate::db::models::conv::{SqlTypeConvert, nonce_to_raw_sql, raw_sql_to_nonce}; @@ -149,7 +154,7 @@ pub(crate) fn select_account( // Backfill account details from database For private accounts, we don't store full details in // the database - let details = if account_id.has_public_state() { + let details = if account_id.is_public() { Some(select_full_account(conn, account_id)?) } else { None @@ -366,9 +371,9 @@ pub struct PublicAccountStateRootsPage { /// Returns up to `page_size` public account IDs, starting after `after_account_id` if provided. /// Results are ordered by `account_id` for stable pagination. /// -/// Public accounts are those with `AccountStorageMode::Public` or `AccountStorageMode::Network`. -/// We identify them by checking `code_commitment IS NOT NULL` - public accounts store their full -/// state (including `code_commitment`), while private accounts only store the `account_commitment`. +/// Public accounts are those with `AccountType::Public`. We identify them by checking +/// against the store. Public accounts store their `code_commitment`, while private accounts only +/// store the `account_commitment`. /// /// # Raw SQL /// @@ -426,9 +431,9 @@ pub(crate) fn select_public_account_ids_paged( /// Returns up to `page_size` public account states, starting after `after_account_id` if provided. /// Results are ordered by `account_id` for stable pagination. /// -/// Public accounts are those with `AccountStorageMode::Public` or `AccountStorageMode::Network`. -/// We identify them by checking `code_commitment IS NOT NULL` - public accounts store their full -/// state (including `code_commitment`), while private accounts only store the `account_commitment`. +/// Public accounts are those with `AccountType::Public`. We identify them by checking +/// against the store. Public accounts store their `code_commitment`, while private accounts only +/// store the `account_commitment`. /// /// # Raw SQL /// @@ -547,7 +552,7 @@ pub(crate) fn select_account_vault_assets( const ROW_OVERHEAD_BYTES: usize = 2 * size_of::() + size_of::(); // key + asset + block_num const MAX_ROWS: usize = MAX_RESPONSE_PAYLOAD_BYTES / ROW_OVERHEAD_BYTES; - if !account_id.has_public_state() { + if !account_id.is_public() { return Err(DatabaseError::AccountNotPublic(account_id)); } @@ -794,7 +799,7 @@ pub(crate) fn select_account_storage_map_values_paged( ) -> Result { use schema::account_storage_map_values as t; - if !account_id.has_public_state() { + if !account_id.is_public() { return Err(DatabaseError::AccountNotPublic(account_id)); } @@ -1154,7 +1159,7 @@ fn prepare_full_account_update( for asset in account.vault().assets() { // Only insert assets with non-zero values for fungible assets let should_insert = match asset { - Asset::Fungible(fungible) => fungible.amount() > 0, + Asset::Fungible(fungible) => fungible.amount().as_u64() > 0, Asset::NonFungible(_) => true, }; if should_insert { @@ -1198,7 +1203,7 @@ fn prepare_partial_account_update( } else { prev_asset.add(delta)? }; - let update_or_remove = if new_balance.amount() == 0 { + let update_or_remove = if new_balance.amount().as_u64() == 0 { None } else { Some(Asset::from(new_balance)) @@ -1255,7 +1260,7 @@ fn prepare_partial_account_update( .ok_or_else(|| { DatabaseError::DataCorrupted(format!("Nonce overflow for account {account_id}")) })?; - let new_nonce = Felt::new(new_nonce_value); + let new_nonce = Felt::new_unchecked(new_nonce_value); // Create minimal account state data for the row insert. let account_state = PartialAccountState { @@ -1283,12 +1288,69 @@ fn prepare_partial_account_update( Ok((AccountStateForInsert::PartialState(account_state), storage, assets)) } +/// Reads the network-account classification of the latest row for `account_id_bytes`. +/// +/// Returns `Ok(None)` if no row exists for this account. +fn select_latest_network_account_type( + conn: &mut SqliteConnection, + account_id_bytes: &[u8], +) -> Result, DatabaseError> { + let raw: Option = QueryDsl::select( + schema::accounts::table.filter( + schema::accounts::account_id + .eq(account_id_bytes) + .and(schema::accounts::is_latest.eq(true)), + ), + schema::accounts::network_account_type, + ) + .first::(conn) + .optional() + .map_err(DatabaseError::Diesel)?; + + raw.map(NetworkAccountType::from_raw_sql) + .transpose() + .map_err(DatabaseError::from) +} + +/// Returns the subset of `account_ids` whose latest committed state is a network account. +/// +/// Unknown ids and non-network accounts are silently omitted. +pub(crate) fn select_network_accounts_subset( + conn: &mut SqliteConnection, + account_ids: &[AccountId], +) -> Result, DatabaseError> { + QueryParamAccountIdLimit::check(account_ids.len())?; + let id_bytes: Vec> = + account_ids.iter().map(miden_crypto::utils::Serializable::to_bytes).collect(); + + let rows: Vec> = + SelectDsl::select(schema::accounts::table, schema::accounts::account_id) + .filter( + schema::accounts::account_id + .eq_any(&id_bytes) + .and( + schema::accounts::network_account_type + .eq(NetworkAccountType::Network.to_raw_sql()), + ) + .and(schema::accounts::is_latest.eq(true)), + ) + .load::>(conn) + .map_err(DatabaseError::Diesel)?; + + rows.into_iter() + .map(|bytes| { + AccountId::read_from_bytes(&bytes).map_err(DatabaseError::DeserializationError) + }) + .collect() +} + /// Attention: Assumes the account details are NOT null! The schema explicitly allows this though! #[tracing::instrument( target = COMPONENT, skip_all, err, )] +#[expect(clippy::too_many_lines)] pub(crate) fn upsert_accounts( conn: &mut SqliteConnection, accounts: &[BlockAccountUpdate], @@ -1300,12 +1362,6 @@ pub(crate) fn upsert_accounts( let account_id_bytes = account_id.to_bytes(); let block_num_raw = block_num.to_raw_sql(); - let network_account_type = if account_id.is_network() { - NetworkAccountType::Network - } else { - NetworkAccountType::None - }; - // Preserve the original creation block when updating existing accounts. let created_at_block_raw = QueryDsl::select( schema::accounts::table.filter( @@ -1344,6 +1400,25 @@ pub(crate) fn upsert_accounts( }, }; + // Classify the account as network or not by looking for the standardized + // `NetworkAccountNoteAllowlist` slot in storage. Only full account states let us inspect + // storage directly; for partial updates we inherit the latest classification from the DB. + let network_account_type = match &account_state { + AccountStateForInsert::Private => NetworkAccountType::None, + AccountStateForInsert::FullAccount(account) => { + if NetworkAccount::new(account.clone()).is_ok() { + NetworkAccountType::Network + } else { + NetworkAccountType::None + } + }, + AccountStateForInsert::PartialState(_) => { + // We do not have full storage here; carry the previous classification forward. + select_latest_network_account_type(conn, &account_id_bytes)? + .unwrap_or(NetworkAccountType::None) + }, + }; + // Insert account _code_ for full accounts (new account creation) if let AccountStateForInsert::FullAccount(ref account) = account_state { let code = account.code(); @@ -1440,7 +1515,7 @@ pub(crate) struct AccountRowInsert { impl AccountRowInsert { /// Creates an insert row for a private account (no public state). - fn new_private( + pub(crate) fn new_private( account_id: AccountId, network_account_type: NetworkAccountType, account_commitment: Word, diff --git a/crates/store/src/db/models/queries/accounts/delta.rs b/crates/store/src/db/models/queries/accounts/delta.rs index 88e5f8d68b..56b70fbfcc 100644 --- a/crates/store/src/db/models/queries/accounts/delta.rs +++ b/crates/store/src/db/models/queries/accounts/delta.rs @@ -181,7 +181,7 @@ pub(super) fn select_vault_balances_by_faucet_ids( if let Some(asset_bytes) = maybe_asset_bytes { let asset = Asset::read_from_bytes(&asset_bytes)?; if let Asset::Fungible(fungible) = asset { - balances.insert(fungible.faucet_id(), fungible.amount()); + balances.insert(fungible.faucet_id(), fungible.amount().as_u64()); } } } diff --git a/crates/store/src/db/models/queries/accounts/delta/tests.rs b/crates/store/src/db/models/queries/accounts/delta/tests.rs index 2f9b69ea94..4ec6759324 100644 --- a/crates/store/src/db/models/queries/accounts/delta/tests.rs +++ b/crates/store/src/db/models/queries/accounts/delta/tests.rs @@ -20,7 +20,6 @@ use miden_protocol::account::{ AccountComponent, AccountDelta, AccountId, - AccountStorageMode, AccountType, StorageMap, StorageMapKey, @@ -29,7 +28,7 @@ use miden_protocol::account::{ }; use miden_protocol::asset::{Asset, FungibleAsset}; use miden_protocol::block::{BlockAccountUpdate, BlockHeader, BlockNumber}; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::testing::account_id::{ ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET, ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET_1, @@ -54,7 +53,7 @@ fn setup_test_db() -> SqliteConnection { fn insert_block_header(conn: &mut SqliteConnection, block_num: BlockNumber) { use crate::db::schema::block_headers; - let secret_key = SecretKey::new(); + let secret_key = SigningKey::new(); let block_header = BlockHeader::new( 1_u8.into(), Word::default(), @@ -117,10 +116,10 @@ fn optimized_delta_matches_full_account_method() { // Create an account with value slots only (no map slots to avoid SmtForest complexity) let slot_value_initial = Word::from([ - Felt::new(INITIAL_SLOT_VALUES[0]), - Felt::new(INITIAL_SLOT_VALUES[1]), - Felt::new(INITIAL_SLOT_VALUES[2]), - Felt::new(INITIAL_SLOT_VALUES[3]), + Felt::new_unchecked(INITIAL_SLOT_VALUES[0]), + Felt::new_unchecked(INITIAL_SLOT_VALUES[1]), + Felt::new_unchecked(INITIAL_SLOT_VALUES[2]), + Felt::new_unchecked(INITIAL_SLOT_VALUES[3]), ]); let component_storage = vec![ @@ -135,13 +134,12 @@ fn optimized_delta_matches_full_account_method() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new(ACCOUNT_SEED) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -179,10 +177,10 @@ fn optimized_delta_matches_full_account_method() { // - Add 500 tokens to the vault (starting from empty) let new_slot_value = Word::from([ - Felt::new(UPDATED_SLOT_VALUES[0]), - Felt::new(UPDATED_SLOT_VALUES[1]), - Felt::new(UPDATED_SLOT_VALUES[2]), - Felt::new(UPDATED_SLOT_VALUES[3]), + Felt::new_unchecked(UPDATED_SLOT_VALUES[0]), + Felt::new_unchecked(UPDATED_SLOT_VALUES[1]), + Felt::new_unchecked(UPDATED_SLOT_VALUES[2]), + Felt::new_unchecked(UPDATED_SLOT_VALUES[3]), ]); let faucet_id = AccountId::try_from(ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET).unwrap(); @@ -208,7 +206,7 @@ fn optimized_delta_matches_full_account_method() { }; // Create a partial delta - let nonce_delta = Felt::new(NONCE_DELTA); + let nonce_delta = Felt::new_unchecked(NONCE_DELTA); let partial_delta = AccountDelta::new( full_account_before.id(), storage_delta.clone(), @@ -219,8 +217,9 @@ fn optimized_delta_matches_full_account_method() { assert!(!partial_delta.is_full_state(), "Delta should be partial, not full state"); // Construct the expected final account by applying the delta - let expected_nonce = - Felt::new(full_account_before.nonce().as_canonical_u64() + nonce_delta.as_canonical_u64()); + let expected_nonce = Felt::new_unchecked( + full_account_before.nonce().as_canonical_u64() + nonce_delta.as_canonical_u64(), + ); let expected_code_commitment = full_account_before.code().commitment(); let mut expected_account = full_account_before.clone(); @@ -276,7 +275,7 @@ fn optimized_delta_matches_full_account_method() { assert_eq!(vault_assets_after.len(), 1, "Should have 1 vault asset"); assert_matches!(&vault_assets_after[0], Asset::Fungible(f) => { assert_eq!(f.faucet_id(), faucet_id, "Faucet ID should match"); - assert_eq!(f.amount(), VAULT_AMOUNT, "Amount should be 500"); + assert_eq!(f.amount().as_u64(), VAULT_AMOUNT, "Amount should be 500"); }); // Verify the account commitment matches @@ -335,13 +334,12 @@ fn optimized_delta_updates_non_empty_vault() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new(ACCOUNT_SEED) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -383,7 +381,7 @@ fn optimized_delta_updates_non_empty_vault() { account.id(), AccountStorageDelta::new(), vault_delta, - Felt::new(NONCE_DELTA), + Felt::new_unchecked(NONCE_DELTA), ) .unwrap(); @@ -405,7 +403,7 @@ fn optimized_delta_updates_non_empty_vault() { assert_eq!(vault_assets_after.len(), 1, "Should have 1 vault asset"); assert_matches!(&vault_assets_after[0], Asset::Fungible(f) => { assert_eq!(f.faucet_id(), faucet_id_1, "Faucet ID should match"); - assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2, "Amount should match"); + assert_eq!(f.amount().as_u64(), ADDED_AMOUNT_BLOCK_2, "Amount should match"); }); let full_account_after = select_full_account(&mut conn, account.id()) @@ -424,7 +422,7 @@ fn optimized_delta_updates_non_empty_vault() { account.id(), AccountStorageDelta::new(), vault_delta_3, - Felt::new(NONCE_DELTA), + Felt::new_unchecked(NONCE_DELTA), ) .unwrap(); @@ -447,7 +445,7 @@ fn optimized_delta_updates_non_empty_vault() { assert_eq!(final_assets.len(), 1, "Should have exactly 1 vault asset"); assert_matches!(&final_assets[0], Asset::Fungible(f) => { assert_eq!(f.faucet_id(), faucet_id_1); - assert_eq!(f.amount(), ADDED_AMOUNT_BLOCK_2 + ADDED_AMOUNT_BLOCK_3, "Expected total of 400"); + assert_eq!(f.amount().as_u64(), ADDED_AMOUNT_BLOCK_2 + ADDED_AMOUNT_BLOCK_3, "Expected total of 400"); }); assert_eq!(full_account_final.vault().root(), expected_vault_root_3); @@ -473,22 +471,22 @@ fn optimized_delta_updates_storage_map_header() { let mut conn = setup_test_db(); let map_key = StorageMapKey::new(Word::from([ - Felt::new(MAP_KEY_VALUES[0]), - Felt::new(MAP_KEY_VALUES[1]), - Felt::new(MAP_KEY_VALUES[2]), - Felt::new(MAP_KEY_VALUES[3]), + Felt::new_unchecked(MAP_KEY_VALUES[0]), + Felt::new_unchecked(MAP_KEY_VALUES[1]), + Felt::new_unchecked(MAP_KEY_VALUES[2]), + Felt::new_unchecked(MAP_KEY_VALUES[3]), ])); let map_value_initial = Word::from([ - Felt::new(MAP_VALUE_INITIAL[0]), - Felt::new(MAP_VALUE_INITIAL[1]), - Felt::new(MAP_VALUE_INITIAL[2]), - Felt::new(MAP_VALUE_INITIAL[3]), + Felt::new_unchecked(MAP_VALUE_INITIAL[0]), + Felt::new_unchecked(MAP_VALUE_INITIAL[1]), + Felt::new_unchecked(MAP_VALUE_INITIAL[2]), + Felt::new_unchecked(MAP_VALUE_INITIAL[3]), ]); let map_value_updated = Word::from([ - Felt::new(MAP_VALUE_UPDATED[0]), - Felt::new(MAP_VALUE_UPDATED[1]), - Felt::new(MAP_VALUE_UPDATED[2]), - Felt::new(MAP_VALUE_UPDATED[3]), + Felt::new_unchecked(MAP_VALUE_UPDATED[0]), + Felt::new_unchecked(MAP_VALUE_UPDATED[1]), + Felt::new_unchecked(MAP_VALUE_UPDATED[2]), + Felt::new_unchecked(MAP_VALUE_UPDATED[3]), ]); let storage_map = StorageMap::with_entries(vec![(map_key, map_value_initial)]).unwrap(); @@ -502,13 +500,12 @@ fn optimized_delta_updates_storage_map_header() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new(ACCOUNT_SEED) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -544,7 +541,7 @@ fn optimized_delta_updates_storage_map_header() { account.id(), storage_delta, AccountVaultDelta::default(), - Felt::new(NONCE_DELTA), + Felt::new_unchecked(NONCE_DELTA), ) .unwrap(); @@ -582,7 +579,7 @@ fn optimized_delta_updates_storage_map_header() { /// Private accounts store only the account commitment, not the full state. #[test] fn upsert_private_account() { - use miden_protocol::account::{AccountIdVersion, AccountStorageMode, AccountType}; + use miden_protocol::account::{AccountIdVersion, AccountType}; // Use deterministic account seed to keep account IDs stable. const ACCOUNT_ID_SEED: [u8; 15] = [20u8; 15]; @@ -597,18 +594,14 @@ fn upsert_private_account() { insert_block_header(&mut conn, block_num); // Create a private account ID - let account_id = AccountId::dummy( - ACCOUNT_ID_SEED, - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Private, - ); + let account_id = + AccountId::dummy(ACCOUNT_ID_SEED, AccountIdVersion::Version1, AccountType::Private); let account_commitment = Word::from([ - Felt::new(COMMITMENT_WORDS[0]), - Felt::new(COMMITMENT_WORDS[1]), - Felt::new(COMMITMENT_WORDS[2]), - Felt::new(COMMITMENT_WORDS[3]), + Felt::new_unchecked(COMMITMENT_WORDS[0]), + Felt::new_unchecked(COMMITMENT_WORDS[1]), + Felt::new_unchecked(COMMITMENT_WORDS[2]), + Felt::new_unchecked(COMMITMENT_WORDS[3]), ]); // Insert as private account @@ -660,10 +653,10 @@ fn upsert_full_state_delta() { // Create an account with storage let slot_value = Word::from([ - Felt::new(SLOT_VALUES[0]), - Felt::new(SLOT_VALUES[1]), - Felt::new(SLOT_VALUES[2]), - Felt::new(SLOT_VALUES[3]), + Felt::new_unchecked(SLOT_VALUES[0]), + Felt::new_unchecked(SLOT_VALUES[1]), + Felt::new_unchecked(SLOT_VALUES[2]), + Felt::new_unchecked(SLOT_VALUES[3]), ]); let component_storage = vec![StorageSlot::with_value(StorageSlotName::mock(SLOT_INDEX), slot_value)]; @@ -675,13 +668,12 @@ fn upsert_full_state_delta() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new(ACCOUNT_SEED) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), diff --git a/crates/store/src/db/models/queries/accounts/tests.rs b/crates/store/src/db/models/queries/accounts/tests.rs index 4b0e8f2da4..de74a87566 100644 --- a/crates/store/src/db/models/queries/accounts/tests.rs +++ b/crates/store/src/db/models/queries/accounts/tests.rs @@ -18,7 +18,6 @@ use miden_protocol::account::{ AccountStorage, AccountStorageDelta, AccountStorageHeader, - AccountStorageMode, AccountType, AccountVaultDelta, StorageMap, @@ -31,7 +30,7 @@ use miden_protocol::account::{ StorageSlotType, }; use miden_protocol::block::{BlockAccountUpdate, BlockHeader, BlockNumber}; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::utils::serde::{Deserializable, Serializable}; use miden_protocol::{EMPTY_WORD, Felt, Word}; use miden_standards::account::auth::AuthSingleSig; @@ -123,14 +122,14 @@ fn reconstruct_account_storage_at_block( fn create_test_account_with_storage() -> (Account, AccountId) { // Create a simple public account with one value storage slot - let account_id = AccountId::dummy( - [1u8; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - ); - - let storage_value = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); + let account_id = AccountId::dummy([1u8; 15], AccountIdVersion::Version1, AccountType::Public); + + let storage_value = Word::from([ + Felt::new_unchecked(1), + Felt::new_unchecked(2), + Felt::new_unchecked(3), + Felt::new_unchecked(4), + ]); let component_storage = vec![StorageSlot::with_value(StorageSlotName::mock(0), storage_value)]; let account_component_code = CodeBuilder::default() @@ -140,13 +139,12 @@ fn create_test_account_with_storage() -> (Account, AccountId) { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([1u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -161,7 +159,7 @@ fn create_test_account_with_storage() -> (Account, AccountId) { fn insert_block_header(conn: &mut SqliteConnection, block_num: BlockNumber) { use crate::db::schema::block_headers; - let secret_key = SecretKey::new(); + let secret_key = SigningKey::new(); let block_header = BlockHeader::new( 1_u8.into(), Word::default(), @@ -203,13 +201,12 @@ fn create_account_with_map_storage( let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); AccountBuilder::new([9u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -307,12 +304,7 @@ fn test_select_account_header_at_block_returns_none_for_nonexistent() { let block_num = BlockNumber::from_epoch(0); insert_block_header(&mut conn, block_num); - let account_id = AccountId::dummy( - [99u8; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - ); + let account_id = AccountId::dummy([99u8; 15], AccountIdVersion::Version1, AccountType::Public); // Query for a non-existent account let result = @@ -506,8 +498,12 @@ fn test_upsert_accounts_updates_is_latest_flag() { upsert_accounts(&mut conn, &[account_update_1], block_num_1).expect("First upsert failed"); // Create modified account with different storage value - let storage_value_modified = - Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]); + let storage_value_modified = Word::from([ + Felt::new_unchecked(10), + Felt::new_unchecked(20), + Felt::new_unchecked(30), + Felt::new_unchecked(40), + ]); let component_storage_modified = vec![StorageSlot::with_value(StorageSlotName::mock(0), storage_value_modified)]; @@ -518,13 +514,12 @@ fn test_upsert_accounts_updates_is_latest_flag() { let component_2 = AccountComponent::new( account_component_code, component_storage_modified, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account_2 = AccountBuilder::new([1u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component_2) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -593,16 +588,11 @@ fn test_upsert_accounts_with_multiple_storage_slots() { let mut conn = setup_test_db(); // Create account with 3 storage slots - let account_id = AccountId::dummy( - [2u8; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - ); + let account_id = AccountId::dummy([2u8; 15], AccountIdVersion::Version1, AccountType::Public); - let slot_value_1 = Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]); - let slot_value_2 = Word::from([Felt::new(5), Felt::new(6), Felt::new(7), Felt::new(8)]); - let slot_value_3 = Word::from([Felt::new(9), Felt::new(10), Felt::new(11), Felt::new(12)]); + let slot_value_1 = Word::from([1, 2, 3, 4u32]); + let slot_value_2 = Word::from([5, 6, 7, 8u32]); + let slot_value_3 = Word::from([9, 10, 11, 12u32]); let component_storage = vec![ StorageSlot::with_value(StorageSlotName::mock(0), slot_value_1), @@ -617,13 +607,12 @@ fn test_upsert_accounts_with_multiple_storage_slots() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([2u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -672,12 +661,7 @@ fn test_upsert_accounts_with_empty_storage() { let mut conn = setup_test_db(); // Create account with no component storage slots (only auth slot) - let account_id = AccountId::dummy( - [3u8; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - ); + let account_id = AccountId::dummy([3u8; 15], AccountIdVersion::Version1, AccountType::Public); let account_component_code = CodeBuilder::default() .compile_component_code("test::interface", "pub proc foo push.1 end") @@ -686,13 +670,12 @@ fn test_upsert_accounts_with_empty_storage() { let component = AccountComponent::new( account_component_code, vec![], - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([3u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -759,9 +742,9 @@ fn test_select_latest_account_storage_ordering_semantics() { let key_2 = StorageMapKey::from_index(2); let key_3 = StorageMapKey::from_index(3); - let value_1 = Word::from([Felt::new(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]); - let value_2 = Word::from([Felt::new(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]); - let value_3 = Word::from([Felt::new(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_1 = Word::from([Felt::new_unchecked(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_2 = Word::from([Felt::new_unchecked(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_3 = Word::from([Felt::new_unchecked(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]); let mut entries = vec![(key_2, value_2), (key_1, value_1), (key_3, value_3)]; entries.reverse(); @@ -804,8 +787,8 @@ fn test_select_latest_account_storage_multiple_slots() { let key_a = StorageMapKey::from_index(1); let key_b = StorageMapKey::from_index(2); - let value_a = Word::from([Felt::new(11), Felt::ZERO, Felt::ZERO, Felt::ZERO]); - let value_b = Word::from([Felt::new(22), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_a = Word::from([Felt::new_unchecked(11), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_b = Word::from([Felt::new_unchecked(22), Felt::ZERO, Felt::ZERO, Felt::ZERO]); let map_a = StorageMap::with_entries(vec![(key_a, value_a)]).unwrap(); let map_b = StorageMap::with_entries(vec![(key_b, value_b)]).unwrap(); @@ -822,13 +805,12 @@ fn test_select_latest_account_storage_multiple_slots() { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountImmutableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([9u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -867,9 +849,9 @@ fn test_select_latest_account_storage_slot_updates() { let key_1 = StorageMapKey::from_index(1); let key_2 = StorageMapKey::from_index(2); - let value_1 = Word::from([Felt::new(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]); - let value_2 = Word::from([Felt::new(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]); - let value_3 = Word::from([Felt::new(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_1 = Word::from([Felt::new_unchecked(10), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_2 = Word::from([Felt::new_unchecked(20), Felt::ZERO, Felt::ZERO, Felt::ZERO]); + let value_3 = Word::from([Felt::new_unchecked(30), Felt::ZERO, Felt::ZERO, Felt::ZERO]); let account = create_account_with_map_storage(slot_name.clone(), vec![(key_1, value_1)]); let account_id = account.id(); @@ -889,9 +871,13 @@ fn test_select_latest_account_storage_slot_updates() { StorageSlotDelta::Map(map_delta), )])); - let partial_delta = - AccountDelta::new(account_id, storage_delta, AccountVaultDelta::default(), Felt::new(1)) - .unwrap(); + let partial_delta = AccountDelta::new( + account_id, + storage_delta, + AccountVaultDelta::default(), + Felt::new_unchecked(1), + ) + .unwrap(); let mut expected_account = account.clone(); expected_account.apply_delta(&partial_delta).unwrap(); @@ -986,7 +972,7 @@ fn test_select_account_vault_at_block_historical_with_updates() { .expect("Query at block 1 should succeed"); assert_eq!(assets_at_block_1.len(), 1, "Should have 1 asset at block 1"); - assert_matches!(&assets_at_block_1[0], Asset::Fungible(f) if f.amount() == 1000); + assert_matches!(&assets_at_block_1[0], Asset::Fungible(f) if f.amount().as_u64() == 1000); // Query at block 2: should see vault_key_1 with 2000 tokens AND vault_key_2 with 500 tokens let assets_at_block_2 = select_account_vault_at_block(&mut conn, account_id, block_2) @@ -997,7 +983,7 @@ fn test_select_account_vault_at_block_historical_with_updates() { // Find the amounts (order may vary) let amounts: Vec = assets_at_block_2 .iter() - .map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount())) + .map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount().as_u64())) .collect(); assert!(amounts.contains(&2000), "Block 2 should have vault_key_1 with 2000 tokens"); @@ -1011,7 +997,7 @@ fn test_select_account_vault_at_block_historical_with_updates() { let amounts: Vec = assets_at_block_3 .iter() - .map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount())) + .map(|a| assert_matches!(a, Asset::Fungible(f) => f.amount().as_u64())) .collect(); assert!(amounts.contains(&3000), "Block 3 should have vault_key_1 with 3000 tokens"); @@ -1024,7 +1010,7 @@ fn test_select_account_vault_at_block_exponential_updates() { const BLOCK_COUNT: u32 = 5; use assert_matches::assert_matches; - use miden_protocol::asset::{AssetVaultKey, FungibleAsset}; + use miden_protocol::asset::{AssetCallbackFlag, AssetVaultKey, FungibleAsset}; use miden_protocol::testing::account_id::ACCOUNT_ID_PUBLIC_FUNGIBLE_FAUCET; let mut conn = setup_test_db(); @@ -1051,7 +1037,7 @@ fn test_select_account_vault_at_block_exponential_updates() { .expect("upsert_accounts failed"); } - let vault_key = AssetVaultKey::new_fungible(faucet_id).unwrap(); + let vault_key = AssetVaultKey::new_fungible(faucet_id, AssetCallbackFlag::Disabled); for (index, block) in blocks.iter().enumerate() { let amount = 1u64 << index; @@ -1068,7 +1054,7 @@ fn test_select_account_vault_at_block_exponential_updates() { let expected_amount = 1u64 << index; assert_matches!( &assets_at_block[0], - Asset::Fungible(f) if f.amount() == expected_amount + Asset::Fungible(f) if f.amount().as_u64() == expected_amount ); } } @@ -1139,7 +1125,7 @@ fn test_select_account_vault_at_block_with_deletion() { let assets_at_block_3 = select_account_vault_at_block(&mut conn, account_id, block_3) .expect("Query at block 3 should succeed"); assert_eq!(assets_at_block_3.len(), 1, "Should have 1 asset at block 3"); - assert_matches!(&assets_at_block_3[0], Asset::Fungible(f) if f.amount() == 2000); + assert_matches!(&assets_at_block_3[0], Asset::Fungible(f) if f.amount().as_u64() == 2000); } // ACCOUNT CODE PRUNING TESTS @@ -1194,19 +1180,15 @@ fn build_account_with_code(push_value: u32) -> Account { component_code, vec![StorageSlot::with_value( StorageSlotName::mock(0), - Word::from([Felt::new(1), Felt::ZERO, Felt::ZERO, Felt::ZERO]), + Word::from([Felt::new_unchecked(1), Felt::ZERO, Felt::ZERO, Felt::ZERO]), )], - AccountComponentMetadata::new( - "code_prune_test", - [AccountType::RegularAccountUpdatableCode], - ), + AccountComponentMetadata::new("code_prune_test"), ) .unwrap(); // Seed [2u8; 32] keeps the account ID distinct from the other test helpers. AccountBuilder::new([2u8; 32]) - .account_type(AccountType::RegularAccountUpdatableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -1357,3 +1339,48 @@ fn test_prune_account_code_retains_revisited_code() { "latest account must reference code A" ); } + +#[test] +#[miden_node_test_macro::enable_logging] +fn network_accounts_subset_classifies_correctly() { + use crate::db::models::queries::accounts::{ + AccountRowInsert, + NetworkAccountType, + select_network_accounts_subset, + }; + + let mut conn = setup_test_db(); + let block_num = BlockNumber::from(1); + insert_block_header(&mut conn, block_num); + + // Three accounts with distinct classifications. AccountIds are dummies — the queries only care + // about the (account_id, network_account_type, is_latest) tuple, not protocol-level validity. + let network_id = AccountId::dummy([1u8; 15], AccountIdVersion::Version1, AccountType::Public); + let public_id = AccountId::dummy([2u8; 15], AccountIdVersion::Version1, AccountType::Public); + let private_id = AccountId::dummy([3u8; 15], AccountIdVersion::Version1, AccountType::Private); + let unknown_id = AccountId::dummy([4u8; 15], AccountIdVersion::Version1, AccountType::Public); + + for (id, ty) in [ + (network_id, NetworkAccountType::Network), + (public_id, NetworkAccountType::None), + (private_id, NetworkAccountType::None), + ] { + let row = AccountRowInsert::new_private(id, ty, Word::default(), block_num, block_num); + diesel::insert_into(crate::db::schema::accounts::table) + .values(&row) + .execute(&mut conn) + .unwrap(); + } + + // Batched lookup returns only the network-classified id; public, private, and unknown ids are + // all omitted. + let subset = + select_network_accounts_subset(&mut conn, &[network_id, public_id, private_id, unknown_id]) + .unwrap(); + assert_eq!(subset.len(), 1); + assert!(subset.contains(&network_id)); + + // Empty input slice short-circuits to an empty result. + let empty = select_network_accounts_subset(&mut conn, &[]).unwrap(); + assert!(empty.is_empty()); +} diff --git a/crates/store/src/db/models/queries/notes.rs b/crates/store/src/db/models/queries/notes.rs index 20777011fe..02524b5805 100644 --- a/crates/store/src/db/models/queries/notes.rs +++ b/crates/store/src/db/models/queries/notes.rs @@ -239,8 +239,8 @@ pub(crate) fn select_existing_note_commitments( let note_commitments = serialize_vec(note_commitments.iter()); - let raw_commitments = SelectDsl::select(schema::notes::table, schema::notes::note_commitment) - .filter(schema::notes::note_commitment.eq_any(¬e_commitments)) + let raw_commitments = SelectDsl::select(schema::notes::table, schema::notes::note_id) + .filter(schema::notes::note_id.eq_any(¬e_commitments)) .load::>(conn)?; let commitments = raw_commitments @@ -344,7 +344,7 @@ pub(crate) fn select_note_inclusion_proofs( schema::notes::inclusion_path, ), ) - .filter(schema::notes::note_commitment.eq_any(note_commitments)) + .filter(schema::notes::note_id.eq_any(note_commitments)) .order_by(schema::notes::committed_at.asc()) .load::<(i64, Vec, i32, i32, Vec)>(conn)?; @@ -382,6 +382,7 @@ pub(crate) fn select_note_inclusion_proofs( /// batch_index, /// note_index, /// note_id, +/// note_commitment, /// note_type, /// sender, /// tag, @@ -396,14 +397,14 @@ pub(crate) fn select_note_inclusion_proofs( /// ``` pub(crate) fn select_note_sync_records( conn: &mut SqliteConnection, - note_commitments: &[Word], + note_ids: &[NoteId], ) -> Result, DatabaseError> { - QueryParamNoteCommitmentLimit::check(note_commitments.len())?; + QueryParamNoteCommitmentLimit::check(note_ids.len())?; - let note_commitments = serialize_vec(note_commitments.iter()); + let note_id_bytes: Vec> = note_ids.iter().map(|id| id.as_word().to_bytes()).collect(); let raw_notes = SelectDsl::select(schema::notes::table, NoteSyncRecordRawRow::as_select()) - .filter(schema::notes::note_commitment.eq_any(note_commitments)) + .filter(schema::notes::note_id.eq_any(note_id_bytes)) .order_by(schema::notes::committed_at.asc()) .load::(conn)?; @@ -411,7 +412,7 @@ pub(crate) fn select_note_sync_records( .into_iter() .map(|raw_note| { let note: NoteSyncRecord = raw_note.try_into()?; - Ok((NoteId::from_raw(note.note_id), note)) + Ok((note.note_id, note)) }) .collect() } @@ -598,7 +599,8 @@ pub struct NoteSyncRecordRawRow { pub committed_at: i64, // BlockNumber #[diesel(embed)] pub block_note_index: BlockNoteIndexRawRow, - pub note_id: Vec, // BlobDigest + pub details_commitment: Vec, // BlobDigest + pub note_id: Vec, // BlobDigest #[diesel(embed)] pub metadata: NoteMetadataRawRow, pub inclusion_path: Vec, // SparseMerklePath @@ -610,7 +612,7 @@ impl TryInto for NoteSyncRecordRawRow { let block_num = BlockNumber::from_raw_sql(self.committed_at)?; let note_index = self.block_note_index.try_into()?; - let note_id = Word::read_from_bytes(&self.note_id[..])?; + let note_id = NoteId::from_raw(Word::read_from_bytes(&self.note_id[..])?); let inclusion_path = SparseMerklePath::read_from_bytes(&self.inclusion_path[..])?; let (metadata, _attachments) = self.metadata.try_into()?; Ok(NoteSyncRecord { @@ -642,8 +644,8 @@ pub struct NoteRecordWithScriptRawJoined { pub note_index: i32, // index within batch // #[diesel(embed)] // pub note_index: BlockNoteIndexRaw, + pub details_commitment: Vec, pub note_id: Vec, - pub note_commitment: Vec, pub note_type: i32, pub sender: Vec, // AccountId @@ -667,8 +669,8 @@ impl From<(NoteRecordRawRow, Option>)> for NoteRecordWithScriptRawJoined committed_at, batch_index, note_index, + details_commitment, note_id, - note_commitment, note_type, sender, tag, @@ -682,8 +684,8 @@ impl From<(NoteRecordRawRow, Option>)> for NoteRecordWithScriptRawJoined committed_at, batch_index, note_index, + details_commitment, note_id, - note_commitment, note_type, sender, tag, @@ -708,8 +710,8 @@ impl TryInto for NoteRecordWithScriptRawJoined { batch_index, note_index, // block note index ^^^ + details_commitment, note_id, - note_commitment, note_type, sender, @@ -730,8 +732,8 @@ impl TryInto for NoteRecordWithScriptRawJoined { let (metadata, attachments) = metadata.try_into()?; let committed_at = BlockNumber::from_raw_sql(committed_at)?; + let details_commitment = Word::read_from_bytes(&details_commitment[..])?; let note_id = Word::read_from_bytes(¬e_id[..])?; - let note_commitment = Word::read_from_bytes(¬e_commitment[..])?; let script = script.map(|script| NoteScript::read_from_bytes(&script[..])).transpose()?; let details = if let NoteDetailsRawRow { assets: Some(assets), @@ -760,8 +762,8 @@ impl TryInto for NoteRecordWithScriptRawJoined { Ok(NoteRecord { block_num: committed_at, note_index, + details_commitment, note_id, - note_commitment, metadata, details, attachments, @@ -778,8 +780,8 @@ pub struct NoteRecordRawRow { pub batch_index: i32, pub note_index: i32, // index within batch + pub details_commitment: Vec, pub note_id: Vec, - pub note_commitment: Vec, pub note_type: i32, pub sender: Vec, // AccountId @@ -917,8 +919,8 @@ pub struct NoteInsertRow { pub batch_index: i32, pub note_index: i32, // index within batch + pub details_commitment: Vec, pub note_id: Vec, - pub note_commitment: Vec, pub note_type: i32, pub sender: Vec, // AccountId @@ -951,8 +953,8 @@ impl From<(NoteRecord, Option)> for NoteInsertRow { committed_at: note.block_num.to_raw_sql(), batch_index: idx_to_raw_sql(note.note_index.batch_idx()), note_index: idx_to_raw_sql(note.note_index.note_idx_in_batch()), + details_commitment: note.details_commitment.to_bytes(), note_id: note.note_id.to_bytes(), - note_commitment: note.note_commitment.to_bytes(), note_type: note_type_to_raw_sql(note.metadata.note_type() as u8), sender: note.metadata.sender().to_bytes(), tag: note.metadata.tag().to_raw_sql(), diff --git a/crates/store/src/db/models/queries/transactions.rs b/crates/store/src/db/models/queries/transactions.rs index 4551219068..ad1413dd7c 100644 --- a/crates/store/src/db/models/queries/transactions.rs +++ b/crates/store/src/db/models/queries/transactions.rs @@ -20,7 +20,7 @@ use miden_node_utils::limiter::{ }; use miden_protocol::account::AccountId; use miden_protocol::block::BlockNumber; -use miden_protocol::note::NoteHeader; +use miden_protocol::note::{NoteHeader, NoteId}; use miden_protocol::transaction::{ InputNoteCommitment, InputNotes, @@ -308,17 +308,17 @@ fn with_output_note_proofs( use miden_protocol::Word; use miden_protocol::asset::FungibleAsset; - // Pre-deserialize output notes to collect commitments for the batch lookup. + // Pre-deserialize output notes to collect IDs for the batch lookup. let mut tx_output_notes = Vec::with_capacity(raw_transactions.len()); - let mut all_note_commitments = Vec::new(); + let mut all_note_ids: Vec = Vec::new(); for raw in &raw_transactions { let notes: Vec = Deserializable::read_from_bytes(&raw.output_notes)?; - all_note_commitments.extend(notes.iter().map(NoteHeader::to_commitment)); + all_note_ids.extend(notes.iter().map(NoteHeader::id)); tx_output_notes.push(notes); } let mut output_notes_by_id = std::collections::BTreeMap::new(); - for chunk in all_note_commitments.chunks(QueryParamNoteCommitmentLimit::LIMIT) { + for chunk in all_note_ids.chunks(QueryParamNoteCommitmentLimit::LIMIT) { output_notes_by_id.extend(select_note_sync_records(conn, chunk)?); } @@ -332,7 +332,10 @@ fn with_output_note_proofs( // table were erased (created and consumed in the same batch). let output_note_proofs = output_notes .iter() - .filter_map(|note| output_notes_by_id.get(¬e.id()).cloned()) + .filter_map(|note| { + let key = note.id(); + output_notes_by_id.get(&key).cloned() + }) .collect(); let header = TransactionHeader::new_unchecked( diff --git a/crates/store/src/db/schema.rs b/crates/store/src/db/schema.rs index 82faec3924..3217abd94b 100644 --- a/crates/store/src/db/schema.rs +++ b/crates/store/src/db/schema.rs @@ -64,8 +64,8 @@ diesel::table! { committed_at -> BigInt, batch_index -> Integer, note_index -> Integer, + details_commitment -> Binary, note_id -> Binary, - note_commitment -> Binary, note_type -> Integer, sender -> Binary, tag -> Integer, diff --git a/crates/store/src/db/tests.rs b/crates/store/src/db/tests.rs index 93153f0a39..e48e979768 100644 --- a/crates/store/src/db/tests.rs +++ b/crates/store/src/db/tests.rs @@ -17,7 +17,6 @@ use miden_protocol::account::{ AccountId, AccountIdVersion, AccountStorageDelta, - AccountStorageMode, AccountType, AccountVaultDelta, StorageMapKey, @@ -34,7 +33,7 @@ use miden_protocol::block::{ BlockNoteTree, BlockNumber, }; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use miden_protocol::crypto::merkle::SparseMerklePath; use miden_protocol::crypto::merkle::mmr::{Forest, Mmr}; use miden_protocol::crypto::merkle::smt::SmtProof; @@ -44,6 +43,7 @@ use miden_protocol::note::{ NoteAttachment, NoteAttachments, NoteDetails, + NoteDetailsCommitment, NoteHeader, NoteId, NoteMetadata, @@ -99,12 +99,12 @@ fn create_block(conn: &mut SqliteConnection, block_num: BlockNumber) { num_to_word(7), num_to_word(8), num_to_word(9), - SecretKey::new().public_key(), + SigningKey::new().public_key(), test_fee_params(), 11_u8.into(), ); - let dummy_signature = SecretKey::new().sign(block_header.commitment()); + let dummy_signature = SigningKey::new().sign(block_header.commitment()); conn.transaction(|conn| { queries::insert_block_header(conn, &block_header, &dummy_signature)?; @@ -198,7 +198,7 @@ fn sql_select_nullifiers() { pub fn create_note(account_id: AccountId) -> Note { let coin_seed: [u64; 4] = rand::rng().random(); - let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.map(Felt::new).into()))); + let rng = Arc::new(Mutex::new(RandomCoin::new(coin_seed.map(Felt::new_unchecked).into()))); let mut rng = rng.lock().unwrap(); P2idNote::create( account_id, @@ -237,8 +237,8 @@ fn sql_select_notes() { let note = NoteRecord { block_num, note_index: BlockNoteIndex::new(0, i.try_into().unwrap()).unwrap(), + details_commitment: num_to_word(u64::try_from(i).unwrap()), note_id: num_to_word(u64::try_from(i).unwrap()), - note_commitment: num_to_word(u64::try_from(i).unwrap()), metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), attachments: new_note.attachments().clone(), @@ -282,8 +282,8 @@ fn sql_select_note_script_by_root() { let note = NoteRecord { block_num, note_index: BlockNoteIndex::new(0, 0.try_into().unwrap()).unwrap(), + details_commitment: num_to_word(0), note_id: num_to_word(0), - note_commitment: num_to_word(0), metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), attachments: new_note.attachments().clone(), @@ -309,15 +309,10 @@ fn make_account_and_note( conn: &mut SqliteConnection, block_num: BlockNumber, init_seed: [u8; 32], - storage_mode: AccountStorageMode, + account_type: AccountType, ) -> (AccountId, Note) { conn.transaction(|conn| { - let account = mock_account_code_and_storage( - AccountType::RegularAccountUpdatableCode, - storage_mode, - [], - Some(init_seed), - ); + let account = mock_account_code_and_storage(account_type, [], Some(init_seed)); let account_id = account.id(); queries::upsert_accounts( conn, @@ -342,8 +337,7 @@ fn sql_unconsumed_network_notes() { let mut conn = create_db(); // Create account. - let account_note = - make_account_and_note(&mut conn, 0.into(), [1u8; 32], AccountStorageMode::Network); + let account_note = make_account_and_note(&mut conn, 0.into(), [1u8; 32], AccountType::Public); // Create 2 blocks. create_block(&mut conn, 0.into()); @@ -364,8 +358,8 @@ fn sql_unconsumed_network_notes() { let note = NoteRecord { block_num: 0.into(), // Created on same block. note_index: BlockNoteIndex::new(0, i as usize).unwrap(), + details_commitment: num_to_word(i.into()), note_id: num_to_word(i.into()), - note_commitment: num_to_word(i.into()), metadata, details: None, attachments, @@ -436,12 +430,8 @@ fn sql_select_accounts() { // test multiple entries let mut state = vec![]; for i in 0..10u8 { - let account_id = AccountId::dummy( - [i; 15], - AccountIdVersion::Version1, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Private, - ); + let account_id = + AccountId::dummy([i; 15], AccountIdVersion::Version1, AccountType::Private); let account_commitment = num_to_word(u64::from(i)); state.push(AccountInfo { summary: AccountSummary { @@ -748,13 +738,13 @@ fn db_block_header() { num_to_word(7), num_to_word(8), num_to_word(9), - SecretKey::new().public_key(), + SigningKey::new().public_key(), test_fee_params(), 11_u8.into(), ); // test insertion - let dummy_signature = SecretKey::new().sign(block_header.commitment()); + let dummy_signature = SigningKey::new().sign(block_header.commitment()); queries::insert_block_header(conn, &block_header, &dummy_signature).unwrap(); // test fetch unknown block header @@ -781,12 +771,12 @@ fn db_block_header() { num_to_word(17), num_to_word(18), num_to_word(19), - SecretKey::new().public_key(), + SigningKey::new().public_key(), test_fee_params(), 21_u8.into(), ); - let dummy_signature = SecretKey::new().sign(block_header2.commitment()); + let dummy_signature = SigningKey::new().sign(block_header2.commitment()); queries::insert_block_header(conn, &block_header2, &dummy_signature).unwrap(); let res = queries::select_block_header_by_block_num(conn, None).unwrap(); @@ -828,7 +818,7 @@ fn notes() { &NoteAttachments::default(), ); - let note_header = NoteHeader::new(new_note.id(), note_metadata); + let note_header = NoteHeader::new(new_note.details_commitment(), note_metadata); let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -836,8 +826,8 @@ fn notes() { let note = NoteRecord { block_num: block_num_1, note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), attachments: NoteAttachments::default(), @@ -867,8 +857,8 @@ fn notes() { let note2 = NoteRecord { block_num: block_num_2, note_index: note.note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: note.metadata, details: None, attachments: NoteAttachments::default(), @@ -934,7 +924,7 @@ fn note_sync_across_multiple_blocks() { PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), &NoteAttachments::default(), ); - let note_header = NoteHeader::new(new_note.id(), note_metadata); + let note_header = NoteHeader::new(new_note.details_commitment(), note_metadata); let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -942,8 +932,8 @@ fn note_sync_across_multiple_blocks() { let note = NoteRecord { block_num, note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), attachments: NoteAttachments::default(), @@ -956,10 +946,10 @@ fn note_sync_across_multiple_blocks() { // Build an MMR with enough leaves to cover all blocks (0..=3). let mut mmr = Mmr::default(); for _ in 0..=3u32 { - mmr.add(Word::default()); + mmr.add(Word::default()).unwrap(); } // Use block_end + 1 as the MMR forest, same as State::sync_notes. - let mmr_forest = Forest::new(4); + let mmr_forest = Forest::new(4).unwrap(); // A single call to get_note_sync_multi should return all 3 blocks. let block_range = BlockNumber::GENESIS..=BlockNumber::from(3); @@ -1016,7 +1006,7 @@ fn note_sync_multi_respects_payload_limit() { PartialNoteMetadata::new(sender, NoteType::Public).with_tag(tag.into()), &NoteAttachments::default(), ); - let note_header = NoteHeader::new(new_note.id(), note_metadata); + let note_header = NoteHeader::new(new_note.details_commitment(), note_metadata); let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -1024,8 +1014,8 @@ fn note_sync_multi_respects_payload_limit() { let note = NoteRecord { block_num, note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), attachments: NoteAttachments::default(), @@ -1074,7 +1064,7 @@ fn note_sync_no_matching_tags() { PartialNoteMetadata::new(sender, NoteType::Public).with_tag(10u32.into()), &NoteAttachments::default(), ); - let note_header = NoteHeader::new(new_note.id(), note_metadata); + let note_header = NoteHeader::new(new_note.details_commitment(), note_metadata); let values = [(note_index, ¬e_header)]; let notes_db = BlockNoteTree::with_entries(values).unwrap(); let inclusion_path = notes_db.open(note_index); @@ -1082,8 +1072,8 @@ fn note_sync_no_matching_tags() { let note = NoteRecord { block_num, note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: note_metadata, details: Some(NoteDetails::from(&new_note)), attachments: NoteAttachments::default(), @@ -1177,9 +1167,13 @@ fn sql_account_storage_map_values_insertion() { map2.insert(key1, value3); let delta2 = BTreeMap::from_iter([(slot_name.clone(), StorageSlotDelta::Map(map2))]); let storage2 = AccountStorageDelta::from_raw(delta2); - let delta2 = - AccountDelta::new(account_id, storage2, AccountVaultDelta::default(), Felt::new(2)) - .unwrap(); + let delta2 = AccountDelta::new( + account_id, + storage2, + AccountVaultDelta::default(), + Felt::new_unchecked(2), + ) + .unwrap(); insert_account_delta(conn, account_id, block2, &delta2); let storage_map_values = queries::select_account_storage_map_values_paged( @@ -1324,7 +1318,7 @@ fn select_storage_map_sync_values_for_network_account() { create_block(&mut conn, block_num); let (account_id, _) = - make_account_and_note(&mut conn, block_num, [42u8; 32], AccountStorageMode::Network); + make_account_and_note(&mut conn, block_num, [42u8; 32], AccountType::Public); let slot_name = StorageSlotName::mock(7); let key = StorageMapKey::from_index(1); let value = num_to_word(10); @@ -1682,11 +1676,11 @@ async fn reconstruct_storage_map_from_db_returns_limit_exceeded_for_single_block // UTILITIES // ------------------------------------------------------------------------------------------- fn num_to_word(n: u64) -> Word { - [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(n)].into() + [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new_unchecked(n)].into() } fn num_to_storage_map_key(n: u64) -> StorageMapKey { - StorageMapKey::new(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new(n)])) + StorageMapKey::new(Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::new_unchecked(n)])) } fn num_to_nullifier(n: u64) -> Nullifier { @@ -1711,13 +1705,12 @@ fn create_account_with_code(code_str: &str, seed: [u8; 32]) -> Account { let component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", [AccountType::RegularAccountUpdatableCode]), + AccountComponentMetadata::new("test"), ) .unwrap(); AccountBuilder::new(seed) - .account_type(AccountType::RegularAccountUpdatableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -1737,10 +1730,7 @@ fn mock_block_transaction(account_id: AccountId, num: u64) -> TransactionHeader let input_notes = InputNotes::new_unchecked(notes); let output_notes = vec![NoteHeader::new( - NoteId::new( - Word::try_from([num, num, 0, 0]).unwrap(), - Word::try_from([0, 0, num, num]).unwrap(), - ), + NoteDetailsCommitment::from_raw(Word::try_from([num, num, 0, 0]).unwrap()), NoteMetadata::new( PartialNoteMetadata::new(account_id, NoteType::Public) .with_tag(NoteTag::new(num as u32)), @@ -1791,7 +1781,6 @@ fn insert_transactions(conn: &mut SqliteConnection) -> usize { fn mock_account_code_and_storage( account_type: AccountType, - storage_mode: AccountStorageMode, assets: impl IntoIterator, init_seed: Option<[u8; 32]>, ) -> Account { @@ -1817,13 +1806,12 @@ fn mock_account_code_and_storage( let account_component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("counter_contract", AccountType::all()), + AccountComponentMetadata::new("counter_contract"), ) .unwrap(); AccountBuilder::new(init_seed.unwrap_or([0; 32])) .account_type(account_type) - .storage_mode(storage_mode) .with_assets(assets) .with_component(account_component) .with_auth_component(AuthSingleSig::new( @@ -1847,12 +1835,7 @@ fn test_select_account_code_by_commitment() { create_block(&mut conn, block_num_1); // Create an account with code at block 1 using the existing mock function - let account = mock_account_code_and_storage( - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - [], - None, - ); + let account = mock_account_code_and_storage(AccountType::Public, [], None); // Get the code commitment and bytes before inserting let code_commitment = account.code().commitment(); @@ -1981,7 +1964,7 @@ async fn genesis_with_account_assets() { let account_component = AccountComponent::new( account_component_code, Vec::new(), - AccountComponentMetadata::new("foo", AccountType::all()), + AccountComponentMetadata::new("foo"), ) .unwrap(); @@ -1989,8 +1972,7 @@ async fn genesis_with_account_assets() { let fungible_asset = FungibleAsset::new(faucet_id, 1000).unwrap(); let account = AccountBuilder::new([1u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component) .with_assets([fungible_asset.into()]) .with_auth_component(AuthSingleSig::new( @@ -2021,11 +2003,21 @@ async fn genesis_with_account_storage_map() { let storage_map = StorageMap::with_entries(vec![ ( StorageMapKey::from_index(1u32), - Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]), + Word::from([ + Felt::new_unchecked(10), + Felt::new_unchecked(20), + Felt::new_unchecked(30), + Felt::new_unchecked(40), + ]), ), ( StorageMapKey::from_index(2u32), - Word::from([Felt::new(50), Felt::new(60), Felt::new(70), Felt::new(80)]), + Word::from([ + Felt::new_unchecked(50), + Felt::new_unchecked(60), + Felt::new_unchecked(70), + Felt::new_unchecked(80), + ]), ), ]) .unwrap(); @@ -2043,13 +2035,12 @@ async fn genesis_with_account_storage_map() { let account_component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("foo", AccountType::all()), + AccountComponentMetadata::new("foo"), ) .unwrap(); let account = AccountBuilder::new([2u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -2081,7 +2072,12 @@ async fn genesis_with_account_assets_and_storage() { let storage_map = StorageMap::with_entries(vec![( StorageMapKey::from_index(100u32), - Word::from([Felt::new(1), Felt::new(2), Felt::new(3), Felt::new(4)]), + Word::from([ + Felt::new_unchecked(1), + Felt::new_unchecked(2), + Felt::new_unchecked(3), + Felt::new_unchecked(4), + ]), )]) .unwrap(); @@ -2098,13 +2094,12 @@ async fn genesis_with_account_assets_and_storage() { let account_component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("foo", AccountType::all()), + AccountComponentMetadata::new("foo"), ) .unwrap(); let account = AccountBuilder::new([3u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component) .with_assets([fungible_asset.into()]) .with_auth_component(AuthSingleSig::new( @@ -2139,13 +2134,12 @@ async fn genesis_with_multiple_accounts() { let account_component1 = AccountComponent::new( account_component_code, Vec::new(), - AccountComponentMetadata::new("foo", AccountType::all()), + AccountComponentMetadata::new("foo"), ) .unwrap(); let account1 = AccountBuilder::new([1u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component1) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -2163,13 +2157,12 @@ async fn genesis_with_multiple_accounts() { let account_component2 = AccountComponent::new( account_component_code, Vec::new(), - AccountComponentMetadata::new("bar", AccountType::all()), + AccountComponentMetadata::new("bar"), ) .unwrap(); let account2 = AccountBuilder::new([2u8; 32]) - .account_type(AccountType::RegularAccountImmutableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component2) .with_assets([fungible_asset.into()]) .with_auth_component(AuthSingleSig::new( @@ -2181,7 +2174,12 @@ async fn genesis_with_multiple_accounts() { let storage_map = StorageMap::with_entries(vec![( StorageMapKey::from_index(5u32), - Word::from([Felt::new(15), Felt::new(25), Felt::new(35), Felt::new(45)]), + Word::from([ + Felt::new_unchecked(15), + Felt::new_unchecked(25), + Felt::new_unchecked(35), + Felt::new_unchecked(45), + ]), )]) .unwrap(); @@ -2193,13 +2191,12 @@ async fn genesis_with_multiple_accounts() { let account_component3 = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("baz", AccountType::all()), + AccountComponentMetadata::new("baz"), ) .unwrap(); let account3 = AccountBuilder::new([3u8; 32]) - .account_type(AccountType::RegularAccountUpdatableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component3) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -2234,8 +2231,7 @@ fn regression_1461_full_state_delta_inserts_vault_assets() { let fungible_asset = FungibleAsset::new(faucet_id, 5000).unwrap(); let account = mock_account_code_and_storage( - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, + AccountType::Public, [fungible_asset.into()], Some([42u8; 32]), ); @@ -2308,7 +2304,7 @@ fn serialization_symmetry_core_types() { assert_eq!(tx_id, restored, "TransactionId serialization must be symmetric"); // NoteId - let note_id = NoteId::new(num_to_word(1), num_to_word(2)); + let note_id = NoteId::from_raw(num_to_word(1)); let bytes = note_id.to_bytes(); let restored = NoteId::read_from_bytes(&bytes).unwrap(); assert_eq!(note_id, restored, "NoteId serialization must be symmetric"); @@ -2326,7 +2322,7 @@ fn serialization_symmetry_block_header() { num_to_word(7), num_to_word(8), num_to_word(9), - SecretKey::new().public_key(), + SigningKey::new().public_key(), test_fee_params(), 11_u8.into(), ); @@ -2350,12 +2346,7 @@ fn serialization_symmetry_assets() { #[test] fn serialization_symmetry_account_code() { - let account = mock_account_code_and_storage( - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - [], - None, - ); + let account = mock_account_code_and_storage(AccountType::Public, [], None); let code = account.code(); let bytes = code.to_bytes(); @@ -2397,8 +2388,7 @@ fn serialization_symmetry_nullifier_vec() { #[test] fn serialization_symmetry_note_id_vec() { - let note_ids: Vec = - (0..5).map(|i| NoteId::new(num_to_word(i), num_to_word(i + 100))).collect(); + let note_ids: Vec = (0..5).map(|i| NoteId::from_raw(num_to_word(i))).collect(); let bytes = note_ids.to_bytes(); let restored: Vec = Deserializable::read_from_bytes(&bytes).unwrap(); assert_eq!(note_ids, restored, "Vec serialization must be symmetric"); @@ -2419,13 +2409,13 @@ fn db_roundtrip_block_header() { num_to_word(7), num_to_word(8), num_to_word(9), - SecretKey::new().public_key(), + SigningKey::new().public_key(), test_fee_params(), 11_u8.into(), ); // Insert - let dummy_signature = SecretKey::new().sign(block_header.commitment()); + let dummy_signature = SigningKey::new().sign(block_header.commitment()); queries::insert_block_header(&mut conn, &block_header, &dummy_signature).unwrap(); // Retrieve @@ -2466,12 +2456,7 @@ fn db_roundtrip_account() { let block_num = BlockNumber::from(1); create_block(&mut conn, block_num); - let account = mock_account_code_and_storage( - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - [], - Some([99u8; 32]), - ); + let account = mock_account_code_and_storage(AccountType::Public, [], Some([99u8; 32])); let account_id = account.id(); let account_commitment = account.to_commitment(); @@ -2517,8 +2502,8 @@ fn db_roundtrip_notes() { let note = NoteRecord { block_num, note_index, + details_commitment: new_note.details_commitment().as_word(), note_id: new_note.id().as_word(), - note_commitment: new_note.commitment(), metadata: *new_note.metadata(), details: Some(NoteDetails::from(&new_note)), attachments: new_note.attachments().clone(), @@ -2538,8 +2523,8 @@ fn db_roundtrip_notes() { assert_eq!(note.note_id, retrieved_note.note_id, "NoteId DB roundtrip must be symmetric"); assert_eq!( - note.note_commitment, retrieved_note.note_commitment, - "Note commitment DB roundtrip must be symmetric" + note.details_commitment, retrieved_note.details_commitment, + "Details commitment DB roundtrip must be symmetric" ); assert_eq!( note.metadata, retrieved_note.metadata, @@ -2652,11 +2637,21 @@ fn db_roundtrip_account_storage_with_maps() { let storage_map = StorageMap::with_entries(vec![ ( StorageMapKey::from_index(1u32), - Word::from([Felt::new(10), Felt::new(20), Felt::new(30), Felt::new(40)]), + Word::from([ + Felt::new_unchecked(10), + Felt::new_unchecked(20), + Felt::new_unchecked(30), + Felt::new_unchecked(40), + ]), ), ( StorageMapKey::from_index(2u32), - Word::from([Felt::new(50), Felt::new(60), Felt::new(70), Felt::new(80)]), + Word::from([ + Felt::new_unchecked(50), + Felt::new_unchecked(60), + Felt::new_unchecked(70), + Felt::new_unchecked(80), + ]), ), ]) .unwrap(); @@ -2674,13 +2669,12 @@ fn db_roundtrip_account_storage_with_maps() { let account_component = AccountComponent::new( account_component_code, component_storage, - AccountComponentMetadata::new("test", AccountType::all()), + AccountComponentMetadata::new("test"), ) .unwrap(); let account = AccountBuilder::new([50u8; 32]) - .account_type(AccountType::RegularAccountUpdatableCode) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_component(account_component) .with_auth_component(AuthSingleSig::new( PublicKeyCommitment::from(EMPTY_WORD), @@ -2762,7 +2756,7 @@ fn db_roundtrip_note_metadata_attachment() { create_block(&mut conn, block_num); let (account_id, _) = - make_account_and_note(&mut conn, block_num, [1u8; 32], AccountStorageMode::Network); + make_account_and_note(&mut conn, block_num, [1u8; 32], AccountType::Public); let target = NetworkAccountTarget::new(account_id, NoteExecutionHint::Always) .expect("NetworkAccountTarget creation should succeed for network account"); @@ -2776,8 +2770,8 @@ fn db_roundtrip_note_metadata_attachment() { let note = NoteRecord { block_num, note_index: BlockNoteIndex::new(0, 0).unwrap(), + details_commitment: num_to_word(1), note_id: num_to_word(1), - note_commitment: num_to_word(1), metadata, details: None, attachments: attachments.clone(), @@ -3194,7 +3188,7 @@ fn account_state_forest_matches_db_storage_map_roots_across_updates() { account_id, storage_2.clone(), AccountVaultDelta::default(), - Felt::new(2), + Felt::new_unchecked(2), ) .unwrap(); @@ -3224,7 +3218,7 @@ fn account_state_forest_matches_db_storage_map_roots_across_updates() { account_id, storage_3.clone(), AccountVaultDelta::default(), - Felt::new(3), + Felt::new_unchecked(3), ) .unwrap(); @@ -3357,7 +3351,7 @@ fn account_state_forest_shared_roots_not_deleted_prematurely() { account2, storage_update.clone(), AccountVaultDelta::default(), - Felt::new(2), + Felt::new_unchecked(2), ) .unwrap(); forest.update_account(block51, &delta2_update).unwrap(); @@ -3366,7 +3360,7 @@ fn account_state_forest_shared_roots_not_deleted_prematurely() { account3, storage_update.clone(), AccountVaultDelta::default(), - Felt::new(2), + Felt::new_unchecked(2), ) .unwrap(); forest.update_account(block52, &delta3_update).unwrap(); @@ -3379,9 +3373,13 @@ fn account_state_forest_shared_roots_not_deleted_prematurely() { let account1_root_after_prune = forest.get_storage_map_root(account1, &slot_name, block01); assert!(account1_root_after_prune.is_some()); - let delta1_update = - AccountDelta::new(account1, storage_update, AccountVaultDelta::default(), Felt::new(2)) - .unwrap(); + let delta1_update = AccountDelta::new( + account1, + storage_update, + AccountVaultDelta::default(), + Felt::new_unchecked(2), + ) + .unwrap(); forest.update_account(block53, &delta1_update).unwrap(); // Prune at block 53 @@ -3501,7 +3499,8 @@ fn account_state_forest_retains_latest_after_100_blocks_and_pruning() { vault_delta_51.add_asset(asset_51.into()).unwrap(); let delta_51 = - AccountDelta::new(account_id, storage_delta_51, vault_delta_51, Felt::new(51)).unwrap(); + AccountDelta::new(account_id, storage_delta_51, vault_delta_51, Felt::new_unchecked(51)) + .unwrap(); forest.update_account(block_51, &delta_51).unwrap(); @@ -3613,8 +3612,8 @@ fn db_roundtrip_transactions() { NoteRecord { block_num, note_index: BlockNoteIndex::new(0, idx).unwrap(), + details_commitment: note.details_commitment().as_word(), note_id: note.id().as_word(), - note_commitment: note.to_commitment(), metadata: *note.metadata(), details: None, attachments: NoteAttachments::default(), @@ -3639,7 +3638,7 @@ fn db_roundtrip_transactions() { .map(|(idx, note)| NoteSyncRecord { block_num, note_index: BlockNoteIndex::new(0, idx).unwrap(), - note_id: note.id().as_word(), + note_id: note.id(), metadata: *note.metadata(), inclusion_path: SparseMerklePath::default(), }) @@ -3671,7 +3670,7 @@ fn db_roundtrip_transactions() { output_note_proofs: expected_sync_records .into_iter() .map(|n| proto::note::NoteInclusionInBlockProof { - note_id: Some(n.note_id.into()), + note_id: Some((&n.note_id).into()), block_num: n.block_num.as_u32(), note_index_in_block: n.note_index.leaf_index_value().into(), inclusion_path: Some(n.inclusion_path.into()), @@ -3891,7 +3890,7 @@ fn account_state_forest_preserves_mixed_slots_independently() { account_id, storage_delta_51, AccountVaultDelta::default(), - Felt::new(51), + Felt::new_unchecked(51), ) .unwrap(); diff --git a/crates/store/src/genesis/config/errors.rs b/crates/store/src/genesis/config/errors.rs index 3a9721c8b4..983eace48a 100644 --- a/crates/store/src/genesis/config/errors.rs +++ b/crates/store/src/genesis/config/errors.rs @@ -1,15 +1,10 @@ use std::path::PathBuf; use miden_protocol::account::AccountId; -use miden_protocol::errors::{ - AccountDeltaError, - AccountError, - AssetError, - FeeError, - TokenSymbolError, -}; +use miden_protocol::errors::{AccountDeltaError, AccountError, AssetError, TokenSymbolError}; use miden_protocol::utils::serde::DeserializationError; use miden_standards::account::faucets::FungibleFaucetError; +use miden_standards::account::policies::TokenPolicyManagerError; use miden_standards::account::wallets::BasicWalletError; use crate::genesis::config::TokenSymbolStr; @@ -64,8 +59,6 @@ pub enum GenesisConfigError { IssuanceOverflow, #[error("missing fee faucet for native asset {0}")] MissingFeeFaucet(TokenSymbolStr), - #[error("fee error")] - FeeError(#[from] FeeError), #[error("faucet account of {0} is not a fungible faucet")] NativeAssetFaucetIsNotPublic(TokenSymbolStr), #[error("faucet account of {0} is not public")] @@ -74,4 +67,6 @@ pub enum GenesisConfigError { InvalidSecretKey(#[from] DeserializationError), #[error("provided signer config is not supported")] UnsupportedSignerConfig, + #[error("token policy manager error")] + TokenPolicyManager(#[from] TokenPolicyManagerError), } diff --git a/crates/store/src/genesis/config/mod.rs b/crates/store/src/genesis/config/mod.rs index 782bfe39c6..b7c897a701 100644 --- a/crates/store/src/genesis/config/mod.rs +++ b/crates/store/src/genesis/config/mod.rs @@ -14,7 +14,6 @@ use miden_protocol::account::{ AccountFile, AccountId, AccountStorageDelta, - AccountStorageMode, AccountType, AccountVaultDelta, FungibleAssetDelta, @@ -25,14 +24,14 @@ use miden_protocol::block::FeeParameters; use miden_protocol::crypto::dsa::ecdsa_k256_keccak::PublicKey; use miden_protocol::crypto::dsa::falcon512_poseidon2::SecretKey as RpoSecretKey; use miden_protocol::errors::TokenSymbolError; -use miden_protocol::{Felt, ONE, Word}; +use miden_protocol::{Felt, ONE}; use miden_standards::AuthMethod; use miden_standards::account::auth::AuthSingleSig; use miden_standards::account::faucets::{FungibleFaucet, TokenName}; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, - PolicyAuthority, + PolicyRegistration, TokenPolicyManager, }; use miden_standards::account::wallets::create_basic_wallet; @@ -212,7 +211,7 @@ impl GenesisConfig { } let fee_parameters = - FeeParameters::new(native_faucet_account_id, fee_parameters.verification_base_fee)?; + FeeParameters::new(native_faucet_account_id, fee_parameters.verification_base_fee); // Track all adjustments, one per faucet account id let mut faucet_issuance = IndexMap::::new(); @@ -220,8 +219,7 @@ impl GenesisConfig { let zero_padding_width = usize::ilog10(std::cmp::max(10, wallet_configs.len())) as usize; // Setup all wallet accounts, which reference the faucet's for their provided assets. - for (index, WalletConfig { has_updatable_code, storage_mode, assets }) in - wallet_configs.into_iter().enumerate() + for (index, WalletConfig { storage_mode, assets }) in wallet_configs.into_iter().enumerate() { tracing::debug!(index, assets = ?assets, "Adding wallet account"); @@ -232,14 +230,7 @@ impl GenesisConfig { }; let init_seed: [u8; 32] = rng.random(); - let account_type = if has_updatable_code { - AccountType::RegularAccountUpdatableCode - } else { - AccountType::RegularAccountImmutableCode - }; - let account_storage_mode = storage_mode.into(); - let mut wallet_account = - create_basic_wallet(init_seed, auth, account_type, account_storage_mode)?; + let mut wallet_account = create_basic_wallet(init_seed, auth, storage_mode.into())?; // Add fungible assets and track the faucet adjustments per faucet/asset. let wallet_fungible_asset_update = @@ -289,7 +280,7 @@ impl GenesisConfig { if total_issuance != 0 { let current_faucet = FungibleFaucet::try_from(faucet_account.storage())?; let new_token_supply = AssetAmount::new(total_issuance)?; - let max_supply = current_faucet.max_supply().as_canonical_u64(); + let max_supply = current_faucet.max_supply().as_u64(); if max_supply < total_issuance { return Err(GenesisConfigError::MaxIssuanceExceeded { max_supply, @@ -297,14 +288,9 @@ impl GenesisConfig { total_issuance, }); } - let new_token_config = Word::new([ - Felt::from(new_token_supply), - current_faucet.max_supply(), - Felt::from(current_faucet.decimals()), - Felt::from(current_faucet.symbol()), - ]); - storage_delta - .set_item(FungibleFaucet::token_config_slot().clone(), new_token_config)?; + let updated_faucet = current_faucet.with_token_supply(new_token_supply)?; + let slot = updated_faucet.token_config_slot_value(); + storage_delta.set_item(slot.name().clone(), slot.value())?; tracing::debug!( "Reducing faucet account {faucet} for {symbol} by {amount}", faucet = faucet_id.to_hex(), @@ -330,7 +316,7 @@ impl GenesisConfig { // sanity check the total issuance against let faucet = FungibleFaucet::try_from(faucet_account.storage())?; - let max_supply = faucet.max_supply().as_canonical_u64(); + let max_supply = faucet.max_supply().as_u64(); if max_supply < total_issuance { return Err(GenesisConfigError::MaxIssuanceExceeded { max_supply, @@ -408,12 +394,9 @@ impl NativeFaucetConfig { .map_err(|e| GenesisConfigError::AccountFileRead(e, full_path.clone()))?; let account = account_file.account; - if account.id().account_type() != AccountType::FungibleFaucet { - return Err(GenesisConfigError::NativeFaucetNotFungible { path: full_path }); - } - - let faucet = FungibleFaucet::try_from(account.storage()) - .expect("validated as fungible faucet above"); + let faucet = FungibleFaucet::try_from(&account).map_err(|_| { + GenesisConfigError::NativeFaucetNotFungible { path: full_path.clone() } + })?; let symbol = TokenSymbolStr::from(faucet.symbol().clone()); Ok((account, symbol, None)) }, @@ -466,15 +449,14 @@ impl FungibleFaucetConfig { // It's similar to `fn create_basic_fungible_faucet`, but we need to cover more cases. let faucet_account = AccountBuilder::new(init_seed) - .account_type(AccountType::FungibleFaucet) - .storage_mode(storage_mode.into()) + .account_type(storage_mode.into()) .with_auth_component(auth) .with_component(faucet) - .with_components(TokenPolicyManager::new( - PolicyAuthority::AuthControlled, - MintPolicyConfig::AllowAll, - BurnPolicyConfig::AllowAll, - )) + .with_components( + TokenPolicyManager::new() + .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?, + ) .build()?; debug_assert_eq!(faucet_account.nonce(), Felt::ZERO); @@ -490,8 +472,6 @@ impl FungibleFaucetConfig { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[serde(deny_unknown_fields)] pub struct WalletConfig { - #[serde(default)] - has_updatable_code: bool, #[serde(default)] storage_mode: StorageMode, assets: Vec, @@ -511,24 +491,20 @@ struct AssetEntry { /// for details #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Default)] pub enum StorageMode { - /// Monitor for `Notes` related to the account, in addition to being `Public`. - #[serde(alias = "network")] - #[default] - Network, /// A publicly stored account, lives on-chain. #[serde(alias = "public")] Public, /// A private account, which must be known by interactors. #[serde(alias = "private")] + #[default] Private, } -impl From for AccountStorageMode { - fn from(mode: StorageMode) -> AccountStorageMode { +impl From for AccountType { + fn from(mode: StorageMode) -> AccountType { match mode { - StorageMode::Network => AccountStorageMode::Network, - StorageMode::Private => AccountStorageMode::Private, - StorageMode::Public => AccountStorageMode::Public, + StorageMode::Public => AccountType::Public, + StorageMode::Private => AccountType::Private, } } } diff --git a/crates/store/src/genesis/config/samples/01-simple.toml b/crates/store/src/genesis/config/samples/01-simple.toml index 2d7af48849..50f85cd164 100644 --- a/crates/store/src/genesis/config/samples/01-simple.toml +++ b/crates/store/src/genesis/config/samples/01-simple.toml @@ -13,12 +13,10 @@ symbol = "WHAT" [[wallet]] assets = [{ amount = 999_000, symbol = "MIDEN" }] storage_mode = "private" -# has_updatable_code = false # default value [[wallet]] assets = [{ amount = 777, symbol = "MIDEN" }] storage_mode = "private" -# has_updatable_code = false # default value [[wallet]] assets = [{ amount = 1, symbol = "WHAT" }] diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac index 6ee9d158a3..1e6a756be9 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_eth.mac differ diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac index 5631e66290..2c9886c6c0 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/agglayer_faucet_usdc.mac differ diff --git a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac index bb22ae3745..ad6425b51c 100644 Binary files a/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac and b/crates/store/src/genesis/config/samples/02-with-account-files/bridge.mac differ diff --git a/crates/store/src/genesis/config/tests.rs b/crates/store/src/genesis/config/tests.rs index 743523186e..84858e8d54 100644 --- a/crates/store/src/genesis/config/tests.rs +++ b/crates/store/src/genesis/config/tests.rs @@ -4,7 +4,7 @@ use std::path::Path; use assert_matches::assert_matches; use miden_protocol::ONE; use miden_protocol::account::delta::AccountUpdateDetails; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SecretKey; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::SigningKey; use super::*; @@ -27,7 +27,7 @@ fn parsing_yields_expected_default_values() -> TestResult { let config_path = write_toml_file(temp_dir.path(), sample_content); let gcfg = GenesisConfig::read_toml_file(&config_path)?; - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (state, _secrets) = gcfg.into_state(signer.public_key())?; let _ = state; // faucets always precede wallet accounts @@ -36,9 +36,9 @@ fn parsing_yields_expected_default_values() -> TestResult { let wallet1 = state.accounts[2].clone(); let wallet2 = state.accounts[3].clone(); - assert!(native_faucet.is_faucet()); - assert!(wallet1.is_regular_account()); - assert!(wallet2.is_regular_account()); + assert!(FungibleFaucet::try_from(&native_faucet).is_ok()); + assert!(FungibleFaucet::try_from(&wallet1).is_err()); + assert!(FungibleFaucet::try_from(&wallet2).is_err()); assert_eq!(native_faucet.nonce(), ONE); assert_eq!(wallet1.nonce(), ONE); @@ -47,22 +47,26 @@ fn parsing_yields_expected_default_values() -> TestResult { { let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); - assert_eq!(faucet.max_supply(), Felt::new(100_000_000_000_000_000)); + assert_eq!(faucet.max_supply().as_u64(), 100_000_000_000_000_000); assert_eq!(faucet.decimals(), 6); assert_eq!(*faucet.symbol(), TokenSymbol::new("MIDEN").unwrap()); } // check account balance, and ensure ordering is retained - assert_matches!(wallet1.vault().get_balance(native_faucet.id()), Ok(val) => { - assert_eq!(val, 999_000); + let faucet_vault_key = miden_protocol::asset::AssetVaultKey::new_fungible( + native_faucet.id(), + miden_protocol::asset::AssetCallbackFlag::Disabled, + ); + assert_matches!(wallet1.vault().get_balance(faucet_vault_key), Ok(val) => { + assert_eq!(val.as_u64(), 999_000); }); - assert_matches!(wallet2.vault().get_balance(native_faucet.id()), Ok(val) => { - assert_eq!(val, 777); + assert_matches!(wallet2.vault().get_balance(faucet_vault_key), Ok(val) => { + assert_eq!(val.as_u64(), 777); }); // check total issuance of the faucet let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); - assert_eq!(faucet.token_supply(), Felt::new(999_777), "Issuance mismatch"); + assert_eq!(faucet.token_supply().as_u64(), 999_777, "Issuance mismatch"); Ok(()) } @@ -71,7 +75,7 @@ fn parsing_yields_expected_default_values() -> TestResult { #[miden_node_test_macro::enable_logging] async fn genesis_accounts_have_nonce_one() -> TestResult { let gcfg = GenesisConfig::default(); - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (state, secrets) = gcfg.into_state(signer.public_key()).unwrap(); let mut iter = secrets.as_account_files(&state); let AccountFileWithName { account_file: status_quo, .. } = iter.next().unwrap().unwrap(); @@ -86,7 +90,7 @@ async fn genesis_accounts_have_nonce_one() -> TestResult { #[test] fn parsing_account_from_file() -> TestResult { use miden_protocol::account::auth::AuthScheme; - use miden_protocol::account::{AccountFile, AccountStorageMode, AccountType}; + use miden_protocol::account::{AccountFile, AccountType}; use miden_standards::AuthMethod; use miden_standards::account::wallets::create_basic_wallet; use tempfile::tempdir; @@ -105,12 +109,7 @@ fn parsing_account_from_file() -> TestResult { approver: (secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2), }; - let test_account = create_basic_wallet( - init_seed, - auth, - AccountType::RegularAccountUpdatableCode, - AccountStorageMode::Public, - )?; + let test_account = create_basic_wallet(init_seed, auth, AccountType::Public)?; let account_id = test_account.id(); @@ -136,7 +135,7 @@ path = "test_account.mac" let gcfg = GenesisConfig::read_toml_file(&config_path)?; // Convert to state and verify the account is included - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (state, _secrets) = gcfg.into_state(signer.public_key())?; assert!(state.accounts.iter().any(|a| a.id() == account_id)); @@ -146,13 +145,13 @@ path = "test_account.mac" #[test] fn parsing_native_faucet_from_file() -> TestResult { use miden_protocol::account::auth::AuthScheme; - use miden_protocol::account::{AccountBuilder, AccountFile, AccountStorageMode, AccountType}; + use miden_protocol::account::{AccountBuilder, AccountFile, AccountType}; use miden_protocol::asset::AssetAmount; use miden_standards::account::auth::AuthSingleSig; use miden_standards::account::policies::{ BurnPolicyConfig, MintPolicyConfig, - PolicyAuthority, + PolicyRegistration, TokenPolicyManager, }; use tempfile::tempdir; @@ -177,15 +176,14 @@ fn parsing_native_faucet_from_file() -> TestResult { .build()?; let faucet_account = AccountBuilder::new(init_seed) - .account_type(AccountType::FungibleFaucet) - .storage_mode(AccountStorageMode::Public) + .account_type(AccountType::Public) .with_auth_component(auth) .with_component(faucet) - .with_components(TokenPolicyManager::new( - PolicyAuthority::AuthControlled, - MintPolicyConfig::AllowAll, - BurnPolicyConfig::AllowAll, - )) + .with_components( + TokenPolicyManager::new() + .with_mint_policy(MintPolicyConfig::AllowAll, PolicyRegistration::Active)? + .with_burn_policy(BurnPolicyConfig::AllowAll, PolicyRegistration::Active)?, + ) .build()?; let faucet_id = faucet_account.id(); @@ -211,7 +209,7 @@ verification_base_fee = 0 let gcfg = GenesisConfig::read_toml_file(&config_path)?; // Convert to state and verify the native faucet is included - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (state, secrets) = gcfg.into_state(signer.public_key())?; assert!(state.accounts.iter().any(|a| a.id() == faucet_id)); @@ -224,7 +222,7 @@ verification_base_fee = 0 #[test] fn native_faucet_from_file_must_be_faucet_type() -> TestResult { use miden_protocol::account::auth::AuthScheme; - use miden_protocol::account::{AccountFile, AccountStorageMode, AccountType}; + use miden_protocol::account::{AccountFile, AccountType}; use miden_standards::AuthMethod; use miden_standards::account::wallets::create_basic_wallet; use tempfile::tempdir; @@ -243,12 +241,7 @@ fn native_faucet_from_file_must_be_faucet_type() -> TestResult { approver: (secret_key.public_key().into(), AuthScheme::Falcon512Poseidon2), }; - let regular_account = create_basic_wallet( - init_seed, - auth, - AccountType::RegularAccountImmutableCode, - AccountStorageMode::Public, - )?; + let regular_account = create_basic_wallet(init_seed, auth, AccountType::Public)?; // Save to file let account_file_path = config_dir.join("not_a_faucet.mac"); @@ -271,7 +264,7 @@ verification_base_fee = 0 let gcfg = GenesisConfig::read_toml_file(&config_path)?; // into_state should fail with NativeFaucetNotFungible error when loading the file - let result = gcfg.into_state(SecretKey::new().public_key()); + let result = gcfg.into_state(SigningKey::new().public_key()); assert!(result.is_err()); let err = result.unwrap_err(); assert!( @@ -304,7 +297,7 @@ path = "does_not_exist.mac" let gcfg = GenesisConfig::read_toml_file(&config_path).unwrap(); // into_state should fail with AccountFileRead error when loading the file - let result = gcfg.into_state(SecretKey::new().public_key()); + let result = gcfg.into_state(SigningKey::new().public_key()); assert!(result.is_err()); let err = result.unwrap_err(); assert!( @@ -316,14 +309,12 @@ path = "does_not_exist.mac" #[tokio::test] #[miden_node_test_macro::enable_logging] async fn parsing_agglayer_sample_with_account_files() -> TestResult { - use miden_protocol::account::AccountType; - // Use the actual sample file path since it references relative .mac files let sample_path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("src/genesis/config/samples/02-with-account-files.toml"); let gcfg = GenesisConfig::read_toml_file(&sample_path)?; - let signer = SecretKey::new(); + let signer = SigningKey::new(); let (state, secrets) = gcfg.into_state(signer.public_key())?; // Should have 4 accounts: @@ -340,36 +331,26 @@ async fn parsing_agglayer_sample_with_account_files() -> TestResult { let usdc_faucet = &state.accounts[3]; // Native faucet should be a fungible faucet (built from parameters) - assert_eq!( - native_faucet.id().account_type(), - AccountType::FungibleFaucet, - "Native faucet should be a FungibleFaucet" - ); - - // Verify native faucet symbol - { - let faucet = FungibleFaucet::try_from(native_faucet.storage()).unwrap(); - assert_eq!(*faucet.symbol(), TokenSymbol::new("MIDEN").unwrap()); - } + let native_faucet_handle = + FungibleFaucet::try_from(native_faucet).expect("Native faucet should be a FungibleFaucet"); + assert_eq!(*native_faucet_handle.symbol(), TokenSymbol::new("MIDEN").unwrap()); - // Bridge account is a regular account (not a faucet) + // Bridge account is not a fungible faucet assert!( - bridge_account.is_regular_account(), - "Bridge account should be a regular account" + FungibleFaucet::try_from(bridge_account).is_err(), + "Bridge account should not be a fungible faucet" ); - // ETH faucet should be a fungible faucet (AggLayer faucet loaded from file) - assert_eq!( - eth_faucet.id().account_type(), - AccountType::FungibleFaucet, - "ETH faucet should be a FungibleFaucet" + // ETH faucet should be an AggLayer faucet loaded from file + assert!( + miden_agglayer::AggLayerFaucet::try_faucet_from_account(eth_faucet).is_ok(), + "ETH faucet should be an AggLayer faucet" ); - // USDC faucet should be a fungible faucet (AggLayer faucet loaded from file) - assert_eq!( - usdc_faucet.id().account_type(), - AccountType::FungibleFaucet, - "USDC faucet should be a FungibleFaucet" + // USDC faucet should be an AggLayer faucet loaded from file + assert!( + miden_agglayer::AggLayerFaucet::try_faucet_from_account(usdc_faucet).is_ok(), + "USDC faucet should be an AggLayer faucet" ); // Only the native faucet generates a secret (built from parameters) @@ -378,7 +359,7 @@ async fn parsing_agglayer_sample_with_account_files() -> TestResult { // Verify the genesis state can be converted to a block let block = state.into_block(&signer)?; - // Verify that non-private accounts (Public and Network) get full Delta details. + // Verify that non-private (Public) accounts get full Delta details. for update in block.inner().body().updated_accounts() { let is_private = update.account_id().is_private(); match update.details() { diff --git a/crates/store/src/genesis/mod.rs b/crates/store/src/genesis/mod.rs index 9de141eade..a9c2ace5e4 100644 --- a/crates/store/src/genesis/mod.rs +++ b/crates/store/src/genesis/mod.rs @@ -11,7 +11,7 @@ use miden_protocol::block::{ FeeParameters, SignedBlock, }; -use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, SecretKey, Signature}; +use miden_protocol::crypto::dsa::ecdsa_k256_keccak::{PublicKey, Signature, SigningKey}; use miden_protocol::crypto::merkle::mmr::{Forest, MmrPeaks}; use miden_protocol::crypto::merkle::smt::{LargeSmt, MemoryStorage, Smt}; use miden_protocol::errors::AccountError; @@ -174,7 +174,7 @@ impl GenesisState { } /// Builds and signs the genesis block with a local secret key. - pub fn into_block(self, signer: &SecretKey) -> anyhow::Result { + pub fn into_block(self, signer: &SigningKey) -> anyhow::Result { let unsigned_block = self.into_unsigned_block()?; let signature = signer.sign(unsigned_block.header().commitment()); unsigned_block.into_block(signature) diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index a2a30d4c84..5ceb432353 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -24,6 +24,49 @@ pub fn default_sqlite_connection_pool_size() -> std::num::NonZeroUsize { DatabaseOptions::default().connection_pool_size } +/// Test-only helpers exposed for downstream integration tests. +/// +/// This module is hidden from public docs and not part of the stable API. It exists so +/// integration tests in sibling crates (e.g. `miden-node-rpc`) can seed network-account +/// rows directly into the store's SQLite database without us widening the visibility of +/// internal diesel types. +#[doc(hidden)] +pub mod test_support { + use std::path::Path; + + use diesel::prelude::*; + use miden_protocol::Word; + use miden_protocol::account::AccountId; + use miden_protocol::block::BlockNumber; + + use crate::db::models::queries::{AccountRowInsert, NetworkAccountType}; + use crate::db::schema; + + /// Opens a fresh connection to the store's SQLite database and inserts a private + /// network-account row for `account_id`, marking it as a network account in the + /// latest state at block 0. + /// + /// Intended for integration tests that need to exercise the network-account gate + /// without running a transaction through the block producer. The store's WAL mode + /// makes a secondary connection safe. + pub fn seed_network_account(db_path: &Path, account_id: AccountId) { + let mut conn = SqliteConnection::establish(db_path.to_str().expect("db path is utf-8")) + .expect("connect to store sqlite"); + + let row = AccountRowInsert::new_private( + account_id, + NetworkAccountType::Network, + Word::default(), + BlockNumber::from(0), + BlockNumber::from(0), + ); + diesel::insert_into(schema::accounts::table) + .values(&row) + .execute(&mut conn) + .expect("insert network account row"); + } +} + // CONSTANTS // ================================================================================================= const COMPONENT: &str = "miden-store"; diff --git a/crates/store/src/server/api.rs b/crates/store/src/server/api.rs index c5d393eb65..406df76631 100644 --- a/crates/store/src/server/api.rs +++ b/crates/store/src/server/api.rs @@ -96,7 +96,7 @@ impl StoreApi { for note in batch.input_notes().iter() { if let Some(header) = note.header() { - unauthenticated_note_commitments.insert(header.to_commitment()); + unauthenticated_note_commitments.insert(header.id().as_word()); } } } diff --git a/crates/store/src/server/rpc_api.rs b/crates/store/src/server/rpc_api.rs index 99158a1ece..c854c61330 100644 --- a/crates/store/src/server/rpc_api.rs +++ b/crates/store/src/server/rpc_api.rs @@ -252,7 +252,7 @@ impl rpc_server::Rpc for StoreApi { SyncAccountVaultError, >(request.account_id)?; - if !account_id.has_public_state() { + if !account_id.is_public() { return Err(SyncAccountVaultError::AccountNotPublic(account_id).into()); } @@ -305,7 +305,7 @@ impl rpc_server::Rpc for StoreApi { SyncAccountStorageMapsError, >(request.account_id)?; - if !account_id.has_public_state() { + if !account_id.is_public() { Err(SyncAccountStorageMapsError::AccountNotPublic(account_id))?; } @@ -374,6 +374,16 @@ impl rpc_server::Rpc for StoreApi { })) } + async fn are_network_accounts( + &self, + request: Request, + ) -> Result, Status> { + let ids = read_account_ids::(request.into_inner().account_ids)?; + let subset = self.state.network_accounts_subset(&ids).await?; + let network_account_ids = subset.into_iter().map(proto::account::AccountId::from).collect(); + Ok(Response::new(proto::store::NetworkAccountIdSubset { network_account_ids })) + } + async fn sync_transactions( &self, request: Request, diff --git a/crates/store/src/state/apply_block.rs b/crates/store/src/state/apply_block.rs index 2cf12a1b10..98abe40e85 100644 --- a/crates/store/src/state/apply_block.rs +++ b/crates/store/src/state/apply_block.rs @@ -318,8 +318,8 @@ impl State { let note_record = NoteRecord { block_num, note_index, + details_commitment: note.details_commitment().as_word(), note_id: note.id().as_word(), - note_commitment: note.to_commitment(), metadata: *note.metadata(), details, attachments, diff --git a/crates/store/src/state/loader.rs b/crates/store/src/state/loader.rs index 72df4fff34..6c64dc1d8b 100644 --- a/crates/store/src/state/loader.rs +++ b/crates/store/src/state/loader.rs @@ -17,7 +17,7 @@ use miden_crypto::merkle::smt::{Backend, ForestInMemoryBackend}; #[cfg(feature = "rocksdb")] use miden_crypto::merkle::smt::{ForestPersistentBackend, PersistentBackendConfig}; #[cfg(feature = "rocksdb")] -use miden_large_smt_backend_rocksdb::RocksDbStorage; +use miden_large_smt_backend_rocksdb::{RocksDbStorage, SmtStorageReader}; #[cfg(feature = "rocksdb")] use miden_node_utils::clap::RocksDbOptions; use miden_protocol::account::{AccountId, AccountStorageHeader, StorageSlotType}; @@ -465,9 +465,10 @@ pub async fn load_mmr(db: &mut Db) -> Result Result, DatabaseError> { + self.db.select_network_accounts_subset(account_ids.to_vec()).await + } + /// Returns network account IDs within the specified block range (based on account creation /// block). /// @@ -797,7 +805,7 @@ impl State { ) -> Result { let AccountRequest { block_num, account_id, details } = account_request; - if details.is_some() && !account_id.has_public_state() { + if details.is_some() && !account_id.is_public() { return Err(GetAccountError::AccountNotPublic(account_id)); } @@ -929,7 +937,7 @@ impl State { storage_requests, } = detail_request; - if !account_id.has_public_state() { + if !account_id.is_public() { return Err(GetAccountError::AccountNotPublic(account_id)); } diff --git a/crates/store/src/state/sync_state.rs b/crates/store/src/state/sync_state.rs index dc890571a4..d2f8fc0940 100644 --- a/crates/store/src/state/sync_state.rs +++ b/crates/store/src/state/sync_state.rs @@ -46,7 +46,7 @@ impl State { if block_from == block_to { return Ok(( MmrDelta { - forest: Forest::new(block_from.as_usize()), + forest: Forest::new(block_from.as_usize()).expect("block index fits in u32"), data: vec![], }, block_header, @@ -72,7 +72,10 @@ impl State { .await .blockchain .as_mmr() - .get_delta(Forest::new(from_forest), Forest::new(to_forest)) + .get_delta( + Forest::new(from_forest).expect("from_forest fits in u32"), + Forest::new(to_forest).expect("to_forest fits in u32"), + ) .map_err(StateSyncError::FailedToBuildMmrDelta)?; Ok((mmr_delta, block_header, signature)) diff --git a/crates/utils/src/crypto.rs b/crates/utils/src/crypto.rs index d203ff75a0..baa2229dc2 100644 --- a/crates/utils/src/crypto.rs +++ b/crates/utils/src/crypto.rs @@ -5,7 +5,7 @@ use rand::Rng; /// Creates a new RPO Random Coin with random seed pub fn get_rpo_random_coin(rng: &mut T) -> RandomCoin { let auth_seed: [u64; 4] = rng.random(); - let rng_seed = Word::from(auth_seed.map(Felt::new)); + let rng_seed = Word::from(auth_seed.map(Felt::new_unchecked)); RandomCoin::new(rng_seed) } diff --git a/crates/utils/src/fee.rs b/crates/utils/src/fee.rs index 33c4472bad..1e2b1fd786 100644 --- a/crates/utils/src/fee.rs +++ b/crates/utils/src/fee.rs @@ -12,5 +12,5 @@ pub fn test_fee() -> FungibleAsset { /// Derive the default fee parameters, compatible with [`fn test_fee`]. pub fn test_fee_params() -> FeeParameters { let faucet = ACCOUNT_ID_FEE_FAUCET.try_into().unwrap(); - FeeParameters::new(faucet, 0).unwrap() + FeeParameters::new(faucet, 0) } diff --git a/docs/external/src/operator/usage.md b/docs/external/src/operator/usage.md index beac148c31..d2bb9e2e7f 100644 --- a/docs/external/src/operator/usage.md +++ b/docs/external/src/operator/usage.md @@ -85,8 +85,6 @@ storage_mode = "public" assets = [{ amount = 999_000_000, symbol = "FUZZY" }] # Storage mode of the wallet account. storage_mode = "private" -# The code of the account can be updated or not. -# has_updatable_code = false # default value ``` To include pre-built accounts (e.g. bridge or wrapped-asset faucets) in the genesis block, use diff --git a/proto/proto/internal/store.proto b/proto/proto/internal/store.proto index 04fb41bb25..c34c06e1ff 100644 --- a/proto/proto/internal/store.proto +++ b/proto/proto/internal/store.proto @@ -21,6 +21,15 @@ service Rpc { // Returns the latest details the specified account. rpc GetAccount(rpc.AccountRequest) returns (rpc.AccountResponse) {} + // Filters the provided account ids down to the subset that are currently + // classified as network accounts. Ids that don't exist in the store, or that + // aren't network accounts, are omitted. The order of `network_account_ids` + // is unspecified. + // + // Callers checking a single id should pass a one-element list and check whether + // the response is non-empty. + rpc AreNetworkAccounts(account.AccountIdList) returns (NetworkAccountIdSubset) {} + // Returns raw block data for the specified block number, optionally including the block proof. rpc GetBlockByNumber(blockchain.BlockRequest) returns (blockchain.MaybeBlock) {} @@ -327,6 +336,16 @@ message MaybeAccountDetails { optional account.AccountDetails details = 1; } +// ARE NETWORK ACCOUNTS +// ================================================================================================ + +// Represents the subset of the provided account ids that are currently classified +// as network accounts. The order of `network_account_ids` is unspecified. +message NetworkAccountIdSubset { + // The network-account subset of the requested ids. + repeated account.AccountId network_account_ids = 1; +} + // GET UNCONSUMED NETWORK NOTES // ================================================================================================ diff --git a/proto/proto/types/account.proto b/proto/proto/types/account.proto index 8fd9729a51..6df8725658 100644 --- a/proto/proto/types/account.proto +++ b/proto/proto/types/account.proto @@ -16,6 +16,12 @@ message AccountId { bytes id = 1; } +// A list of account IDs. +message AccountIdList { + // The list of account IDs. + repeated AccountId account_ids = 1; +} + // The state of an account at a specific block height. message AccountSummary { // The account ID.