From b71e1af63a144ba2d42e59b24d763f90003a29fe Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 May 2026 03:13:54 +0700 Subject: [PATCH 1/4] fix(rs-dpp): use DIP-0002 version 3 in instant asset lock fixture After the rust-dashcore bump in #3617, three round-trip tests in instant_asset_lock_proof::tests started failing with DecodingError("data not consumed entirely when explicitly deserializing"). rust-dashcore PR #726 tightened Transaction decoding: when version < 3 and nTxType != 0 it now decodes as ClassicalWithNonStandardVersionTypeBytes and does not consume any special-transaction payload bytes. The fixture constructed a Transaction with version 0 plus an AssetLockPayload, so encode emitted the payload but decode left those bytes unconsumed. Real DIP-0002 special transactions (AssetLock included) use version 3; the fixture was always malformed and only round-tripped because the old decoder ignored the version field. Set the fixture's version to 3 and update the matching version assertion in test_transaction_accessor. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../asset_lock_proof/instant/instant_asset_lock_proof.rs | 3 ++- .../src/tests/fixtures/instant_asset_lock_proof_fixture.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs index a753758d280..8e978fbf383 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/instant/instant_asset_lock_proof.rs @@ -295,7 +295,8 @@ mod tests { fn test_transaction_accessor() { let proof = raw_instant_asset_lock_proof_fixture(None, None); let tx = proof.transaction(); - assert_eq!(tx.version, 0); + // DIP-0002 special transactions (AssetLock) use version 3. + assert_eq!(tx.version, 3); assert_eq!(tx.lock_time, 0); assert_eq!(tx.input.len(), 1); } diff --git a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs index f00af679eda..f55590029df 100644 --- a/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs +++ b/packages/rs-dpp/src/tests/fixtures/instant_asset_lock_proof_fixture.rs @@ -98,7 +98,7 @@ pub fn instant_asset_lock_proof_transaction_fixture( }); Transaction { - version: 0, + version: 3, lock_time: 0, input: vec![input], output: vec![burn_output, change_output], From 8900e1ebff4a50fbb0a2e144f51dfdd8c7868ef2 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 May 2026 03:27:43 +0700 Subject: [PATCH 2/4] fix(dpp,strategy-tests): use DIP-0002 version 3 in remaining asset-lock tx builders Same root cause as the prior commit: rust-dashcore's tightened decoder (PR dashpay/rust-dashcore#726, in the v0.42-dev bump) refuses to consume a special-transaction payload when the outer Transaction's version is < 3 with a non-zero nTxType, so any AssetLock transaction built with version 0 round-trips with trailing payload bytes and fails decode. The earlier commit only fixed the dpp test fixture. Three other places build AssetLock transactions the same broken way and surface in CI as "data not consumed entirely" failures across drive-abci strategy tests (address_funding_from_asset_lock + address_credit_withdrawal flows): - packages/strategy-tests/src/transitions.rs (two builders used by the drive-abci strategy_tests harness for fixed and dynamic-amount asset locks) - packages/rs-dpp/.../validate_asset_lock_transaction_structure/v0/mod.rs (test-only make_asset_lock_transaction helper) Switch each outer Transaction.version from 0 to 3. The inner AssetLockPayload.version (a separate u8 field) is left alone. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../validate_asset_lock_transaction_structure/v0/mod.rs | 2 +- packages/strategy-tests/src/transitions.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs index b84f8070242..fe354a865a2 100644 --- a/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs +++ b/packages/rs-dpp/src/identity/state_transition/asset_lock_proof/validate_asset_lock_transaction_structure/v0/mod.rs @@ -107,7 +107,7 @@ mod tests { }); Transaction { - version: 0, + version: 3, lock_time: 0, input: inputs, output: vec![burn_output], diff --git a/packages/strategy-tests/src/transitions.rs b/packages/strategy-tests/src/transitions.rs index 96e873e7ba7..fecb5a66aff 100644 --- a/packages/strategy-tests/src/transitions.rs +++ b/packages/strategy-tests/src/transitions.rs @@ -221,7 +221,7 @@ pub fn instant_asset_lock_proof_transaction_fixture( }); Transaction { - version: 0, + version: 3, lock_time: 0, input: vec![input], output: vec![burn_output, change_output], @@ -305,7 +305,7 @@ pub fn instant_asset_lock_proof_transaction_fixture_with_dynamic_amount( }); Transaction { - version: 0, + version: 3, lock_time: 0, input: vec![input], output: vec![burn_output, change_output], From 64a45ab083edc23c004dd98f7fafd625b3309248 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 May 2026 03:48:13 +0700 Subject: [PATCH 3/4] test(drive-abci): rebaseline strategy tests after asset-lock txid shift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumping the asset-lock test fixtures from version=0 to version=3 changes the asset-lock transaction's txid (and therefore every identity id derived from `OutPoint::new(tx.txid(), output_index)`). That cascades into nine strategy_tests with hardcoded expectations: - Six voting tests asserted contender ordering by relying on a specific identifier sort order. Refactor each to look contenders up by identity id, so the assertion is robust to future txid shifts. Drop a brittle document-byte snapshot that embedded the identity id (and would have needed updating on every fixture change anyway). - Two withdrawal tests hardcoded the post-run total identity balances — the new value differs by ~106k duffs because identity ordering changes the order in which fees are accrued. Update to the recomputed total. - One identity-and-document test hardcoded the GroveDB root hash after inserting 150 identities — update to the recomputed hash. All nine tests pass locally; full strategy_tests suite (90 tests) is green. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../test_cases/identity_and_document_tests.rs | 2 +- .../strategy_tests/test_cases/voting_tests.rs | 150 ++++++++++-------- .../test_cases/withdrawal_tests.rs | 4 +- 3 files changed, 85 insertions(+), 71 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs index c5348471b14..91932028afc 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/identity_and_document_tests.rs @@ -346,7 +346,7 @@ mod tests { .unwrap() .unwrap() ), - "975735252c11cea7ef3fbba86928077e37ebe1926972e6ae38e237ce0864100c".to_string() + "9d8167b295676ae3ca225170535c043fd8c5c919394ea708247206553a46cbed".to_string() ) } diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs index 0e291083d65..ab84e0594bc 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs @@ -589,19 +589,23 @@ mod tests { let second_contender = contenders.last().unwrap(); - assert_eq!( - first_contender.document.as_ref().map(hex::encode), - Some("00177f2479090a0286a67d6a1f67b563b51518edd6eea0461829f7d630fd65708d29124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b401000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d00046461736800210129124be7e86f97e959894a67a9cc078c3e0106d4bfcfbf34bc403a4f099925b40101".to_string()) - ); - - assert_eq!( - second_contender.document.as_ref().map(hex::encode), - Some("00490e212593a1d3cc6ae17bf107ab9cb465175e7877fcf7d085ed2fce27be11d68b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb01000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d0004646173680021018b8948a6801501bbe0431e3d994dcf71cf5a2a0939fe51b0e600076199aba4fb0100".to_string()) - ); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); + // Brittle document-byte snapshots removed: the serialized document + // embeds the identity id, which derives from the asset-lock txid and + // shifts whenever the asset-lock fixture's wire format changes. + assert!(first_contender.document.is_some()); + assert!(second_contender.document.is_some()); - assert_eq!(second_contender.identifier, identity1_id.to_vec()); + // Contender ordering follows identifier sort, which depends on the + // asset-lock-derived identity ids. Compare as a set so the assertion + // is robust to those id shifts. + let mut got_ids = vec![ + first_contender.identifier.clone(), + second_contender.identifier.clone(), + ]; + got_ids.sort(); + let mut want_ids = vec![identity1_id.to_vec(), identity2_id.to_vec()]; + want_ids.sort(); + assert_eq!(got_ids, want_ids); assert_eq!(first_contender.vote_count, Some(0)); @@ -945,21 +949,23 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); + // Look up by identity id rather than vec position — contender ordering + // follows identifier sort, which derives from the asset-lock txid and + // shifts whenever the asset-lock fixture's wire format changes. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); - let second_contender = contenders.last().unwrap(); + assert!(identity1_contender.document.is_some()); + assert!(identity2_contender.document.is_some()); - assert!(first_contender.document.is_some()); - - assert!(second_contender.document.is_some()); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); - - assert_eq!(second_contender.identifier, identity1_id.to_vec()); - - assert_eq!(first_contender.vote_count, Some(0)); - - assert_eq!(second_contender.vote_count, Some(0)); + assert_eq!(identity1_contender.vote_count, Some(0)); + assert_eq!(identity2_contender.vote_count, Some(0)); assert_eq!(abstain_vote_tally, Some(124)); } @@ -1306,23 +1312,24 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); + // Look up by identity id rather than vec position — see explanatory + // comment in the abstain-only test above. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); - let second_contender = contenders.last().unwrap(); - - assert!(first_contender.document.is_some()); - - assert!(second_contender.document.is_some()); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); - - assert_eq!(second_contender.identifier, identity1_id.to_vec()); + assert!(identity1_contender.document.is_some()); + assert!(identity2_contender.document.is_some()); // All vote counts are weighted, so for evonodes, these are in multiples of 4 - assert_eq!(first_contender.vote_count, Some(52)); - - assert_eq!(second_contender.vote_count, Some(56)); + assert_eq!(identity1_contender.vote_count, Some(56)); + assert_eq!(identity2_contender.vote_count, Some(52)); assert_eq!(lock_vote_tally, Some(16)); @@ -1686,19 +1693,21 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); - - let second_contender = contenders.last().unwrap(); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); - - assert_eq!(second_contender.identifier, identity1_id.to_vec()); + // Look up by identity id rather than vec position — see explanatory + // comment in the abstain-only test above. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); // All vote counts are weighted, so for evonodes, these are in multiples of 4 - assert_eq!(first_contender.vote_count, Some(60)); - - assert_eq!(second_contender.vote_count, Some(4)); + assert_eq!(identity1_contender.vote_count, Some(4)); + assert_eq!(identity2_contender.vote_count, Some(60)); assert_eq!(lock_vote_tally, Some(4)); @@ -2086,21 +2095,23 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); - - let second_contender = contenders.last().unwrap(); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); - - assert_eq!(second_contender.identifier, identity1_id.to_vec()); + // Look up by identity id rather than vec position — see explanatory + // comment in the abstain-only test above. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); // All vote counts are weighted, so for evonodes, these are in multiples of 4 // 19 votes were cast - assert_eq!(first_contender.vote_count, Some(60)); - - assert_eq!(second_contender.vote_count, Some(4)); + assert_eq!(identity1_contender.vote_count, Some(4)); + assert_eq!(identity2_contender.vote_count, Some(60)); assert_eq!(lock_vote_tally, Some(4)); @@ -2517,24 +2528,27 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); - - let second_contender = contenders.last().unwrap(); - - assert_eq!(first_contender.identifier, identity2_id.to_vec()); - - assert_eq!(second_contender.identifier, identity1_id.to_vec()); + // Look up by identity id rather than vec position — see explanatory + // comment in the abstain-only test above. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); // All vote counts are weighted, so for evonodes, these are in multiples of 4 assert_eq!( ( - first_contender.vote_count, - second_contender.vote_count, + identity1_contender.vote_count, + identity2_contender.vote_count, lock_vote_tally, abstain_vote_tally ), - (Some(64), Some(8), Some(0), Some(0)) + (Some(8), Some(64), Some(0), Some(0)) ); assert_eq!( diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/withdrawal_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/withdrawal_tests.rs index 34166e37cb0..85ab5cd4fbf 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/withdrawal_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/withdrawal_tests.rs @@ -1666,7 +1666,7 @@ mod tests { assert_eq!( total_credits_balance.total_identity_balances, - 409997575280380 + 409997575173840 ); // Around 4100 Dash. assert_eq!( @@ -2393,7 +2393,7 @@ mod tests { assert_eq!( total_credits_balance.total_identity_balances, - 409997575280380 + 409997575173840 ); // Around 4100 Dash. assert_eq!( From 56776ead0691f9272b4b5aee9341a0fd93e13ca4 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 8 May 2026 14:44:26 +0700 Subject: [PATCH 4/4] test(drive-abci): restore document-byte snapshots with rebaselined values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit QuantumExplorer asked to keep the actual document hex assertions instead of falling back to is_some() (PR #3621 review on voting_tests.rs:594). Restore the two snapshots in run_chain_block_two_state_transitions_ conflicting_unique_index_inserted_same_block_version_8 with the new serialized bytes — same identity-lookup pattern used for the other five voting tests, so the snapshots are anchored to identity1/identity2 rather than first/second contender ordering. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../strategy_tests/test_cases/voting_tests.rs | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs index ab84e0594bc..4c7eaa2154c 100644 --- a/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs +++ b/packages/rs-drive-abci/tests/strategy_tests/test_cases/voting_tests.rs @@ -585,31 +585,29 @@ mod tests { assert_eq!(contenders.len(), 2); - let first_contender = contenders.first().unwrap(); + // Look up by identity id rather than vec position — see explanatory + // comment in the abstain-only test below. + let identity1_contender = contenders + .iter() + .find(|c| c.identifier == identity1_id.to_vec()) + .expect("identity1 in contenders"); + let identity2_contender = contenders + .iter() + .find(|c| c.identifier == identity2_id.to_vec()) + .expect("identity2 in contenders"); - let second_contender = contenders.last().unwrap(); + assert_eq!( + identity1_contender.document.as_ref().map(hex::encode), + Some("0021278016512ff707d45a0aa8893a4b1ba67b08158b31bc2bda11a0addb8ab1f4341e6a8e6c01488a75104a8dbc73501ded5cd23abaf688349a8ab34b1157513201000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d000464617368002101341e6a8e6c01488a75104a8dbc73501ded5cd23abaf688349a8ab34b115751320100".to_string()) + ); - // Brittle document-byte snapshots removed: the serialized document - // embeds the identity id, which derives from the asset-lock txid and - // shifts whenever the asset-lock fixture's wire format changes. - assert!(first_contender.document.is_some()); - assert!(second_contender.document.is_some()); - - // Contender ordering follows identifier sort, which depends on the - // asset-lock-derived identity ids. Compare as a set so the assertion - // is robust to those id shifts. - let mut got_ids = vec![ - first_contender.identifier.clone(), - second_contender.identifier.clone(), - ]; - got_ids.sort(); - let mut want_ids = vec![identity1_id.to_vec(), identity2_id.to_vec()]; - want_ids.sort(); - assert_eq!(got_ids, want_ids); - - assert_eq!(first_contender.vote_count, Some(0)); - - assert_eq!(second_contender.vote_count, Some(0)); + assert_eq!( + identity2_contender.document.as_ref().map(hex::encode), + Some("0010b1d465b94520e76daf018ee6b2740c871d51b7ce9690f60fc7a4a70f1bfd6f3a3c745d6c4d3b88ea52976eaab80dbaa77258885b7b6d8e1edfd00fed72414a01000700000187690895980000018769089598000001876908959800077175616e74756d077175616e74756d0004646173680021013a3c745d6c4d3b88ea52976eaab80dbaa77258885b7b6d8e1edfd00fed72414a0101".to_string()) + ); + + assert_eq!(identity1_contender.vote_count, Some(0)); + assert_eq!(identity2_contender.vote_count, Some(0)); } #[stack_size(STACK_SIZE)]