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
- A well-formed
IdentityCreditTransfer (valid amount, nonce, and signature) whose recipientId does not exist passes check_tx and broadcastStateTransition succeeds.
- 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).
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.
- 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.rs — validate_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)
- Have a funded identity A with a TRANSFER / CRITICAL key.
- Pick a 32-byte identifier R for which
getIdentity returns not found.
- Build and sign
IdentityCreditTransfer A → R with a valid amount and A's next nonce; broadcastStateTransition.
- Observe: broadcast succeeds;
waitForStateTransitionResult times out; A's nonce (via getIdentityNonce) is unchanged; the transition is visible in unconfirmed_txs on a Tenderdash node.
- 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.
Expected Behavior
IdentityCreditTransferto arecipientIdthat does not exist should be rejected atcheck_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_txshould be a strict subset of proposal validation ("Ifcheck_txaccepts a transition,process_proposalshould 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
IdentityCreditTransfer(valid amount, nonce, and signature) whoserecipientIddoes not exist passescheck_txandbroadcastStateTransitionsucceeds.unconfirmed_txsand stays there (mempoolttl-durationis 24h withttl-num-blocks = 0in the default config).waitForStateTransitionResulttimes out (rs-dapi takes the "Transaction not found, will wait for future events" path inservices/platform_service/wait_for_state_transition_resultand then hits the deadline); the sender's committed identity nonce does not advance, and no credits are charged.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 invalidate_state.packages/rs-drive-abci/src/execution/validation/state_transition/check_tx_verification/v0/mod.rs—validate_stateis only called whenvalidates_full_state_on_check_tx()is true, and inprocessor/traits/state.rsthat defaults tofalse(onlyMasternodeVoteoverrides it). So the recipient is never looked up oncheck_tx.packages/rs-drive-abci/src/abci/handler/prepare_proposal.rs— the unpaid-consensus-error branch setsTxAction::Removedand 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:
IdentityCreditTransferon thecheck_txpath (it is a single key lookup, the same callvalidate_statealready makes), returningIdentityNotFoundErrorthere; orTxAction::Removedtransition 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)
getIdentityreturns not found.IdentityCreditTransferA → R with a valid amount and A's next nonce;broadcastStateTransition.waitForStateTransitionResulttimes out; A's nonce (viagetIdentityNonce) is unchanged; the transition is visible inunconfirmed_txson a Tenderdash node.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_txbut never leaves the mempool).Your Environment
This issue was written by Claude, an AI agent operated by the account owner; the observations above come from the operator's own testing.