Provider errors related to Coin storage

I am running the same fuel-core and fuels-rs version on my home computer and a server. fuel-core on the server is returning errors about coin storage, but it works fine on my home computer.

fuel-core: v0.42.0
Fuels-rs version: GitHub - FuelLabs/fuels-rs at c37ce72da25234d08eeca1b2774a82c014612423

These are the 2 errors that show up when i try to execute a script to make swaps:


2025-04-07T14:11:27.349883Z ERROR : Provider("io error: Response errors; Validity(TransactionOutputChangeAssetIdNotFound(f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07))")

2025-04-07T14:26:49.545567Z ERROR : provider: io error: Response errors; store error occurred: resource was not found in table `fuel_core_storage::tables::Coins` at the: crates/fuel-core/src/query/coin.rs:28

This is a fuel-vm error that means that you have a change output for an asset that is not inside the inputs.

2 Likes

Ok thank you.
I am calculating the inputs/outputs manually since I lost a significant amount of ETH trying to run a script and not explicitly indicating the change outputs.

Do you have any theories why this breaks only when i run it on another machine?

I had issues with Wallet vs Unlocked when updating fuels-rs. I was not able to use Locked to make static calls with contracts / multicall. Not sure if that is relevant here.

Here is my function for calculating inputs/outputs:

ub async fn get_transaction_inputs_outputs(
    wallet: &Wallet<Locked>,
    assets_in: &[(AssetId, u64)],
    gas_used: u64,
) -> Result<(Vec<Input>, Vec<Output>)> {
    let mut inputs = Vec::new();
    let mut outputs = Vec::new();
    let base_asset = *tokens::ETH;
    let gas_used = u128::from(gas_used);
    let amount_in = u128::from(assets_in[0].1);
    let asset_in = assets_in[0].0;

    // Gather inputs for assets_in
    for (asset, amount) in assets_in {
        let mut amount = u128::from(*amount);

        if amount == 0 {
            panic!("amount in is 0");
        }

        if *asset == base_asset {
            amount += gas_used;
        }

        let asset_inputs: Vec<Input> = wallet
            .get_asset_inputs_for_amount(*asset, amount, None)
            .await?;

        let input_sum: u128 = asset_inputs
            .iter()
            .map(|i| u128::from(i.amount().unwrap_or(0)))
            .sum();

        // get change. difference of amount and sum
        if input_sum > amount {
            outputs.extend(wallet.get_asset_outputs_for_amount(
                wallet.address(),
                *asset,
                u64::try_from(input_sum - amount)?,
            ));
        }

        if input_sum < amount_in {
            return Err(anyhow!(
                "Insufficient input for asset {:?}; required: {}, available: {}",
                asset_in,
                amount_in,
                input_sum
            ));
        }

        inputs.extend(asset_inputs);
    }
    Ok((inputs, outputs))
}

And here is the context I am calling it:

pub async fn execute_swap(
    wallets: (&Wallet<Unlocked<PrivateKeySigner>>, &Wallet<Locked>),
    amount_in: u64,
    amount_out_min: u64,
    pools: Vec<DexSwap>,
    path: Vec<AssetId>,
    amounts_out_formatted: (Vec<u64>, Vec<u64>), // (out_0, out_1)
    recipient: Address,
    deadline: u32,
) -> Result<()> {
    let (wallet_unlocked, wallet_locked) = wallets;
    let script = get_swap_script_instance(wallet_unlocked)?;
    let mira_amm = mira::Mira::new(*mira::AMM_CONTRACT, wallet_unlocked.clone());

    let tx_policies = TxPolicies::default().with_script_gas_limit(GENERAL_SWAP_GAS_LIMIT);

    let (inputs, outputs) = common::get_transaction_inputs_outputs(
        wallet_locked,
        &[(path[0], amount_in)],
        GENERAL_SWAP_GAS_LIMIT,
    )
    .await?;

    let mut call = script
        .main(
            amount_in,
            amount_out_min,
            pools.clone(),
            path.clone(),
            amounts_out_formatted.0.clone(),
            amounts_out_formatted.1.clone(),
            recipient.into(),
            deadline,
        )
        .with_tx_policies(tx_policies)
        .with_contracts(&[&mira_amm, &helper])
        .with_inputs(inputs.clone())
        .with_outputs(outputs.clone())
        .with_variable_output_policy(VariableOutputPolicy::EstimateMinimum);

    call.simulate(Execution::realistic()).await?;

Are you connecting to the same network on both machines? Or are you using local nodes?

Yea they are both on fuel-ignition.
The server node however includes this information when i make a graphql query:

“extensions”: {
“current_consensus_parameters_version”: 5,
“current_fuel_block_height”: 18878358,
“current_stf_version”: 24
}

It is hard to debug this without more context. I would check the tx before sending on each machine and make sure that the input assets match the outputs and change. If you are missing some inputs on the server side I would try to figure out why those inputs are missing.

1 Like

Ok I will try to debug it some more. Just wanted to check if it was a known issue. Thanks!

2 Likes

Are you sure that you both nodes with --utxo-validation argument?

1 Like

Both nodes are run with these flags (relayer/keypair are different on the server/home computers.

./fuel-core/target/release/fuel-core run \
--enable-relayer \
--service-name fuel-mainnet-node \
--keypair <my_keypair> \
--relayer https://eth-mainnet.alchemyapi.io/v2/<alchemy_key> \
--ip=0.0.0.0 --port 4000 --peering-port 30333 \
--db-path /mnt/HC_Volume_102187271/.fuel-custom
--snapshot ./core/chain-configuration/ignition/ \
--utxo-validation --poa-instant false --enable-p2p \
--bootstrap-nodes '/dnsaddr/mainnet.fuel.network'  \
--sync-header-batch-size 100 \
--relayer-v2-listening-contracts=0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf \
--relayer-da-deploy-height=20620434 \
--relayer-log-page-size=100 \
--sync-block-stream-buffer-size 30

Here are the inputs/outputs before attempting to send the script transaction. I am building the inputs/outputs manually so there could be something wrong, but it was working for months before updating recently.

{
  "inputs": [
    {
      "type": "ResourceSigned",
      "resource": {
        "type": "Coin",
        "amount": 428499239,
        "block_created": 18974741,
        "asset_id": "91b3559edb2619cde8ffb2aa7b3c3be97efd794ea46700db7092abeee62281b0",
        "utxo_id": {
          "tx_id": "b8d8dc38f6b3136a210cd57e54c1e7597b7b4b2a2a05a364cf2b407b7aa3a6b7",
          "output_index": 0
        },
        "owner": {
          "hrp": "fuel",
          "hash": "1846923a4621215acd5e5504babe0c3bc84216b99b5309423a40bf21d8ceedd4"
        },
        "status": "Unspent"
      }
    },
    {
      "type": "ResourceSigned",
      "resource": {
        "type": "Coin",
        "amount": 309550,
        "block_created": 18974741,
        "asset_id": "91b3559edb2619cde8ffb2aa7b3c3be97efd794ea46700db7092abeee62281b0",
        "utxo_id": {
          "tx_id": "b8d8dc38f6b3136a210cd57e54c1e7597b7b4b2a2a05a364cf2b407b7aa3a6b7",
          "output_index": 1
        },
        "owner": {
          "hrp": "fuel",
          "hash": "1846923a4621215acd5e5504babe0c3bc84216b99b5309423a40bf21d8ceedd4"
        },
        "status": "Unspent"
      }
    }
  ],
  "outputs": [
    {
      "type": "Coin",
      "to": "1846923a4621215acd5e5504babe0c3bc84216b99b5309423a40bf21d8ceedd4",
      "amount": 428550764,
      "asset_id": "91b3559edb2619cde8ffb2aa7b3c3be97efd794ea46700db7092abeee62281b0"
    },
    {
      "type": "Change",
      "to": "1846923a4621215acd5e5504babe0c3bc84216b99b5309423a40bf21d8ceedd4",
      "amount": 0,
      "asset_id": "91b3559edb2619cde8ffb2aa7b3c3be97efd794ea46700db7092abeee62281b0"
    }
  ]
}

ERROR buy_discount: provider: io error: Response errors; Validity(TransactionOutputChangeAssetIdNotFound(f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07))

@hal3e is there no automatic way for a script to calculate inputs/outputs. i have brought this up before but I lost a lot of money because I didn’t know i had to manually calculate the output utxos. now i’m not even sure if i’m doing it right. it seems the sdk handles the gas utxos, but it really seems sketchy.
can i not just call a script and tell it what coins i am inputting and the SDK handles the rest?

Currently there is no way to do it but in the next release there will be something called assemble_tx which does the exact thing you ask for.

To not loose money you just have to add one change output for every asset_id used in your inputs. Basically telling the node what to do with the excess amount.

in your previous message the error states:

ERROR buy_discount: provider: io error: Response errors; Validity(TransactionOutputChangeAssetIdNotFound(f8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07))
``` I do not know where this asset_id is coming from as it is not listed in the inputs above.

Ok thanks will use that in the future. The part I don’t currently understand is, do I need to calculate the gas inputs/outputs manually also? This could be the cause of the issue.

Sorry for so many questions. Its a bit difficult to test and I’m a little too shell shocked to make changes to the utxo calculation function without being certain.

I would really like to help you out but I can’t without more context. Maybe we could schedule a call somehow?

If you use the TransactionBuilders you will have to specify your inputs and outputs.. we have helpers but it is still up to you to setup everything properly.
First you need to know what you want to do. If you just want to pay for some tx execution you need base asset as input and a change output (for the excess amount). If you want to pay for the tx and transfer base asset to another wallet: You need enough base asset for the fees, you need to add an output to the other wallet and a change output for the excess amount etc. The same applies to any asset_id.. but keep in mind that fees are payed in the base_asset id and it should be always present. (input and change output)