feat: add IdentityCreateFromShieldedPool state transition (shielded-pool-funded identity creation)#3816
Conversation
… 20) Stage 1 of #3813. Adds the DPP layer for a new shielded-pool-funded identity-creation state transition (StateTransitionType = 20), gated to protocol v12 like the rest of the shielded family. - New transition tree under state_transitions/shielded/ identity_create_from_shielded_pool_transition/ (V0 struct: variable public_keys + denomination + Orchard spend + derived identity_id). - identity_id = double_sha256(sorted nullifiers), shared derivation fn. - extra_sighash_data helper binding id + denomination + full key set into the Orchard sighash (anti-redirection), in shielded/mod.rs. - compute_shielded_identity_create_fee predictor + storage-byte constants. - StateTransitionType::IdentityCreateFromShieldedPool = 20 (TAIL) and all StateTransition enum/macro arms. - Versioned denomination set {0.1,0.3,0.5,1.0} DASH in event_constants (v8), empty pre-v12; serialization version field across v1/v2. - New basic consensus error ShieldedInvalidDenominationError (code 10827). cargo check -p dpp (default + all-features + tests) green; 15 new unit tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…verify (type 20) Stage 2 + 4 of #3813. rs-drive action + booking: - New action tree state_transition_action/shielded/ identity_create_from_shielded_pool/ (carries built Identity, notes, denomination, fee_amount, pool balance). Transformer builds the new identity (id + keys + balance = denomination). - StateTransitionAction::IdentityCreateFromShieldedPoolAction (TAIL) + user_fee_increase + dispatch arms. - Converter emits insert_nullifiers + AddNewIdentity{balance=denomination} + AddToSystemCredits(denomination) + insert_notes(change) + UpdateTotalBalance(pool - denomination). Conservation: pool debit == system credit (net 0); fee moved from identity at execution. - Converter method-version key across struct/v1/v2. rs-drive strict merged prove/verify (built strict from day one, cf #3812): - prove arm: merge nullifier PathQuery + full_identity_query (limits cleared before merge). - verify arm: verify_query_with_absence_proof (limit u16::MAX), partition by PATH (nullifier tree vs identity subtrees), reconstruct the Identity, require all nullifiers spent. - New StateTransitionProofResult::VerifiedIdentityWithShieldedNullifiers. cargo check -p drive (default + --features verify) green; 6 converter conservation tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… booking (type 20) Stage 3 of #3813. - New validation module identity_create_from_shielded_pool/ with transform_into_action: stateful checks (pool balance, anchor exists, nullifier replay) + key-structure validation + per-key proof-of-possession over the transition signable bytes + builds the action (id re-derived from nullifiers, canonical). - shielded_proof trait: proof verification arm binding identity id + denomination + full key set into the Orchard extra_sighash_data (anti-redirection), value_balance == denomination; new ShieldedMinFeeKind::IdentityCreate{num_keys} min-fee gate (denomination >= compute_shielded_identity_create_fee). - New ExecutionEvent::PaidFromShieldedPoolToNewIdentity (create-then- deduct, funded from the pool): meters the GroveDB write + adds the flat compute fee as additional_fixed_fee_cost, moves total_fee from the new identity into the fee pools. validate_fees_of_event affordability gate (denomination >= total_fee -> IdentityInsufficientBalanceError). - All processor predicate/dispatch trait arms (is_allowed gated on SHIELDED_POOL_INITIAL_PROTOCOL_VERSION, basic_structure, state, identity_based_signature, identity_nonces, address_*, identity_balance) + transformer dispatch. - rs-platform-version: validation-version field across v1-v8 (Some(0) basic_structure in v8, None pre-v12). cargo check -p drive-abci green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p2 (type 20) Stage 5 (part 1) of #3813. - wasm-dpp2 state_transition.rs: action-type-number => 20 + all five grouping matches (owner-id, identity-nonce, set-owner, set-identity-contract-nonce, set-identity-nonce). - wasm-dpp2 proof_result: new VerifiedIdentityWithShieldedNullifiersWasm wrapper + convert arm + TS union entry. - wasm-dpp (legacy): BasicError::ShieldedInvalidDenominationError mapping + state_transition_factory arm. cargo check --workspace green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage 5 (part 2) of #3813. New `IdentityCreateFromShieldedPool` SDK trait on `Sdk` (gated by the `shielded` feature): builds the transition from a pre-built Orchard bundle + the new identity's public keys (with per-key PoP signatures), validates structure, and broadcasts. Mirrors `unshield`. cargo check -p dash-sdk --features shielded green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ok docs (#3813) Stage 6 (part 1) of #3813. - dpp tests: extra_sighash_data layout + binding (id / denomination / full key set are all committed → anti-redirection/anti-key-swap), and compute_shielded_identity_create_fee monotonic growth with key count. - book: shielded-fees.md fee-extraction row (metered + moved-from-identity model, fixed denomination, exact value_balance) + error-codes.md 10827 (ShieldedInvalidDenominationError). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… rejection (#3813) Adds a negative test exercising the new strict merged-query verify arm: an empty proof cannot satisfy verify_query_with_absence_proof over the merged {nullifier-tree, identity} query, so the verifier rejects. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…surface (#3813) Stage 5 (part 3) of #3813 — the client-wallet surface (built by the swift-rust-ffi-engineer agent, integrated + compile-verified here). - rs-dpp: new shielded `builder/identity_create_from_shielded_pool.rs` (Orchard spend bundle binding the identity payload via extra_sighash_data, exact-equality value_balance == denomination, change note back to pool, per-key proof-of-possession signing) + builder/mod.rs registration. - rs-platform-wallet: `identity_create_from_shielded_pool` operation + `ShieldedFeeKind::IdentityCreate { num_keys }` note-selection variant (denomination − fee reservation, exact-equality model). - rs-platform-wallet-ffi: `platform_wallet_manager_shielded_identity_create_from_pool` extern "C" fn marshalling to the wallet operation. - swift-sdk: `shieldedIdentityCreateFromPool(...)` wrapper bridging to the FFI (persist/load/bridge only; all logic stays in Rust). cargo check -p platform-wallet-ffi green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
📖 Book Preview built successfully. Download the preview from the workflow artifacts. Updated at 2026-06-09T10:44:31.282Z |
|
✅ Review complete (commit 73b58d7) |
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Convergent blocking issue across four agents: the wire identity_id field is excluded from the platform sighash, not Orchard-bound, and never validated against derive_identity_id_from_actions(&actions). Consensus creates the identity at the derived id, but proof generation (rs-drive prove) and SDK proof verification both query the trusted wire id, so a relayer can mutate that field and make an honestly-executed transition unverifiable through the SDK proof API. A single equality check in validate_structure fixes the source of the divergence. Also flagging a wallet/SDK broadcast inconsistency where the doc-comment claims the helper waits for proven execution while it only broadcasts.
🔴 1 blocking | 🟡 2 suggestion(s) | 💬 2 nitpick(s)
3 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rs`:
- [BLOCKING] packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rs:49-54: Wire `identity_id` is unauthenticated and malleable — desyncs prove/verify from the canonical state
`identity_id` carries `#[platform_signable(exclude_from_sig_hash)]`, the Orchard `extra_sighash_data` is built from `derive_identity_id_from_actions(&v0.actions)` (not from this field), and `validate_structure` (packages/rs-dpp/.../v0/state_transition_validation.rs) never checks `v0.identity_id == derive_identity_id_from_actions(&v0.actions)`. The transformer at packages/rs-drive/src/state_transition_action/shielded/identity_create_from_shielded_pool/v0/transformer.rs:33 silently re-derives and ignores the wire field, so a relayer or block proposer can overwrite this field with any 32 bytes without invalidating the Orchard binding or any per-key PoP, and the transition still executes — creating the identity at the derived id.
The wire field is then trusted as canonical downstream:
- `IdentityCreateFromShieldedPoolTransitionV0::modified_data_ids()` returns `vec![self.identity_id]` (state_transition_like.rs:31) — block events and indexers see a different id than was written.
- `prove_state_transition_v0` builds the merged path query from `st.identity_id()` (prove_state_transition/v0/mod.rs:454).
- `verify_state_transition_was_executed_with_proof` rebuilds the same merged query from `st.identity_id()` (verify_state_transition_was_executed_with_proof/v0/mod.rs:1520).
Result: a mutated wire id makes the prover prove absence at the wrong path; the verifier returns `IncompleteProof("... created identity is absent or incomplete in the proof")` for a successfully-executed transition. Not a fund-theft / overwrite vector (the derived id is collision-resistant and consensus uses it), but a soundness break in the SDK proof API and a state/indexer consistency footgun.
Fix at the source: enforce equality in `validate_structure` (or at the top of `transform_into_action_v0`), rejecting the transition with a basic consensus error when `wire_identity_id != derive_identity_id_from_actions(&actions)`. That single check makes the wire id non-malleable consensus-wide and leaves the existing prove/verify/`modified_data_ids` paths correct without further changes.
In `packages/rs-platform-wallet/src/wallet/shielded/operations.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/wallet/shielded/operations.rs:738-743: Wallet helper claims to wait for proven execution but only broadcasts; `finalize_pending` fires on relay-ACK
The doc-comment at lines 739-740 says the SDK helper '…waits for proven execution', but `Sdk::identity_create_from_shielded_pool` at packages/rs-sdk/src/platform/transition/identity_create_from_shielded_pool.rs:60 only calls `state_transition.broadcast(self, settings)`. The three sibling shielded spends in the same file — `unshield` (~line 410), `transfer` (~line 515), `withdraw` (~line 620) — all call `state_transition.broadcast_and_wait::<StateTransitionProofResult>(sdk, None)` so `finalize_pending` runs only after proven inclusion. As written, this helper marks notes spent on broadcast-ACK, so a Platform-level rejection after relay leaves the local store believing the spend succeeded (and the `cancel_pending` fallback never fires).
Either switch the SDK helper to `broadcast_and_wait::<StateTransitionProofResult>` for parity with the other shielded ops, or drop the misleading 'waits for proven execution' wording.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/processor/v0/mod.rs:233-239: Halo2 verification runs before per-key PoP validation — partial DoS amplifier
For `IdentityCreateFromShieldedPool`, `validate_shielded_proof` runs the full Halo2 verification at line 234, while per-key proof-of-possession validation only happens later inside `transform_into_action`. Per-key PoP signature bytes are excluded from the platform `signable_bytes` (Signable form) and are not bound into the Orchard `extra_sighash_data` (which binds id+denomination+keys, not PoP signatures), so an attacker who sees one valid mempool transition can flip a PoP byte and resubmit. Each variant pays full Orchard verification cost before being rejected at the later PoP loop, without consuming nullifiers or charging fees.
The nullifier-based `unique_identifiers()` does help dedupe at the mempool level, but uniqueness rejection typically happens after stateless validation has already done the expensive work. Move the new identity's key-structure + PoP validation ahead of `validate_shielded_proof` so malformed PoP signatures are rejected before paying for Halo2 verification.
…-Halo2, broadcast-and-wait (#3816) Addresses thepastaclaw review on PR #3816: - BLOCKING: enforce `identity_id == derive_identity_id_from_actions(&actions)` in `validate_structure` (basic, InvalidIdentifierError), making the wire id authoritative consensus-wide so prove/verify/modified_data_ids (which trust the wire field) can never desync from the canonical derived id. Was: wire id excluded from sighash + re-derived only in the action transformer, so a relayer could mutate it and make an honestly-executed transition unverifiable through the SDK proof API. + rejection test. - DoS hardening: run the cheap key-structure + per-key proof-of-possession checks inside `validate_shielded_proof` BEFORE Halo2 (the PoP sigs aren't covered by the Orchard proof, so a flipped PoP byte previously wasted full proof verification before rejection in transform_into_action). Removed the now-redundant checks from transform_into_action (which no longer needs signable_bytes/execution_context). - SDK: identity_create_from_shielded_pool now broadcast_and_wait::< StateTransitionProofResult> for parity with unshield/transfer/withdraw, so the wallet's finalize_pending only runs on proven inclusion (not relay-ACK) and the cancel_pending fallback fires on a post-relay Platform rejection. cargo check --workspace green; 16 dpp transition tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed
|
…nt table (#3816) The fees/overview.md ExecutionEvent table enumerates each variant and the transitions that use it; add the new IdentityCreateFromShieldedPool event (eighth variant) and update the count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Cumulative incremental review at head 08679ff vs prior 7e1734e. Prior reconciliation: (1) wire identity_id malleability — FIXED via validate_structure equality check + regression test; (2) wallet broadcast_and_wait — FIXED, SDK helper now returns StateTransitionProofResult; (3) Halo2-before-PoP DoS amplifier — FIXED, key structure + per-key PoP now run inside validate_shielded_proof before Halo2; (4) unchecked u64 note accumulation — STILL VALID, carried forward; (5) classifier-trait test fixtures — STILL VALID, carried forward. One new latest-delta blocking issue: the PoP-before-Halo2 move dropped fee accounting for the per-key signature verifications because validate_shielded_proof carries no execution context and the transformer's add_operation calls were removed, so successful Type 20 transitions underpay by one SignatureVerification cost per key.
🔴 1 blocking | 💬 2 nitpick(s)
2 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs:385-408: New latest-delta: PoP-before-Halo2 move dropped per-key signature-verification fee accounting
Moving `IdentityCreateFromShieldedPool` key-structure and per-key PoP validation into `validate_shielded_proof` correctly fixes the prior DoS-amplifier ordering, but `validate_shielded_proof` takes only `&self` and `platform_version` — no `StateTransitionExecutionContext`. The previous transformer-side loop added one `ValidationOperation::SignatureVerification(SignatureVerificationOperation::new(key.key_type()))` per key (matching `IdentityCreate` at `identity_create/identity_and_signatures/v0/mod.rs:32-34`); those `add_operation` calls were removed alongside this move and not re-added anywhere. The downstream `PaidFromShieldedPoolToNewIdentity` event consumes `execution_context.operations_consume()` (`execution_event/mod.rs:612`) and `validate_fees_of_event` folds those operations into the metered fee (`validate_fees_of_event/v0/mod.rs:308-312`), while `additional_fixed_fee_cost = compute_shielded_identity_create_fee` only covers per-key STORAGE bytes (`compute_shielded_identity_create_fee_v0` at `rs-dpp/.../compute_minimum_shielded_fee/v0/mod.rs:214-249`), not signature-verification CPU. Net effect: successful Type 20 transitions underpay by one signature-verification cost per key (consensus-critical fee divergence vs. plain `IdentityCreate`). Keep the early PoP rejection (cheap reverify is fine), but either (a) thread an execution context / `&mut Vec<ValidationOperation>` into this validation path and record one `SignatureVerification` per key here, or (b) leave a thin accounting-only PoP pass in `transform_into_action_v0` that records the operations on the success path.
…note sum + test fixtures (#3816) Addresses thepastaclaw follow-up review on 08679ff: - BLOCKING: the PoP-before-Halo2 move dropped the per-key SignatureVerification fee accounting (validate_shielded_proof has no execution context). Restore it: keep the early PoP *rejection* in validate_shielded_proof, and add a thin accounting-only pass in transform_into_action_v0 (success path) that records one SignatureVerification op per key WITHOUT re-verifying — so a Type 20 transition is charged the same signature-verification CPU as a plain IdentityCreate. Re-threads execution_context into the transformer. - Carried-forward: checked u64 accumulation in shielded note selection (try_fold + checked_add; legitimate values are supply-bounded, but never trust the store blindly). - Carried-forward: add IdentityCreateFromShieldedPool to the has_shielded_proof_validation / has_shielded_minimum_fee_validation true-list test fixtures. cargo check --workspace green; 11 shielded_proof tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds IdentityCreateFromShieldedPool across DPP, Drive/ABCI, wallet/SDK/FFI, versioning, docs, and WASM surfaces. Introduces denomination validation, identity-create fee computation, builder, transform/action flow, proof/verify plumbing, and a VerifiedIdentityWithShieldedNullifiers result. ChangesShielded identity create from pool
Estimated code review effort Possibly related issues
Possibly related PRs
Suggested labels Suggested reviewers
Poem
✨ Finishing Touches🧪 Generate unit tests (beta)
|
…e + unique-key-hash state checks (#3816) P1 (codex): the transition creates an identity but was grouped with the non-creating shielded transitions, so it skipped the identity-create state checks. A public key already registered to another identity (or, vanishingly, a pre-existing identity at the derived id) would then fail INSIDE the AddNewIdentity write at execution as an internal Drive error instead of a clean consensus rejection. Add both checks to transform_into_action (where this transition does all its stateful validation, like Unshield), mirroring IdentityCreate's validate_state: - IdentityAlreadyExistsError if an identity already exists at the derived id (double_sha256(sorted nullifiers) — collision-resistant + single-use, so practically unreachable, but a clean rejection beats an internal error). - validate_unique_identity_public_key_hashes_not_in_state over the new keys (a failure is a plain rejection — no asset lock to penalize). The lookups are accounted into the execution context for fee metering. cargo check --workspace green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Latest delta 08679ff..febc057 is docs-only (one row added to book/src/fees/overview.md). No new findings in this delta. Reconciling prior findings against current head: all 3 prior findings remain STILL VALID and are carried forward. The blocking per-key signature-verification fee-accounting regression (introduced by the PoP-before-Halo2 hoist) still drives REQUEST_CHANGES; the two nitpicks remain. The earlier wire-id malleability, broadcast-and-wait, and Halo2-before-PoP findings were FIXED at 08679ff and remain fixed at febc057.
🔴 1 blocking | 💬 2 nitpick(s)
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs:385-409: [Carried-forward STILL VALID] Hoisted per-key PoP loop does not record SignatureVerification operations (fee-accounting regression vs IdentityCreate)
The PoP-before-Halo2 hoist at lines 385-409 correctly rejects malformed key structure and bad per-key proof-of-possession before paying for Orchard verification — good DoS hardening — but `validate_shielded_proof` takes only `&self` and `platform_version`, so the per-key `verify_signature` calls cannot record `ValidationOperation::SignatureVerification(SignatureVerificationOperation::new(key.key_type()))` into a `StateTransitionExecutionContext`. The analogous plain-identity path adds exactly this operation per key (`state_transitions/identity_create/identity_and_signatures/v0/mod.rs:32` and `state_transitions/identity_create/state/v0/mod.rs:233`); the prior transformer-side loop did the same for Type 20 and was removed alongside the move. `PaidFromShieldedPoolToNewIdentity` later folds `execution_context.operations_consume()` into the metered fee, while the fixed `compute_shielded_identity_create_fee` only prices Halo2/per-action shielded work plus per-key STORAGE bytes — not per-key signature-verification CPU. Net effect: every successful Type 20 transition underpays by one signature-verification cost per key (consensus-critical economic divergence vs IdentityCreate; also lets up to `num_keys` unmetered asymmetric verifications run on rejection paths before nullifiers burn). Fix options: (a) thread an execution context or `&mut Vec<ValidationOperation>` into `validate_shielded_proof` for this arm and record one SignatureVerification per key here, or (b) keep this loop as a cheap DoS short-circuit and re-add a thin accounting-only PoP pass to `transform_into_action_v0` on the success path so the operations land in the cost ledger.
|
Heads-up: the latest Since then, |
|
✅ DashSDKFFI.xcframework built for this PR.
SwiftPM (host the zip at a stable URL, then use): .binaryTarget(
name: "DashSDKFFI",
url: "https://your.cdn.example/DashSDKFFI.xcframework.zip",
checksum: "250d8387d5083f283ca265412eb97e60be04afda4e166020bb0ad26e4a7eb566"
)Xcode manual integration:
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs (1)
237-270: ⚡ Quick winAdd the new transition to the
has_is_allowed_validationtest matrix.
StateTransition::IdentityCreateFromShieldedPool(_)is now classified as requiring allowed-validation, but it isn’t included intransitions_requiring_allowed_validation(), leaving this behavior unguarded by regression tests.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs` around lines 237 - 270, Add the missing StateTransition::IdentityCreateFromShieldedPool variant to the transitions_requiring_allowed_validation() test matrix so that IdentityCreateFromShieldedPool is covered by the has_is_allowed_validation tests; update the Vec returned by transitions_requiring_allowed_validation() to include a StateTransition::IdentityCreateFromShieldedPool entry (construct the V0 variant with its default value or call the existing factory helper, e.g. StateTransition::IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition::V0(IdentityCreateFromShieldedPoolTransitionV0::default())) or make_identity_create_from_shielded_pool_transition() if that helper exists).packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs (1)
202-230: ⚡ Quick winAdd
IdentityCreateFromShieldedPoolto the “no pre-check” classifier test.Line 85 updates runtime classification, but the negative test set doesn’t include this new variant yet. Add it to lock the behavior and prevent future regressions in
has_identity_minimum_balance_pre_check_validation().🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs` around lines 202 - 230, The test should_return_false_for_transitions_without_balance_pre_check is missing the new StateTransition variant IdentityCreateFromShieldedPool; update the transitions Vec in that test to include a tuple like ("IdentityCreateFromShieldedPool", StateTransition::IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition::V0(IdentityCreateFromShieldedPoolTransitionV0::default()))) so the negative classifier for has_identity_minimum_balance_pre_check_validation() covers the new variant and prevents regressions.packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs (1)
726-877: ⚡ Quick winAdd direct fee-gate tests for
IdentityCreateFromShieldedPool.The new
ShieldedMinFeeKind::IdentityCreate { num_keys }path is implemented, butvalidate_minimum_shielded_feetests here still only exercise withdrawal cases. Please add at least:denomination < min_feereject, boundary accept at minimum, and a key-count-scaling case.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs` around lines 726 - 877, Add unit tests exercising validate_minimum_shielded_fee for the new ShieldedMinFeeKind::IdentityCreate { num_keys } path: create a StateTransition representing an IdentityCreateFromShieldedPool (the IdentityCreate variant) and call validate_minimum_shielded_fee(platform_version) in three tests — (1) denomination < computed_identity_min_fee should be rejected, (2) denomination == computed_identity_min_fee should be accepted, and (3) a multi-key case where num_keys > 1 increases the required fee so that a denomination that would pass for one key is rejected for the scaled fee (and accepted when increased to the scaled minimum). In each test compute the expected minimum fee the same way validate_minimum_shielded_fee does for ShieldedMinFeeKind::IdentityCreate { num_keys }, use PlatformVersion::latest(), and assert result.is_valid() or matches the appropriate ConsensusError as in the existing withdrawal tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rs`:
- Around line 119-130: The accumulation and subtraction use unchecked u64
arithmetic and must be made safe: replace the unchecked sum of spends (currently
using spends.iter().map(...).sum() assigned to total_spent) with a checked fold
(e.g., try_fold or checked_add) that returns a ProtocolError::ShieldedBuildError
on overflow, and compute change_amount using checked_sub(denomination)
(returning the same ShieldedBuildError on underflow) instead of direct
subtraction; update the error messages to indicate overflow/underflow and
reference the same symbols (spends, note.value().inner(), total_spent,
denomination, change_amount, ProtocolError::ShieldedBuildError) so callers can
locate the fix.
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/v0/mod.rs`:
- Around line 58-103: The code builds a local drive_operations vec (created in
this block and populated by read_pool_total_balance,
validate_minimum_pool_notes, validate_anchor_exists, validate_nullifiers) but
never flushes those operations into the execution_context before any early
return or on success, so metering is lost; before each return (every place that
returns Ok(...) including the ConsensusValidationResult error returns and the
final success return) call the execution_context method that records drive
operations (e.g., add_drive_operations or extend
execution_context.drive_operations) to push the local drive_operations into
execution_context, then continue returning the same value—ensure this flush
occurs right before all early returns from this function and the final return so
reads are correctly metered.
In
`@packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/identity_create_from_shielded_pool_transition.rs`:
- Around line 33-51: Before emitting the IdentityOperation and SystemOperation,
validate that v0.identity.balance() equals v0.denomination and abort/return an
error if not; specifically, in the conversion path that creates the new identity
(where you push IdentityOperation(IdentityOperationType::AddNewIdentity {
identity: v0.identity, ... }) and
SystemOperation(SystemOperationType::AddToSystemCredits { amount:
v0.denomination }), add a guard that compares v0.identity.balance() to
v0.denomination and fails the conversion (or returns a clear error/result) when
they differ so you cannot emit inconsistent accounting ops.
In
`@packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs`:
- Around line 1520-1651: Recompute the identity id from the transition actions
(using the same derivation logic the state transition uses) and verify it equals
st.identity_id(); then, before returning VerifiedIdentityWithShieldedNullifiers,
compare the reconstructed identity's balance to st.denomination() and its public
key set to st.public_keys() (order/ID-insensitive comparison), and return an
appropriate ProofError (e.g. IncorrectProof or IncompleteProof) if any mismatch
occurs; locate this logic near the post-proof reconstruction (the loop over
proved_key_values, the identity build into dpp::prelude::Identity, and the
return of VerifiedIdentityWithShieldedNullifiers) and insert these checks there.
In `@packages/rs-platform-wallet/src/wallet/shielded/note_selection.rs`:
- Around line 216-240: select_notes_for_denomination currently only checks
denomination > predicted_fee, but must first reject denominations not in the
protocol-versioned allowed set to avoid wasting expensive prove work; before
calling select_notes (and before reserving notes) validate denomination against
the platform-version rules (e.g., call the protocol helper that returns allowed
shielded denominations or an is_valid_shielded_denomination(platform_version,
denomination) function) and return a PlatformWalletError::ShieldedBuildError if
not allowed; update select_notes_for_denomination to perform this check early
(use the function name select_notes_for_denomination to locate the code) and add
a regression test that calls the public entry (the path that leads into
build_identity_create_from_shielded_pool_transition) with a non-member
denomination to assert it fails fast without performing proof construction.
In `@packages/wasm-dpp/src/state_transition/state_transition_factory.rs`:
- Around line 86-89: In the match arm handling
StateTransition::ShieldedWithdrawal and
StateTransition::IdentityCreateFromShieldedPool inside state_transition_factory
(state_transition_factory.rs), replace the panic-inducing todo! with returning a
recoverable error: construct and return Err(JsValue) (e.g., JsValue::from_str)
containing a clear message like "shielded transitions not yet implemented" so
the wasm caller receives a JS error instead of panicking; ensure the enclosing
function's Result error type is compatible with JsValue (or convert
appropriately) so the match arm returns Err(...) rather than panicking.
---
Nitpick comments:
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs`:
- Around line 202-230: The test
should_return_false_for_transitions_without_balance_pre_check is missing the new
StateTransition variant IdentityCreateFromShieldedPool; update the transitions
Vec in that test to include a tuple like ("IdentityCreateFromShieldedPool",
StateTransition::IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition::V0(IdentityCreateFromShieldedPoolTransitionV0::default())))
so the negative classifier for
has_identity_minimum_balance_pre_check_validation() covers the new variant and
prevents regressions.
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs`:
- Around line 237-270: Add the missing
StateTransition::IdentityCreateFromShieldedPool variant to the
transitions_requiring_allowed_validation() test matrix so that
IdentityCreateFromShieldedPool is covered by the has_is_allowed_validation
tests; update the Vec returned by transitions_requiring_allowed_validation() to
include a StateTransition::IdentityCreateFromShieldedPool entry (construct the
V0 variant with its default value or call the existing factory helper, e.g.
StateTransition::IdentityCreateFromShieldedPool(IdentityCreateFromShieldedPoolTransition::V0(IdentityCreateFromShieldedPoolTransitionV0::default()))
or make_identity_create_from_shielded_pool_transition() if that helper exists).
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs`:
- Around line 726-877: Add unit tests exercising validate_minimum_shielded_fee
for the new ShieldedMinFeeKind::IdentityCreate { num_keys } path: create a
StateTransition representing an IdentityCreateFromShieldedPool (the
IdentityCreate variant) and call validate_minimum_shielded_fee(platform_version)
in three tests — (1) denomination < computed_identity_min_fee should be
rejected, (2) denomination == computed_identity_min_fee should be accepted, and
(3) a multi-key case where num_keys > 1 increases the required fee so that a
denomination that would pass for one key is rejected for the scaled fee (and
accepted when increased to the scaled minimum). In each test compute the
expected minimum fee the same way validate_minimum_shielded_fee does for
ShieldedMinFeeKind::IdentityCreate { num_keys }, use PlatformVersion::latest(),
and assert result.is_valid() or matches the appropriate ConsensusError as in the
existing withdrawal tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e22d148-90ed-4a65-b38d-0e868b5d98bc
📒 Files selected for processing (89)
book/src/error-handling/error-codes.mdbook/src/fees/overview.mdbook/src/fees/shielded-fees.mdpackages/rs-dpp/src/errors/consensus/basic/basic_error.rspackages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rspackages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_denomination_error.rspackages/rs-dpp/src/errors/consensus/codes.rspackages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rspackages/rs-dpp/src/shielded/builder/mod.rspackages/rs-dpp/src/shielded/compute_minimum_shielded_fee/mod.rspackages/rs-dpp/src/shielded/compute_minimum_shielded_fee/v0/mod.rspackages/rs-dpp/src/shielded/mod.rspackages/rs-dpp/src/state_transition/mod.rspackages/rs-dpp/src/state_transition/proof_result.rspackages/rs-dpp/src/state_transition/state_transition_types.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/accessors/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/accessors/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/methods/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/methods/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/state_transition_estimated_fee_validation.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/state_transition_like.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/state_transition_validation.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/state_transition_like.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/state_transition_validation.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/types.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/v0_methods.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/version.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/version.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rspackages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rspackages/rs-drive-abci/src/execution/platform_events/state_transition_processing/validate_fees_of_event/v0/mod.rspackages/rs-drive-abci/src/execution/types/execution_event/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/v0/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rspackages/rs-drive/src/prove/prove_state_transition/v0/mod.rspackages/rs-drive/src/state_transition_action/action_convert_to_operations/mod.rspackages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/identity_create_from_shielded_pool_transition.rspackages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/mod.rspackages/rs-drive/src/state_transition_action/mod.rspackages/rs-drive/src/state_transition_action/shielded/identity_create_from_shielded_pool/mod.rspackages/rs-drive/src/state_transition_action/shielded/identity_create_from_shielded_pool/transformer.rspackages/rs-drive/src/state_transition_action/shielded/identity_create_from_shielded_pool/v0/mod.rspackages/rs-drive/src/state_transition_action/shielded/identity_create_from_shielded_pool/v0/transformer.rspackages/rs-drive/src/state_transition_action/shielded/mod.rspackages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rspackages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rspackages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rspackages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rspackages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rspackages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rspackages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rspackages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rspackages/rs-platform-wallet-ffi/src/shielded_send.rspackages/rs-platform-wallet/src/wallet/platform_wallet.rspackages/rs-platform-wallet/src/wallet/shielded/note_selection.rspackages/rs-platform-wallet/src/wallet/shielded/operations.rspackages/rs-sdk/src/platform/transition.rspackages/rs-sdk/src/platform/transition/identity_create_from_shielded_pool.rspackages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ManagedPlatformWallet.swiftpackages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swiftpackages/wasm-dpp/src/errors/consensus/consensus_error.rspackages/wasm-dpp/src/state_transition/state_transition_factory.rspackages/wasm-dpp2/src/state_transitions/base/state_transition.rspackages/wasm-dpp2/src/state_transitions/proof_result/convert.rspackages/wasm-dpp2/src/state_transitions/proof_result/shielded.rs
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Latest delta 52bb6d7..4085683 adds identity-absence and unique-key-hash state checks in transform_into_action_v0 (mirroring IdentityCreate::validate_state_v0), with no new defects in the change itself. All three previously-fixed prior findings remain fixed at head, and the three carried-forward STILL VALID findings have been resolved by 52bb6d7 for PoP fee accounting and checked u64 sum, and partially resolved for classifier fixtures (shielded_proof.rs now includes Type 20; is_allowed.rs and identity_based_signature.rs fixtures still omit it). Validate_minimum_shielded_fee still does not exercise the IdentityCreate { num_keys } arm, and the new state checks added in this delta ship without targeted unit tests.
🟡 4 suggestion(s)
3 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs:238-271: Carried-forward (narrowed): transitions_requiring_allowed_validation fixture omits IdentityCreateFromShieldedPool
Production at lines 40 and 83 includes `IdentityCreateFromShieldedPool(_)` in both `has_is_allowed_validation` and `validate_is_allowed` (gated by `SHIELDED_POOL_INITIAL_PROTOCOL_VERSION`), but the test fixture `transitions_requiring_allowed_validation()` lists only Shield/ShieldedTransfer/Unshield/ShieldFromAssetLock/ShieldedWithdrawal among shielded variants. The shielded_proof.rs portion of the prior classifier-fixture finding was fixed at 52bb6d7e, but this fixture was not updated. A future refactor that accidentally drops Type 20 from the v12 activation gate or the `has_is_allowed_validation` arm would not be caught by `should_return_true_for_transitions_requiring_allowed_check`. Add Type 20 to the fixture alongside the other shielded variants.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs:354-396: Carried-forward (narrowed): transitions_not_using_identity_in_state fixture omits IdentityCreateFromShieldedPool
Production at line 182 (`uses_identity_in_state`) and line 211 (`validates_signature_based_on_identity_info`) both classify `IdentityCreateFromShieldedPool(_)` as `false`, but the test fixture `transitions_not_using_identity_in_state()` only enumerates the original five shielded variants plus the address-based and IdentityCreate transitions. A future refactor that flips Type 20 into the identity-based-signature group — which would cause the processor to expect a fetched identity for an id that does not yet exist — would not be caught. Add Type 20 to this fixture so the two `#[test]`s downstream of it cover the new variant.
In `packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs:726-878: Carried-forward (narrowed): validate_minimum_shielded_fee tests do not exercise the IdentityCreate { num_keys } arm
The `validate_minimum_shielded_fee` test module has thorough boundary tests for ShieldedWithdrawal (below-min, at-min, at-max, above-max, i64::MAX guard) but never exercises the new `ShieldedMinFeeKind::IdentityCreate { num_keys }` dispatch arm wired at lines 218 and 275. The min-fee gate is consensus-critical (the affordability gate uses `compute_shielded_identity_create_fee` and rejects when `denomination ≥ total_fee` does not hold) and scales with `num_keys`. Add at least three boundary tests parallel to the withdrawal ones: (a) denomination == compute_shielded_identity_create_fee(actions, num_keys) → rejected; (b) denomination > compute_shielded_identity_create_fee(...) → accepted; (c) min-fee strictly monotonic in `num_keys`. Without these, a refactor that swapped the wrong fee formula into the IdentityCreate arm or routed the variant to the wrong `ShieldedMinFeeKind` would slip through.
In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/v0/mod.rs:112-137: New latest-delta finding: IdentityAlreadyExists + unique-key-hash state checks ship without targeted tests
The latest delta adds two consensus-facing early-out branches in `transform_into_action_v0`: (1) `derive_identity_id_from_actions` → `fetch_identity_balance` → `IdentityAlreadyExistsError` (lines 112-120), and (2) `validate_unique_identity_public_key_hashes_not_in_state` → returns the key-already-in-state errors (lines 122-137). These are the exact branches that convert a would-be internal Drive error during execution into a clean consensus rejection — the stated goal of the delta — but the local `tests.rs` module has no test exercising either rejection path (no hits for `IdentityAlreadyExistsError` or `validate_unique_identity_public_key_hashes_not_in_state` in the variant's test file). This does not require the heavy real-Orchard full-block harness: a targeted transformer test can seed pool balance/anchor/nullifier state, pre-create an identity whose id equals `derive_identity_id_from_actions(&actions)`, and pre-register a public-key hash, then assert the transformer returns the expected consensus errors with no action. Without coverage, the exact regression this delta fixes can come back as an internal Drive error.
…denomination gate, recoverable errors (#3816) - verify arm: recompute identity_id from actions (reject wire mismatch) and bind the proof to the transition's declared public-key set, so a tampered transition can't reuse a valid {nullifiers,identity} proof with a different id/keys. (Balance is NOT checked vs denomination — the identity holds denomination − metered total_fee, not recoverable here; denomination is bound via the Orchard extra_sighash_data at consensus.) - builder: checked u64 sum of spend values (overflow → ShieldedBuildError). - converter: assert identity.balance == denomination before emitting ops (defense against the two sources of truth diverging → mint/burn). - note_selection: reject a non-member denomination up-front (before the Orchard prove) + regression tests. - wasm-dpp factory: replace todo!() panic with a recoverable Err(JsValue). - swift: withExtendedLifetime(identitySigner) keepalive (the `_ = signer` folklore can be elided in -O builds → use-after-free of the passUnretained ctx). - tests: validate_minimum_shielded_fee IdentityCreate gate (below-min reject, boundary accept, key-count scaling); is_allowed + identity_balance classifier fixtures for the new transition. cargo check --workspace green; new + affected tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ieldedPool state checks + classifier fixture (#3816) Addresses thepastaclaw suggestions (the is_allowed fixture + the validate_minimum_shielded_fee IdentityCreate tests were already added in a9c8e13): - New transformer rejection tests (tests.rs was a placeholder): seed pool balance/anchor/min-notes, then assert transform_into_action_v0 returns a clean consensus error with no action for (1) an identity already existing at the derived id (IdentityAlreadyExistsError) and (2) a public-key hash already registered to another identity (DuplicatedIdentityPublicKeyIdStateError). These pin the exact branches the codex P1 added (which otherwise would come back as internal Drive errors during AddNewIdentity execution). - Add IdentityCreateFromShieldedPool to the identity_based_signature transitions_not_using_identity_in_state() classifier fixture. cargo check --workspace green; new tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Latest delta ed377d5 lands the targeted transformer tests for the IdentityAlreadyExists / DuplicatedIdentityPublicKey state checks and the missing IdentityCreateFromShieldedPool entry in the transitions_not_using_identity_in_state classifier fixture. All four carried-forward findings from prior review at 4085683 are resolved. Three new low-severity, in-scope issues remain on the cumulative diff: a narrowed classifier-fixture gap in a different test in the same file, a *const vs *mut SignerHandle declaration inconsistency in the new FFI entry point, and a divergence in the new WASM proof-result wrapper that returns an IdentityWasm instance instead of a plain object and omits fromObject/fromJSON (the sibling VerifiedIdentityWithAddressInfos does both). No blockers.
🟡 1 suggestion(s) | 💬 2 nitpick(s)
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/wasm-dpp2/src/state_transitions/proof_result/shielded.rs`:
- [SUGGESTION] packages/wasm-dpp2/src/state_transitions/proof_result/shielded.rs:441-454: `VerifiedIdentityWithShieldedNullifiersWasm.toObject` returns an `IdentityWasm` wrapper, and `fromObject`/`fromJSON` are missing
`to_object()` places `self.identity.clone().into()` into the returned object, which gives JS consumers the exported `IdentityWasm` class instance under `.identity` — not the plain identity object. The sibling wrapper for the address-funded variant in `proof_result/address_funds.rs:77` calls `self.identity.to_object()?` so callers get a plain JS object that round-trips through `JSON.stringify`/`JSON.parse`. The same `address_funds.rs` wrapper also exposes `fromObject`/`fromJSON` constructors that this new wrapper omits, so JS callers cannot round-trip the serialized form back into the typed wrapper. Mirror the address-funded pattern for consistency.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v3.1-dev #3816 +/- ##
=============================================
- Coverage 87.16% 69.91% -17.26%
=============================================
Files 2627 19 -2608
Lines 322573 2712 -319861
=============================================
- Hits 281185 1896 -279289
+ Misses 41388 816 -40572
🚀 New features to boost your workflow:
|
…mShieldedPool converter + bind read_only/contract_bounds + fixes (#3816) BLOCKING (chain halt): the converter emitted AddToSystemCredits{denomination} for a pool->new-identity move. Since v12 the shielded pool balance is itself a right-hand-side term of the credit-conservation equation, so this move is RHS-internal (AddNewIdentity +denom offset by the pool decrement -denom), exactly like Unshield (pool->address, which emits NO AddToSystemCredits). AddToSystemCredits writes the LHS scalar, so it over-minted by `denomination` => end-of-block CorruptedCreditsNotBalanced => deterministic chain halt on the first type-20 block. Remove the op (the transition now mirrors Unshield). Rewrite the two converter unit tests that asserted the mint as correct to model the real equation (no LHS change; identity balance offset by the pool decrement). Fix the now-wrong AddToSystemCredits wording in the action doc and book/fees/shielded-fees.md. Also in this commit: - Fix the strategy_tests build break (CI: macOS Rust workspace tests, E0004): add IdentityCreateFromShieldedPoolAction to the shielded no-op catch-all in verify_state_transitions.rs (the harness generates no shielded transitions). - MEDIUM (malleability): bind each key's read_only + contract_bounds into the Orchard extra_sighash_data (single shared helper => builder/verifier stay in lockstep), so they can't be flipped on an all-hash-key transition whose per-key PoP binds nothing. + preimage-binding test. - Review nits: FFI signer_identity_handle *const -> *mut SignerHandle (crate convention / cbindgen header parity); wasm VerifiedIdentityWithShieldedNullifiers toObject now emits a plain identity object and gains fromObject/fromJSON (mirrors the address-funded sibling). cargo check --workspace + drive-abci --tests green; dpp (166) + drive converter (6) + sighash (3) + drive-abci classifier/transformer (20) tests pass; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
… type 20 in the signature-negative classifier fixture (#3816) - WASM round-trip (codex): the new VerifiedIdentityWithShieldedNullifiers fromObject/fromJSON unchecked-cast the nullifiers property as js_sys::Map, but toJSON normalizes the Map to a plain object — so after JSON.parse(JSON.stringify(toJSON())) the getter advertised a Map that was really a plain object (.size/.get()/iteration broken). Use read_map_property (the existing Map-or-plain-object helper the sibling wrappers use) to rebuild a real Map. - Carried-forward classifier fixture: add IdentityCreateFromShieldedPool to the second negative fixture (transitions_without_sig_validation) backing should_return_false_for_non_identity_signed_transitions (the first fixture was already covered). cargo check green; identity_based_signature (4) tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Carried-forward prior findings are both FIXED at current head: wasm-dpp2 now rebuilds nullifiers through read_map_property, and the Type 20 transition is now included in the non-identity-signed negative fixture. New latest-delta findings: none. Cumulative review still leaves one in-scope test-coverage suggestion for the new consensus-critical Type 20 path because the strategy/full-block happy path remains skipped/deferred.
🟡 1 suggestion(s)
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs`:
- [SUGGESTION] packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs:1457-1463: Add a full happy-path execution/prove/verify test for Type 20
This PR adds the consensus-critical `IdentityCreateFromShieldedPool` transition, but the strategy proof-verification harness explicitly skips `IdentityCreateFromShieldedPoolAction`. The surrounding coverage is mostly isolated unit coverage plus an empty-proof verifier rejection; it does not execute a valid Orchard-backed Type 20 transition through the production block path and then prove/verify the resulting merged nullifier-plus-identity query. That leaves the riskiest composition points untested: create-then-deduct fee execution, pool balance decrement, post-fee identity balance, spent nullifiers, and byte-identical strict proof reconstruction. Add one full-block or strategy-style happy-path test that builds a valid transition, finalizes it, proves the result, and verifies `VerifiedIdentityWithShieldedNullifiers`.
…yCreateFromShieldedPool (#3816) Adds the strongest tractable guard for the chain-halt class the blocking AddToSystemCredits over-mint belonged to: seed a balanced funded shielded pool, apply the Type 20 converter's high-level drive operations through a REAL Drive, and assert the end-of-block invariant `calculate_total_credits_balance().ok()` still balances. Verified it FAILS (off by `denomination`) if AddToSystemCredits is reintroduced, and passes with the fix. No Orchard proof is needed — credit conservation is independent of proof verification (the converter only books balances). Re the reviewer's full build->prove->execute->prove/verify happy-path request: that depends on the shared shielded-strategy harness, which is a pre-existing repo-wide TODO disabled for EVERY shielded transition (the shielded `OperationType` build handlers are commented out in strategy.rs and the module is commented out in test_cases/mod.rs / feature-gated). Building it for Type 20 alone is out of scope; this conservation test + the existing op-level converter test + the strict-verify empty-proof unit test cover the consensus-critical composition points reachable without that harness. 3 transition tests pass; clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Incremental + cumulative review for 4e13526d17ae32729c1154b82124c04e1e6710b1 complete.
Carried-forward prior finding classification: STILL VALID / intentionally deferred. The latest delta adds a useful Drive-backed sum-tree credit-conservation regression for IdentityCreateFromShieldedPool and introduces no new findings, but it still does not add the full Orchard-backed production execution plus strict merged nullifier+identity prove/verify happy path. That prior Type 20 coverage finding was already raised on the PR, so I am not re-posting a duplicate inline thread on this commit.
CodeRabbit reported 0 inline findings for this push; no CodeRabbit reactions were needed.
🟡 1 carried-forward suggestion(s)
1 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
…instead of free-reject) (#3816) Type 20 was the only identity-create that free-rejected on a stateful failure; the asset-lock and address-funded creates charge a penalty (PartiallyUseAssetLock / BumpAddressInputNonces) because they have a chargeable collateral. Give Type 20 the same: a new wire field `send_to_address_on_creation_failure: PlatformAddress` is the collateral. On a unique-public-key-hash collision the spend is finalized and the value is credited to that address minus a penalty, instead of a free reject (so the spend is deterministic/final and the failure is anti-grief). Design: - New field bound into BOTH the platform sighash (per-key PoP signs it) and the Orchard `extra_sighash_data` (so a relayer cannot redirect the fallback). - The state checks (identity-exists + unique-key-hash) move from transform_into_action into a real `validate_state` (Type 20 now has_state_validation=true; advanced_structure_with_state stays false so the cheap PoP/key checks remain BEFORE Halo2). The processor delivers no pre-built action, so the state.rs dispatch builds the success action via transform exactly once, then branches. - The failure does NOT add a new StateTransitionAction variant: it produces an `UnshieldAction` (pool->address minus fee) — topologically identical — reusing Unshield's converter, `PaidFromShieldedPool` event, and conservation. The penalty (unique_key_already_present + processing fee) is capped at the denomination so the Unshield converter cannot underflow. Mirrors how IdentityCreate produces a PartiallyUseAssetLock action on failure. Threaded the fallback through the sighash, try_from_bundle, the builder, the SDK broadcast helper, the wallet op, the FFI (required 21-byte PlatformAddress), and the Swift wrapper. Verified: validate_state mirrors IdentityCreateFromAddresses; the processor runs transform exactly once (no double sig-verification accounting); the failure path conserves (new failure-path sum-tree conservation test + reuses Unshield's proven booking). cargo check --workspace green; clippy clean; dpp shielded (166) + drive converter (7) + the 4 transition tests (incl. the Unshield-fallback branch + both conservation tests) pass. Follow-up (non-consensus): the wallet should treat a fallback outcome as a spend (finalize the reservation, learn the credit went to the fallback) rather than cancel; that needs the SDK to surface executed-with-fallback distinctly from rejected. Tracked separately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…llback (#3816) CI `clippy --all-features -- -D warnings` failed on `self .send_to_address_on_creation_failure().clone()` (PlatformAddress derives Copy). Use a deref copy. Verified the exact CI invocation now exits 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rs (2)
184-191:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake the bundle/nullifier consistency check a real runtime error.
debug_assert_eq!disappears in release builds, so the one check meant to catch a drift between the precomputedidentity_idand the bundle’s published nullifiers will not run in production. If this assumption ever breaks, callers burn Orchard proving time and only see a downstream rejection.Suggested change
- debug_assert_eq!( - identity_id, - derive_identity_id_from_actions(&sb.actions), - "bound identity id must match the id re-derived from the bundle's published nullifiers" - ); + let derived_identity_id = derive_identity_id_from_actions(&sb.actions); + if identity_id != derived_identity_id { + return Err(ProtocolError::ShieldedBuildError( + "bound identity id does not match the id re-derived from the bundle's published nullifiers" + .to_string(), + )); + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rs` around lines 184 - 191, The debug-only check using debug_assert_eq!(identity_id, derive_identity_id_from_actions(&sb.actions)) must be made a runtime guard: replace the debug_assert_eq! with an actual conditional that compares identity_id to derive_identity_id_from_actions(&sb.actions) and returns an appropriate Err (or early error result) when they differ, including a clear error message; ensure the function signature propagates this error type (or map to the existing InvalidShieldedProofError / builder error path) so callers receive a deterministic runtime failure instead of relying on a debug-only assertion.
108-147:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFail fast on consensus-invalid denominations before proving.
This builder still accepts any
denominationthat fits inu64/i64and is covered by the selected notes, even though Type-20 exits are protocol-versioned fixed denominations and the new identity must also remain affordable after fees. That means callers can pay the Orchard proving/signing cost here only to be rejected later by transition validation/ABCI. Please reuse the same denomination-set check used by the transition validator, and rejectpredicted_fee >= denominationin this builder as well so impossible transitions never reachbuild_spend_bundle.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rs` around lines 108 - 147, After computing fee via compute_shielded_identity_create_fee, validate that the requested denomination is one of the protocol's allowed Type-20 denominations (reuse the same denomination-set logic the transition validator uses) and also reject cases where the predicted fee is greater-or-equal to the denomination (i.e., if fee >= denomination return ProtocolError::ShieldedBuildError with a clear message); perform these checks (using the existing denomination variable and fee) before proceeding to change_amount or calling build_spend_bundle so impossible transitions are rejected early.packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rs (1)
188-198:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAssert that the derived-id collision stays actionless.
This test documents a free rejection, but it only checks the error. Please also assert
!result.has_data()(or the equivalent) so a regression that accidentally starts returning a fallback action cannot slip through.Suggested assertion
assert!(!result.is_valid(), "expected a consensus rejection"); + assert!( + !result.has_data(), + "identity-id collisions should remain a free rejection without a carried action" + ); assert_matches!( result.errors.as_slice(), [ConsensusError::StateError(🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rs` around lines 188 - 198, Add an assertion to ensure the derived-id collision produces no fallback action: after the existing checks on result.is_valid() and result.errors, assert that the transition produced no state-changing data by calling assert!(!result.has_data(), "expected no fallback action on identity collision"); locate this near the existing uses of result in the test (the block that checks result.is_valid() and result.errors) so regressions that add a fallback are caught.
🧹 Nitpick comments (2)
packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rs (1)
150-185: ⚡ Quick winAdd the fallback address to the signable-bytes regression test.
send_to_address_on_creation_failureis now part of the authorization story, but this test only proves that keys and denomination affectsignable_bytes(). Adding one mutation/assertion for the fallback address would lock in the non-redirectability guarantee.Suggested test extension
let mut other_denom = base.clone(); other_denom.denomination = 30_000_000_000; assert_ne!( base_bytes, other_denom.signable_bytes().expect("signable bytes"), "changing the denomination must change the signable bytes" ); + + let mut other_failure_address = base.clone(); + other_failure_address.send_to_address_on_creation_failure = + PlatformAddress::P2pkh([1u8; 20]); + assert_ne!( + base_bytes, + other_failure_address.signable_bytes().expect("signable bytes"), + "changing the failure redirect address must change the signable bytes" + ); // identity_id is excluded from the sighash (it is derived from the nullifiers), so changing // it alone must NOT change the signable bytes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rs` around lines 150 - 185, Extend the test_signable_bytes_commit_to_keys_and_denomination test to also mutate the send_to_address_on_creation_failure (fallback) address on the transition created by make_v0() and assert that base.signable_bytes() != mutated.signable_bytes(), ensuring that changing the fallback address changes signable_bytes(); locate the test function and add a block similar to the other mutations (use base.clone(), set send_to_address_on_creation_failure to a different value, then call signable_bytes() and assert_ne) so the non-redirectability guarantee is locked in.packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs (1)
249-253: ⚡ Quick winCover the new
has_state_validation()arm in the regression fixture.
IdentityCreateFromShieldedPool(_)was added to thetrueset here, but theshould_return_true_for_transitions_with_state_validationtest below never instantiates that variant. Adding it will catch future regressions in this dispatch table.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs` around lines 249 - 253, The test should_return_true_for_transitions_with_state_validation does not instantiate the new StateTransition::IdentityCreateFromShieldedPool variant added to has_state_validation; update the regression fixture used by that test to include a case constructing StateTransition::IdentityCreateFromShieldedPool so the test covers this new true arm. Locate the test (should_return_true_for_transitions_with_state_validation) and the fixture that builds transition instances, add an instance of StateTransition::IdentityCreateFromShieldedPool to the list of transitions asserted true, and ensure any required mock data or helper constructors used by the fixture are provided so the new variant can be instantiated.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/rs-platform-wallet-ffi/src/shielded_send.rs`:
- Around line 368-379: The error path handling parse_optional_surplus_output for
send_to_address_on_creation_failure_bytes is returning a generic surplus_output
decode error to callers; update the failure branch so that when
parse_optional_surplus_output returns Err(...) you convert or rewrap that error
into a PlatformWalletFFIResult::err that names the parameter
send_to_address_on_creation_failure_bytes (not surplus_output). Locate the call
site using parse_optional_surplus_output and the variable
send_to_address_on_creation_failure_bytes in shielded_send.rs and replace the
Err(result) return with code that returns
PlatformWalletFFIResult::err(PlatformWalletFFIResultCode::ErrorInvalidParameter,
"<parameter_name> must be 21 PlatformAddress bytes") or otherwise include the
parameter name, or modify parse_optional_surplus_output to accept a
parameter-name argument and propagate it through to the returned error so
callers (like this one) report the correct parameter.
---
Outside diff comments:
In `@packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rs`:
- Around line 184-191: The debug-only check using debug_assert_eq!(identity_id,
derive_identity_id_from_actions(&sb.actions)) must be made a runtime guard:
replace the debug_assert_eq! with an actual conditional that compares
identity_id to derive_identity_id_from_actions(&sb.actions) and returns an
appropriate Err (or early error result) when they differ, including a clear
error message; ensure the function signature propagates this error type (or map
to the existing InvalidShieldedProofError / builder error path) so callers
receive a deterministic runtime failure instead of relying on a debug-only
assertion.
- Around line 108-147: After computing fee via
compute_shielded_identity_create_fee, validate that the requested denomination
is one of the protocol's allowed Type-20 denominations (reuse the same
denomination-set logic the transition validator uses) and also reject cases
where the predicted fee is greater-or-equal to the denomination (i.e., if fee >=
denomination return ProtocolError::ShieldedBuildError with a clear message);
perform these checks (using the existing denomination variable and fee) before
proceeding to change_amount or calling build_spend_bundle so impossible
transitions are rejected early.
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rs`:
- Around line 188-198: Add an assertion to ensure the derived-id collision
produces no fallback action: after the existing checks on result.is_valid() and
result.errors, assert that the transition produced no state-changing data by
calling assert!(!result.has_data(), "expected no fallback action on identity
collision"); locate this near the existing uses of result in the test (the block
that checks result.is_valid() and result.errors) so regressions that add a
fallback are caught.
---
Nitpick comments:
In
`@packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rs`:
- Around line 150-185: Extend the
test_signable_bytes_commit_to_keys_and_denomination test to also mutate the
send_to_address_on_creation_failure (fallback) address on the transition created
by make_v0() and assert that base.signable_bytes() != mutated.signable_bytes(),
ensuring that changing the fallback address changes signable_bytes(); locate the
test function and add a block similar to the other mutations (use base.clone(),
set send_to_address_on_creation_failure to a different value, then call
signable_bytes() and assert_ne) so the non-redirectability guarantee is locked
in.
In
`@packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs`:
- Around line 249-253: The test
should_return_true_for_transitions_with_state_validation does not instantiate
the new StateTransition::IdentityCreateFromShieldedPool variant added to
has_state_validation; update the regression fixture used by that test to include
a case constructing StateTransition::IdentityCreateFromShieldedPool so the test
covers this new true arm. Locate the test
(should_return_true_for_transitions_with_state_validation) and the fixture that
builds transition instances, add an instance of
StateTransition::IdentityCreateFromShieldedPool to the list of transitions
asserted true, and ensure any required mock data or helper constructors used by
the fixture are provided so the new variant can be instantiated.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 477a2079-184c-4581-880b-8a2ab37c0af4
📒 Files selected for processing (26)
packages/rs-dpp/src/shielded/builder/identity_create_from_shielded_pool.rspackages/rs-dpp/src/shielded/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/accessors/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/accessors/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/methods/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/methods/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/mod.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/state_transition_validation.rspackages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/v0_methods.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rspackages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/v0/mod.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/tests.rspackages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/transform_into_action/v0/mod.rspackages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rspackages/rs-platform-wallet-ffi/src/shielded_send.rspackages/rs-platform-wallet/src/wallet/platform_wallet.rspackages/rs-platform-wallet/src/wallet/shielded/operations.rspackages/rs-sdk/src/platform/transition/identity_create_from_shielded_pool.rspackages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerShieldedSync.swift
✅ Files skipped from review due to trivial changes (1)
- packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/mod.rs
🚧 Files skipped from review as they are similar to previous changes (11)
- packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/accessors/mod.rs
- packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs
- packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/methods/v0/mod.rs
- packages/rs-platform-wallet/src/wallet/platform_wallet.rs
- packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/v0_methods.rs
- packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs
- packages/rs-dpp/src/state_transition/state_transitions/shielded/identity_create_from_shielded_pool_transition/v0/state_transition_validation.rs
- packages/rs-dpp/src/shielded/mod.rs
- packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs
- packages/rs-platform-wallet/src/wallet/shielded/operations.rs
- packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/shielded_proof.rs
…FFI param name, test coverage (#3816) - builder (rs-dpp): reject a non-member denomination and `fee >= denomination` BEFORE the ~30s Orchard prove (consensus rejects both later); make the bound-id-vs-bundle-nullifier consistency check a real runtime error instead of a debug-only `debug_assert_eq!`. - FFI: rename `parse_optional_surplus_output` -> `parse_optional_platform_address` with a `field_name` arg, so a malformed fallback address reports `send_to_address_on_creation_failure_bytes` rather than `surplus_output`. - tests: assert the identity-id collision stays a free rejection (`!has_data()`); add a fallback-address mutation to the signable-bytes non-redirectability test; cover IdentityCreateFromShieldedPool in the has_state_validation true-list. CI clippy gate (--all-features -D warnings) exits 0; dpp + drive-abci tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…per) (#3816) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
BLOCKING execution bug in the new fallback path: validate_state_v0 returns the duplicate-key Unshield fallback with attached consensus errors, but the routed PaidFromShieldedPool execution event only applies its operations when consensus_errors.is_empty(); otherwise it returns UnpaidConsensusExecutionError and process_validation_result_v0 maps that to InternalError. The intended chargeable behavior (nullifiers consumed, pool debited, fallback address credited, penalty booked) never happens — the unit test for failure-path conservation bypasses execute_event_v0 by directly applying the converter ops, so this is uncaught. Carried-forward prior finding (strategy-harness Type 20 happy-path) re-validated as STILL VALID / INTENTIONALLY DEFERRED.
🔴 1 blocking | 🟡 2 suggestion(s) | 💬 2 nitpick(s)
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/v0/mod.rs:112-115: Duplicate-key fallback Unshield is never executed — nullifiers not consumed, pool not debited, penalty never paid
The fallback path returns `new_with_data_and_errors(StateTransitionAction::UnshieldAction(failure_action), unique_public_key_validation_result.errors)` so the design intent is to finalize the spend and credit the fallback address minus the penalty. But the downstream routing breaks that:
- `UnshieldAction` is converted to `ExecutionEvent::PaidFromShieldedPool` (`execution_event/mod.rs:546`).
- `execute_event_v0` for `PaidFromShieldedPool` (`execute_event/v0/mod.rs:548-583`) only calls `apply_drive_operations` when `consensus_errors.is_empty()`; otherwise it returns `UnpaidConsensusExecutionError(consensus_errors)` with no state changes.
- `process_validation_result_v0` (`process_raw_state_transitions/v0/mod.rs:334`) then maps that to `StateTransitionExecutionResult::InternalError`.
Net effect on a duplicate public-key-hash collision: nullifiers are NOT inserted, the shielded pool is NOT decremented, the fallback address is NOT credited, the penalty is NOT booked into the fee pools, and the transition is reported as an internal error instead of a chargeable failure. An attacker that can build a valid Halo 2 proof against a victim's already-registered key hash can force validators to do the (expensive) proof verification repeatedly because the nullifiers never get consumed.
The new `failure_path_unshield_converter_ops_preserve_sum_tree_credit_conservation` unit test does not catch this because it manually calls `apply_drive_operations` on the converter ops; it never exercises the `PaidFromShieldedPool` execution arm.
Fix: route the fallback through an execution path that applies the ops despite attached consensus errors (mirroring `Paid`/`PaidFromAddressInputs`, which support `UnsuccessfulPaidExecution` with errors), and add an end-to-end test that builds a duplicate-key Type 20 transition, runs it through `process_state_transitions`, and asserts the nullifiers are inserted, the pool is decremented by `denomination`, the fallback address is credited by `denomination − penalty`, and the fee pools gain `penalty`.
- [SUGGESTION] packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_create_from_shielded_pool/state/v0/mod.rs:90-117: Failure-path penalty does not include the flat shielded compute fee the success path charges
The SUCCESS path's `PaidFromShieldedPoolToNewIdentity` event adds `additional_fixed_fee_cost = compute_shielded_verification_fee` (the flat fee covering Halo 2 verification CPU GroveDB metering cannot measure). The FAILURE path's penalty is `unique_key_already_present + processing_fee`, capped at `denomination`, with no flat compute-fee component. Validators still ran the same Halo 2 verification on the failure path, so the proposer is undercompensated relative to the success path (and relative to the other shielded transitions' fee model). Not exploitable — conservation holds and the penalty cap prevents underflow — but it is a quiet asymmetry. Either document it or fold `compute_shielded_verification_fee` (or `ShieldedMinFeeKind::IdentityCreate{num_keys}`) into the penalty floor.
In `packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs`:
- [SUGGESTION] packages/rs-drive-abci/tests/strategy_tests/verify_state_transitions.rs:1457-1463: Carried-forward (STILL VALID, INTENTIONALLY DEFERRED): no full Orchard-backed Type 20 execute/prove/verify happy path
Re-validated against head 56881e88. The strategy proof-verification harness still groups `IdentityCreateFromShieldedPoolAction` with the other shielded variants and skips the full build→prove→execute→prove/verify roundtrip. The latest delta added a real-Drive failure-path conservation regression but no Orchard-backed end-to-end Type 20 success-path or fallback-path coverage. The repo-wide shielded `OperationType` build handlers are still commented out in `strategy.rs`, so this is intentionally deferred to a shielded-strategy enablement effort rather than this PR. Carrying forward as a suggestion only.
… charge despite collision errors (#3816) BLOCKING (codex): the duplicate-key fallback returned an UnshieldAction WITH the collision errors (new_with_data_and_errors), but PaidFromShieldedPool's execute arm only applied ops when consensus_errors.is_empty() — so the fallback NEVER executed: nullifiers not consumed, pool not debited, fallback address not credited, penalty not booked, and the result was reported as InternalError. An attacker with a valid Halo2 proof against an already-registered key hash could force repeated (expensive) proof verification for free (nullifiers never burn). The conservation unit test missed it by applying converter ops directly, bypassing execute_event. Fix: PaidFromShieldedPool now applies the ops REGARDLESS of attached consensus errors (the ordinary shielded spends never carry data+errors, so they are unaffected), then reports SuccessfulPaidExecution (no errors) or UnsuccessfulPaidExecution (errors) — mirroring PaidFromAddressInputs' UnsuccessfulPaidExecution path for BumpAddressInputNonces. So the fallback now charges and reports a PaidConsensusError. Added a regression test that drives the REAL execute_event path and asserts UnsuccessfulPaidExecution + the pool debited by the denomination (verified it FAILS — UnpaidConsensusExecutionError — without the fix). Also addressed in this commit: - Fee parity (codex): fold compute_shielded_verification_fee into the failure penalty floor — the proposer ran the same Halo2 verification on the failure path that the success path charges via additional_fixed_fee_cost. - Nitpick: debug_assert!(action.is_none()) in the Type 20 validate_state dispatch so the (currently dead) pre-built-action branch fails loudly if a future change starts pre-building the action and would route around transform's checks. cargo check --workspace + clippy (-D warnings) green; 5 transition tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
The only delta vs the prior review (56881e8→e636a06) is a one-line clippy fix replacing a redundant .clone() on Copy PlatformAddress. All four substantive prior findings remain valid at HEAD e636a06. The duplicate-key chargeable fallback is still routed through ExecutionEvent::PaidFromShieldedPool, whose execute_event arm explicitly returns UnpaidConsensusExecutionError when consensus_errors is non-empty — so the fallback's spend finalization (nullifier insert, pool debit, fallback-address credit, penalty booking) never runs. Note: claude-security-auditor's claim that finding #1 was FIXED is incorrect; the code at execute_event/v0/mod.rs:553-582 still gates ops on consensus_errors.is_empty().
🔴 1 blocking | 💬 1 nitpick(s)
1 additional finding(s) omitted (not in diff).
2 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs`:
- [BLOCKING] packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs:548-583: Type 20 duplicate-key fallback UnshieldAction is silently discarded — nullifiers not consumed, pool not debited, no penalty paid
Verified at HEAD e636a069. The chargeable fallback in `identity_create_from_shielded_pool/state/v0/mod.rs:103-115` constructs a real `UnshieldTransitionAction` (full denomination spend, penalty fee, original notes/anchor) and wraps it with `new_with_data_and_errors(UnshieldAction, errors)`. `ExecutionEvent::create_from_state_transition_action` (execution_event/mod.rs:542-550) maps that into `ExecutionEvent::PaidFromShieldedPool { operations, fees_to_add_to_pool }` while preserving the errors. But the `PaidFromShieldedPool` arm at execute_event/v0/mod.rs:553-582 is:
```rust
if consensus_errors.is_empty() {
apply_drive_operations(...) ; book storage/processing fees
} else {
Ok(UnpaidConsensusExecutionError(consensus_errors))
}
The fallback by construction always carries a DuplicatedIdentityPublicKeyIdStateError, so the else branch fires every time. Consequences:
- Orchard spend nullifiers are never inserted into the recent-blocks nullifier tree — replay protection is broken on this path; the same valid Type 20 proof can be re-submitted across blocks, forcing every validator to re-run Halo 2 verification for free.
- The shielded pool
total_balanceis never debited. send_to_address_on_creation_failurenever receivesdenomination - penalty.- The penalty/fee never reaches the fee pools.
process_validation_result_v0mapsUnpaidConsensusExecutionErrortoStateTransitionExecutionResult::InternalError(...)— a corrupted-code-style outcome, not the documentedPaidConsensusError.
The docstring at state/v0/mod.rs:65-69 explicitly claims this fallback reuses PaidFromShieldedPool execution event and conservation — but the executor does not honor that for the error-bearing variant. Compare to PaidFromAssetLockToPool (execute_event/v0/mod.rs:584-627) which folds fee-validation errors into all_errors but is structurally the same trap. The model the docs reference is closer to the paid_from_identity_function machinery used by PaidFromShieldedPoolToNewIdentity / asset-lock-to-identity paths, which book the penalty even on failure.
The unit test at identity_create_from_shielded_pool/tests.rs:421-572 hand-applies the converter ops directly through Drive, bypassing execute_event entirely, which is why this regressed in CI. Fix options: (a) introduce a distinct ExecutionEvent variant (e.g. ChargeableUnshieldOnFailure) whose arm applies ops + carves the penalty regardless of consensus_errors, then route the Type 20 fallback through it from create_from_state_transition_action; or (b) split the existing PaidFromShieldedPool arm so a fallback-marked action still calls apply_drive_operations even with errors (preferably after an affordability check that fee_amount <= amount). Either way, add an end-to-end test that drives process_state_transition (not just validate_state) and asserts: PaidConsensusError (not UnpaidConsensusError); nullifiers in the nullifier tree post-block; fallback-address balance += denomination - penalty; pool balance -= denomination; sum-tree conservation holds.
</details>
_Note: the PR branch advanced to `c85f0d8a` before posting, so this assigned-SHA review for `e636a069` is posted as a top-level review body after the exact-SHA duplicate guard._
|
Heads-up: this At HEAD, The implemented fix is option (b) from the prompt (apply ops despite errors in the existing arm; penalty already capped at the amount so |
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Latest push resolves both prior consensus-critical findings: the Type 20 fallback now executes through execute_event (ops applied regardless of attached errors → UnsuccessfulPaidExecution, nullifiers consumed, pool debited), and the failure-path penalty now includes compute_shielded_verification_fee for fee parity with the success path. A new regression test (failure_path_charge_executes_through_execute_event) exercises the real path. Remaining items are non-blocking: prior nitpicks #3 (UnshieldTransitionActionV0::fee_amount doc invariant is now violated by the Type 20 producer) and #4 (unreachable Some(action) branch retained behind a debug_assert), plus one new architectural concern — the unconditional apply branch in PaidFromShieldedPool relies on a comment-enforced (not type-enforced) invariant that only Type 20 ever attaches errors to that event.
🟡 1 suggestion(s) | 💬 3 nitpick(s)
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs`:
- [SUGGESTION] packages/rs-drive-abci/src/execution/platform_events/state_transition_processing/execute_event/v0/mod.rs:548-598: New: PaidFromShieldedPool applies ops unconditionally — invariant is comment-enforced, not type-enforced
The new arm removes the prior `consensus_errors.is_empty()` gate and applies drive operations regardless of attached errors. The comment correctly states that today only the IdentityCreateFromShieldedPool duplicate-key fallback constructs a `PaidFromShieldedPool` event carrying both data and errors (other shielded variants are data-only on success, error-only on rejection). However, nothing in the type system enforces that: `StateTransitionAction::UnshieldAction` is shared between ordinary Unshield and the Type 20 fallback, so a future shielded validator that mistakenly calls `new_with_data_and_errors` on an ordinary Unshield/ShieldedTransfer/ShieldedWithdrawal would silently commit a side-effectful spend on the failure path and pay the proposer for a rejected transition — a hard-to-spot consensus regression. Harden by either (a) carrying an explicit `chargeable_failure: bool` (or new `PaidFromShieldedPoolWithFallback` variant) on the event so the contract is mechanical, or (b) at this site, asserting that when `consensus_errors` is non-empty the originating action came from the Type 20 fallback (panic otherwise) so divergence surfaces in tests instead of in production state.
…review nits (#3816) Addresses thepastaclaw's non-blocking findings on the fallback feature: - SUGGESTION (type-enforcement): the PaidFromShieldedPool execute arm applied ops regardless of attached errors, relying on a COMMENT that only the Type 20 fallback ever carries data+errors. Make it mechanical: add `chargeable_failure: bool` to UnshieldTransitionActionV0 (false for ordinary Unshield, true only for the IdentityCreateFromShieldedPool fallback) + to the PaidFromShieldedPool event. The execute arm now applies-despite-errors ONLY when chargeable_failure is set; an error-bearing event WITHOUT the flag (a future misuse of new_with_data_and_errors on an ordinary shielded spend) fails SAFE — no side-effectful spend, no proposer payment — and debug_asserts so it surfaces in tests. Also clarifies the UnshieldTransitionActionV0::fee_amount doc (the fallback uses it as the penalty, not the unshield fee). - NITPICK: the dead pre-built-action branch in the Type 20 validate_state dispatch now returns Err(CorruptedCodeExecution) explicitly (runtime-enforced in release too) instead of a debug-only assertion. - NITPICK: FFI fallback decode uses a self-documenting parse_required_platform_- address(ptr, field_name) (no-length-arg case) + a module-level PLATFORM_ADDRESS_LEN, instead of hardcoding the literal 21. cargo check --workspace + clippy (-D warnings) green; the 5 transition tests (incl. failure_path_charge_executes_through_execute_event) pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Cumulative incremental review of PR #3816 at head 37de254. Prior findings: #1 (PaidFromShieldedPool apply-despite-errors invariant) FIXED via type-enforced chargeable_failure: bool on ExecutionEvent::PaidFromShieldedPool and UnshieldTransitionActionV0, with executor fail-safe gate; #2 (Unshield fee_amount doc) FIXED with both-producer documentation; #3 (Type 20 unreachable Some(action) branch) FIXED — now returns CorruptedCodeExecution in release; #4 (FFI hardcoded len=21) FIXED via new parse_required_platform_address helper. Latest delta introduces one new blocking regression: the new required chargeable_failure field was not propagated to five test-only struct literals in rs-drive's unshield_transition.rs, breaking cargo check -p drive --tests with five E0063 errors.
🔴 1 blocking
1 additional finding(s) omitted (not in diff).
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs`:
- [BLOCKING] packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs:108-117: New `chargeable_failure` field breaks `cargo test -p drive` — five test constructors not updated
The latest delta added a required `chargeable_failure: bool` field to `UnshieldTransitionActionV0` (packages/rs-drive/src/state_transition_action/shielded/unshield/v0/mod.rs:34). The struct does not derive `Default`, so every construction site must list the field explicitly. The production transformer and the two test sites in `shielded/unshield/mod.rs` were updated, but the five `UnshieldTransitionActionV0 { ... }` literals in this test module were missed: `make_action` at line 109, `test_fee_exceeds_amount_returns_error` at line 228, `test_balance_underflow_returns_error` at line 245, `test_minimum_shielded_fee_covers_actual_grovedb_write_cost` at line 299, and `test_net_zero_unshield_skips_address_credit` at line 352. `cargo check -p drive --tests` fails with five `E0063: missing field 'chargeable_failure'` errors at exactly those lines; the entire drive unit-test target — including the new consensus-critical fee/conservation regression tests this PR depends on — is unbuildable as shipped. All five are ordinary `Unshield` actions, not the Type 20 fallback, so each needs `chargeable_failure: false`. The suggestion patches `make_action`; apply the same one-line addition at 228, 245, 299, and 352.
…iterals (#3816) The new required `chargeable_failure: bool` on UnshieldTransitionActionV0 was not propagated to five test-only struct literals in the converter's test module (make_action + 4 fee/conservation regression tests), breaking `cargo check -p drive --tests` with E0063. All five are ordinary Unshield actions, so `chargeable_failure: false`. `cargo check -p drive --tests` green; 18 drive unshield/converter tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Fixed in 94f42ae: added (My earlier verification ran |
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Cumulative incremental review. Prior blocking finding (chargeable_failure missing from 5 Unshield converter test literals) is FIXED at current head 94f42ae — the latest delta consists exclusively of those 5 single-line additions at lines 116, 236, 254, 309, and 363 of packages/rs-drive/src/state_transition_action/action_convert_to_operations/shielded/unshield_transition.rs, each correctly setting chargeable_failure: false for ordinary (non-Type-20) Unshield actions. No new findings in the latest delta; no carried-forward prior findings remain.
…lded fee predictor (#3816) The shielded identity-create fee predictor had two bespoke calibrated storage-byte constants (SHIELDED_IDENTITY_CREATE_BASE_STORAGE_BYTES = 1200, SHIELDED_IDENTITY_CREATE_PER_KEY_STORAGE_BYTES = 350) that duplicated — and diverged ~3-5x from — the codebase's existing identity-create cost calibration. Replace the storage-byte term with the SAME constants the non-shielded IdentityCreate / IdentityCreateFromAddresses transitions use in their StateTransitionEstimatedFeeValidation::calculate_min_required_fee: identity_create_fee = compute_minimum_shielded_fee_v0(num_actions) + identity_create_base_cost + num_keys * identity_key_in_creation_cost This gives one source of truth for the cost of creating an identity, so the shielded predictor cannot drift from the consensus minimum the create is actually subject to. The two byte constants are removed. Consensus-safe: the predicted value only feeds the cheap early `denomination >= fee` gate and the client-side note-selection predictor. The converter books `denomination` (not the predicted fee), the actual charged fee is metered GroveDB cost + compute_shielded_verification_fee, and the authoritative non-negative-balance check in validate_fees_of_event uses the real metered total_fee — all unchanged. Lowering the early gate only stops it from over-rejecting valid denominations; underfunded ones are still caught by the authoritative check via the same paid_from_identity_function path as PaidFromAssetLock. Also drop a clone() on the Copy PlatformAddress in a shield_from_asset_lock test (would fail the -D warnings clippy gate). Tests: dpp 166 shielded + 4 fee, platform-wallet 4 denomination, drive-abci 14 shielded_proof + 5 Type 20; full-workspace clippy --all-features --all-targets clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the branch up to date with v3.1-dev (adds the rs-platform-wallet-storage crate + base fixes to the asset-lock penalty and unshield/withdrawal proof verification paths) and regenerate Cargo.lock so the PR-merge lockfile satisfies the merged manifests. The CI `pull_request` event tests merge(branch, v3.1-dev) with `cargo clippy --workspace --all-features --locked`, which was failing with "lock file needs to be updated" because the two divergent Cargo.lock files textually merged into a lockfile inconsistent with the merged Cargo.tomls. Auto-merge was conflict-free; `cargo clippy --workspace --all-features --locked -- --no-deps -D warnings` passes clean on the merged tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Cumulative incremental review at head 9a855a1. Prior review at 94f42ae had no findings to reconcile (carried-forward prior findings: none). The latest delta is a focused refactor swapping bespoke storage-byte constants for the existing consensus identity_create_base_cost + identity_key_in_creation_cost constants, aligning the shielded identity-create predictor with the non-shielded predictors; the smaller floor is dwarfed by the smallest legal denomination and the authoritative metered check is unchanged. One in-scope documentation suggestion: the updated docstring overstates the fallback-on-failure coverage (the fallback applies only to unique-key-hash collisions, not to metered insufficiency).
🟡 1 suggestion(s)
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-dpp/src/shielded/compute_minimum_shielded_fee/v0/mod.rs`:
- [SUGGESTION] packages/rs-dpp/src/shielded/compute_minimum_shielded_fee/v0/mod.rs:211-214: Docstring overstates the fallback-on-failure coverage of shielded identity-create
The new comment claims residual under-funding from a metered cost above this floor is 'absorbed by the transition's fallback-on-failure path, which credits the fallback address minus a penalty.' That fallback is only emitted on the unique-public-key-hash collision branch in `identity_create_from_shielded_pool/state/v0/mod.rs`. If `validate_fees_of_event` later finds `denomination < total_fee`, it returns `IdentityInsufficientBalanceError` and execution rejects the transition through the standard unpaid path — no `UnshieldTransitionAction` is built, no fallback address is credited, and the nullifiers are not consumed. The text should scope the fallback wording to key-hash collisions only and call out that metered insufficiency is a plain free rejection. Practically harmless because the smallest legitimate denomination (10^10 credits) far exceeds the max-key floor (~41M credits), but the predictor's docstring is read by clients and validators and shouldn't claim coverage the executor doesn't provide.
…_assert; correct fallback docstring (#3816) Addresses three review comments: 1. (shumkov) The three `*_extra_sighash_data` builders in shielded/mod.rs were unversioned hand-rolled consensus preimages. Platform serialization (`signable_bytes()`) cannot be used here: it EXCLUDES `identity_id` (`exclude_from_sig_hash`) and does not bind the per-key `read_only` / `contract_bounds` that the proof-of-possession misses — exactly the fields the Orchard binding signature must commit. Per the suggestion, version the methods: add `dpp.methods.shielded_extra_sighash_data` and dispatch `X(.., platform_version) -> X_v0(..)` (mirroring the sibling `compute_minimum_shielded_fee_v0` pattern). v0 is byte-identical to the prior layout, so zero consensus change. Threaded `&PlatformVersion` through the 6 prod call sites (3 builders + 3 verifiers); byte-layout/construction tests call the `_v0` impls directly. 2. (shumkov) Removed the `debug_assert!(false, ..)` in execute_event's PaidFromShieldedPool fail-safe. A debug_assert panics in debug but no-ops in release — a non-deterministic divergence in consensus-execution code. Replaced with a `tracing::error!` plus the deterministic UnpaidConsensusExecutionError return in BOTH builds. 3. (thepastaclaw/codex) The compute_shielded_identity_create_fee_v0 docstring overstated fallback coverage. The fallback-address-minus-penalty path is emitted ONLY on the unique-public-key-hash collision branch; metered insufficiency (`denomination < total_fee` in validate_fees_of_event) is a plain unpaid free rejection (IdentityInsufficientBalanceError, no nullifier consumed). Scoped the wording accordingly. Tests: dpp 166 shielded, platform-wallet denomination, drive-abci shielded_proof 14 / unshield 18 / shielded_withdrawal 21 / Type 20: 5 — all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… file (#3816) Per review feedback, split the consensus-critical platform-sighash preimage code out of the catch-all `shielded/mod.rs` into a dedicated `shielded/sighash.rs`: SIGHASH_DOMAIN, compute_platform_sighash, the three `*_extra_sighash_data` version dispatchers + their `_v0` impls, and their byte-layout tests. The public paths are unchanged (re-exported from `shielded/mod.rs`), so no caller changes. `mod.rs` keeps the storage-byte fee constants and the OrchardBundleParams / SerializedAction wire structs. Pure code move — no logic change; the 7 sighash tests pass unchanged as `shielded::sighash::tests::*` (166 shielded tests green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Code Review
Cumulative review of PR #3816 at head 04fa1b6. Prior finding (docstring overstating fallback coverage in compute_shielded_identity_create_fee_v0) is FIXED — the new docstring correctly identifies validate_fees_of_event as the affordability gate, scopes fallback to the unique-public-key-hash collision branch, and disclaims authoritative-consensus-fee status. New findings in the latest delta: one nitpick — a stale accounting comment in execute_event_v0 still describes the obsolete AddToSystemCredits/RemoveFromSystemCredits booking that the IdentityCreateFromShieldedPool converter explicitly forbids (the converter enforces no LHS scalar movement because the credits move within the RHS balance trees). No blocking or suggestion-level defects observed.
💬 1 nitpick(s)
…nsition_processing/execute_event/v0/mod.rs Co-authored-by: Lil Claw <pasta+claw@dashboost.org>
Issue being fixed or feature implemented
Implements #3813 — a new Dash Platform state transition
IdentityCreateFromShieldedPool(StateTransitionType = 20) that spends Orchard shielded-pool notes to create a brand-new Platform identity, without first unshielding to a transparent address (which would deanonymize the funding source and require a second transition).It is a hybrid: spend mechanics from
Unshield, variable-key output mechanics fromIdentityCreate, and the strict merged prove/verify fromShieldFromAssetLock. Version-gated to protocol v12 (SHIELDED_POOL_INITIAL_PROTOCOL_VERSION), like the rest of the shielded family.The exit amount is a fixed denomination (versioned set
{0.1, 0.3, 0.5, 1.0}DASH) so every identity-creation exit of a given size is indistinguishable on-chain, maximizing the anonymity set (mirroringShieldedTransfer's exact-fee uniformity).What was done?
Implemented end-to-end across the stack (the issue's staged checklist, delivered as one mega PR):
DPP (
rs-dpp) — newidentity_create_from_shielded_pool_transitiontree (StateTransitionType = 20, TAIL-appended; allStateTransitionenum/macro/match arms).identity_id = double_sha256(sorted nullifiers)(deterministic, unique, non-malleable). Newidentity_create_from_shielded_extra_sighash_datahelper binding identity id + denomination + the full public-key set into the Orchard sighash (anti-redirection / anti-key-swap, thesurplus_outputlesson). Newcompute_shielded_identity_create_feepredictor + storage-byte constants. Versioned denomination set inevent_constants. New basic errorShieldedInvalidDenominationError(code 10827).Action + booking + conservation (
rs-drive) — new action/transformer/converter andStateTransitionActionvariant. The converter emitsinsert_nullifiers+AddNewIdentity{balance = denomination}+AddToSystemCredits(denomination)+ change-note inserts +UpdateTotalBalance(pool − denomination). Credit conservation: pool−= denomination, system credits+= denomination, then the fee is moved from the identity balance into the fee pools (net0).drive-abci validation + execution — new validation module (stateful pool/anchor/nullifier-replay checks + key-structure validation + per-key proof-of-possession over the signable bytes + id re-derivation);
shielded_proofproof-verify arm (Orchard bundle reconstruct/verify,value_balance == denominationexactly, extra-sighash binding) + newShieldedMinFeeKind::IdentityCreate{num_keys}min-fee gate; newExecutionEvent::PaidFromShieldedPoolToNewIdentity(create-then-deduct, funded from the pool — meters the GroveDB write, adds the flat compute fee, movestotal_feeout of the new identity);validate_fees_of_eventaffordability gate (denomination ≥ total_fee→IdentityInsufficientBalanceError); all processor predicate/dispatch trait arms; v12 gating viais_allowed.Strict merged prove/verify (
rs-drive) — built STRICT from day one (cf. #3812): merged nullifier-tree +full_identity_queryPathQuery, verified withverify_query_with_absence_proof(limitu16::MAX), partitioned by path (nullifier tree vs identity subtrees — both keys are 32 bytes). NewStateTransitionProofResult::VerifiedIdentityWithShieldedNullifiers.Client surface —
dash-sdkbroadcast helper;wasm-dpp2(type-code20+ the five grouping matches + newVerifiedIdentityWithShieldedNullifierswrapper);wasm-dpplegacy error/factory;rs-platform-walletoperation +ShieldedFeeKind::IdentityCreatenote-selection;rs-platform-wallet-ffiextern "C"fn;swift-sdkshieldedIdentityCreateFromPoolbridge (built via theswift-rust-ffi-engineeragent; all business logic stays in Rust behind the FFI).Version gating (
rs-platform-version) — serialization version, validation version, converter method version, and the denomination set wired acrossv1–v8(active in v8 / v12, disabled earlier).Docs —
book/shielded-fees fee-extraction row + error-code 10827.How Has This Been Tested?
cargo check --workspaceis green;cargo check -p drive --no-default-features --features verifyis green;dppclippy clean. New unit/integration tests, all passing:AddToSystemCredits == denomination(not net), identity balance == full denomination, pool−= denomination, op-level supply delta== 0, underflow rejection.ShieldedInvalidDenominationError), empty actions/keys/proof and zero anchor rejected, serialization round-trip.compute_shielded_identity_create_feematchesbase + (BASE + num_keys×PER_KEY)×rateand grows strictly with the key count.verify_query_with_absence_proofarm.shielded_proof(11) andStateTransitionType(7) suites still pass.Remaining test follow-up (documented, not in this PR): the full-block sum-tree conservation / prove-verify roundtrip / malleability strategy-test integration with real Orchard proving — the heaviest harness-dependent piece. The conservation invariant itself is unit-tested at the converter level (the consensus-critical part). The Swift wrapper compiles syntactically and bridges the FFI correctly; a full iOS xcframework build was out of scope here.
Breaking Changes
None to an activated protocol version. This is additive and v12-gated (
SHIELDED_POOL_INITIAL_PROTOCOL_VERSION); pre-v12 the transition returnsStateTransitionNotActiveError. All new enum variants / error codes / version fields are TAIL-appended so existing bincode wire discriminants are unchanged.Checklist:
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Behavior / Fees
Documentation
Chores