#Wrong sequence number when building a soroban transaction

80 messages · Page 1 of 1 (latest)

honest horizon
#

Hey everyone!

Has anyone else faced issues when using js-soroban-cllient regarding the sequence number?

In the snippet below, we're creating a soroban transaction and during this process we fetch the accoung using getAccount, build the transaction and then use the prepareTransaction before moving on to submit it.
During these steps, on the first console.log we can see the envelope correctly has the right sequence number for the next valid transaction, while at the second console.log the sequence number has been incremented once more, which is just 1 above the right sequence.

Yesterday while testing we noticed that sometimes the asynchrony was even larger, up to +3 numbers of difference. I wonder if it could be the case that when preparing the transaction it is updated with information from the RPC server that is not correctly synchronized.

Additional info:

We're using testnet with the RPC https://soroban-testnet.stellar.org:443

Snippet:

...
const sourceAccount = await server.getAccount(
    'GA7FLYHBEVVZK6NCR7I3Y352VW4D7UNALTIZF4GDLULHA3P2CH5LM56D'
  )

  const transaction = new SorobanClient.TransactionBuilder(sourceAccount, {
    fee: sorobanConfig.fee,
    networkPassphrase: sorobanConfig.network.passphrase,
  })
    .addOperation(operation)
    .setTimeout(sorobanConfig.txTimeout)
    .build()

  console.log(transaction.toXDR())

  try {
    const preparedTransaction = await server.prepareTransaction(transaction)
    console.log(preparedTransaction.toXDR())

... proceed to submit
elfin pond
#

Are you submitting your transactions asynchronously? If so its possible theyre getting processed out of order?

#

It shouldnt increment unless you have submitted another tx or somehow increment it in your code

honest horizon
#

We're manually running through this snippet just once but it seems this specific wallet is already out of synch. Every attempt we do(synchronous) it is now always being returned from the prepareTransaction one sequence higher.

Right now, to doublecheck, we created a new account and used it instead of the previous one. The fresh new account went through this snippet seamleasly with the right sequence. It logged the right sequence before the prepareTransaction and was returned form it with the right one.

Let me run some more testings here with other accounts to try and better isolate the behavior

elfin pond
#

And nothing else is submitting tx on that account? Its odd because it looks to me like u load account each time so it gets thr sequence from the api. It should only have incremented if you submitted a tx. Maybe you and another dev sharing the same privkey?

#

You could also use channel accounts basically use a different source for each submission if you do need to submit groups in async

honest horizon
#

Just did a "Vacuum" test:

  1. Created a fresh new account to be the tx source
  2. Executed the snippet to perform the transaction (Attempt #1)
  3. Executed the snippet to perform the transaction again (Attempt #2)

This seems to be the condition make it go out of sync. The first attempt works but the second one is out of sync.
Did this full test 3 times now and got the exact same results.

Not sure if it might be related but the transaction we're building is a transaction to wrap a classic token.

Here are the XDRs for reference:

  • Account used: GBBFTYRPV2QEAFN7LQVF2YFRC3JZXTYFR2X5STQLXZ2P2TKXDBYHQCSU

Attempt #1 Using a freshly created classic asset.

Envelope before prepareTransaction:

AAAAAgAAAABCWeIvrqBAFb9cKl1gsRbTm88Fjq/ZTgu+dP1NVxhweAAPQkAAIXEDAAAAAQAAAAEAAAAAAAAAAAAAAABlOU93AAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAUVVUkMAAAAAlCsn5kxA4I6jefW+tTg82rb69LIWTKDcDXvpF+asJjQAAAABAAAAAAAAAAAAAAAA

Envelope after prepareTransaction:

Result:

AAAAAgAAAABCWeIvrqBAFb9cKl1gsRbTm88Fjq/ZTgu+dP1NVxhweAASxyUAIXEDAAAAAQAAAAEAAAAAAAAAAAAAAABlOU93AAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAUVVUkMAAAAAlCsn5kxA4I6jefW+tTg82rb69LIWTKDcDXvpF+asJjQAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAGAAAAAU3kV4DAk7MGMr+5y5wPjYMHgAcVCDJdKDlI+HYcLVKLAAAAFAAAAAEAA5NyAAAAMAAAAdwAAAAAAADcZwAAAAFXGHB4AAAAQHKgVLYOBMrJ7tV5Zgv2JNJ1hq+OTEnDmEmjyYv5AY2VNA5eEqCP8PmB+LFCeEmuYHIdvyX8H6nz/0a5S9K4jgU=```

Token was wrapped successfully!

(continue on next message due to limit...)
#

Attempt #2 Using another freshly created classic asset.

Envelope before prepareTransaction:

AAAAAgAAAABCWeIvrqBAFb9cKl1gsRbTm88Fjq/ZTgu+dP1NVxhweAAPQkAAIXEDAAAAAgAAAAEAAAAAAAAAAAAAAABlOVGMAAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAUxNU0MAAAAAjAuTiyy680Cohao0hZHlxsAF8pdnk/Jpt5Ze/CGCY6MAAAABAAAAAAAAAAAAAAAA

Envelope after prepareTransaction:

AAAAAgAAAABCWeIvrqBAFb9cKl1gsRbTm88Fjq/ZTgu+dP1NVxhweAASxyUAIXEDAAAAAQAAAAEAAAAAAAAAAAAAAABlOVGMAAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAUxNU0MAAAAAjAuTiyy680Cohao0hZHlxsAF8pdnk/Jpt5Ze/CGCY6MAAAABAAAAAAAAAAEAAAAAAAAAAAAAAAEAAAAGAAAAAbHn/+vwmTMfdkzEg4Ip8oALCy29ldmUryGY9qnb1lvkAAAAFAAAAAEAA5NyAAAAMAAAAdwAAAAAAADcZwAAAAA=

Result:

AAAAAAAAAAD////7AAAAAA==\
elfin pond
#

Ah yes when u wrap the token it will submit a tx right? Do you wanna put your code in runkit so i can look closer and run some tests?

honest horizon
#

The original one is mixed with the sandbox but I asked for the relevant snippet for the engineer and this is the main function. It was based on a ecosystem implementation. I don't recall exactly if we referred to Blend or a different repository for that.

#
export const wrapClassicAsset = async (
  wrapArgs: {
    assetCode: string
    assetIssuerPk: string
  },
): Promise<string> => {
  const asset = new SorobanClient.Asset(
    wrapArgs.assetCode,
    wrapArgs.assetIssuerPk
  )
  const xdrAsset = asset.toXDRObject()
  const networkId = hash(Buffer.from(SELECTED_NETWORK.passphrase))
  const preimage = xdr.HashIdPreimage.envelopeTypeContractId(
    new xdr.HashIdPreimageContractId({
      networkId: networkId,
      contractIdPreimage:
        xdr.ContractIdPreimage.contractIdPreimageFromAsset(xdrAsset),
    })
  )
  const contractId = SorobanClient.StrKey.encodeContract(hash(preimage.toXDR()))
  const createContractArgs = new xdr.CreateContractArgs({
    contractIdPreimage:
      xdr.ContractIdPreimage.contractIdPreimageFromAsset(xdrAsset),
    executable: xdr.ContractExecutable.contractExecutableToken(),
  })
  const func =
    SorobanClient.xdr.HostFunction.hostFunctionTypeCreateContract(
      createContractArgs
    )
  const options: SorobanClient.OperationOptions.InvokeHostFunction = {
    func,
    auth: [],
  }
  const operation = SorobanClient.Operation.invokeHostFunction(options)
  const sourceAccount = await server.getAccount(
    ‘GAGYBIOLMFIS6COHSVJUCYRXCNWLPAPWSND66RM34CKQOQDKCV65AW6U’
  )
  const transaction = new SorobanClient.TransactionBuilder(sourceAccount, {
    fee: sorobanConfig.fee,
    networkPassphrase: sorobanConfig.network.passphrase,
  })
    .addOperation(operation)
    .setTimeout(sorobanConfig.txTimeout)
    .build()
  console.log(transaction.toXDR())
  try {
    const preparedTransaction = await server.prepareTransaction(transaction)
    console.log(preparedTransaction.toXDR())
    const signedTx = await signWithSecret(
      preparedTransaction,
      ‘SDKAN5EFLFL32BJUAGTPRNBNM75RHFFB5Y35LER7QYR6EFKTYY45OM3B’
    )
    console.log(signedTx.toXDR())
    await submitSorobanTx(signedTx);
    return contractId
  } catch (e) {
    throw e
  }
}
honest horizon
elfin pond
#

@green vault if its from blend which good chance... he can give some advice too

elfin pond
#

You have to submit explicitly but im not sure how the wrapper works

frail zinc
#

Adding here, this is the function that does the submit:

  signedTx: SorobanClient.Transaction | SorobanClient.FeeBumpTransaction
): Promise<
  | SorobanClient.SorobanRpc.GetTransactionResponse
  | SorobanClient.SorobanRpc.GetTransactionStatus
> => {
  try {
    const response: SorobanRpc.SendTransactionResponse =
      await server.sendTransaction(signedTx)

    if (response.status === 'ERROR') {
      console.log('ERROR: Tx failed!: ', response)
      throw new Error('failed transaction!')
    }

    const txHash = response.hash

    let updatedTransaction = await server.getTransaction(txHash)

    const waitUntil = new Date(
      Date.now() + sorobanConfig.txTimeout * 1000
    ).valueOf()

    const waitTime = 1000

    const initial = Date.now()
    while (
      Date.now() < waitUntil &&
      updatedTransaction.status ===
        SorobanClient.SorobanRpc.GetTransactionStatus.NOT_FOUND
    ) {
      await new Promise(resolve => setTimeout(resolve, waitTime))

      updatedTransaction = await server.getTransaction(txHash)
    }

    const final = Date.now()

    console.log('Duration ' + (final - initial))

    return updatedTransaction
  } catch (e) {
    console.log('Error during transaction submission: ')
    console.log(e)
    return SorobanClient.SorobanRpc.GetTransactionStatus.FAILED
  }
}```
green vault
#

assembleTransaction only really appends the SorobanData from the simulation on to the transaction, and returns a TransactionBuilder with the information.

Looks like from your XDRs the problem is that the sequence number was decremented by one in attempt #2 after preparing.

Can you confirm you are fetching a new account each time you are running this? Secondly, I remember seeing a similar bad seq number problem, but I don't remember the fix for it.

I'd recommend trying to manually call simulateTransaction and assembleTransaction instead of prepare, and see if that solves it. There is a chance prepare overwrites the acct sequence number for same reason.

#

How quickly are these being submitted, and are you waiting for confirmation from the RPC before submitting the next one?

honest horizon
# green vault `assembleTransaction` only really appends the `SorobanData` from the simulation ...

Well noted!

Looks like from your XDRs the problem is that the sequence number was decremented by one in attempt #2 after preparing.
The weird thing is that sometimes we see it increasing while others we see it decreasing. Yesterday we were so out of sync that it was using a number 3 sequences away from the correct.

Can you confirm you are fetching a new account each time you are running this? Secondly, I remember seeing a similar bad seq number problem, but I don't remember the fix for it.
Yeah, in this snippet I removed to fit the size but we added console.log right after building the envelop and after preparing and that's when they diverge

How quickly are these being submitted, and are you waiting for confirmation from the RPC before submitting the next one?
In these attempts, we triggered manually so we waited for the whole execution to be fully processed and reflect on-chain to be extra careful.

I'd recommend trying to manually call simulateTransaction and assembleTransaction instead of prepare, and see if that solves it. There is a chance prepare overwrites the acct sequence number for same reason.
Great suggestion! We'll try this right away and report back!

green vault
honest horizon
#

Thanks man! I'll have a look at it!

We just tried using simulate + assemble and got the same issue.

The changed portion of the snippet was:

const transaction = new SorobanClient.TransactionBuilder(sourceAccount, {
    fee: sorobanConfig.fee,
    networkPassphrase: sorobanConfig.network.passphrase,
  })
    .addOperation(operation)
    .setTimeout(sorobanConfig.txTimeout)
    .build()

  console.log(transaction.toXDR())

  try {
    //const preparedTransaction = await server.prepareTransaction(transaction)
    const simulate = await server.simulateTransaction(transaction)
    console.log(simulate)
    const assembleTransaction = SorobanClient.assembleTransaction(
      transaction,
      sorobanConfig.network.passphrase,
      simulate
    ).build()

    console.log(assembleTransaction.toXDR())

The first console.log right after building it returns this (9413636304928770):

AAAAAgAAAAANgKHLYVEvCceVU0FiNxNst4H2k0fvRZvglQdAahV90AAPQkAAIXGnAAAAAgAAAAEAAAAAAAAAAAAAAABlOWAJAAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAkNPSU5CAAAAAAAAAAAAAADWC5t+e/PtGReNKG6zDMzc/l8fM8H/ba6l/WvZvUvEHQAAAAEAAAAAAAAAAAAAAAA=

The last console.log after the assemble returns (9413636304928769):

AAAAAgAAAAANgKHLYVEvCceVU0FiNxNst4H2k0fvRZvglQdAahV90AAS4dUAIXGnAAAAAQAAAAEAAAAAAAAAAAAAAABlOWAJAAAAAAAAAAEAAAAAAAAAGAAAAAEAAAABAAAAAkNPSU5CAAAAAAAAAAAAAADWC5t+e/PtGReNKG6zDMzc/l8fM8H/ba6l/WvZvUvEHQAAAAEAAAAAAAAAAQAAAAAAAAAAAAAAAQAAAAYAAAABNZ73+YfwbUALajiLlSIovSGfNVx5rlmwWQJLl7D+KvoAAAAUAAAAAQADpbwAAAAwAAAB7AAAAAAAAOFAAAAAAA==
knotty sinew
#

cc @forest musk

robust aurora
#

@shell tulip @real tartan @chilly star @red wasp 👆🏻 This looks like an RPC bug?

@knotty sinew reported more details about it here and that it only appears to be happening on the public testnet rpc, not the public futurenet rpc.

If we have different versions deployed to testnet and futurenet, that might indicate a bug introduced recently into the rpc. Either way it sounds like a bug in the rpc.

red wasp
#

it can't be an rpc bug because simulation doesn't return anything about the sequence number

chilly star
#

the same rpc version has been deployed on futurenet and testnet, they are both running the docker image - docker.io/stellar/soroban-rpc:20.0.0-rc4-40

robust aurora
#

Could this be a bug in the js-soroban-client?

red wasp
#

this is because assembling the transaction clones the original and it adjusts the sequence number by 1

robust aurora
#

@red wasp Why would it reduce the seq by 1?

red wasp
#

good question - I'm trying to remember

#

something to do with it getting incremented when you build the tx

robust aurora
#

The auto incrementing logic strikes again.

red wasp
#

so in theory if you have 70 and clone it, it will have 69, but then when you build it should be 70 again... but that's not happening here?

#

(more accurately, if you clone with 70, it will give you a tx whose source has a seqno of 69 [since 70 isn't consumed yet], and it will consume it when you .build(); but again, that doesn't seem to be happening here?)

robust aurora
#

In this discussion it is stated the problem only occurs when using testnet, not using futurenet: #🔸|help message

red wasp
#

I have some transactions that work the first time, but the second time using the same source account I get this error
can you clarify what this means @knotty sinew?

#

when you say "using the same source account", the minute details matter

#

because of the odd fact that transaction assembly clones/fakes the original source account

knotty sinew
red wasp
#

oh weird, maybe it IS an RPC bug... if this line is returning an out-of-date sequence number:

const source_account = await rpc.getAccount(source);

#

when you say "use the account I used as a source account" - you just mean the public key rather than the source_account object, right? I'm asking to distinguish between "we retrieve the account info from the rpc every time" vs. "we usually try to track the seqnum locally to avoid the network fetch"

#

as those would help differentiate the source of the bug

#

and for "run the script again" - do you mean ever, or like immediately-ish? what happens if you sleep for 30s? that would also help me blame rpc :p

knotty sinew
knotty sinew
red wasp
#

so getAccount gives you 21, the first submission uses 22 (as it should), and the second uses 21 again???

#

I'm using a code that worked until a few days ago, and now I'm getting the Bad seq error every time I submit transactions using Testnet. [...] In futurenet it works without problems
this is so so so so weird

red wasp
#

lol got it

#

the sequence numbers are too big for parseInt on testnet 🤦‍♂️

robust aurora
#

cc @somber siren if you're having the same issue, it's being discussed here.

robust aurora
red wasp
#

parseInt(9413636304928770)-1
9413636304928768
big brain javascript 🧠

robust aurora
#

Of course, it's rounding on lack of precision?

elfin pond
#

that'd do it but it's wierd i didn't encounter the issue i was just testing on testnet yesterday

red wasp
#

I bet your seqnums are just smaller lol

robust aurora
red wasp
#

yes but cloneFrom doesn't

robust aurora
#

Can we issue a patch?

Backport to v1.0.0-beta.2.1?

#

Backport to v1.0.0-beta.3.1 ideally too?

red wasp
#

backporting to a prerelease 😢 beta.3 has been out for weeks

#

yeah sorry y'all I think I'm just going to push a beta.4 with this fix because backporting stellar-base is going to be a nightmare

#

if it's any consolation, beta.3 is just a bunch of helpers that decode xdr from the rpc for you lol - porting should be easy

#

you can also use the _ equivalent and not port anything, e.g. s._getTransaction over s.getTransaction (where the latter does decoding for you)

red wasp
red wasp
#

kk, beta.4 will have this fix on npm soon

robust aurora
#

@knotty sinew @honest horizon @elfin pond @frail zinc @green vault Thanks for reporting and helping identify the problem. Please let us know if this ☝🏻 resolves the issue for you folks.

red wasp
#

(btw everyone should port to stellar-sdk when they have bandwidth as that is the unified package we want people to use come mainnet)

robust aurora
#

Is that fix in stellar-sdk? We only merged PRs for base and client.

chilly star
elfin pond
red wasp
#

I've been tinkering with replacing axios but unfortunately fetch is not a drop-in by any means, as far as robustness goes

#

also can't you shim/polyfill eventsource to be empty? 🤔 I can probably drop it from the default deps

elfin pond