Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Transaction is outdated" error when running the official transfer example #1921

Open
Erio-Harrison opened this issue Feb 10, 2025 · 8 comments

Comments

@Erio-Harrison
Copy link

Description

When running the balance transfer example from the subxt documentation with a local substrate-contracts-node, I'm consistently receiving a "Transaction is outdated" error. The node is running in dev mode and the metadata is up to date, but the transaction fails to submit.

Environment

  • substrate-contracts-node version: v0.35.0
  • Running in Docker container
  • Windows environment
  • Using the official example code without modifications

Steps to Reproduce

  1. Start substrate-contracts-node in Docker using the following configuration:
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Shanghai

RUN apt-get update && apt-get install -y \
    curl \
    wget \
    && rm -rf /var/lib/apt/lists/*

RUN curl -L -o substrate-contracts-node.tar.gz https://github.com/paritytech/substrate-contracts-node/releases/download/v0.35.0/substrate-contracts-node-linux.tar.gz && \
    tar -xvzf substrate-contracts-node.tar.gz && \
    mv artifacts/substrate-contracts-node-linux/substrate-contracts-node /usr/local/bin/ && \
    rm -rf artifacts substrate-contracts-node.tar.gz

WORKDIR /substrate

EXPOSE 9944 9933 30333

CMD ["substrate-contracts-node", "--dev", "--rpc-external"]
  1. Run the following example code:
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new API client, configured to talk to Polkadot nodes.
    let api = OnlineClient::<PolkadotConfig>::new().await?;

    // Build a balance transfer extrinsic.
    let dest = dev::bob().public_key().into();
    let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

    // Submit the balance transfer extrinsic from Alice, and wait for it to be successful
    // and in a finalized block. We get back the extrinsic events if all is well.
    let from = dev::alice();
    let events = api
        .tx()
        .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
        .await?
        .wait_for_finalized_success()
        .await?;

    // Find a Transfer event and print it.
    let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
    if let Some(event) = transfer_event {
        println!("Balance transfer success: {event:?}");
    }

    Ok(())
}
  1. Generate fresh metadata using:
subxt metadata -f bytes > metadata.scale

and either:

subxt metadata --url ws://127.0.0.1:9944 -f bytes > metadata.scale

Error Output

Error: Rpc(ClientError(Call(ErrorObject { code: ServerError(1010), message: "Invalid Transaction", data: Some(RawValue("Transaction is outdated")) })))

Additional Information

  • The node is accessible (confirmed via health check endpoint)
  • Using fresh metadata generated from the local node
  • The example is copied directly from the official documentation
  • The error persists after node restart and regenerating metadata

Questions

  1. Is there any additional configuration needed for the local node?
  2. Could this be related to the transaction timing or block time in dev mode?
  3. Are there any known issues with the current version regarding transaction submission?

What I've Tried

  • Regenerating metadata
  • Restarting the node
  • Confirming node health via RPC endpoints
  • Using different transaction submission methods
@jsdw
Copy link
Collaborator

jsdw commented Feb 10, 2025

This appears to be a ndoe error (see https://github.com/paritytech/polkadot-sdk/blob/2970ab151402a94c146800c769953cf6fdb6ef1d/substrate/primitives/runtime/src/transaction_validity.rs#L108). I think the ususal reason for that is that the transaction is being submitted with a nonce equal to or lower than the current account nonce.

Subxt should get the right nonce automatically. Is something else incrementing the account nonce at the same time I wonder, so that there is arace and subxt submits with an outdated one? Does the error happen every time you run the example, and even with a fresh instance of the node started?

One way to check the nonce of an account is to use the PJS UI and point it at your local node, ie go to https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/runtime and then run the accountNonceApi's accountNonce call using ALICE as the selected account.

@Erio-Harrison
Copy link
Author

This appears to be a ndoe error (see https://github.com/paritytech/polkadot-sdk/blob/2970ab151402a94c146800c769953cf6fdb6ef1d/substrate/primitives/runtime/src/transaction_validity.rs#L108). I think the ususal reason for that is that the transaction is being submitted with a nonce equal to or lower than the current account nonce.

Subxt should get the right nonce automatically. Is something else incrementing the account nonce at the same time I wonder, so that there is arace and subxt submits with an outdated one? Does the error happen every time you run the example, and even with a fresh instance of the node started?

One way to check the nonce of an account is to use the PJS UI and point it at your local node, ie go to https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/runtime and then run the accountNonceApi's accountNonce call using ALICE as the selected account.

Hi, thank you for response.

When I executed the following Rust code with a fresh instance of the node started (also with subxt metadata -f bytes > metadata.scale):

#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new API client, configured to talk to Polkadot nodes.
    let api = OnlineClient::<PolkadotConfig>::new().await?;

    // Build a balance transfer extrinsic.
    let dest = dev::bob().public_key().into();
    let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

    // Submit the balance transfer extrinsic from Alice, and wait for it to be successful
    // and in a finalized block. We get back the extrinsic events if all is well.
    let from = dev::alice();
    let events = api
        .tx()
        .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
        .await?
        .wait_for_finalized_success()
        .await?;

    // Find a Transfer event and print it.
    let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
    if let Some(event) = transfer_event {
        println!("Balance transfer success: {event:?}");
    }

    Ok(())
}

First execution:

  • The program compiles successfully but hangs indefinitely when running the transaction.
  • Output:
    Compiling trading-core v0.1.0 (C:\Users\u7541\Desktop\rust-trade\trading-core) 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.74s
    Running `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe`
    (Hangs here)
    

Second execution:

  • The program runs but fails immediately with an error:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
    Running `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe`
    Error: Rpc(ClientError(Call(ErrorObject { code: ServerError(1010), message: "Invalid Transaction", data: Some(RawValue("Transaction is outdated")) })))
    error: process didn't exit successfully: `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe` (exit code: 1)
    

Execute the following Rust code:

use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("Starting program...");
    
    let api = OnlineClient::<PolkadotConfig>::new().await?;
    println!("API client created successfully");

    let dest = dev::bob().public_key().into();
    let from = dev::alice();
    
    println!("Building transfer transaction...");
    let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

    let block = api.blocks().at_latest().await?;
    println!("Current block number: {}", block.number());

    println!("Submitting transaction...");
    let tx = api
        .tx()
        .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
        .await?;

    println!("Transaction submitted, waiting for confirmation...");
    match tx.wait_for_in_block().await {
        Ok(in_block) => {
            println!("Transaction included in block: {:?}", in_block.block_hash());
            match in_block.wait_for_success().await {
                Ok(events) => {
                    println!("Transaction successful!");
                    if let Some(event) = events.find_first::<polkadot::balances::events::Transfer>()? {
                        println!("Transfer event: {:?}", event);
                    }
                }
                Err(e) => println!("Transaction failed: {:?}", e),
            }
        }
        Err(e) => println!("Failed to get in block: {:?}", e),
    }

    println!("Program completed");
    Ok(())
}

The first execution succeeds, with output like this:

Starting program...
API client created successfully
Building transfer transaction...
Current block number: 0
Submitting transaction...
Transaction submitted, waiting for confirmation...
Transaction included in block: 0x83e31196f711c4278a76361a00a071c02398119cab0515744ea859658d278d50
Transaction successful!
Transfer event: Transfer { from: ..., to: ..., amount: 10000 }
Program completed

The second execution fails, with the following error:

Starting program...
API client created successfully
Building transfer transaction...
Current block number: 0
Submitting transaction...
Error: Rpc(ClientError(Call(ErrorObject { code: ServerError(1010), message: "Invalid Transaction", data: Some(RawValue("Transaction is outdated")) })))
error: process didn't exit successfully: `example_check.exe` (exit code: 1)

Any insights into this issue would be appreciated. Thanks!

@Erio-Harrison
Copy link
Author

Erio-Harrison commented Feb 11, 2025

This appears to be a ndoe error (see https://github.com/paritytech/polkadot-sdk/blob/2970ab151402a94c146800c769953cf6fdb6ef1d/substrate/primitives/runtime/src/transaction_validity.rs#L108). I think the ususal reason for that is that the transaction is being submitted with a nonce equal to or lower than the current account nonce.
Subxt should get the right nonce automatically. Is something else incrementing the account nonce at the same time I wonder, so that there is arace and subxt submits with an outdated one? Does the error happen every time you run the example, and even with a fresh instance of the node started?
One way to check the nonce of an account is to use the PJS UI and point it at your local node, ie go to https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/runtime and then run the accountNonceApi's accountNonce call using ALICE as the selected account.

Hi, thank you for response.

When I executed the following Rust code with a fresh instance of the node started (also with subxt metadata -f bytes > metadata.scale):

#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::::new().await?;

// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
// and in a finalized block. We get back the extrinsic events if all is well.
let from = dev::alice();
let events = api
    .tx()
    .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
    .await?
    .wait_for_finalized_success()
    .await?;

// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
    println!("Balance transfer success: {event:?}");
}

Ok(())

}
First execution:

  • The program compiles successfully but hangs indefinitely when running the transaction.
  • Output:
    Compiling trading-core v0.1.0 (C:\Users\u7541\Desktop\rust-trade\trading-core) 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.74s
    Running `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe`
    (Hangs here)
    

Second execution:

  • The program runs but fails immediately with an error:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.54s
    Running `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe`
    Error: Rpc(ClientError(Call(ErrorObject { code: ServerError(1010), message: "Invalid Transaction", data: Some(RawValue("Transaction is outdated")) })))
    error: process didn't exit successfully: `C:\Users\u7541\Desktop\rust-trade\target\debug\examples\example_check.exe` (exit code: 1)
    

Execute the following Rust code:

use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "metadata.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box> {
println!("Starting program...");

let api = OnlineClient::<PolkadotConfig>::new().await?;
println!("API client created successfully");

let dest = dev::bob().public_key().into();
let from = dev::alice();

println!("Building transfer transaction...");
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

let block = api.blocks().at_latest().await?;
println!("Current block number: {}", block.number());

println!("Submitting transaction...");
let tx = api
    .tx()
    .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
    .await?;

println!("Transaction submitted, waiting for confirmation...");
match tx.wait_for_in_block().await {
    Ok(in_block) => {
        println!("Transaction included in block: {:?}", in_block.block_hash());
        match in_block.wait_for_success().await {
            Ok(events) => {
                println!("Transaction successful!");
                if let Some(event) = events.find_first::<polkadot::balances::events::Transfer>()? {
                    println!("Transfer event: {:?}", event);
                }
            }
            Err(e) => println!("Transaction failed: {:?}", e),
        }
    }
    Err(e) => println!("Failed to get in block: {:?}", e),
}

println!("Program completed");
Ok(())

}
The first execution succeeds, with output like this:

Starting program...
API client created successfully
Building transfer transaction...
Current block number: 0
Submitting transaction...
Transaction submitted, waiting for confirmation...
Transaction included in block: 0x83e31196f711c4278a76361a00a071c02398119cab0515744ea859658d278d50
Transaction successful!
Transfer event: Transfer { from: ..., to: ..., amount: 10000 }
Program completed

The second execution fails, with the following error:

Starting program...
API client created successfully
Building transfer transaction...
Current block number: 0
Submitting transaction...
Error: Rpc(ClientError(Call(ErrorObject { code: ServerError(1010), message: "Invalid Transaction", data: Some(RawValue("Transaction is outdated")) })))
error: process didn't exit successfully: `example_check.exe` (exit code: 1)

Additionally, I noticed that during the successful transaction execution, on the Polkadot.js UI at https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/runtime, under Alice's account, when checking submit runtime call → accountNonceApi.accountNonce: u32, the nonce increased from 0 to 1.

@Erio-Harrison
Copy link
Author

Any insights into this issue would be appreciated. Thanks!

@niklasad1
Copy link
Member

niklasad1 commented Feb 12, 2025

Hey @Erio-Harrison

As James said, can you double-check that you don't have any concurrent applications that uses the account "//Alice" that send transactions?

However, subxt is using AccountNonceApi_account_nonce`` which doesn't account for what's currently is inside the transaction pool. You could test to use system_account_next_index` instead to fetch the nonce yourself to workaround that.

I don't remember how the API looks like like v0.32 but if you update to the latest version you could do something like:

        let rpc = RpcClient::from_url("ws://localhost:9944".to_string()).await?; 
        let client = OnlineClient::from_rpc_client(rpc.clone()));

        let from = dev::alice();
        let dest = dev::bob().public_key().into();

	let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

	let nonce = client.rpc().system_account_next_index(signer.account_id()).await?;
	let xt_cfg = DefaultExtrinsicParamsBuilder::default().nonce(nonce).build();
	let xt = client.tx().create_signed(balance_transfer_tx, &from.into(), xt_cfg).await?;

	let events = xt.submit_and_watch().await?.wait_for_finalized_success().await?;

Anyway, this should only occur if you concurrent transactions in the tx pool otherwise I have no hunch what's is going on.

@jsdw
Copy link
Collaborator

jsdw commented Feb 14, 2025

@Erio-Harrison I did the following:

  1. git pull polkadot-sdk repo to latest, and cargo build --release to build the latest polkadot binaries
  2. run polkadot --dev using these binaries
  3. copy the following code into a subxt example to run:
#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create a new API client, configured to talk to Polkadot nodes.
    let api = OnlineClient::<PolkadotConfig>::new().await?;

    // Build a balance transfer extrinsic.
    let dest = dev::bob().public_key().into();
    let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

    // Submit the balance transfer extrinsic from Alice, and wait for it to be successful
    // and in a finalized block. We get back the extrinsic events if all is well.
    let from = dev::alice();
    let events = api
        .tx()
        .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
        .await?
        .wait_for_finalized_success()
        .await?;

    // Find a Transfer event and print it.
    let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
    if let Some(event) = transfer_event {
        println!("Balance transfer success: {event:?}");
    }

    Ok(())
}

I ran this a few times and didn't run into any issue.

This at least tells me that there is something with the substrate-contracts-node which is in issue. I hadn't noticed that you were pointing to that node until after I tested the above, so would have to try that next :)

@Erio-Harrison
Copy link
Author

Hey @Erio-Harrison

As James said, can you double-check that you don't have any concurrent applications that uses the account "//Alice" that send transactions?

However, subxt is using AccountNonceApi_account_nonce which doesn't account for what's currently is inside the transaction pool. You could test to use ``system_account_next_index` instead to fetch the nonce yourself to workaround that.

I don't remember how the API looks like like v0.32 but if you update to the latest version you could do something like:

    let rpc = RpcClient::from_url("ws://localhost:9944".to_string()).await?; 
    let client = OnlineClient::from_rpc_client(rpc.clone()));

    let from = dev::alice();
    let dest = dev::bob().public_key().into();

let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

let nonce = client.rpc().system_account_next_index(signer.account_id()).await?;
let xt_cfg = DefaultExtrinsicParamsBuilder::default().nonce(nonce).build();
let xt = client.tx().create_signed(balance_transfer_tx, &from.into(), xt_cfg).await?;

let events = xt.submit_and_watch().await?.wait_for_finalized_success().await?;
Anyway, this should only occur if you concurrent transactions in the tx pool otherwise I have no hunch what's is going on.

Thanks a lot. I will try to use different API.

@Erio-Harrison
Copy link
Author

@Erio-Harrison I did the following:

  1. git pull polkadot-sdk repo to latest, and cargo build --release to build the latest polkadot binaries
  2. run polkadot --dev using these binaries
  3. copy the following code into a subxt example to run:

#![allow(missing_docs)]
use subxt::{OnlineClient, PolkadotConfig};
use subxt_signer::sr25519::dev;

#[subxt::subxt(runtime_metadata_path = "../artifacts/polkadot_metadata_full.scale")]
pub mod polkadot {}

#[tokio::main]
async fn main() -> Result<(), Box> {
// Create a new API client, configured to talk to Polkadot nodes.
let api = OnlineClient::::new().await?;

// Build a balance transfer extrinsic.
let dest = dev::bob().public_key().into();
let balance_transfer_tx = polkadot::tx().balances().transfer_allow_death(dest, 10_000);

// Submit the balance transfer extrinsic from Alice, and wait for it to be successful
// and in a finalized block. We get back the extrinsic events if all is well.
let from = dev::alice();
let events = api
    .tx()
    .sign_and_submit_then_watch_default(&balance_transfer_tx, &from)
    .await?
    .wait_for_finalized_success()
    .await?;

// Find a Transfer event and print it.
let transfer_event = events.find_first::<polkadot::balances::events::Transfer>()?;
if let Some(event) = transfer_event {
    println!("Balance transfer success: {event:?}");
}

Ok(())

}
I ran this a few times and didn't run into any issue.

This at least tells me that there is something with the substrate-contracts-node which is in issue. I hadn't noticed that you were pointing to that node until after I tested the above, so would have to try that next :)

Thanks a lot. Yeah, the substrate-test-node may have some errors. I will also double check it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants