2

I'm using subxt with the aim to handle bursts of transactions concurrently through a single account. I understand the nonce must be incremented, however I'm unsure of the exact lifecycle of a tx and at what stage the next nonce is expected.

I have tested with a simple atomic counter that I manually increment, however it seems if increment and submit too fast the tx's never make it into the block.

Do I need to ensure that tx A (nonce 21) has made it into the block before tx B (nonce 22) can be submitted?

I'm wondering if there are any typical strategies to handle this, whether some sort of queue is required, or I if can achieve this with a simple counter.

Example

    let mut signer = PairSigner::new(pair);
    if let Some(nonce) = nonce {
        signer.set_nonce(nonce.inc_next());
    }

    let api = ClientBuilder::new()
        .set_url(env.rpc.url.clone())
        .build()
        .await
        .to()?
        .to_runtime_api::<RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>();

    let submitted = api
        .tx()
        .system()
        .remark(self.0.as_bytes().to_vec())
        .sign_and_submit_then_watch_default(&signer)
        .await
        .to()?;

    let tx = submitted.wait_for_in_block().await?;
    let events = tx.wait_for_success().await.to()?;
    let hash = events.block_hash();
    let header = api
        .client
        .rpc()
        .header(Some(hash))
        .await
        .to()?
        .ok_or_else(|| Error::Generic("extrinsic header is None".into()))?;

    Ok(header.number)
2
  • Possibly you are not signing the extrinsics with the Era::Immortal and they become invalid before making it into the block. Could you post your code please? Commented May 2, 2022 at 11:09
  • It looks like the default paramters for subxt include Era::Immortal as seen here. Also added a code example
    – myson
    Commented May 2, 2022 at 12:34

1 Answer 1

3

Your code looks reasonable, but i would rather send the extrinsics in serial and not concurrently. Otherwise you will create a lot of RPC connections and DOS your node.
I am working on something similar where I need to send a large number of extrinsics to a node. It uses transfer_keep_alive and is send in batches of 50.
Some things that I noticed:

  • Sending extrinsics to the node can sometimes get stuck right away. I don't know why, but retrying makes it work. In this case it somehow does not submit the first batch of extrinsics.
  • Sending too large batches can break the RPC or overload the transaction pool. You can adjust the pool options via arguments --pool-limit 10000000 and --pool-kbytes 100000000.
  • The speed that needs to be archived is capped by the maximal number of TPS that the chain can reach. If you chain can only do 1000TPS, then there is not much use to optimize the sender script for 4000.
  • Watching an extrinsic takes up a lot of RPC resources. It is faster to first send all extrinsics and then later on verify their success by querying the blocks and searching for events.
let mut signer = PairSigner::new(AccountKeyring::Alice.pair());

let api = ClientBuilder::new()
    .set_url(&url)
    .build()
    .await?
    .to_runtime_api::<runtime::RuntimeApi<DefaultConfig, PolkadotExtrinsicParams<DefaultConfig>>>(
    );

let ext_deposit = api.constants().balances().existential_deposit().unwrap();

// Pre-sign the extrinsics for more speed.
// This is probably not needed since rust is already very fast with this.
let mut txs = Vec::new();
info!("Signing {} transactions", num_ext);
for i in 0..num_ext {
    let receiver = AccountKeyring::Bob.to_account_id().into();
    signer.set_nonce(i as u32);
    let tx_params = Params::new().era(Era::Immortal, *api.client.genesis());
    let tx = api
        .tx()
        .balances()
        .transfer(receiver, ext_deposit)
        .create_signed(&signer, tx_params)
        .await?;
    txs.push(tx);
}

// Send the transactions in chunks.
const CHUNK_SIZE: usize = 50;

info!("Sending {} transactions in chunks of {}", num_ext, CHUNK_SIZE);
for chunk in txs.chunks(CHUNK_SIZE) {
    let mut hashes = Vec::new();
    for tx in chunk {
        let hash = api.client.rpc().submit_extrinsic(tx);
        hashes.push(hash);
    }
    try_join_all(hashes).await?;
}

Hope this helps.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.