Duplicate nonce error when calling state update function concurrently

Hi Support,

When calling an update state function of a smart contract using rust sdk almost concurrently (2 calls), I get the error.

Call failed: status: InvalidArgument, message: “Duplicate nonce”, details: [], metadata: MetadataMap { headers: {“content-type”: “application/grpc”, “date”: “Tue, 08 Nov 2022 20:08:09 GMT”}

Below is how I call the receive function where I get the nonce from account info. I’m probably getting the same account nonce here the error so I wonder how I can provide unique nonce for every call?

Also, should I clone the endpoint::Client everytiime I make a call? It says in the code documentation/comment that I should.

let mut client = endpoints::Client::connect(endpoints::Endpoint::try_from(self.config.concordium_url.to_string())?, self.config.concordium_rpc_user.to_string()).await?;

let consensus_info = client.get_consensus_status().await?;

// Get the initial nonce at the last finalized block.
let acc_info: AccountInfo = client.get_account_info(&self.account.address, &consensus_info.last_finalized_block).await?;

// set expiry to now + configured expiry
let expiry: TransactionTime = TransactionTime::from_seconds((Utc::now().timestamp() + self.config.concordium_transaction_expiry) as u64);

let tx = send::update_contract(
&self.account.account_keys,
self.account.address,
acc_info.account_nonce,
expiry,
payload,
self.config.concordium_max_energy.into(),
);

let item = BlockItem::AccountTransaction(tx);
// submit the transaction to the chain
let transaction_hash = client.send_block_item(&item).await?;
let (bh, bs) = client.wait_until_finalized(&transaction_hash).await?;
if bs.is_reject() {
bail!(“Contract update failed: transaction rejected”)
} else {
Ok((transaction_hash.to_string(), bh.into()))
}

Best

Hi @ulapcyber,

Thank you for your question!

get_account_info will take into account any pending transactions in the node when it gives you the account nonce, but if you create the two transactions at nearly the same time, it might not have time to give you the correct nonce for the last call.
I don’t know your exact use case, but the Nonce is a simple wrapper over a u64, so if you know or can control the order of the two transactions, then you can increment the nonce by one for the second transaction. Otherwise, you could use something like a Mutex, but I would try the simpler option mentioned above first.

You do not need to clone the client for every call, it is only if you want to make concurrent calls because the calls take a mutable reference to the client (&mut) of which you can only have one at a time. But the client is designed with concurrency in mind, so cloning it is very cheap.

Let me know if you have further questions :blush:

Have a nice day
/ Kasper

Hi Kasper,

Thanks for the response. I unfortunately don’t have any control to the state update call. So any request can come in to call the update function and this could be concurrent. I tried using .next() to increment by default so that any call would need to increment the nonce and avoid a clash but unfortunately it resulted to a “Nonce too large” error. So it seems like Mutex is the only way? Or am I incrementing it the wrong way?

all the best

Hi,

get_account_info will return the exact nonce that you need to use, you cannot use one that is larger, which is why you get that error if you always increment the nonce manually.
So yes, you need to use a mutex or perhaps AtomicU64. I don’t expect you would see any noticeable difference in performance between the two :blush:.

Best of luck
/ Kasper

1 Like

As another solution which might work better in some scenarios, it might be better to structure the code in a way that you have a dedicated “transaction sender” worker/task/thread.

All the other threads can enqueue requests to send the transaction and the worker then adds the nonce/expiry, etc and actually sends it. Then you can ensure an order and have a relatively clean architecture if you use either tokio::mpsc for example.

As a slight correction to what @Kasper wrote as well, the get_account_info does not account for pending transactions. There is an endpoint get_next_account_nonce which does do that, but as @Kasper mentioned, it will have similar issues with concurrency.

Thanks Kasper, Abizjak.

Just to understand though, this limitation around nonce – it seems to me then that one client (to one concordium node) can only make a transaction request one at a time (no concurrent as everyone will be blocked if there is mutex or if there is a queue)? Would having multiple nodes and rust clients pointing to different nodes fix it somehow?

No. The limitation, and it is essentially necessary, is that transactions from a given account are ordered by the nonce. There is no problem if you are submitting transactions from multiple accounts. There is no coordination needed between those.

It does not matter how many connections you have to the node, or how many clients.

Did that make sense?

Thanks @abizjak. Makes sense. It looks like a problem for us then since we are only using one account that updates state on-chain.
Just to be sure, if ordered, does it mean that the nonce will only be incremented when the transaction is completed so all our other requests will be blocked (when we use mutex for nonce) until the transaction is completed? At least that is how it is behaving when we tested it with mutex.

No, you just have to submit them in order (by increasing nonce). But you don’t have to wait for them to be committed or finalized.

The node will process all of them.

For example, we have a small generator script for testing that just sends transactions from one account to the node.

It works in the way I suggested above, by having a dedicated worker task for sending transactions. But as you can see we submit many transactions to the node without waiting for anyhting.

Let me know if you experience issues.

Hi Abizjak, interesting approach, in the script example you know the nonce by query account info and can use next to have the correct order for the following account transactions.

I was wondering how to know this in a use-case where you have requests for account transactions coming in randomly and independently via a stateless backend service. In such use-case how can you know if the nonce you get from account info is updated (from last account transaction) and therefor next in order - or if you need to +1 to avoid duplicate nonce error?

Thanks!

There is also the GetNextAccountNOnce endpoint Client in concordium_rust_sdk::endpoints - Rust

which will account for any pending transactions. Now there is still a possibility for a race condition, if you query this at the same time as submitting some transaction, then you might still have issues.

The only fully reliable solution is to have some synchronisation or a dedicated service that submits transactions and keeps track of the nonce itself. The node just cannot do this for you fully reliably.

1 Like

Your “stateless” service, is that the one that is submitting transactions to the node?

Thanks - yes the stateless will submit to the node - and this will be randomly and concurrently. Thinking that the design of the node should support this - so clients should not worry about order. Just like inserting into cloud database you would not worry about order. Thanks for info - really helpful

While I agree that it would be more convenient for clients for the node to do this, it cannot really do this in general without a lot of additional complexity. It is certainly possible though.

The sequence number is a necessity. Users must be able to order their transactions, otherwise bad bakers could reorder them however they liked. Accepting this, the issue becomes whether the node should accept transactions out of order, or should it always demand the right order, but the user must always decide that order somehow. And this must be objective, i.e., it cannot be the timestamp that the node received the transaction. The order must be signed by the sender in such an open, permissionless network.

This then necessitates some sort of race condition or locking. Because the sequence of steps is

  • construct the transaction, this includes deciding on the order
  • send the transaction to the node so that it is broadcast and included in a block.

The first step takes a bit of time at least, so running this concurrently means that either you have to have some locking mechanism or some central source of truth to manage the ordering.

The node could have an API that would give out tickets per account assuming they will be used, but this is adding logic to the node that is easily replaced by a small service you can run yourself.

1 Like

Thanks for background info and approach. Such approach would generic and could handle nonce for many use-cases with concurrency considerations. I am not sure yet what we will do, but in case we make some code to handle this, we will get back.

If you want to go with this sort of stateless service approach my recommended approach at this time would be to have one small stateful service that is the one that can submit transactions, and has your account keys to do so.

This service is the only one that can submit transactions and has an API that can be used to make requests for those transactions. The other services do not need to care about nonces, etc.

Yes agree, we currently looking into using Kafka streams with per account topics to handle incoming traffic and a single consumer per account (nonce) strategy.