Skip to content
This repository was archived by the owner on Jan 22, 2025. It is now read-only.

Commit 2f80949

Browse files
authoredJun 27, 2022
fix: always use the nonce as the recent blockhash; never overwrite it (#25829)
1 parent 0b3de2b commit 2f80949

File tree

2 files changed

+155
-27
lines changed

2 files changed

+155
-27
lines changed
 

‎src/transaction.ts

+19-12
Original file line numberDiff line numberDiff line change
@@ -327,17 +327,24 @@ export class Transaction {
327327
return this._message;
328328
}
329329

330-
const {nonceInfo} = this;
331-
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
332-
this.recentBlockhash = nonceInfo.nonce;
333-
this.instructions.unshift(nonceInfo.nonceInstruction);
330+
let recentBlockhash;
331+
let instructions: TransactionInstruction[];
332+
if (this.nonceInfo) {
333+
recentBlockhash = this.nonceInfo.nonce;
334+
if (this.instructions[0] != this.nonceInfo.nonceInstruction) {
335+
instructions = [this.nonceInfo.nonceInstruction, ...this.instructions];
336+
} else {
337+
instructions = this.instructions;
338+
}
339+
} else {
340+
recentBlockhash = this.recentBlockhash;
341+
instructions = this.instructions;
334342
}
335-
const {recentBlockhash} = this;
336343
if (!recentBlockhash) {
337344
throw new Error('Transaction recentBlockhash required');
338345
}
339346

340-
if (this.instructions.length < 1) {
347+
if (instructions.length < 1) {
341348
console.warn('No instructions provided');
342349
}
343350

@@ -351,8 +358,8 @@ export class Transaction {
351358
throw new Error('Transaction fee payer required');
352359
}
353360

354-
for (let i = 0; i < this.instructions.length; i++) {
355-
if (this.instructions[i].programId === undefined) {
361+
for (let i = 0; i < instructions.length; i++) {
362+
if (instructions[i].programId === undefined) {
356363
throw new Error(
357364
`Transaction instruction index ${i} has undefined program id`,
358365
);
@@ -361,7 +368,7 @@ export class Transaction {
361368

362369
const programIds: string[] = [];
363370
const accountMetas: AccountMeta[] = [];
364-
this.instructions.forEach(instruction => {
371+
instructions.forEach(instruction => {
365372
instruction.keys.forEach(accountMeta => {
366373
accountMetas.push({...accountMeta});
367374
});
@@ -471,7 +478,7 @@ export class Transaction {
471478
});
472479

473480
const accountKeys = signedKeys.concat(unsignedKeys);
474-
const instructions: CompiledInstruction[] = this.instructions.map(
481+
const compiledInstructions: CompiledInstruction[] = instructions.map(
475482
instruction => {
476483
const {data, programId} = instruction;
477484
return {
@@ -484,7 +491,7 @@ export class Transaction {
484491
},
485492
);
486493

487-
instructions.forEach(instruction => {
494+
compiledInstructions.forEach(instruction => {
488495
invariant(instruction.programIdIndex >= 0);
489496
instruction.accounts.forEach(keyIndex => invariant(keyIndex >= 0));
490497
});
@@ -497,7 +504,7 @@ export class Transaction {
497504
},
498505
accountKeys,
499506
recentBlockhash,
500-
instructions,
507+
instructions: compiledInstructions,
501508
});
502509
}
503510

‎test/transaction.test.ts

+136-15
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,122 @@ describe('Transaction', () => {
230230
expect(message.header.numReadonlySignedAccounts).to.eq(0);
231231
expect(message.header.numReadonlyUnsignedAccounts).to.eq(1);
232232
});
233+
234+
it('uses the nonce as the recent blockhash when compiling nonce-based transactions', () => {
235+
const nonce = new PublicKey(1);
236+
const nonceAuthority = new PublicKey(2);
237+
const nonceInfo = {
238+
nonce: nonce.toBase58(),
239+
nonceInstruction: SystemProgram.nonceAdvance({
240+
noncePubkey: nonce,
241+
authorizedPubkey: nonceAuthority,
242+
}),
243+
};
244+
const transaction = new Transaction({
245+
feePayer: nonceAuthority,
246+
nonceInfo,
247+
});
248+
const message = transaction.compileMessage();
249+
expect(message.recentBlockhash).to.equal(nonce.toBase58());
250+
});
251+
252+
it('prepends the nonce advance instruction when compiling nonce-based transactions', () => {
253+
const nonce = new PublicKey(1);
254+
const nonceAuthority = new PublicKey(2);
255+
const nonceInfo = {
256+
nonce: nonce.toBase58(),
257+
nonceInstruction: SystemProgram.nonceAdvance({
258+
noncePubkey: nonce,
259+
authorizedPubkey: nonceAuthority,
260+
}),
261+
};
262+
const transaction = new Transaction({
263+
feePayer: nonceAuthority,
264+
nonceInfo,
265+
}).add(
266+
SystemProgram.transfer({
267+
fromPubkey: nonceAuthority,
268+
lamports: 1,
269+
toPubkey: new PublicKey(3),
270+
}),
271+
);
272+
const message = transaction.compileMessage();
273+
expect(message.instructions).to.have.length(2);
274+
const expectedNonceAdvanceCompiledInstruction = {
275+
accounts: [1, 4, 0],
276+
data: (() => {
277+
const expectedData = Buffer.alloc(4);
278+
expectedData.writeInt32LE(
279+
4 /* SystemInstruction::AdvanceNonceAccount */,
280+
0,
281+
);
282+
return bs58.encode(expectedData);
283+
})(),
284+
programIdIndex: (() => {
285+
let foundIndex = -1;
286+
message.accountKeys.find((publicKey, ii) => {
287+
if (publicKey.equals(SystemProgram.programId)) {
288+
foundIndex = ii;
289+
return true;
290+
}
291+
});
292+
return foundIndex;
293+
})(),
294+
};
295+
expect(message.instructions[0]).to.deep.equal(
296+
expectedNonceAdvanceCompiledInstruction,
297+
);
298+
});
299+
300+
it('does not prepend the nonce advance instruction when compiling nonce-based transactions if it is already there', () => {
301+
const nonce = new PublicKey(1);
302+
const nonceAuthority = new PublicKey(2);
303+
const nonceInfo = {
304+
nonce: nonce.toBase58(),
305+
nonceInstruction: SystemProgram.nonceAdvance({
306+
noncePubkey: nonce,
307+
authorizedPubkey: nonceAuthority,
308+
}),
309+
};
310+
const transaction = new Transaction({
311+
feePayer: nonceAuthority,
312+
nonceInfo,
313+
})
314+
.add(nonceInfo.nonceInstruction)
315+
.add(
316+
SystemProgram.transfer({
317+
fromPubkey: nonceAuthority,
318+
lamports: 1,
319+
toPubkey: new PublicKey(3),
320+
}),
321+
);
322+
const message = transaction.compileMessage();
323+
expect(message.instructions).to.have.length(2);
324+
const expectedNonceAdvanceCompiledInstruction = {
325+
accounts: [1, 4, 0],
326+
data: (() => {
327+
const expectedData = Buffer.alloc(4);
328+
expectedData.writeInt32LE(
329+
4 /* SystemInstruction::AdvanceNonceAccount */,
330+
0,
331+
);
332+
return bs58.encode(expectedData);
333+
})(),
334+
programIdIndex: (() => {
335+
let foundIndex = -1;
336+
message.accountKeys.find((publicKey, ii) => {
337+
if (publicKey.equals(SystemProgram.programId)) {
338+
foundIndex = ii;
339+
return true;
340+
}
341+
});
342+
return foundIndex;
343+
})(),
344+
};
345+
expect(message.instructions[0]).to.deep.equal(
346+
expectedNonceAdvanceCompiledInstruction,
347+
);
348+
});
233349
});
234350

235351
if (process.env.TEST_LIVE) {
@@ -455,15 +571,8 @@ describe('Transaction', () => {
455571
);
456572
transferTransaction.sign(account1);
457573

458-
let expectedData = Buffer.alloc(4);
459-
expectedData.writeInt32LE(4, 0);
460-
461-
expect(transferTransaction.instructions).to.have.length(2);
462-
expect(transferTransaction.instructions[0].programId).to.eql(
463-
SystemProgram.programId,
464-
);
465-
expect(transferTransaction.instructions[0].data).to.eql(expectedData);
466-
expect(transferTransaction.recentBlockhash).to.eq(nonce);
574+
expect(transferTransaction.instructions).to.have.length(1);
575+
expect(transferTransaction.recentBlockhash).to.be.undefined;
467576

468577
const stakeAccount = Keypair.generate();
469578
const voteAccount = Keypair.generate();
@@ -476,12 +585,8 @@ describe('Transaction', () => {
476585
);
477586
stakeTransaction.sign(account1);
478587

479-
expect(stakeTransaction.instructions).to.have.length(2);
480-
expect(stakeTransaction.instructions[0].programId).to.eql(
481-
SystemProgram.programId,
482-
);
483-
expect(stakeTransaction.instructions[0].data).to.eql(expectedData);
484-
expect(stakeTransaction.recentBlockhash).to.eq(nonce);
588+
expect(stakeTransaction.instructions).to.have.length(1);
589+
expect(stakeTransaction.recentBlockhash).to.be.undefined;
485590
});
486591

487592
it('parse wire format and serialize', () => {
@@ -596,6 +701,22 @@ describe('Transaction', () => {
596701
expect(compiledMessage3).not.to.eql(message);
597702
});
598703

704+
it('constructs a transaction with nonce info', () => {
705+
const nonce = new PublicKey(1);
706+
const nonceAuthority = new PublicKey(2);
707+
const nonceInfo = {
708+
nonce: nonce.toBase58(),
709+
nonceInstruction: SystemProgram.nonceAdvance({
710+
noncePubkey: nonce,
711+
authorizedPubkey: nonceAuthority,
712+
}),
713+
};
714+
const transaction = new Transaction({nonceInfo});
715+
expect(transaction.recentBlockhash).to.be.undefined;
716+
expect(transaction.lastValidBlockHeight).to.be.undefined;
717+
expect(transaction.nonceInfo).to.equal(nonceInfo);
718+
});
719+
599720
it('constructs a transaction with last valid block height', () => {
600721
const blockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k';
601722
const lastValidBlockHeight = 1234;

0 commit comments

Comments
 (0)
This repository has been archived.