Skip to content

Identity update bills no validation reads — local execution_context is dropped (follow-up to #3697) #3700

@QuantumExplorer

Description

@QuantumExplorer

Identity update bills no validation reads — local execution_context is dropped

Summary

IdentityUpdateTransition::validate_state_v0 creates a local state_transition_execution_context, threads it into five validators, and drops it on every return path. All the ValidationOperation::PrecalculatedOperation entries those validators push are lost — the user is not billed for the grovedb reads that ran. This is the same B7 pattern from #3670 (which fixed the batch transformer's dropped local), applied to identity_update.

This issue tracks the follow-up to PR #3697 (audit entries N6/N7), and closes the broader N8/N9/N10 leaks for identity_update at the same time. CodeRabbit flagged this on #3697.

Where the leak is

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/mod.rs:92-118:

impl StateTransitionStateValidation for IdentityUpdateTransition {
    fn validate_state<C: CoreRPCLike>(
        &self,
        _action: Option<StateTransitionAction>,
        platform: &PlatformRef<C>,
        _validation_mode: ValidationMode,
        _block_info: &BlockInfo,
        _execution_context: &mut StateTransitionExecutionContext,  // <-- ignored
        tx: TransactionArg,
    ) -> Result<ConsensusValidationResult<StateTransitionAction>, Error> {
        // ...
        0 => self.validate_state_v0(platform, tx, platform_version),  // <-- ctx not passed
        // ...
    }
}

The outer trait correctly receives execution_context: &mut StateTransitionExecutionContext from the processor (processor/traits/state.rs:32-40, processor/v0/mod.rs:324). The IdentityUpdate dispatcher then drops it on the floor.

packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_update/state/v0/mod.rs:40-164:

fn validate_state_v0<C: CoreRPCLike>(
    &self,
    platform: &PlatformRef<C>,
    tx: TransactionArg,
    platform_version: &PlatformVersion,
) -> Result<ConsensusValidationResult<StateTransitionAction>, Error> {
    let mut state_transition_execution_context =
        StateTransitionExecutionContext::default_for_platform_version(platform_version)?;
    // ... five validators push PrecalculatedOperations into this context ...
    // ... function returns; context dropped; bills lost.
}

The five validators currently leaking reads into the dropped local:

Validator Audit entry Severity
validate_unique_identity_public_key_hashes_not_in_state N10 HIGH
validate_identity_public_key_ids_dont_exist_in_state N8 HIGH
validate_identity_public_keys_contract_bounds N6/N7 (billing wired up by #3697 but stuck in this local) HIGH
validate_identity_public_key_ids_exist_in_state N9 HIGH
validate_master_key_uniqueness — (no grovedb reads, but takes the ctx)

Fix shape

Mirror the B7 fix from #3670 — version-gate the change for chain replay safety, no behavior change pre-PROTOCOL_VERSION_12.

  1. Add _v1-suffixed sibling to validate_state_v0 that takes execution_context: &mut StateTransitionExecutionContext and uses it instead of constructing a local one.
  2. Bump identity_update_state_transition.state: 0 → 1 inside DRIVE_ABCI_VALIDATION_VERSIONS_V8 (the V12 table, already touched by fix(drive-abci): correct DECRYPTION bounds branch + bill grovedb reads in bounds validation #3697's bounds fix).
  3. Update the IdentityUpdate inner dispatcher in identity_update/mod.rs to:
    • Drop the _ prefix on execution_context.
    • Add a 1 => arm that calls validate_state_v1(platform, tx, execution_context, platform_version).
    • Keep the 0 => arm calling validate_state_v0(platform, tx, platform_version) (which keeps its local-and-dropped behavior verbatim — byte-identical for PROTOCOL_VERSION_11 replay).
  4. v0 remains untouched. v1 is byte-identical to v0 except for the context plumbing.

Consensus implications

Activating v1 changes the fee a user pays for an identity_update that hits any of the five validators above. That is consensus-affecting (a chain that runs v1 will compute a different fee than one that runs v0 for the same transition). It must therefore be gated behind a protocol-version bump — folding into PROTOCOL_VERSION_12 (the next not-yet-shipped version) is appropriate.

Tests to add

  • A test that exercises validate_state end-to-end on V12 and asserts the outer execution_context receives PrecalculatedOperation entries with non-zero processing fees from at least one of the five validators.
  • A v0 baseline test asserting the legacy "drop the local" behavior is preserved (no entries reach the outer context).

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions