feat: thread new blockheight expiry strategy through `sendAndConfirmTransaction` (#25227)
* chore: extract expirable blockhash record into its own type * fix: the local latest blockhash cache now fetches and stores lastValidBlockHeight * fix: allow people to supply a confirmation strategy to sendAndConfirmRawTransaction * test: upgrade RPC helpers to use blockheight confirmation method * test: patch up tests to use blockheight based confirmation strategy * fix: eliminate deprecated construction of Transaction inside simulateTransaction * test: eliminate deprecated constructions of Transaction in tests
This commit is contained in:
parent
abd4ef889e
commit
4ea39c1cd3
|
@ -284,15 +284,18 @@ export type RpcResponseAndContext<T> = {
|
||||||
value: T;
|
value: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type BlockhashWithExpiryBlockHeight = Readonly<{
|
||||||
|
blockhash: Blockhash;
|
||||||
|
lastValidBlockHeight: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A strategy for confirming transactions that uses the last valid
|
* A strategy for confirming transactions that uses the last valid
|
||||||
* block height for a given blockhash to check for transaction expiration.
|
* block height for a given blockhash to check for transaction expiration.
|
||||||
*/
|
*/
|
||||||
export type BlockheightBasedTransactionConfimationStrategy = {
|
export type BlockheightBasedTransactionConfimationStrategy = {
|
||||||
signature: TransactionSignature;
|
signature: TransactionSignature;
|
||||||
blockhash: Blockhash;
|
} & BlockhashWithExpiryBlockHeight;
|
||||||
lastValidBlockHeight: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
|
@ -2218,12 +2221,12 @@ export class Connection {
|
||||||
/** @internal */ _disableBlockhashCaching: boolean = false;
|
/** @internal */ _disableBlockhashCaching: boolean = false;
|
||||||
/** @internal */ _pollingBlockhash: boolean = false;
|
/** @internal */ _pollingBlockhash: boolean = false;
|
||||||
/** @internal */ _blockhashInfo: {
|
/** @internal */ _blockhashInfo: {
|
||||||
recentBlockhash: Blockhash | null;
|
latestBlockhash: BlockhashWithExpiryBlockHeight | null;
|
||||||
lastFetch: number;
|
lastFetch: number;
|
||||||
simulatedSignatures: Array<string>;
|
simulatedSignatures: Array<string>;
|
||||||
transactionSignatures: Array<string>;
|
transactionSignatures: Array<string>;
|
||||||
} = {
|
} = {
|
||||||
recentBlockhash: null,
|
latestBlockhash: null,
|
||||||
lastFetch: 0,
|
lastFetch: 0,
|
||||||
transactionSignatures: [],
|
transactionSignatures: [],
|
||||||
simulatedSignatures: [],
|
simulatedSignatures: [],
|
||||||
|
@ -3322,11 +3325,11 @@ export class Connection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the latest blockhash from the cluster
|
* Fetch the latest blockhash from the cluster
|
||||||
* @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
|
* @return {Promise<BlockhashWithExpiryBlockHeight>}
|
||||||
*/
|
*/
|
||||||
async getLatestBlockhash(
|
async getLatestBlockhash(
|
||||||
commitment?: Commitment,
|
commitment?: Commitment,
|
||||||
): Promise<{blockhash: Blockhash; lastValidBlockHeight: number}> {
|
): Promise<BlockhashWithExpiryBlockHeight> {
|
||||||
try {
|
try {
|
||||||
const res = await this.getLatestBlockhashAndContext(commitment);
|
const res = await this.getLatestBlockhashAndContext(commitment);
|
||||||
return res.value;
|
return res.value;
|
||||||
|
@ -3337,13 +3340,11 @@ export class Connection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the latest blockhash from the cluster
|
* Fetch the latest blockhash from the cluster
|
||||||
* @return {Promise<{blockhash: Blockhash, lastValidBlockHeight: number}>}
|
* @return {Promise<BlockhashWithExpiryBlockHeight>}
|
||||||
*/
|
*/
|
||||||
async getLatestBlockhashAndContext(
|
async getLatestBlockhashAndContext(
|
||||||
commitment?: Commitment,
|
commitment?: Commitment,
|
||||||
): Promise<
|
): Promise<RpcResponseAndContext<BlockhashWithExpiryBlockHeight>> {
|
||||||
RpcResponseAndContext<{blockhash: Blockhash; lastValidBlockHeight: number}>
|
|
||||||
> {
|
|
||||||
const args = this._buildArgs([], commitment);
|
const args = this._buildArgs([], commitment);
|
||||||
const unsafeRes = await this._rpcRequest('getLatestBlockhash', args);
|
const unsafeRes = await this._rpcRequest('getLatestBlockhash', args);
|
||||||
const res = create(unsafeRes, GetLatestBlockhashRpcResult);
|
const res = create(unsafeRes, GetLatestBlockhashRpcResult);
|
||||||
|
@ -3989,7 +3990,9 @@ export class Connection {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async _recentBlockhash(disableCache: boolean): Promise<Blockhash> {
|
async _blockhashWithExpiryBlockHeight(
|
||||||
|
disableCache: boolean,
|
||||||
|
): Promise<BlockhashWithExpiryBlockHeight> {
|
||||||
if (!disableCache) {
|
if (!disableCache) {
|
||||||
// Wait for polling to finish
|
// Wait for polling to finish
|
||||||
while (this._pollingBlockhash) {
|
while (this._pollingBlockhash) {
|
||||||
|
@ -3997,8 +4000,8 @@ export class Connection {
|
||||||
}
|
}
|
||||||
const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
|
const timeSinceFetch = Date.now() - this._blockhashInfo.lastFetch;
|
||||||
const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
|
const expired = timeSinceFetch >= BLOCKHASH_CACHE_TIMEOUT_MS;
|
||||||
if (this._blockhashInfo.recentBlockhash !== null && !expired) {
|
if (this._blockhashInfo.latestBlockhash !== null && !expired) {
|
||||||
return this._blockhashInfo.recentBlockhash;
|
return this._blockhashInfo.latestBlockhash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4008,21 +4011,25 @@ export class Connection {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
async _pollNewBlockhash(): Promise<Blockhash> {
|
async _pollNewBlockhash(): Promise<BlockhashWithExpiryBlockHeight> {
|
||||||
this._pollingBlockhash = true;
|
this._pollingBlockhash = true;
|
||||||
try {
|
try {
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
const cachedLatestBlockhash = this._blockhashInfo.latestBlockhash;
|
||||||
|
const cachedBlockhash = cachedLatestBlockhash
|
||||||
|
? cachedLatestBlockhash.blockhash
|
||||||
|
: null;
|
||||||
for (let i = 0; i < 50; i++) {
|
for (let i = 0; i < 50; i++) {
|
||||||
const {blockhash} = await this.getRecentBlockhash('finalized');
|
const latestBlockhash = await this.getLatestBlockhash('finalized');
|
||||||
|
|
||||||
if (this._blockhashInfo.recentBlockhash != blockhash) {
|
if (cachedBlockhash !== latestBlockhash.blockhash) {
|
||||||
this._blockhashInfo = {
|
this._blockhashInfo = {
|
||||||
recentBlockhash: blockhash,
|
latestBlockhash,
|
||||||
lastFetch: Date.now(),
|
lastFetch: Date.now(),
|
||||||
transactionSignatures: [],
|
transactionSignatures: [],
|
||||||
simulatedSignatures: [],
|
simulatedSignatures: [],
|
||||||
};
|
};
|
||||||
return blockhash;
|
return latestBlockhash;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sleep for approximately half a slot
|
// Sleep for approximately half a slot
|
||||||
|
@ -4048,13 +4055,11 @@ export class Connection {
|
||||||
let transaction;
|
let transaction;
|
||||||
if (transactionOrMessage instanceof Transaction) {
|
if (transactionOrMessage instanceof Transaction) {
|
||||||
let originalTx: Transaction = transactionOrMessage;
|
let originalTx: Transaction = transactionOrMessage;
|
||||||
transaction = new Transaction({
|
transaction = new Transaction();
|
||||||
recentBlockhash: originalTx.recentBlockhash,
|
transaction.feePayer = originalTx.feePayer;
|
||||||
nonceInfo: originalTx.nonceInfo,
|
|
||||||
feePayer: originalTx.feePayer,
|
|
||||||
signatures: [...originalTx.signatures],
|
|
||||||
});
|
|
||||||
transaction.instructions = transactionOrMessage.instructions;
|
transaction.instructions = transactionOrMessage.instructions;
|
||||||
|
transaction.nonceInfo = originalTx.nonceInfo;
|
||||||
|
transaction.signatures = originalTx.signatures;
|
||||||
} else {
|
} else {
|
||||||
transaction = Transaction.populate(transactionOrMessage);
|
transaction = Transaction.populate(transactionOrMessage);
|
||||||
// HACK: this function relies on mutating the populated transaction
|
// HACK: this function relies on mutating the populated transaction
|
||||||
|
@ -4066,7 +4071,11 @@ export class Connection {
|
||||||
} else {
|
} else {
|
||||||
let disableCache = this._disableBlockhashCaching;
|
let disableCache = this._disableBlockhashCaching;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
transaction.recentBlockhash = await this._recentBlockhash(disableCache);
|
const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
|
||||||
|
disableCache,
|
||||||
|
);
|
||||||
|
transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
|
||||||
|
transaction.recentBlockhash = latestBlockhash.blockhash;
|
||||||
|
|
||||||
if (!signers) break;
|
if (!signers) break;
|
||||||
|
|
||||||
|
@ -4154,7 +4163,11 @@ export class Connection {
|
||||||
} else {
|
} else {
|
||||||
let disableCache = this._disableBlockhashCaching;
|
let disableCache = this._disableBlockhashCaching;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
transaction.recentBlockhash = await this._recentBlockhash(disableCache);
|
const latestBlockhash = await this._blockhashWithExpiryBlockHeight(
|
||||||
|
disableCache,
|
||||||
|
);
|
||||||
|
transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
|
||||||
|
transaction.recentBlockhash = latestBlockhash.blockhash;
|
||||||
transaction.sign(...signers);
|
transaction.sign(...signers);
|
||||||
if (!transaction.signature) {
|
if (!transaction.signature) {
|
||||||
throw new Error('!signature'); // should never happen
|
throw new Error('!signature'); // should never happen
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import type {Buffer} from 'buffer';
|
import type {Buffer} from 'buffer';
|
||||||
|
|
||||||
import {Connection} from '../connection';
|
import {
|
||||||
|
BlockheightBasedTransactionConfimationStrategy,
|
||||||
|
Connection,
|
||||||
|
} from '../connection';
|
||||||
import type {TransactionSignature} from '../transaction';
|
import type {TransactionSignature} from '../transaction';
|
||||||
import type {ConfirmOptions} from '../connection';
|
import type {ConfirmOptions} from '../connection';
|
||||||
|
|
||||||
|
@ -11,14 +14,57 @@ import type {ConfirmOptions} from '../connection';
|
||||||
*
|
*
|
||||||
* @param {Connection} connection
|
* @param {Connection} connection
|
||||||
* @param {Buffer} rawTransaction
|
* @param {Buffer} rawTransaction
|
||||||
|
* @param {BlockheightBasedTransactionConfimationStrategy} confirmationStrategy
|
||||||
* @param {ConfirmOptions} [options]
|
* @param {ConfirmOptions} [options]
|
||||||
* @returns {Promise<TransactionSignature>}
|
* @returns {Promise<TransactionSignature>}
|
||||||
*/
|
*/
|
||||||
export async function sendAndConfirmRawTransaction(
|
export async function sendAndConfirmRawTransaction(
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
rawTransaction: Buffer,
|
rawTransaction: Buffer,
|
||||||
|
confirmationStrategy: BlockheightBasedTransactionConfimationStrategy,
|
||||||
options?: ConfirmOptions,
|
options?: ConfirmOptions,
|
||||||
|
): Promise<TransactionSignature>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Calling `sendAndConfirmRawTransaction()` without a `confirmationStrategy`
|
||||||
|
* is no longer supported and will be removed in a future version.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-redeclare
|
||||||
|
export async function sendAndConfirmRawTransaction(
|
||||||
|
connection: Connection,
|
||||||
|
rawTransaction: Buffer,
|
||||||
|
options?: ConfirmOptions,
|
||||||
|
): Promise<TransactionSignature>;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-redeclare
|
||||||
|
export async function sendAndConfirmRawTransaction(
|
||||||
|
connection: Connection,
|
||||||
|
rawTransaction: Buffer,
|
||||||
|
confirmationStrategyOrConfirmOptions:
|
||||||
|
| BlockheightBasedTransactionConfimationStrategy
|
||||||
|
| ConfirmOptions
|
||||||
|
| undefined,
|
||||||
|
maybeConfirmOptions?: ConfirmOptions,
|
||||||
): Promise<TransactionSignature> {
|
): Promise<TransactionSignature> {
|
||||||
|
let confirmationStrategy:
|
||||||
|
| BlockheightBasedTransactionConfimationStrategy
|
||||||
|
| undefined;
|
||||||
|
let options: ConfirmOptions | undefined;
|
||||||
|
if (
|
||||||
|
confirmationStrategyOrConfirmOptions &&
|
||||||
|
Object.prototype.hasOwnProperty.call(
|
||||||
|
confirmationStrategyOrConfirmOptions,
|
||||||
|
'lastValidBlockHeight',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
confirmationStrategy =
|
||||||
|
confirmationStrategyOrConfirmOptions as BlockheightBasedTransactionConfimationStrategy;
|
||||||
|
options = maybeConfirmOptions;
|
||||||
|
} else {
|
||||||
|
options = confirmationStrategyOrConfirmOptions as
|
||||||
|
| ConfirmOptions
|
||||||
|
| undefined;
|
||||||
|
}
|
||||||
const sendOptions = options && {
|
const sendOptions = options && {
|
||||||
skipPreflight: options.skipPreflight,
|
skipPreflight: options.skipPreflight,
|
||||||
preflightCommitment: options.preflightCommitment || options.commitment,
|
preflightCommitment: options.preflightCommitment || options.commitment,
|
||||||
|
@ -29,12 +75,11 @@ export async function sendAndConfirmRawTransaction(
|
||||||
sendOptions,
|
sendOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const status = (
|
const commitment = options && options.commitment;
|
||||||
await connection.confirmTransaction(
|
const confirmationPromise = confirmationStrategy
|
||||||
signature,
|
? connection.confirmTransaction(confirmationStrategy, commitment)
|
||||||
options && options.commitment,
|
: connection.confirmTransaction(signature, commitment);
|
||||||
)
|
const status = (await confirmationPromise).value;
|
||||||
).value;
|
|
||||||
|
|
||||||
if (status.err) {
|
if (status.err) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
|
@ -176,9 +176,9 @@ if (process.env.TEST_LIVE) {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('simulate transaction without signature verification', async () => {
|
it('simulate transaction without signature verification', async () => {
|
||||||
const simulatedTransaction = new Transaction({
|
const simulatedTransaction = new Transaction();
|
||||||
feePayer: payerAccount.publicKey,
|
simulatedTransaction.feePayer = payerAccount.publicKey;
|
||||||
}).add({
|
simulatedTransaction.add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
|
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
|
||||||
],
|
],
|
||||||
|
|
|
@ -1131,7 +1131,11 @@ describe('Connection', function () {
|
||||||
const badTransactionSignature = 'bad transaction signature';
|
const badTransactionSignature = 'bad transaction signature';
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
connection.confirmTransaction(badTransactionSignature),
|
connection.confirmTransaction({
|
||||||
|
blockhash: 'sampleBlockhash',
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
signature: badTransactionSignature,
|
||||||
|
}),
|
||||||
).to.be.rejectedWith('signature must be base58 encoded');
|
).to.be.rejectedWith('signature must be base58 encoded');
|
||||||
|
|
||||||
await mockRpcResponse({
|
await mockRpcResponse({
|
||||||
|
@ -2899,11 +2903,11 @@ describe('Connection', function () {
|
||||||
const accountFrom = Keypair.generate();
|
const accountFrom = Keypair.generate();
|
||||||
const accountTo = Keypair.generate();
|
const accountTo = Keypair.generate();
|
||||||
|
|
||||||
const {blockhash} = await helpers.latestBlockhash({connection});
|
const latestBlockhash = await helpers.latestBlockhash({connection});
|
||||||
|
|
||||||
const transaction = new Transaction({
|
const transaction = new Transaction({
|
||||||
feePayer: accountFrom.publicKey,
|
feePayer: accountFrom.publicKey,
|
||||||
recentBlockhash: blockhash,
|
...latestBlockhash,
|
||||||
}).add(
|
}).add(
|
||||||
SystemProgram.transfer({
|
SystemProgram.transfer({
|
||||||
fromPubkey: accountFrom.publicKey,
|
fromPubkey: accountFrom.publicKey,
|
||||||
|
@ -3825,7 +3829,11 @@ describe('Connection', function () {
|
||||||
{skipPreflight: true},
|
{skipPreflight: true},
|
||||||
);
|
);
|
||||||
|
|
||||||
await connection.confirmTransaction(signature);
|
await connection.confirmTransaction({
|
||||||
|
blockhash: transaction.recentBlockhash,
|
||||||
|
lastValidBlockHeight: transaction.lastValidBlockHeight,
|
||||||
|
signature,
|
||||||
|
});
|
||||||
|
|
||||||
const response = (await connection.getSignatureStatus(signature)).value;
|
const response = (await connection.getSignatureStatus(signature)).value;
|
||||||
if (response !== null) {
|
if (response !== null) {
|
||||||
|
|
|
@ -185,7 +185,8 @@ const processTransaction = async ({
|
||||||
commitment: Commitment;
|
commitment: Commitment;
|
||||||
err?: any;
|
err?: any;
|
||||||
}) => {
|
}) => {
|
||||||
const blockhash = (await latestBlockhash({connection})).blockhash;
|
const {blockhash, lastValidBlockHeight} = await latestBlockhash({connection});
|
||||||
|
transaction.lastValidBlockHeight = lastValidBlockHeight;
|
||||||
transaction.recentBlockhash = blockhash;
|
transaction.recentBlockhash = blockhash;
|
||||||
transaction.sign(...signers);
|
transaction.sign(...signers);
|
||||||
|
|
||||||
|
@ -222,7 +223,10 @@ const processTransaction = async ({
|
||||||
result: true,
|
result: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await connection.confirmTransaction(signature, commitment);
|
return await connection.confirmTransaction(
|
||||||
|
{blockhash, lastValidBlockHeight, signature},
|
||||||
|
commitment,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const airdrop = async ({
|
const airdrop = async ({
|
||||||
|
|
|
@ -339,9 +339,10 @@ describe('StakeProgram', () => {
|
||||||
lockup: new Lockup(0, 0, from.publicKey),
|
lockup: new Lockup(0, 0, from.publicKey),
|
||||||
lamports: amount,
|
lamports: amount,
|
||||||
});
|
});
|
||||||
const createWithSeedTransaction = new Transaction({recentBlockhash}).add(
|
const createWithSeedTransaction = new Transaction({
|
||||||
createWithSeed,
|
blockhash: recentBlockhash,
|
||||||
);
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add(createWithSeed);
|
||||||
|
|
||||||
expect(createWithSeedTransaction.instructions).to.have.length(2);
|
expect(createWithSeedTransaction.instructions).to.have.length(2);
|
||||||
const systemInstructionType = SystemInstruction.decodeInstructionType(
|
const systemInstructionType = SystemInstruction.decodeInstructionType(
|
||||||
|
@ -368,9 +369,10 @@ describe('StakeProgram', () => {
|
||||||
votePubkey: vote.publicKey,
|
votePubkey: vote.publicKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const delegateTransaction = new Transaction({recentBlockhash}).add(
|
const delegateTransaction = new Transaction({
|
||||||
delegate,
|
blockhash: recentBlockhash,
|
||||||
);
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add(delegate);
|
||||||
const anotherStakeInstructionType = StakeInstruction.decodeInstructionType(
|
const anotherStakeInstructionType = StakeInstruction.decodeInstructionType(
|
||||||
delegateTransaction.instructions[0],
|
delegateTransaction.instructions[0],
|
||||||
);
|
);
|
||||||
|
|
|
@ -23,7 +23,10 @@ describe('Transaction', () => {
|
||||||
const account3 = Keypair.generate();
|
const account3 = Keypair.generate();
|
||||||
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
||||||
const programId = Keypair.generate().publicKey;
|
const programId = Keypair.generate().publicKey;
|
||||||
const transaction = new Transaction({recentBlockhash}).add({
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: account3.publicKey, isSigner: true, isWritable: false},
|
{pubkey: account3.publicKey, isSigner: true, isWritable: false},
|
||||||
{pubkey: payer.publicKey, isSigner: true, isWritable: true},
|
{pubkey: payer.publicKey, isSigner: true, isWritable: true},
|
||||||
|
@ -49,7 +52,10 @@ describe('Transaction', () => {
|
||||||
const other = Keypair.generate();
|
const other = Keypair.generate();
|
||||||
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
||||||
const programId = Keypair.generate().publicKey;
|
const programId = Keypair.generate().publicKey;
|
||||||
const transaction = new Transaction({recentBlockhash}).add({
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: other.publicKey, isSigner: true, isWritable: true},
|
{pubkey: other.publicKey, isSigner: true, isWritable: true},
|
||||||
{pubkey: payer.publicKey, isSigner: true, isWritable: true},
|
{pubkey: payer.publicKey, isSigner: true, isWritable: true},
|
||||||
|
@ -101,7 +107,10 @@ describe('Transaction', () => {
|
||||||
const payer = Keypair.generate();
|
const payer = Keypair.generate();
|
||||||
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
const recentBlockhash = Keypair.generate().publicKey.toBase58();
|
||||||
const programId = Keypair.generate().publicKey;
|
const programId = Keypair.generate().publicKey;
|
||||||
const transaction = new Transaction({recentBlockhash}).add({
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add({
|
||||||
keys: [{pubkey: payer.publicKey, isSigner: true, isWritable: false}],
|
keys: [{pubkey: payer.publicKey, isSigner: true, isWritable: false}],
|
||||||
programId,
|
programId,
|
||||||
});
|
});
|
||||||
|
@ -121,11 +130,11 @@ describe('Transaction', () => {
|
||||||
const accountFrom = Keypair.generate();
|
const accountFrom = Keypair.generate();
|
||||||
const accountTo = Keypair.generate();
|
const accountTo = Keypair.generate();
|
||||||
|
|
||||||
const {blockhash} = await helpers.latestBlockhash({connection});
|
const latestBlockhash = await helpers.latestBlockhash({connection});
|
||||||
|
|
||||||
const transaction = new Transaction({
|
const transaction = new Transaction({
|
||||||
feePayer: accountFrom.publicKey,
|
feePayer: accountFrom.publicKey,
|
||||||
recentBlockhash: blockhash,
|
...latestBlockhash,
|
||||||
}).add(
|
}).add(
|
||||||
SystemProgram.transfer({
|
SystemProgram.transfer({
|
||||||
fromPubkey: accountFrom.publicKey,
|
fromPubkey: accountFrom.publicKey,
|
||||||
|
@ -149,10 +158,16 @@ describe('Transaction', () => {
|
||||||
lamports: 123,
|
lamports: 123,
|
||||||
});
|
});
|
||||||
|
|
||||||
const transaction = new Transaction({recentBlockhash}).add(transfer);
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add(transfer);
|
||||||
transaction.sign(account1, account2);
|
transaction.sign(account1, account2);
|
||||||
|
|
||||||
const partialTransaction = new Transaction({recentBlockhash}).add(transfer);
|
const partialTransaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add(transfer);
|
||||||
partialTransaction.setSigners(account1.publicKey, account2.publicKey);
|
partialTransaction.setSigners(account1.publicKey, account2.publicKey);
|
||||||
expect(partialTransaction.signatures[0].signature).to.be.null;
|
expect(partialTransaction.signatures[0].signature).to.be.null;
|
||||||
expect(partialTransaction.signatures[1].signature).to.be.null;
|
expect(partialTransaction.signatures[1].signature).to.be.null;
|
||||||
|
@ -196,7 +211,10 @@ describe('Transaction', () => {
|
||||||
const programId = Keypair.generate().publicKey;
|
const programId = Keypair.generate().publicKey;
|
||||||
|
|
||||||
it('setSigners', () => {
|
it('setSigners', () => {
|
||||||
const transaction = new Transaction({recentBlockhash}).add({
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
|
{pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
|
||||||
{pubkey: payer.publicKey, isSigner: false, isWritable: true},
|
{pubkey: payer.publicKey, isSigner: false, isWritable: true},
|
||||||
|
@ -224,7 +242,10 @@ describe('Transaction', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sign', () => {
|
it('sign', () => {
|
||||||
const transaction = new Transaction({recentBlockhash}).add({
|
const transaction = new Transaction({
|
||||||
|
blockhash: recentBlockhash,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add({
|
||||||
keys: [
|
keys: [
|
||||||
{pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
|
{pubkey: duplicate1.publicKey, isSigner: true, isWritable: true},
|
||||||
{pubkey: payer.publicKey, isSigner: false, isWritable: true},
|
{pubkey: payer.publicKey, isSigner: false, isWritable: true},
|
||||||
|
@ -263,14 +284,18 @@ describe('Transaction', () => {
|
||||||
lamports: 123,
|
lamports: 123,
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgTransaction = new Transaction({recentBlockhash}).add(
|
const latestBlockhash = {
|
||||||
transfer1,
|
blockhash: recentBlockhash,
|
||||||
transfer2,
|
lastValidBlockHeight: 9999,
|
||||||
);
|
};
|
||||||
|
|
||||||
|
const orgTransaction = new Transaction({
|
||||||
|
...latestBlockhash,
|
||||||
|
}).add(transfer1, transfer2);
|
||||||
orgTransaction.sign(account1, account2);
|
orgTransaction.sign(account1, account2);
|
||||||
|
|
||||||
const newTransaction = new Transaction({
|
const newTransaction = new Transaction({
|
||||||
recentBlockhash: orgTransaction.recentBlockhash,
|
...latestBlockhash,
|
||||||
signatures: orgTransaction.signatures,
|
signatures: orgTransaction.signatures,
|
||||||
}).add(transfer1, transfer2);
|
}).add(transfer1, transfer2);
|
||||||
|
|
||||||
|
@ -292,10 +317,10 @@ describe('Transaction', () => {
|
||||||
lamports: 123,
|
lamports: 123,
|
||||||
});
|
});
|
||||||
|
|
||||||
const orgTransaction = new Transaction({recentBlockhash}).add(
|
const orgTransaction = new Transaction({
|
||||||
transfer1,
|
blockhash: recentBlockhash,
|
||||||
transfer2,
|
lastValidBlockHeight: 9999,
|
||||||
);
|
}).add(transfer1, transfer2);
|
||||||
orgTransaction.sign(account1);
|
orgTransaction.sign(account1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -363,8 +388,9 @@ describe('Transaction', () => {
|
||||||
lamports: 49,
|
lamports: 49,
|
||||||
});
|
});
|
||||||
const expectedTransaction = new Transaction({
|
const expectedTransaction = new Transaction({
|
||||||
recentBlockhash,
|
blockhash: recentBlockhash,
|
||||||
feePayer: sender.publicKey,
|
feePayer: sender.publicKey,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
}).add(transfer);
|
}).add(transfer);
|
||||||
expectedTransaction.sign(sender);
|
expectedTransaction.sign(sender);
|
||||||
|
|
||||||
|
@ -493,9 +519,10 @@ describe('Transaction', () => {
|
||||||
toPubkey: recipient,
|
toPubkey: recipient,
|
||||||
lamports: 49,
|
lamports: 49,
|
||||||
});
|
});
|
||||||
const expectedTransaction = new Transaction({recentBlockhash}).add(
|
const expectedTransaction = new Transaction({
|
||||||
transfer,
|
blockhash: recentBlockhash,
|
||||||
);
|
lastValidBlockHeight: 9999,
|
||||||
|
}).add(transfer);
|
||||||
|
|
||||||
// Empty signature array fails.
|
// Empty signature array fails.
|
||||||
expect(expectedTransaction.signatures).to.have.length(0);
|
expect(expectedTransaction.signatures).to.have.length(0);
|
||||||
|
@ -607,8 +634,9 @@ describe('Transaction', () => {
|
||||||
const acc1Writable = Keypair.generate();
|
const acc1Writable = Keypair.generate();
|
||||||
const acc2Writable = Keypair.generate();
|
const acc2Writable = Keypair.generate();
|
||||||
const t0 = new Transaction({
|
const t0 = new Transaction({
|
||||||
recentBlockhash: 'HZaTsZuhN1aaz9WuuimCFMyH7wJ5xiyMUHFCnZSMyguH',
|
blockhash: 'HZaTsZuhN1aaz9WuuimCFMyH7wJ5xiyMUHFCnZSMyguH',
|
||||||
feePayer: signer.publicKey,
|
feePayer: signer.publicKey,
|
||||||
|
lastValidBlockHeight: 9999,
|
||||||
});
|
});
|
||||||
t0.add(
|
t0.add(
|
||||||
new TransactionInstruction({
|
new TransactionInstruction({
|
||||||
|
|
Loading…
Reference in New Issue