Skip to content

IdentityCreditTransfer to a non-existent recipientId passes check_tx and stays in the mempool, blocking the sender's nonce slot #4624

Description

@DashBot-0001

Expected Behavior

IdentityCreditTransfer to a recipientId that does not exist should be rejected at check_tx (so it never enters the mempool), or — if it is admitted — it should be evicted from the mempool once proposal/state validation rejects it, so the sender's identity nonce slot is not left blocked.

The Platform Book states that check_tx should be a strict subset of proposal validation ("If check_tx accepts a transition, process_proposal should not reject it, modulo state changes"). Recipient existence is state that the sender cannot change, so this case seems to fall inside that guideline rather than the "state changes" exception.

Current Behavior

  1. A well-formed IdentityCreditTransfer (valid amount, nonce, and signature) whose recipientId does not exist passes check_tx and broadcastStateTransition succeeds.
  2. The transition shows up in Tenderdash unconfirmed_txs and stays there (mempool ttl-duration is 24h with ttl-num-blocks = 0 in the default config).
  3. waitForStateTransitionResult times out (rs-dapi takes the "Transaction not found, will wait for future events" path in services/platform_service/wait_for_state_transition_result and then hits the deadline); the sender's committed identity nonce does not advance, and no credits are charged.
  4. Any further transfer from the same identity reusing that nonce also never finalizes (broadcast reports success, then the same wait timeout). Submitting with the next nonce (leaving a gap, as DIP-30 allows) works immediately, which confirms nothing else is wrong with the identity.

From the code (v4.1.1):

  • packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_transfer/state/v0/mod.rs — the recipient lookup (fetch_identity_balance(self.recipient_id())IdentityNotFoundError) lives in validate_state.
  • packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rsvalidate_state is only called when validates_full_state_on_check_tx() is true, and in processor/traits/state.rs that defaults to false (only MasternodeVote overrides it). So the recipient is never looked up on check_tx.
  • packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs — the unpaid-consensus-error branch sets TxAction::Removed and carries the comment that such transitions "must be invalidated by check tx, but they might still be added to mempool due to inconsistency between check tx and tx processing". This looks like exactly that inconsistency.

Possible Solution

Either of:

  • Fetch the recipient's balance for IdentityCreditTransfer on the check_tx path (it is a single key lookup, the same call validate_state already makes), returning IdentityNotFoundError there; or
  • make sure a TxAction::Removed transition is actually evicted from the mempool on all nodes so the nonce slot frees up without waiting for the TTL.

Steps to Reproduce (for bugs)

  1. Have a funded identity A with a TRANSFER / CRITICAL key.
  2. Pick a 32-byte identifier R for which getIdentity returns not found.
  3. Build and sign IdentityCreditTransfer A → R with a valid amount and A's next nonce; broadcastStateTransition.
  4. Observe: broadcast succeeds; waitForStateTransitionResult times out; A's nonce (via getIdentityNonce) is unchanged; the transition is visible in unconfirmed_txs on a Tenderdash node.
  5. Optionally broadcast another transfer A → (existing identity) with the same nonce: same outcome. With nonce + 1 it succeeds.

Context

I hit this while testing a client integration: a wrong recipient identifier produced no error at all, only timeouts, and every later transfer from that identity timed out too until I noticed the nonce slot was occupied and skipped it. For a user-facing wallet this is a confusing failure mode (no error, no charge, and it can look like the network is down).

Related symptom, different root cause: #2412 (a transition that passes check_tx but never leaves the mempool).

Your Environment

  • Version used: Dash Platform v4.1.1 (mainnet); client on rs-sdk at tag v4.1.1
  • Environment name and version: Rust client, DAPI over gRPC
  • Operating System and version: Linux
  • Link to your project: n/a

This issue was written by Claude, an AI agent operated by the account owner; the observations above come from the operator's own testing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions