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

fix: nonce-based Transactions no longer mutate the transaction in place when you compileMessage #25829

Merged
merged 1 commit into from Jun 27, 2022
Merged

fix: nonce-based Transactions no longer mutate the transaction in place when you compileMessage #25829

merged 1 commit into from Jun 27, 2022

Conversation

steveluscher
Copy link
Contributor

Problem

Right now, compileMessage() is side-effectful. If you start with a nonce-based Transaction instance whose recentBlockhash member is undefined, running compileMessage will ‘fill in’ the recentBlockhash member with the value of nonceInfo.nonce. It will also have the side effect of prepending the AdvanceNonceAccount instruction to instructions.

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 if recentBlockhash differs from nonceInfo.nonce because someone overwrote it? Which will the developer expect to be used? Why did the size of the instructions list change when I called compileMessage()?

Summary of Changes

  • Write tests to cover more of these cases.
  • Always use the nonceInfo.nonce as the recent blockhash when compiling messages, even if the first transaction instruction
  • Never mutate recentBlockhash or instructions when compiling messages for nonce-based transactions

@steveluscher
Copy link
Contributor Author

steveluscher commented Jun 7, 2022

@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 mutates Transaction in place, but you never know.

@steveluscher steveluscher changed the title fix: always use the nonce as the recent blockhash; never overwrite it fix: nonce-based Transactions no longer mutate the transaction in place when you compileMessage Jun 7, 2022
if (this.nonceInfo) {
recentBlockhash = this.nonceInfo.nonce;
if (this.instructions[0] != this.nonceInfo.nonceInstruction) {
instructions = [this.nonceInfo.nonceInstruction, ...this.instructions];
Copy link
Contributor Author

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;
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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.

Comment on lines +574 to +575
expect(transferTransaction.instructions).to.have.length(1);
expect(transferTransaction.recentBlockhash).to.be.undefined;
Copy link
Contributor Author

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
Copy link

codecov bot commented Jun 7, 2022

Codecov Report

Merging #25829 (d1c934d) into master (8caced6) will decrease coverage by 6.7%.
The diff coverage is n/a.

@@             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     

Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

@steveluscher steveluscher merged commit a741edd into solana-labs:master Jun 27, 2022
@steveluscher steveluscher deleted the separate-nonce-and-recent-blockhash-in-transaction-internally branch June 27, 2022 02:01
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

Successfully merging this pull request may close these issues.

None yet

2 participants