From c350fd0e44e71e72fd1f5abee86f19bf99c682dd Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 07:42:57 +0700 Subject: [PATCH 1/7] fix: bump grovedb to restore v11 subtree-insert consensus (block 245,344) drive 4.0.0-beta.2 upgraded grovedb to a18f7929, which (via grovedb PR #752) changed the non-batch insert dispatch so ProvableCountSumTree / CountSumTree / ProvableCountTree are written as layered subtrees (Op::PutLayeredReference) instead of plain values (Op::Put). That changes the parent node's value_hash and thus the app-hash. These tree types are created on the v11 activation (transition_to_version_11), so a beta.2 node computes a different app-hash than the v4.1.0-era binaries that produced the canonical chain, and stalls at the v11 activation block (testnet height 245,344: drive computes 98DD9B vs the canonical 29B639) crash-looping instead of syncing. Bump grovedb a18f7929 -> e5a904d7 (dashpay/grovedb fix), restoring the v4.1.0 Op::Put dispatch. No drive code changes. Adds a regression test pinning the post-insert grovedb root. Verified on testnet hp-mn-7: the fixed image computes 29B639 for block 245,344 and syncs past it (245,344 -> 245,861+), where beta.2 was stuck. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 30 +++---- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +- packages/rs-drive/Cargo.toml | 12 +-- .../grove_insert_if_not_exists/mod.rs | 80 +++++++++++++++++++ packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 8 files changed, 109 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7022fb027f..99d93fb9fd8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2689,7 +2689,7 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "axum 0.8.9", "bincode", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "blake3", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "integer-encoding", "intmap", @@ -2769,7 +2769,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "blake3", @@ -2782,7 +2782,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "bincode_derive", @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "grovedb-costs", "hex", @@ -2809,7 +2809,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "bincode_derive", @@ -2835,7 +2835,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "blake3", @@ -2846,7 +2846,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "hex", ] @@ -2854,7 +2854,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "bincode", "byteorder", @@ -2870,7 +2870,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "blake3", "grovedb-costs", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2898,7 +2898,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "hex", "itertools 0.14.0", @@ -2907,7 +2907,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=a18f7929460ef9c5d814f61ff84d8805b2a1761b#a18f7929460ef9c5d814f61ff84d8805b2a1761b" +source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" dependencies = [ "serde", "serde_with 3.20.0", diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 6bfb4536a9e..489f5909464 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 7059bba18e0..1fe5cad314b 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 48b3d8cfc75..9c7e53eb9ae 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs index 0524064598a..cfe2c8a41b4 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs @@ -51,3 +51,83 @@ impl Drive { } } } + +#[cfg(test)] +mod v11_consensus_regression_tests { + use crate::util::test_helpers::setup::setup_drive; + use dpp::version::PlatformVersion; + use grovedb::Element; + use grovedb_path::SubtreePath; + + /// Protocol-v11 (`GROVE_V2`) consensus regression guard — testnet block 245,344. + /// + /// `transition_to_version_11` inserts an `empty_provable_count_sum_tree` + /// (CLEAR_ADDRESS_POOL) and an `empty_count_sum_tree` (ADDRESS_BALANCES) via + /// `grove_insert_if_not_exists`. grovedb must insert these as plain values + /// (`Op::Put`), NOT as layered subtrees (`Op::PutLayeredReference`): the latter + /// folds the child root into the parent node's `value_hash`, changing the + /// grovedb root and breaking consensus on replay (a beta.2 node computed + /// `98DD9B…` instead of the canonical `29B639…` and stalled at block 245,344). + /// This pins the post-insert root so the dispatch cannot silently regress + /// again. `empty_sum_tree` is the unchanged control (layered in all versions). + #[test] + fn provable_count_sum_tree_insert_preserves_v11_consensus_root() { + let platform_version = PlatformVersion::latest(); + let drive = setup_drive(None); + + // control: empty_sum_tree at root key [56] (AddressBalances). + drive + .grove_insert_if_not_exists( + SubtreePath::empty(), + &[56u8], + Element::empty_sum_tree(), + None, + None, + &platform_version.drive, + ) + .expect("insert sum_tree at [56]"); + let root_1 = drive + .grove + .root_hash(None, &platform_version.drive.grove_version) + .unwrap() + .unwrap(); + + // the regressed op: empty_provable_count_sum_tree at [56, 'c']. + let pcs_path: Vec> = vec![vec![56u8]]; + drive + .grove_insert_if_not_exists( + pcs_path.as_slice().into(), + b"c", + Element::empty_provable_count_sum_tree(), + None, + None, + &platform_version.drive, + ) + .expect("insert provable_count_sum_tree at [56,'c']"); + let root_2 = drive + .grove + .root_hash(None, &platform_version.drive.grove_version) + .unwrap() + .unwrap(); + + eprintln!("root_1 (control sum_tree) = {root_1:?}"); + eprintln!("root_2 (provable_count_sum_tree ins) = {root_2:?}"); + + // grovedb v4.1.0 (`Op::Put`) golden roots. With the layered-subtree + // regression, `root_2` differs and v11 consensus breaks. + const GOLDEN_1: [u8; 32] = [ + 193, 62, 168, 151, 156, 164, 202, 8, 147, 137, 134, 209, 196, 32, 2, 85, 18, 100, 97, + 227, 62, 160, 254, 196, 250, 171, 84, 176, 58, 38, 16, 116, + ]; + const GOLDEN_2: [u8; 32] = [ + 35, 99, 15, 178, 25, 57, 206, 47, 187, 195, 100, 28, 97, 85, 113, 230, 135, 22, 34, + 126, 72, 125, 158, 90, 116, 94, 214, 136, 96, 195, 235, 46, + ]; + assert_eq!(root_1, GOLDEN_1, "control sum_tree root changed unexpectedly"); + assert_eq!( + root_2, GOLDEN_2, + "ProvableCountSumTree insert no longer matches grovedb v4.1.0 (Op::Put) — \ + protocol-v11 consensus regression (testnet block 245,344)" + ); + } +} diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index cbcd9702ad8..38dd2e73f0e 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index e9ea678eb55..04555f45da2 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -49,7 +49,7 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg", zeroize = "1" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 97bdbcb1300..d549eddfb9f 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "a18f7929460ef9c5d814f61ff84d8805b2a1761b", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", features = [ "client", "sqlite", ], optional = true } From e84b9907c0cf5e6b385219c2ff2644950bc33803 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 14:48:44 +0700 Subject: [PATCH 2/7] fix: repoint grovedb to develop (grovedb#759 version-gated v11 dispatch) Bump grovedb a18f7929 -> 41786daa (develop HEAD, which includes grovedb#759), replacing the earlier e5a904d7 (#757). #759 version-gates add_element_on_transaction instead of changing it unconditionally: GROVE_V1/GROVE_V2 (protocol <= v11) keep plain Op::Put (matching grovedb v4.1.0 / the live v11 chain), GROVE_V3 (protocol v12) uses the layered subtree, consistent with the batch path. This is the proper fix and supersedes the unconditional restore in the now-closed grovedb#757. The rs-drive regression test now pins BOTH sides of the version gate so neither can silently flip: - v11 / GROVE_V2 -> canonical Op::Put golden roots (consensus unchanged) - v12 / GROVE_V3 -> a different provable_count_sum_tree root (layered) Green on the new rev: v11 root_2 matches the canonical (29B639-producing) Op::Put root; v12 root_2 differs as expected. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 52 +++---- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 8 +- packages/rs-drive/Cargo.toml | 12 +- .../grove_insert_if_not_exists/mod.rs | 132 +++++++++++------- packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 8 files changed, 118 insertions(+), 94 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99d93fb9fd8..e464c89e69f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,7 +513,7 @@ dependencies = [ "bitflags 2.11.1", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -2689,7 +2689,7 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "axum 0.8.9", "bincode", @@ -2727,7 +2727,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "blake3", @@ -2743,7 +2743,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -2759,7 +2759,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "integer-encoding", "intmap", @@ -2769,7 +2769,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "blake3", @@ -2782,7 +2782,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "bincode_derive", @@ -2797,7 +2797,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "grovedb-costs", "hex", @@ -2809,7 +2809,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "bincode_derive", @@ -2835,7 +2835,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "blake3", @@ -2846,7 +2846,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "hex", ] @@ -2854,7 +2854,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "bincode", "byteorder", @@ -2870,7 +2870,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "blake3", "grovedb-costs", @@ -2889,7 +2889,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2898,7 +2898,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "hex", "itertools 0.14.0", @@ -2907,7 +2907,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=e5a904d70b2b7cdf078abcf47a51f076d8d468a4#e5a904d70b2b7cdf078abcf47a51f076d8d468a4" +source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" dependencies = [ "serde", "serde_with 3.20.0", @@ -2945,9 +2945,9 @@ dependencies = [ [[package]] name = "halo2_gadgets" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45824ce0dd12e91ec0c68ebae2a7ed8ae19b70946624c849add59f1d1a62a143" +checksum = "fb2a697cad929f706b7987fe804ad57d43622cd37463ba7e4d662a926fdcfea3" dependencies = [ "arrayvec", "bitvec", @@ -3301,7 +3301,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -4531,8 +4531,8 @@ dependencies = [ [[package]] name = "orchard" -version = "0.13.1" -source = "git+https://github.com/dashpay/orchard.git?rev=898258d76aab2822249492aede59a02d49278fff#898258d76aab2822249492aede59a02d49278fff" +version = "0.14.0" +source = "git+https://github.com/dashpay/orchard.git?tag=dashified-0.14.0#f05557390a5843bc4eb04c66d8140bc9ef0fe9b7" dependencies = [ "aes", "bitvec", @@ -5112,7 +5112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck 0.4.1", - "itertools 0.10.5", + "itertools 0.13.0", "log", "multimap", "petgraph", @@ -5133,7 +5133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5146,7 +5146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.117", @@ -5276,7 +5276,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.6.3", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -5314,7 +5314,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 489f5909464..26b49517a39 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 1fe5cad314b..c72600bea21 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,7 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -107,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -121,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 9c7e53eb9ae..079eaacd8a8 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs index cfe2c8a41b4..b1da74d00d5 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs @@ -63,58 +63,22 @@ mod v11_consensus_regression_tests { /// /// `transition_to_version_11` inserts an `empty_provable_count_sum_tree` /// (CLEAR_ADDRESS_POOL) and an `empty_count_sum_tree` (ADDRESS_BALANCES) via - /// `grove_insert_if_not_exists`. grovedb must insert these as plain values - /// (`Op::Put`), NOT as layered subtrees (`Op::PutLayeredReference`): the latter - /// folds the child root into the parent node's `value_hash`, changing the - /// grovedb root and breaking consensus on replay (a beta.2 node computed - /// `98DD9B…` instead of the canonical `29B639…` and stalled at block 245,344). - /// This pins the post-insert root so the dispatch cannot silently regress - /// again. `empty_sum_tree` is the unchanged control (layered in all versions). + /// `grove_insert_if_not_exists`. Under the v11 grove version (`GROVE_V2`) grovedb + /// must insert these as plain values (`Op::Put`), NOT as layered subtrees + /// (`Op::PutLayeredReference`): the latter folds the child root into the parent + /// node's `value_hash`, changing the grovedb root and breaking consensus on replay + /// (a beta.2 node computed `98DD9B…` instead of the canonical `29B639…` and stalled + /// at block 245,344). + /// + /// grovedb #759 version-gates this dispatch: `GROVE_V1`/`GROVE_V2` keep `Op::Put` + /// (slot v0); `GROVE_V3` (protocol v12) adopts the layered subtree (slot v1), + /// consistent with the batch path. This test pins BOTH sides of the gate so neither + /// can silently flip: v11/`GROVE_V2` → the canonical `Op::Put` roots, and + /// v12/`GROVE_V3` → a *different* `provable_count_sum_tree` root (intentional + /// layered behaviour). `empty_sum_tree` is the unchanged control (layered always). #[test] fn provable_count_sum_tree_insert_preserves_v11_consensus_root() { - let platform_version = PlatformVersion::latest(); - let drive = setup_drive(None); - - // control: empty_sum_tree at root key [56] (AddressBalances). - drive - .grove_insert_if_not_exists( - SubtreePath::empty(), - &[56u8], - Element::empty_sum_tree(), - None, - None, - &platform_version.drive, - ) - .expect("insert sum_tree at [56]"); - let root_1 = drive - .grove - .root_hash(None, &platform_version.drive.grove_version) - .unwrap() - .unwrap(); - - // the regressed op: empty_provable_count_sum_tree at [56, 'c']. - let pcs_path: Vec> = vec![vec![56u8]]; - drive - .grove_insert_if_not_exists( - pcs_path.as_slice().into(), - b"c", - Element::empty_provable_count_sum_tree(), - None, - None, - &platform_version.drive, - ) - .expect("insert provable_count_sum_tree at [56,'c']"); - let root_2 = drive - .grove - .root_hash(None, &platform_version.drive.grove_version) - .unwrap() - .unwrap(); - - eprintln!("root_1 (control sum_tree) = {root_1:?}"); - eprintln!("root_2 (provable_count_sum_tree ins) = {root_2:?}"); - - // grovedb v4.1.0 (`Op::Put`) golden roots. With the layered-subtree - // regression, `root_2` differs and v11 consensus breaks. + // grovedb v4.1.0 (`Op::Put`) golden roots — the canonical protocol-v11 chain. const GOLDEN_1: [u8; 32] = [ 193, 62, 168, 151, 156, 164, 202, 8, 147, 137, 134, 209, 196, 32, 2, 85, 18, 100, 97, 227, 62, 160, 254, 196, 250, 171, 84, 176, 58, 38, 16, 116, @@ -123,11 +87,71 @@ mod v11_consensus_regression_tests { 35, 99, 15, 178, 25, 57, 206, 47, 187, 195, 100, 28, 97, 85, 113, 230, 135, 22, 34, 126, 72, 125, 158, 90, 116, 94, 214, 136, 96, 195, 235, 46, ]; - assert_eq!(root_1, GOLDEN_1, "control sum_tree root changed unexpectedly"); + + // Insert empty_sum_tree at [56] (AddressBalances, the control) then + // empty_provable_count_sum_tree at [56,'c'] (CLEAR_ADDRESS_POOL, the regressed + // op) under `pv`, returning the grovedb root after each insert. + fn insert_v11_address_trees(pv: &PlatformVersion) -> ([u8; 32], [u8; 32]) { + let drive = setup_drive(None); + drive + .grove_insert_if_not_exists( + SubtreePath::empty(), + &[56u8], + Element::empty_sum_tree(), + None, + None, + &pv.drive, + ) + .expect("insert sum_tree at [56]"); + let root_1 = drive + .grove + .root_hash(None, &pv.drive.grove_version) + .unwrap() + .unwrap(); + + let pcs_path: Vec> = vec![vec![56u8]]; + drive + .grove_insert_if_not_exists( + pcs_path.as_slice().into(), + b"c", + Element::empty_provable_count_sum_tree(), + None, + None, + &pv.drive, + ) + .expect("insert provable_count_sum_tree at [56,'c']"); + let root_2 = drive + .grove + .root_hash(None, &pv.drive.grove_version) + .unwrap() + .unwrap(); + (root_1, root_2) + } + + // v11 / GROVE_V2 — the consensus-locked path: must be `Op::Put` (golden). + let (v11_root_1, v11_root_2) = + insert_v11_address_trees(PlatformVersion::get(11).expect("protocol v11")); + eprintln!("v11 root_1 (control sum_tree) = {v11_root_1:?}"); + eprintln!("v11 root_2 (provable_count_sum_tree) = {v11_root_2:?}"); + assert_eq!( + v11_root_1, GOLDEN_1, + "v11 control sum_tree root changed unexpectedly" + ); assert_eq!( - root_2, GOLDEN_2, - "ProvableCountSumTree insert no longer matches grovedb v4.1.0 (Op::Put) — \ - protocol-v11 consensus regression (testnet block 245,344)" + v11_root_2, GOLDEN_2, + "ProvableCountSumTree insert under GROVE_V2 no longer matches grovedb v4.1.0 \ + (Op::Put) — protocol-v11 consensus regression (testnet block 245,344)" + ); + + // v12 / GROVE_V3 — intentionally layered (grovedb #759 version gate): the + // provable_count_sum_tree root MUST differ from the v11 Op::Put golden. + let (_v12_root_1, v12_root_2) = + insert_v11_address_trees(PlatformVersion::get(12).expect("protocol v12")); + eprintln!("v12 root_2 (provable_count_sum_tree) = {v12_root_2:?}"); + assert_ne!( + v12_root_2, GOLDEN_2, + "GROVE_V3 (protocol v12) must use the layered-subtree dispatch (grovedb #759) — \ + a root matching the v11 Op::Put value means the version gate was lost" ); } } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 38dd2e73f0e..2eea55c5a27 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 04555f45da2..480990dbf20 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -49,7 +49,7 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg", zeroize = "1" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index d549eddfb9f..436567385d9 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "e5a904d70b2b7cdf078abcf47a51f076d8d468a4", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", features = [ "client", "sqlite", ], optional = true } From 954b40d09af64ede3de4e5443d8568ffa43466f4 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 16:27:07 +0700 Subject: [PATCH 3/7] fix(drive-abci): migrate Orchard shielded verifier to orchard 0.14 The grovedb develop bump in this PR pulls orchard 0.13.1 -> 0.14.0 via grovedb-commitment-tree (grovedb#758, an Orchard circuit-soundness fix). orchard 0.14 changed the bundle-reconstruction API; adapt the consensus verifier (reconstruct_and_verify_bundle) preserving exact acceptance semantics: - Action::from_parts now returns Result (was Option). Keep the IdentityRk rejection (same message) and KEEP the new InvalidEpk rejection (the soundness fix #758 ships). ActionFromPartsError is #[non_exhaustive] -> unknown future variants default to reject, never accept. - Bundle::from_parts -> Bundle::try_from_parts(.., ProofSizeEnforcement::Unenforced). Unenforced matches 0.13 (no proof-size check), keeping consensus acceptance byte-for-byte; Strict would add a new rejection that did not exist before. - Spend-auth signatures stay attached inline to each Action, so action<->signature pairing/ordering is preserved structurally. Add a direct orchard dep on rs-drive-abci pinned to the same git tag grovedb-commitment-tree resolves to (cargo unifies to one orchard build) so ProofSizeEnforcement / ActionFromPartsError can be named. Migration is confined to this one verifier file; all other shielded code (rs-dpp builder, rs-platform-wallet, wasm-dpp2) compiles unchanged. Validated: 143 drive-abci shielded tests pass (0 failed), incl. valid-proof acceptance and security-audit rejection (mutated value_balance, zeroed spend-auth/binding sigs) round-trips with real Halo 2 proofs from the 0.14 builder. v11 consensus regression test unaffected and green. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 1 + packages/rs-drive-abci/Cargo.toml | 7 ++ .../state_transitions/shielded_common/mod.rs | 64 +++++++++++++++++-- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e464c89e69f..ddf3edf5512 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2048,6 +2048,7 @@ dependencies = [ "metrics-exporter-prometheus", "mockall", "nonempty", + "orchard", "platform-version", "prost 0.14.3", "rand 0.8.6", diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index c72600bea21..6c37e571b72 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -83,6 +83,13 @@ async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +# orchard 0.14 made the only constructor for an `Authorized` bundle +# (`Bundle::try_from_parts`) require a `ProofSizeEnforcement` argument, which +# `grovedb-commitment-tree` does not re-export. The shielded verifier needs to +# name that enum to reconstruct the bundle, so orchard is pinned here to the +# exact same git tag that `grovedb-commitment-tree` resolves to (cargo unifies +# this to a single orchard build; no second copy is introduced). +orchard = { git = "https://github.com/dashpay/orchard.git", tag = "dashified-0.14.0", features = ["circuit"] } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index 3a003d6f308..3b5aaab3fa0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -18,6 +18,14 @@ use grovedb_commitment_tree::{ ExtractedNoteCommitment, Flags, NoteBytesData, Nullifier, Proof, TransmittedNoteCiphertext, ValueCommitment, VerifyingKey, }; +// `ProofSizeEnforcement` is the orchard 0.14 argument to the only public +// constructor for an authorized bundle (`Bundle::try_from_parts`). It is not +// re-exported by `grovedb-commitment-tree`, so we name it through the orchard +// crate directly (pinned to the same git tag in Cargo.toml, so cargo unifies +// to a single orchard build). `ActionFromPartsError` lets us distinguish the +// two rejection causes that `Action::from_parts` now reports. +use orchard::bundle::ProofSizeEnforcement; +use orchard::ActionFromPartsError; use std::sync::OnceLock; /// Orchard bundle flags byte: only outputs are real (spends are dummy). @@ -127,9 +135,18 @@ pub fn reconstruct_and_verify_bundle( InvalidShieldedProofError::new("invalid value commitment bytes".to_string()) })?; - // `Action::from_parts` returns `None` when `rk` is the identity key - // (an Orchard hardening added upstream in 0.13). Reject those as - // malformed rather than silently dropping them. + // `Action::from_parts` rejects malformed actions instead of silently + // dropping them. In orchard 0.14 it returns `Result<_, ActionFromPartsError>` + // (was `Option` in 0.13) and now enforces TWO invariants: + // - `IdentityRk`: the randomizer key `rk` must be non-identity (the + // hardening that already existed in 0.13). + // - `InvalidEpk`: the ephemeral public key `epk` must encode a + // non-identity point (a NEW invariant in 0.14 — the circuit + // soundness fix). Rejecting this is REQUIRED to preserve soundness; + // we must not weaken it back to acceptance. + // We keep the original "identity randomizer key" message for the + // `IdentityRk` case (byte-for-byte compatible with the 0.13 error path) + // and surface the new `InvalidEpk` rejection with its own message. let action = Action::from_parts( nullifier, rk, @@ -142,8 +159,17 @@ pub fn reconstruct_and_verify_bundle( cv_net, redpallas::Signature::from(a.spend_auth_sig), ) - .ok_or_else(|| { - InvalidShieldedProofError::new("action has identity randomizer key".to_string()) + .map_err(|e| match e { + ActionFromPartsError::IdentityRk => { + InvalidShieldedProofError::new("action has identity randomizer key".to_string()) + } + ActionFromPartsError::InvalidEpk => InvalidShieldedProofError::new( + "action has invalid ephemeral public key (identity or undecodable epk)".to_string(), + ), + // `ActionFromPartsError` is `#[non_exhaustive]`. Any future + // rejection variant added upstream MUST also be rejected here — + // defaulting to acceptance would weaken consensus soundness. + other => InvalidShieldedProofError::new(format!("malformed orchard action: {other}")), })?; orchard_actions.push(action); } @@ -165,13 +191,37 @@ pub fn reconstruct_and_verify_bundle( let actions_nonempty = nonempty::NonEmpty::from_vec(orchard_actions) .ok_or_else(|| InvalidShieldedProofError::new("bundle has no actions".to_string()))?; - let bundle = Bundle::from_parts( + // Reconstruct the authorized bundle. + // + // orchard 0.14 removed the old generic `Bundle::from_parts` constructor. The + // only public constructor for a `Bundle` is now + // `Bundle::try_from_parts`, which additionally takes a `ProofSizeEnforcement`. + // + // We pass `ProofSizeEnforcement::Unenforced` to preserve EXACT consensus + // acceptance semantics: the old `Bundle::from_parts` performed NO proof-size + // check, and the platform validates proof bytes nowhere else, so a bundle + // whose proof length is non-canonical was accepted (and then verified by the + // Halo 2 circuit) under 0.13. Choosing `Strict` here would introduce a NEW + // rejection (`BundleError::NonCanonicalProofSize`) that did not exist before, + // changing consensus behavior for an edge case — which is forbidden. With + // `Unenforced`, `try_from_parts` cannot fail, but we still surface any error + // defensively rather than unwrapping. + // + // The actions already carry their per-action `redpallas::Signature` + // (attached above in `Action::from_parts`), so action↔signature pairing and + // ordering are preserved structurally — there is no separate signature list + // to reorder. + let bundle = Bundle::try_from_parts( actions_nonempty, orchard_flags, value_balance, orchard_anchor, authorized, - ); + ProofSizeEnforcement::Unenforced, + ) + .map_err(|e| { + InvalidShieldedProofError::new(format!("failed to reconstruct authorized bundle: {e}")) + })?; // Compute the platform sighash: SHA-256(domain || bundle_commitment || extra_data). // The bundle commitment is the Orchard BundleCommitment (BLAKE2b-256 per ZIP-244), From d8474dc04c8d8f04e5922aaadc1de47bc947f557 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 20:47:18 +0700 Subject: [PATCH 4/7] =?UTF-8?q?test:=20address=20PR=20review=20=E2=80=94?= =?UTF-8?q?=20doc=20fix,=20pin=20v12=20control=20root,=20cover=20from=5Fpa?= =?UTF-8?q?rts=20rejections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses review findings on #3789 (all test-only; verifier unchanged): - rs-drive v11 regression test: correct the doc (the [56] control is an empty_sum_tree, not empty_count_sum_tree; only the provable-count-sum tree is Op::Put — sum_tree is layered), and pin the v12/GROVE_V3 control root (assert_eq! v12_root_1 == GOLDEN_1) so grovedb #759's version gate cannot silently widen to re-layer plain sum_tree. - rs-drive-abci shielded verifier: add direct tests for the two Action::from_parts rejection arms that had no coverage. IdentityRk (rk=[0;32] — decodes, unlike the [2;32] decode-failure case, then rejected) and InvalidEpk (valid non-identity rk + identity epk=[0;32]). InvalidEpk is the orchard 0.14 circuit-soundness reject; these lock it so a future refactor cannot silently map it to acceptance. Base built from a real unauthorized Orchard bundle with precondition guards so it fails loud if the base ever stops reaching from_parts. Verified: rs-drive v11 regression green (both gate sides); drive-abci reconstruct_and_verify_bundle 6 passed (4 existing + 2 new); fmt + clippy clean. Co-Authored-By: Claude Opus 4.8 --- .../state_transitions/shielded_common/mod.rs | 195 ++++++++++++++++++ .../grove_insert_if_not_exists/mod.rs | 28 ++- 2 files changed, 213 insertions(+), 10 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index 3b5aaab3fa0..4d3741bb5b6 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -477,6 +477,201 @@ mod tests { err.message() ); } + + // ---------------------------------------------------------------- + // `Action::from_parts` rejection paths (orchard 0.14 `map_err` arms) + // + // These pin the two consensus-critical rejections that are surfaced + // ONLY inside the `Action::from_parts(...).map_err(...)` arms of + // `reconstruct_and_verify_bundle`: + // - `ActionFromPartsError::IdentityRk` ("identity randomizer key") + // - `ActionFromPartsError::InvalidEpk` ("invalid ephemeral public key") + // + // The earlier error-path tests above never reach those arms: they fail + // at the encrypted-note-size check, the empty-actions check, the + // `rk`-DECODE step ([2u8;32] is not a valid VK encoding, rejected + // *before* `from_parts`), or the flags check. A future refactor that + // mistakenly mapped `InvalidEpk` to acceptance would slip past CI + // without these tests. The `InvalidEpk` rejection is the orchard 0.14 + // circuit-soundness fix and MUST stay a rejection. + // ---------------------------------------------------------------- + + use grovedb_commitment_tree::{ + redpallas, Anchor, Builder, BundleType, DashMemo, Flags as OrchardFlags, + FullViewingKey, NoteValue, Scope, SpendingKey, + }; + + /// Builds a `SerializedAction` whose `nullifier`, `rk`, `cmx`, `cv_net`, + /// and `encrypted_note` (including a real, non-identity `epk`) are all + /// genuine, canonically-encoded Orchard values — so an action built from + /// it decodes cleanly through nullifier → rk → cmx → cv_net and actually + /// REACHES `Action::from_parts`. The bytes are read off a real + /// (unauthorized) output-only Orchard bundle; this needs NO proving key + /// (we never call `create_proof`), so it is cheap. + /// + /// Tests then mutate exactly one field to exercise a single `from_parts` + /// rejection arm. The function asserts each base field decodes, so if a + /// future orchard encoding change broke the precondition the test would + /// fail LOUDLY here rather than silently passing for the wrong reason. + fn valid_base_serialized_action() -> dpp::shielded::SerializedAction { + let sk = SpendingKey::from_bytes([0u8; 32]).expect("valid spending key"); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, Scope::External); + + let mut builder = Builder::::new( + BundleType::Transactional { + flags: OrchardFlags::SPENDS_DISABLED, + bundle_required: false, + }, + Anchor::empty_tree(), + ); + builder + .add_output(None, recipient, NoteValue::from_raw(5_000), [0u8; 36]) + .expect("add_output"); + + let mut rng = rand::rngs::OsRng; + let (unauthorized, _) = builder + .build::(&mut rng) + .expect("build unauthorized bundle") + .expect("bundle is non-empty"); + + // Read genuine, canonically-encoded fields off the first action. + let action = unauthorized.actions().first(); + let enc = action.encrypted_note(); + let mut encrypted_note = Vec::with_capacity(ENCRYPTED_NOTE_SIZE); + encrypted_note.extend_from_slice(&enc.epk_bytes); + encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref()); + encrypted_note.extend_from_slice(&enc.out_ciphertext); + + let base = dpp::shielded::SerializedAction { + nullifier: action.nullifier().to_bytes(), + rk: <[u8; 32]>::from(action.rk()), + cmx: action.cmx().to_bytes(), + encrypted_note, + cv_net: action.cv_net().to_bytes(), + spend_auth_sig: [6u8; 64], + }; + + // Precondition guards: confirm the base reaches `from_parts` by + // checking that every field the verifier decodes BEFORE `from_parts` + // is valid, and that the base epk is itself a valid non-identity + // point (so flipping it to the identity is what the InvalidEpk test + // isolates). + assert_eq!(base.encrypted_note.len(), ENCRYPTED_NOTE_SIZE); + assert!( + Option::::from(Nullifier::from_bytes(&base.nullifier)).is_some(), + "base nullifier must decode" + ); + assert!( + redpallas::VerificationKey::::try_from(base.rk).is_ok(), + "base rk must decode as a (non-identity) verification key" + ); + assert!( + Option::::from(ExtractedNoteCommitment::from_bytes( + &base.cmx + )) + .is_some(), + "base cmx must decode" + ); + assert!( + Option::::from(ValueCommitment::from_bytes(&base.cv_net)) + .is_some(), + "base cv_net must decode" + ); + base + } + + /// `Action::from_parts` -> `ActionFromPartsError::IdentityRk`. + /// + /// `rk = [0u8; 32]` is the canonical encoding of the RedPallas identity + /// verification key: it DECODES successfully (so it passes the verifier's + /// pre-`from_parts` rk-decode step, unlike the [2u8;32] decode-failure + /// case in `test_invalid_rk_returns_error`), and `from_parts` then + /// rejects it because the randomizer key is the identity. Pins the + /// `IdentityRk => "identity randomizer key"` arm. + #[test] + fn test_identity_rk_returns_error() { + let mut action = valid_base_serialized_action(); + // Sanity: the identity VK encoding must DECODE (else we'd be + // re-testing the decode-failure path, not the from_parts arm). + assert!( + redpallas::VerificationKey::::try_from([0u8; 32]).is_ok(), + "identity rk [0;32] must decode so it reaches Action::from_parts" + ); + action.rk = [0u8; 32]; // RedPallas identity verification key + + let result = reconstruct_and_verify_bundle( + &[action], + FLAGS_SPENDS_AND_OUTPUTS, + 0, + &[42u8; 32], + &[0u8; 100], + &[0u8; 64], + &[], + ); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.message().contains("identity randomizer key"), + "expected 'identity randomizer key' error from the IdentityRk arm, got: {}", + err.message() + ); + } + + /// `Action::from_parts` -> `ActionFromPartsError::InvalidEpk`. + /// + /// This is the orchard 0.14 circuit-soundness reject. The action is valid + /// everywhere the verifier checks before `from_parts` — including a + /// genuine, non-identity `rk` derived exactly like orchard's own + /// `non_identity_rk()` test helper (the scalar `1` as a RedPallas + /// `SigningKey`, then its `VerificationKey`) — so it passes the rk-decode + /// step AND the `IdentityRk` check, reaching the epk invariant. Its `epk` + /// is then set to `[0u8; 32]`, the canonical Pallas identity encoding, + /// which is NOT a valid `KA^{Orchard}` public key, so `from_parts` + /// rejects with `InvalidEpk`. Pins the + /// `InvalidEpk => "invalid ephemeral public key"` arm. + #[test] + fn test_identity_epk_returns_invalid_epk_error() { + let mut action = valid_base_serialized_action(); + + // Non-identity rk: scalar 1 (little-endian) -> SigningKey -> VK -> bytes. + let mut scalar_one = [0u8; 32]; + scalar_one[0] = 1; + let signing_key = redpallas::SigningKey::::try_from(scalar_one) + .expect("scalar 1 is a valid RedPallas signing key"); + let vk = redpallas::VerificationKey::::from(&signing_key); + let non_identity_rk = <[u8; 32]>::from(vk); + // Guard: this rk must NOT be the identity (else we'd trip IdentityRk + // instead of reaching the epk check). + assert_ne!( + non_identity_rk, [0u8; 32], + "scalar-1 verification key must be non-identity" + ); + action.rk = non_identity_rk; + + // Set the ephemeral public key (first 32 bytes of encrypted_note) to + // the canonical Pallas identity encoding — an invalid epk. + action.encrypted_note[..EPK_SIZE].copy_from_slice(&[0u8; EPK_SIZE]); + + let result = reconstruct_and_verify_bundle( + &[action], + FLAGS_SPENDS_AND_OUTPUTS, + 0, + &[42u8; 32], + &[0u8; 100], + &[0u8; 64], + &[], + ); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.message().contains("invalid ephemeral public key"), + "expected 'invalid ephemeral public key' error from the InvalidEpk arm, got: {}", + err.message() + ); + } } // ========================================== diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs index b1da74d00d5..3a41b10e98e 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists/mod.rs @@ -61,14 +61,14 @@ mod v11_consensus_regression_tests { /// Protocol-v11 (`GROVE_V2`) consensus regression guard — testnet block 245,344. /// - /// `transition_to_version_11` inserts an `empty_provable_count_sum_tree` - /// (CLEAR_ADDRESS_POOL) and an `empty_count_sum_tree` (ADDRESS_BALANCES) via - /// `grove_insert_if_not_exists`. Under the v11 grove version (`GROVE_V2`) grovedb - /// must insert these as plain values (`Op::Put`), NOT as layered subtrees - /// (`Op::PutLayeredReference`): the latter folds the child root into the parent - /// node's `value_hash`, changing the grovedb root and breaking consensus on replay - /// (a beta.2 node computed `98DD9B…` instead of the canonical `29B639…` and stalled - /// at block 245,344). + /// This models the v11 AddressBalances tree-set built by `transition_to_version_11`: an + /// `empty_sum_tree` root at `[56]` (the control) plus an `empty_provable_count_sum_tree` + /// (CLEAR_ADDRESS_POOL, `[56,'c']`) under it, both via `grove_insert_if_not_exists`. Under the + /// v11 grove version (`GROVE_V2`) the provable-count-sum tree must be inserted as a plain value + /// (`Op::Put`), NOT a layered subtree (`Op::PutLayeredReference`): the latter folds the child + /// root into the parent node's `value_hash`, changing the grovedb root and breaking consensus on + /// replay (a beta.2 node computed `98DD9B…` instead of the canonical `29B639…` and stalled at + /// block 245,344). /// /// grovedb #759 version-gates this dispatch: `GROVE_V1`/`GROVE_V2` keep `Op::Put` /// (slot v0); `GROVE_V3` (protocol v12) adopts the layered subtree (slot v1), @@ -144,10 +144,18 @@ mod v11_consensus_regression_tests { ); // v12 / GROVE_V3 — intentionally layered (grovedb #759 version gate): the - // provable_count_sum_tree root MUST differ from the v11 Op::Put golden. - let (_v12_root_1, v12_root_2) = + // provable_count_sum_tree root MUST differ from the v11 Op::Put golden, while the + // empty_sum_tree control MUST stay on GOLDEN_1 — the gate is scoped to + // CountSumTree / ProvableCount[Sum]Tree only, never plain sum_tree. + let (v12_root_1, v12_root_2) = insert_v11_address_trees(PlatformVersion::get(12).expect("protocol v12")); + eprintln!("v12 root_1 (control sum_tree) = {v12_root_1:?}"); eprintln!("v12 root_2 (provable_count_sum_tree) = {v12_root_2:?}"); + assert_eq!( + v12_root_1, GOLDEN_1, + "v12 control sum_tree root changed — grovedb #759's version gate must affect only \ + CountSumTree / ProvableCount[Sum]Tree, never plain empty_sum_tree" + ); assert_ne!( v12_root_2, GOLDEN_2, "GROVE_V3 (protocol v12) must use the layered-subtree dispatch (grovedb #759) — \ From d40bbdf26a9cad7d4b9e99badb41f03fae9e4918 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 21:12:21 +0700 Subject: [PATCH 5/7] refactor(drive-abci): drop direct orchard dep, import via grovedb-commitment-tree grovedb#760 (merged) re-exports ProofSizeEnforcement + ActionFromPartsError from grovedb-commitment-tree, so the shielded verifier no longer needs a direct orchard dependency. Bump grovedb rev 41786daa -> 40521b83 (develop HEAD incl. #760), import both symbols from grovedb_commitment_tree alongside the ~15 other orchard types it already re-exports, and remove the orchard entry from rs-drive-abci/Cargo.toml. This resolves the review nitpick about orchard being pinned by a mutable tag while neighbors pin by rev: there is now no direct orchard dependency at all. Cargo.lock keeps a single orchard 0.14.0 (now only transitive via grovedb-commitment-tree; no direct drive-abci edge). Verified: drive-abci reconstruct_and_verify_bundle 6 passed; v11 regression green; fmt + clippy clean. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 45 +++++++++---------- packages/rs-dpp/Cargo.toml | 2 +- packages/rs-drive-abci/Cargo.toml | 15 ++----- .../state_transitions/shielded_common/mod.rs | 14 ++---- packages/rs-drive/Cargo.toml | 12 ++--- packages/rs-platform-version/Cargo.toml | 2 +- packages/rs-platform-wallet/Cargo.toml | 2 +- packages/rs-sdk/Cargo.toml | 2 +- 8 files changed, 39 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddf3edf5512..93d6b1b47a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -513,7 +513,7 @@ dependencies = [ "bitflags 2.11.1", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "regex", @@ -2048,7 +2048,6 @@ dependencies = [ "metrics-exporter-prometheus", "mockall", "nonempty", - "orchard", "platform-version", "prost 0.14.3", "rand 0.8.6", @@ -2690,7 +2689,7 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "axum 0.8.9", "bincode", @@ -2728,7 +2727,7 @@ dependencies = [ [[package]] name = "grovedb-bulk-append-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "blake3", @@ -2744,7 +2743,7 @@ dependencies = [ [[package]] name = "grovedb-commitment-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "blake3", "grovedb-bulk-append-tree", @@ -2760,7 +2759,7 @@ dependencies = [ [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "integer-encoding", "intmap", @@ -2770,7 +2769,7 @@ dependencies = [ [[package]] name = "grovedb-dense-fixed-sized-merkle-tree" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "blake3", @@ -2783,7 +2782,7 @@ dependencies = [ [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "bincode_derive", @@ -2798,7 +2797,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "grovedb-costs", "hex", @@ -2810,7 +2809,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "bincode_derive", @@ -2836,7 +2835,7 @@ dependencies = [ [[package]] name = "grovedb-merkle-mountain-range" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "blake3", @@ -2847,7 +2846,7 @@ dependencies = [ [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "hex", ] @@ -2855,7 +2854,7 @@ dependencies = [ [[package]] name = "grovedb-query" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "bincode", "byteorder", @@ -2871,7 +2870,7 @@ dependencies = [ [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "blake3", "grovedb-costs", @@ -2890,7 +2889,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2899,7 +2898,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "hex", "itertools 0.14.0", @@ -2908,7 +2907,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=41786daa51495743fc5d7223065d848c36b481f0#41786daa51495743fc5d7223065d848c36b481f0" +source = "git+https://github.com/dashpay/grovedb?rev=40521b831ca0792e4d63f4d8949c45f07517641e#40521b831ca0792e4d63f4d8949c45f07517641e" dependencies = [ "serde", "serde_with 3.20.0", @@ -3302,7 +3301,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -5113,7 +5112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ "heck 0.4.1", - "itertools 0.13.0", + "itertools 0.10.5", "log", "multimap", "petgraph", @@ -5134,7 +5133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.117", @@ -5147,7 +5146,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.13.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.117", @@ -5277,7 +5276,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.5.10", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -5315,7 +5314,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.3", "tracing", "windows-sys 0.59.0", ] diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 26b49517a39..186f4d3f0d6 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,7 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml index 6c37e571b72..4eb4317a39d 100644 --- a/packages/rs-drive-abci/Cargo.toml +++ b/packages/rs-drive-abci/Cargo.toml @@ -82,14 +82,7 @@ derive_more = { version = "1.0", features = ["from", "deref", "deref_mut"] } async-trait = "0.1.77" console-subscriber = { version = "0.4", optional = true } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f", optional = true } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } -# orchard 0.14 made the only constructor for an `Authorized` bundle -# (`Bundle::try_from_parts`) require a `ProofSizeEnforcement` argument, which -# `grovedb-commitment-tree` does not re-export. The shielded verifier needs to -# name that enum to reconstruct the bundle, so orchard is pinned here to the -# exact same git tag that `grovedb-commitment-tree` resolves to (cargo unifies -# this to a single orchard build; no second copy is introduced). -orchard = { git = "https://github.com/dashpay/orchard.git", tag = "dashified-0.14.0", features = ["circuit"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } nonempty = "0.11" # Shielded-pool snapshot needs raw RocksDB SstFileWriter + ingest_external_file_cf # bindings, and blake3 for the snapshot-file checksum. @@ -114,7 +107,7 @@ dpp = { path = "../rs-dpp", default-features = false, features = [ drive = { path = "../rs-drive", features = ["fixtures-and-mocks"] } drive-proof-verifier = { path = "../rs-drive-proof-verifier" } strategy-tests = { path = "../strategy-tests" } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", features = ["client"] } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", features = ["client"] } assert_matches = "1.5.0" drive-abci = { path = ".", features = ["testing-config", "mocks", "shielded_test_data"] } bls-signatures = { git = "https://github.com/dashpay/bls-signatures", rev = "0842b17583888e8f46c252a4ee84cdfd58e0546f" } @@ -128,8 +121,8 @@ integer-encoding = { version = "4.0.0" } # For dump_only_default_and_aux_cfs_under_shielded_subtree_prefix — same # subtree-prefix algorithm grovedb uses internally. -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } [features] default = ["bls-signatures"] diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index 4d3741bb5b6..63c3d397762 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -14,18 +14,10 @@ use drive::fees::op::LowLevelDriveOperation; use drive::grovedb::TransactionArg; use drive::state_transition_action::StateTransitionAction; use grovedb_commitment_tree::{ - redpallas, Action, Anchor, Authorized, BatchValidator, Bundle, DashMemo, - ExtractedNoteCommitment, Flags, NoteBytesData, Nullifier, Proof, TransmittedNoteCiphertext, - ValueCommitment, VerifyingKey, + redpallas, Action, ActionFromPartsError, Anchor, Authorized, BatchValidator, Bundle, DashMemo, + ExtractedNoteCommitment, Flags, NoteBytesData, Nullifier, Proof, ProofSizeEnforcement, + TransmittedNoteCiphertext, ValueCommitment, VerifyingKey, }; -// `ProofSizeEnforcement` is the orchard 0.14 argument to the only public -// constructor for an authorized bundle (`Bundle::try_from_parts`). It is not -// re-exported by `grovedb-commitment-tree`, so we name it through the orchard -// crate directly (pinned to the same git tag in Cargo.toml, so cargo unifies -// to a single orchard build). `ActionFromPartsError` lets us distinguish the -// two rejection causes that `Action::from_parts` now reports. -use orchard::bundle::ProofSizeEnforcement; -use orchard::ActionFromPartsError; use std::sync::OnceLock; /// Orchard bundle flags byte: only outputs are real (spends are dummy). diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 079eaacd8a8..808402e721a 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index 2eea55c5a27..c5d7ad5d9d5 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e" } [features] mock-versions = [] diff --git a/packages/rs-platform-wallet/Cargo.toml b/packages/rs-platform-wallet/Cargo.toml index 480990dbf20..1d51abe1506 100644 --- a/packages/rs-platform-wallet/Cargo.toml +++ b/packages/rs-platform-wallet/Cargo.toml @@ -49,7 +49,7 @@ image = { version = "0.25", default-features = false, features = ["png", "jpeg", zeroize = "1" # Shielded pool (optional, behind `shielded` feature) -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", optional = true } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", optional = true } # Direct `rusqlite` access so `FileBackedShieldedStore::open_path` can set # WAL + synchronous=NORMAL pragmas before handing the connection to # `ClientPersistentCommitmentTree`. Version locked to match the rev grovedb diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml index 436567385d9..6deab0d0536 100644 --- a/packages/rs-sdk/Cargo.toml +++ b/packages/rs-sdk/Cargo.toml @@ -18,7 +18,7 @@ drive = { path = "../rs-drive", default-features = false, features = [ ] } drive-proof-verifier = { path = "../rs-drive-proof-verifier", default-features = false } -grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "41786daa51495743fc5d7223065d848c36b481f0", features = [ +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "40521b831ca0792e4d63f4d8949c45f07517641e", features = [ "client", "sqlite", ], optional = true } From 25d0b4426c65ff45123da411b85becd282ff862e Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 21:22:32 +0700 Subject: [PATCH 6/7] fix(drive-abci): enforce canonical proof size (ProofSizeEnforcement::Strict) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shielded verifier reconstructs the Authorized bundle with ProofSizeEnforcement::Strict instead of Unenforced: a proof whose byte-length is not canonical for the bundle's action count is rejected (BundleError::NonCanonicalProofSize). This is the sound, anti-malleability policy — a non-canonical proof is not a valid Orchard proof and must not enter consensus. Protocol-v12 shielded is not yet activated on the canonical chain, so there is no live acceptance set to preserve; we set the strict policy from the start rather than inheriting orchard 0.13's lack of a proof-size check. Verified: full shielded suite 145 passed / 0 failed — Strict accepts genuine canonical proofs (test_valid_shielded_{transfer,withdrawal}_proof_succeeds) and still rejects mutated/invalid proofs; no NonCanonicalProofSize false-rejections. Co-Authored-By: Claude Opus 4.8 --- .../state_transitions/shielded_common/mod.rs | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index 63c3d397762..62c9d154fae 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -183,33 +183,18 @@ pub fn reconstruct_and_verify_bundle( let actions_nonempty = nonempty::NonEmpty::from_vec(orchard_actions) .ok_or_else(|| InvalidShieldedProofError::new("bundle has no actions".to_string()))?; - // Reconstruct the authorized bundle. - // - // orchard 0.14 removed the old generic `Bundle::from_parts` constructor. The - // only public constructor for a `Bundle` is now - // `Bundle::try_from_parts`, which additionally takes a `ProofSizeEnforcement`. - // - // We pass `ProofSizeEnforcement::Unenforced` to preserve EXACT consensus - // acceptance semantics: the old `Bundle::from_parts` performed NO proof-size - // check, and the platform validates proof bytes nowhere else, so a bundle - // whose proof length is non-canonical was accepted (and then verified by the - // Halo 2 circuit) under 0.13. Choosing `Strict` here would introduce a NEW - // rejection (`BundleError::NonCanonicalProofSize`) that did not exist before, - // changing consensus behavior for an edge case — which is forbidden. With - // `Unenforced`, `try_from_parts` cannot fail, but we still surface any error - // defensively rather than unwrapping. - // - // The actions already carry their per-action `redpallas::Signature` - // (attached above in `Action::from_parts`), so action↔signature pairing and - // ordering are preserved structurally — there is no separate signature list - // to reorder. + // Reconstruct the `Bundle` (`try_from_parts` is orchard 0.14's only + // public constructor for it). `ProofSizeEnforcement::Strict` rejects a proof + // whose byte-length is not canonical for the action count — anti-malleability. + // Spend-auth signatures were attached per-action in `Action::from_parts` above, + // so action↔signature pairing is preserved with no separate list to reorder. let bundle = Bundle::try_from_parts( actions_nonempty, orchard_flags, value_balance, orchard_anchor, authorized, - ProofSizeEnforcement::Unenforced, + ProofSizeEnforcement::Strict, ) .map_err(|e| { InvalidShieldedProofError::new(format!("failed to reconstruct authorized bundle: {e}")) From 6ef45ed1915abeaef74e40085e66f51cba7cf30c Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 4 Jun 2026 21:51:31 +0700 Subject: [PATCH 7/7] test(drive-abci): pin ProofSizeEnforcement::Strict with a NonCanonicalProofSize test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds test_noncanonical_proof_size_rejected_under_strict: a bundle with a valid action but a non-canonical proof byte-length is rejected by Bundle::try_from_parts(.., Strict) (NonCanonicalProofSize). This distinguishes Strict from Unenforced — the positive round-trip tests use canonical proofs and pass under either setting, so without this guard a refactor could silently flip the policy. Addresses the remaining review point that no test pinned the proof-size enforcement decision. Verified: reconstruct_and_verify_bundle tests 7 passed (4 existing + IdentityRk + InvalidEpk + this). Co-Authored-By: Claude Opus 4.8 --- .../state_transitions/shielded_common/mod.rs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs index 62c9d154fae..1a78c9f7854 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/shielded_common/mod.rs @@ -649,6 +649,41 @@ mod tests { err.message() ); } + + /// `Bundle::try_from_parts(.., ProofSizeEnforcement::Strict)` -> + /// `BundleError::NonCanonicalProofSize`. + /// + /// Pins the proof-size policy. The base action is valid, so reconstruction + /// clears `Action::from_parts` and reaches `try_from_parts`; the proof + /// byte-length (100) is not canonical for a single-action bundle, so + /// `Strict` rejects it. This is what distinguishes `Strict` from + /// `Unenforced`: under `Unenforced` the bundle would build and this test + /// would fail. The positive round-trip tests use canonical proofs and pass + /// under either setting, so without this test a refactor could silently flip + /// the policy. A 32-byte zero anchor (field element 0) is used so anchor + /// decoding succeeds and we reach the proof-size check. + #[test] + fn test_noncanonical_proof_size_rejected_under_strict() { + let action = valid_base_serialized_action(); + let result = reconstruct_and_verify_bundle( + &[action], + FLAGS_SPENDS_AND_OUTPUTS, + 0, + &[0u8; 32], + &[0u8; 100], // non-canonical proof length for a 1-action bundle + &[0u8; 64], + &[], + ); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert!( + err.message() + .contains("failed to reconstruct authorized bundle"), + "expected NonCanonicalProofSize rejection from try_from_parts(Strict), got: {}", + err.message() + ); + } } // ==========================================