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

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: solana-labs/solana-web3.js
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.44.2
Choose a base ref
...
head repository: solana-labs/solana-web3.js
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.44.3
Choose a head ref
  • 2 commits
  • 3 files changed
  • 1 contributor

Commits on Jun 21, 2022

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0b3de2b View commit details

Commits on Jun 27, 2022

  1. Copy the full SHA
    2f80949 View commit details
Showing with 157 additions and 29 deletions.
  1. +2 −2 src/connection.ts
  2. +19 −12 src/transaction.ts
  3. +136 −15 test/transaction.test.ts
4 changes: 2 additions & 2 deletions src/connection.ts
Original file line number Diff line number Diff line change
@@ -191,9 +191,9 @@ type Subscription = BaseSubscription &
StatefulSubscription &
DistributiveOmit<SubscriptionConfig, 'callback'>;

type RpcRequest = (methodName: string, args: Array<any>) => any;
type RpcRequest = (methodName: string, args: Array<any>) => Promise<any>;

type RpcBatchRequest = (requests: RpcParams[]) => any;
type RpcBatchRequest = (requests: RpcParams[]) => Promise<any[]>;

/**
* @internal
31 changes: 19 additions & 12 deletions src/transaction.ts
Original file line number Diff line number Diff line change
@@ -327,17 +327,24 @@ export class Transaction {
return this._message;
}

const {nonceInfo} = this;
if (nonceInfo && this.instructions[0] != nonceInfo.nonceInstruction) {
this.recentBlockhash = nonceInfo.nonce;
this.instructions.unshift(nonceInfo.nonceInstruction);
let recentBlockhash;
let instructions: TransactionInstruction[];
if (this.nonceInfo) {
recentBlockhash = this.nonceInfo.nonce;
if (this.instructions[0] != this.nonceInfo.nonceInstruction) {
instructions = [this.nonceInfo.nonceInstruction, ...this.instructions];
} else {
instructions = this.instructions;
}
} else {
recentBlockhash = this.recentBlockhash;
instructions = this.instructions;
}
const {recentBlockhash} = this;
if (!recentBlockhash) {
throw new Error('Transaction recentBlockhash required');
}

if (this.instructions.length < 1) {
if (instructions.length < 1) {
console.warn('No instructions provided');
}

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

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

const programIds: string[] = [];
const accountMetas: AccountMeta[] = [];
this.instructions.forEach(instruction => {
instructions.forEach(instruction => {
instruction.keys.forEach(accountMeta => {
accountMetas.push({...accountMeta});
});
@@ -471,7 +478,7 @@ export class Transaction {
});

const accountKeys = signedKeys.concat(unsignedKeys);
const instructions: CompiledInstruction[] = this.instructions.map(
const compiledInstructions: CompiledInstruction[] = instructions.map(
instruction => {
const {data, programId} = instruction;
return {
@@ -484,7 +491,7 @@ export class Transaction {
},
);

instructions.forEach(instruction => {
compiledInstructions.forEach(instruction => {
invariant(instruction.programIdIndex >= 0);
instruction.accounts.forEach(keyIndex => invariant(keyIndex >= 0));
});
@@ -497,7 +504,7 @@ export class Transaction {
},
accountKeys,
recentBlockhash,
instructions,
instructions: compiledInstructions,
});
}

151 changes: 136 additions & 15 deletions test/transaction.test.ts
Original file line number Diff line number Diff line change
@@ -230,6 +230,122 @@ describe('Transaction', () => {
expect(message.header.numReadonlySignedAccounts).to.eq(0);
expect(message.header.numReadonlyUnsignedAccounts).to.eq(1);
});

it('uses the nonce as the recent blockhash when compiling nonce-based transactions', () => {
const nonce = new PublicKey(1);
const nonceAuthority = new PublicKey(2);
const nonceInfo = {
nonce: nonce.toBase58(),
nonceInstruction: SystemProgram.nonceAdvance({
noncePubkey: nonce,
authorizedPubkey: nonceAuthority,
}),
};
const transaction = new Transaction({
feePayer: nonceAuthority,
nonceInfo,
});
const message = transaction.compileMessage();
expect(message.recentBlockhash).to.equal(nonce.toBase58());
});

it('prepends the nonce advance instruction when compiling nonce-based transactions', () => {
const nonce = new PublicKey(1);
const nonceAuthority = new PublicKey(2);
const nonceInfo = {
nonce: nonce.toBase58(),
nonceInstruction: SystemProgram.nonceAdvance({
noncePubkey: nonce,
authorizedPubkey: nonceAuthority,
}),
};
const transaction = new Transaction({
feePayer: nonceAuthority,
nonceInfo,
}).add(
SystemProgram.transfer({
fromPubkey: nonceAuthority,
lamports: 1,
toPubkey: new PublicKey(3),
}),
);
const message = transaction.compileMessage();
expect(message.instructions).to.have.length(2);
const expectedNonceAdvanceCompiledInstruction = {
accounts: [1, 4, 0],
data: (() => {
const expectedData = Buffer.alloc(4);
expectedData.writeInt32LE(
4 /* SystemInstruction::AdvanceNonceAccount */,
0,
);
return bs58.encode(expectedData);
})(),
programIdIndex: (() => {
let foundIndex = -1;
message.accountKeys.find((publicKey, ii) => {
if (publicKey.equals(SystemProgram.programId)) {
foundIndex = ii;
return true;
}
});
return foundIndex;
})(),
};
expect(message.instructions[0]).to.deep.equal(
expectedNonceAdvanceCompiledInstruction,
);
});

it('does not prepend the nonce advance instruction when compiling nonce-based transactions if it is already there', () => {
const nonce = new PublicKey(1);
const nonceAuthority = new PublicKey(2);
const nonceInfo = {
nonce: nonce.toBase58(),
nonceInstruction: SystemProgram.nonceAdvance({
noncePubkey: nonce,
authorizedPubkey: nonceAuthority,
}),
};
const transaction = new Transaction({
feePayer: nonceAuthority,
nonceInfo,
})
.add(nonceInfo.nonceInstruction)
.add(
SystemProgram.transfer({
fromPubkey: nonceAuthority,
lamports: 1,
toPubkey: new PublicKey(3),
}),
);
const message = transaction.compileMessage();
expect(message.instructions).to.have.length(2);
const expectedNonceAdvanceCompiledInstruction = {
accounts: [1, 4, 0],
data: (() => {
const expectedData = Buffer.alloc(4);
expectedData.writeInt32LE(
4 /* SystemInstruction::AdvanceNonceAccount */,
0,
);
return bs58.encode(expectedData);
})(),
programIdIndex: (() => {
let foundIndex = -1;
message.accountKeys.find((publicKey, ii) => {
if (publicKey.equals(SystemProgram.programId)) {
foundIndex = ii;
return true;
}
});
return foundIndex;
})(),
};
expect(message.instructions[0]).to.deep.equal(
expectedNonceAdvanceCompiledInstruction,
);
});
});

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

let expectedData = Buffer.alloc(4);
expectedData.writeInt32LE(4, 0);

expect(transferTransaction.instructions).to.have.length(2);
expect(transferTransaction.instructions[0].programId).to.eql(
SystemProgram.programId,
);
expect(transferTransaction.instructions[0].data).to.eql(expectedData);
expect(transferTransaction.recentBlockhash).to.eq(nonce);
expect(transferTransaction.instructions).to.have.length(1);
expect(transferTransaction.recentBlockhash).to.be.undefined;

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

expect(stakeTransaction.instructions).to.have.length(2);
expect(stakeTransaction.instructions[0].programId).to.eql(
SystemProgram.programId,
);
expect(stakeTransaction.instructions[0].data).to.eql(expectedData);
expect(stakeTransaction.recentBlockhash).to.eq(nonce);
expect(stakeTransaction.instructions).to.have.length(1);
expect(stakeTransaction.recentBlockhash).to.be.undefined;
});

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

it('constructs a transaction with nonce info', () => {
const nonce = new PublicKey(1);
const nonceAuthority = new PublicKey(2);
const nonceInfo = {
nonce: nonce.toBase58(),
nonceInstruction: SystemProgram.nonceAdvance({
noncePubkey: nonce,
authorizedPubkey: nonceAuthority,
}),
};
const transaction = new Transaction({nonceInfo});
expect(transaction.recentBlockhash).to.be.undefined;
expect(transaction.lastValidBlockHeight).to.be.undefined;
expect(transaction.nonceInfo).to.equal(nonceInfo);
});

it('constructs a transaction with last valid block height', () => {
const blockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k';
const lastValidBlockHeight = 1234;