datahaven-xyz/datahaven · error · Error

InvalidNonce

InvalidNonce

Error message

InvalidNonce

What it means

`process_message` throws `Error::InvalidNonce` when `Nonce::<T>::get(nonce)` is already set, meaning a message with this nonce was previously processed. The pallet uses nonces for exactly-once delivery; replaying an already-handled message is rejected to prevent double-crediting of assets or double-execution.

Solutions

  1. Check `Nonce::<T>::get(nonce)` (or the public nonce query) before submitting; skip the message if it is already marked processed.
  2. Configure the relayer to track successfully delivered nonces persistently and deduplicate retries.
  3. In tests, use fresh nonces per run or reset pallet storage between runs.

Example fix

// before: blind resubmit causes InvalidNonce
pallet.process_message(relayer, message.clone())?;
pallet.process_message(relayer, message)?; // replay

// after: dedupe by nonce
if Nonce::<Runtime>::get(message.nonce) { return Ok(()); } // already processed
pallet.process_message(relayer, message)?;
Defensive patterns

Strategy: validation

Validate before calling

if (await api.query.inboundQueueV2.nonce(message.nonce)) { console.log('already processed'); return; }

Type guard

function isUnprocessed(nonce, processedSet) { return !processedSet.has(nonce.toString()); }

Try / catch

try { pallet.process_message(relayer, message); } catch (e) { if (matches!(e, Error::InvalidNonce)) { markDelivered(message.nonce); return Ok(()); } else { throw e; } }

Prevention

When it happens

Trigger: Calling `process_message` with a message whose nonce already exists in the `Nonce` storage map — i.e. a replayed or duplicated message after the original was successfully processed.

Common situations: A relayer resubmitting the same message after a timeout without knowing the first submission succeeded; replaying historical Gateway events after a node resync; test harnesses reusing fixture messages across runs without resetting state.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of datahaven-xyz/datahaven@edcb13dbbc (2026-09-13). Data as JSON: /api/errors/1aac9d74cad343f7. Report an issue: GitHub.

Appendix: source

Thrown at operator/pallets/inbound-queue-v2/src/lib.rs:230

            ensure_root(origin)?;
            OperatingMode::<T>::set(mode);
            Self::deposit_event(Event::OperatingModeChanged { mode });
            Ok(())
        }
    }

    impl<T: Config> Pallet<T> {
        pub fn process_message(relayer: T::AccountId, message: Message) -> DispatchResult {
            // Verify that the message was submitted from the known Gateway contract
            ensure!(
                T::GatewayAddress::get() == message.gateway,
                Error::<T>::InvalidGateway
            );

            let (nonce, relayer_fee) = (message.nonce, message.relayer_fee);

            // Verify the message has not been processed
            ensure!(!Nonce::<T>::get(nonce.into()), Error::<T>::InvalidNonce);

            // Process message
            let message_id = T::MessageProcessor::process_message(relayer.clone(), message)?;

            // Pay relayer reward if needed
            if !relayer_fee.is_zero() {
                T::RewardPayment::register_reward(
                    &relayer,
                    T::DefaultRewardKind::get(),
                    relayer_fee,
                );
            }

            // Mark message as received
            Nonce::<T>::set(nonce.into());

            // Emit event with the message_id
            Self::deposit_event(Event::MessageReceived { nonce, message_id });

View on GitHub (pinned to edcb13dbbc)