-
Notifications
You must be signed in to change notification settings - Fork 4.4k
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
The solana-web3.js transaction confirmation logic is very broken #23949
Comments
Examples of folks tripping over this: |
@stellaw1 and I spent some time last night talking about this issue and we have an idea of what we should do and wanted to get some feedback and clarification on a few things. Based on what we've been able to research, it seems like we're going to need to use We also took a look at Does this seem reasonable? |
There's also
Not
Precisely. Human time is the enemy |
We saw this one too and wondered if it would work in this case.
Thanks for clarifying this. I'm still trying to better understand what commitment means, so this helps. Still a lot to learn! |
isBlockhashValid is weaker than determining the lastValidBlockHeight and just waiting for that block height. isBlockhashValid may return |
polling with
|
No, polling getBlockHeight won’t fail if the RPC endpoint backend you hit is behind |
@mvines you also mentioned nonce-based transactions in the issue, but neither of us has any experience with nonce accounts. Would |
Could somebody kindly provide a code sample of how this might work in practice? Cheers |
You can check for reference how the Rust client sends and confirms transactions. The logic is as follows in psuedocode:
Using isBlockhashValid and getSignatureStatus There is some logic about retries in there that you can add as well.
Shouldn't this fail the same way if you hit a bad backend? |
Thanks @jacobcreech this makes sense. I was just checking the web3.js source code and right now we can't use |
Nope. If the RPC endpoint backend is behind:
This is why
Yes I think so. The NonceAccount.nonce field will change when the nonce is advanced and that's your only indication save indication that the previous nonce transaction is now invalid. The nonce can be advanced explicitly to expire a previous unexecuted transaction that was signed with that nonce as well, but this is probably best left as a client-level decision. |
I've been looking at the Rust client code and this is what I understand so far. If I've got anything wrong, please let me know: I see where send_and_confirm_transaction uses |
Please note that the Rust client logic is also broken in a similar way, in that it uses |
I'm curious about |
I was curious about that. I also noticed that the Rust client |
The JS and Rust clients are both broken in different ways I'm afraid. |
@mvines
|
We came up with two solutions for getting an accurate
|
Don't do any magic guessing on numbers. A lot can happen between transaction creation and sending.
This requires RPC changes. You'll still run into an issue where the blockhash doesn't exist on a bad backend and you get an error. I believe the solution lies in changing
Where
And then use that to determine a transaction's validity |
@jacobcreech sounds good, we'll get this done and update here. UPDATE: looking into this more, if we edit
we would also have to make changes to |
@steveluscher we wanted to get your thoughts on our approach for this issue. I spent some time on this last night and this is what I ended up with so far: Updating We spent a lot of time looking into this and figuring out a way to do this correctly, but it seems like the only way to do this right is to introduce breaking changes. |
Sure thing! I’ll catch up on this thread tomorrow. |
On the topic of breaking changesRather than to patch in
On the topic of block height / nonce trackingI think we've come up with some good strategies for tracking the current block height and nonce status, but all of them require a lot of logic on the client and a lot of chatter over the wire. Crazy idea here: @t-nelson, @mvines, what would you think about adding an argument to the
|
@steveluscher This is really helpful. Thank you! Do you think for now we should go ahead with the current strategies for tracking block height and nonce status until there can be more discussion about changing the |
Let's wait just a bit to hear from the RPC/validator folks. The best code is the code you didn't have to write :) |
On the topic of cancellationOh, I forgot to address one note above. @mvines mentioned that if The way is to make |
Just note that an abort doesn’t mean that the transaction won’t execute after the front-end aborts it |
What change should we make to the Rust client @mvines? Is the idea to add a |
Yeah, I looked at
Should we not be scared off by that? Is it good to start relying upon? |
Thanks for that, @t-nelson. You've given me a lot to think about. I've written and delete this response multiple times as I've been diving in and out of both implementations trying to map them out in my head. In general, what I've found is an imperative API for building and sending transactions – in both Rust and JS – that give devs lots of opportunities to ‘get it wrong.’
I want to create pits of success for Solana developers, rather than to leave them footguns. The combination of a stateful I'm going to continue to think on this. In the meantime, let's make the smallest changes possible to materially improve confirmation successes with the current API. I propose the following. First PR: Update the
|
A couple other things to consider:
|
@steveluscher and @mvines - @stellaw1 and I have started working on the |
Draft PR has been created. Looking forward to your thoughts on how we can make this work! |
Can’t wait to look this over; sorry for the delay! I’ll be there soon. |
No worries! So far, we've only made changes to |
oh, poor |
I think we should also consider switching the runtime to actually expire transactions using block heights as we all assumed it did. That approach seems better because then skipped slots won't impact how many blocks a transaction could be committed to |
This is interesting. I took a look at the conversation on the issue. Learning something new every day. Thanks for referencing this.
cc: @jstarry just curious, did you mean using slots? So rather than polling |
My comment wasn't very clear so I just updated it. I meant that we can keep the planned RPC and client behavior and update the runtime to use the behavior we thought it was using. |
Got it. Thanks for the update! |
It would be really important to expand the documentation to show good examples how to handle the different outcomes confirmTransaction can have. Important to consider:
|
Opening a separate issue to track the development of the nonce-based confirmation strategy: #25303. |
From where this |
That call signature needs one correction: const transaction = new Transaction({...latestBlockhash}).add(/* ... */);
/* ... */
await confirmTransaction({
...latestBlockhash,
signature,
}); |
Just confirming that this has been resolved because i'm hitting related errors so want to confirm it's an issue w/ my code and not the underlying library |
The correct approach to confirming a normal transaction is to fetch the last valid block height for the transaction blockhash, then wait until the chain has advanced beyond that block height before declaring the transaction expired.
A transaction that uses a nonce is a little different, since those transactions are technically valid until the nonce is advanced. For nonce-based transactions, the right approach is to check the corresponding nonce account to see if the nonce has been advanced while periodically retrying the transaction (although some form of timeout or mechanism for the caller to abort seems nice)
The big offenders:
1. confirmTransaction
This method simply waits for up to 60 seconds. This is wrong because a transaction's blockhash may be valid for longer than 60 seconds, so it's very possible for the transaction to execute after this method returns a failure. The signature of confirmTransaction does not include the blockhash of the transaction so it's literally impossible to do the right thing without a signature change. This method probably just should be deprecated.
2. sendAndConfirmTransaction and sendAndConfirmRawTransaction
These two just need to be reimplemented to not use
confirmTransaction
and instead follow the approach outlined at the start of this issue.The text was updated successfully, but these errors were encountered: