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
fix: nonce-based Transactions no longer mutate the transaction in place when you compileMessage
#25829
fix: nonce-based Transactions no longer mutate the transaction in place when you compileMessage
#25829
Conversation
@jstarry, @jordansexton, I came across this when implementing #25303. This is, of course, the kind of thing that I should just just leave well alone, but here we are. Can I put your heads together to see if there's anything about this PR that would break something, somewhere? Nobody should be relying on the fact that |
compileMessage
if (this.nonceInfo) { | ||
recentBlockhash = this.nonceInfo.nonce; | ||
if (this.instructions[0] != this.nonceInfo.nonceInstruction) { | ||
instructions = [this.nonceInfo.nonceInstruction, ...this.instructions]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy this.instructions
if you're going to mess with it.
let recentBlockhash; | ||
let instructions: TransactionInstruction[]; | ||
if (this.nonceInfo) { | ||
recentBlockhash = this.nonceInfo.nonce; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Always use the nonce as the recentBlockhash, but only in the scope of the compileMessage
function.
@@ -327,17 +327,24 @@ export class Transaction { | |||
return this._message; | |||
} | |||
|
|||
const {nonceInfo} = this; | |||
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug here. If the nonce advance instruction already happened to be at the head of the list (eg. because someone put it there) we wouldn't end up using the nonce as the recentBlockhash
in the Message
.
expect(transferTransaction.instructions).to.have.length(1); | ||
expect(transferTransaction.recentBlockhash).to.be.undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this test represent the prior behavior that I've changed. No longer does simply calling sign()
(which in turn calls compileMessage()
) mutate the Transaction
instance.
Codecov Report
@@ Coverage Diff @@
## master #25829 +/- ##
===========================================
- Coverage 82.1% 75.4% -6.8%
===========================================
Files 628 40 -588
Lines 171471 2347 -169124
Branches 0 339 +339
===========================================
- Hits 140878 1771 -139107
+ Misses 30593 459 -30134
- Partials 0 117 +117 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great!
Problem
Right now,
compileMessage()
is side-effectful. If you start with a nonce-basedTransaction
instance whoserecentBlockhash
member isundefined
, runningcompileMessage
will ‘fill in’ therecentBlockhash
member with the value ofnonceInfo.nonce
. It will also have the side effect of prepending theAdvanceNonceAccount
instruction toinstructions
.compileMessage
has no right to do this. It should perform reads only. The only thing that can result from this is confusion. Why does a nonce-based transaction have no recent blockhash at one point in time, but does in another? What ifrecentBlockhash
differs fromnonceInfo.nonce
because someone overwrote it? Which will the developer expect to be used? Why did the size of the instructions list change when I calledcompileMessage()
?Summary of Changes
nonceInfo.nonce
as the recent blockhash when compiling messages, even if the first transaction instructionrecentBlockhash
orinstructions
when compiling messages for nonce-based transactions