feat: add skipPreflight option

This commit is contained in:
Justin Starry 2020-06-03 19:55:42 +08:00 committed by Michael Vines
parent a6e07e44da
commit 8547ae43ce
12 changed files with 263 additions and 68 deletions

23
web3.js/module.d.ts vendored
View File

@ -41,12 +41,26 @@ declare module '@solana/web3.js' {
slot: number;
};
export type SendOptions = {
skipPreflight: boolean;
};
export type ConfirmOptions = {
confirmations: number;
skipPreflight: boolean;
};
export type RpcResponseAndContext<T> = {
context: Context;
value: T;
};
export type Commitment = 'max' | 'recent' | 'root' | 'single' | 'singleGossip';
export type Commitment =
| 'max'
| 'recent'
| 'root'
| 'single'
| 'singleGossip';
export type LargestAccountsFilter = 'circulating' | 'nonCirculating';
@ -269,12 +283,15 @@ declare module '@solana/web3.js' {
sendTransaction(
transaction: Transaction,
signers: Array<Account>,
options?: SendOptions,
): Promise<TransactionSignature>;
sendEncodedTransaction(
encodedTransaction: string,
options?: SendOptions,
): Promise<TransactionSignature>;
sendRawTransaction(
wireTransaction: Buffer | Uint8Array | Array<number>,
options?: SendOptions,
): Promise<TransactionSignature>;
onAccountChange(
publickey: PublicKey,
@ -794,14 +811,14 @@ declare module '@solana/web3.js' {
connection: Connection,
transaction: Transaction,
signers: Array<Account>,
confirmations?: number,
options?: ConfirmOptions,
): Promise<TransactionSignature>;
// === src/util/send-and-confirm-raw-transaction.js ===
export function sendAndConfirmRawTransaction(
connection: Connection,
wireTransaction: Buffer,
confirmations?: number,
options?: ConfirmOptions,
): Promise<TransactionSignature>;
// === src/util/cluster.js ===

View File

@ -54,12 +54,26 @@ declare module '@solana/web3.js' {
slot: number,
};
declare export type SendOptions = {
skipPreflight: boolean;
};
declare export type ConfirmOptions = {
confirmations: number;
skipPreflight: boolean;
};
declare export type RpcResponseAndContext<T> = {
context: Context,
value: T,
};
declare export type Commitment = 'max' | 'recent' | 'root' | 'single' | 'singleGossip';
declare export type Commitment =
| 'max'
| 'recent'
| 'root'
| 'single'
| 'singleGossip';
declare export type LargestAccountsFilter = 'circulating' | 'nonCirculating';
@ -282,12 +296,15 @@ declare module '@solana/web3.js' {
sendTransaction(
transaction: Transaction,
signers: Array<Account>,
options?: SendOptions,
): Promise<TransactionSignature>;
sendEncodedTransaction(
encodedTransaction: string,
options?: SendOptions,
): Promise<TransactionSignature>;
sendRawTransaction(
wireTransaction: Buffer | Uint8Array | Array<number>,
options?: SendOptions,
): Promise<TransactionSignature>;
onAccountChange(
publickey: PublicKey,
@ -809,14 +826,14 @@ declare module '@solana/web3.js' {
connection: Connection,
transaction: Transaction,
signers: Array<Account>,
confirmations: ?number,
options: ?ConfirmOptions,
): Promise<TransactionSignature>;
// === src/util/send-and-confirm-raw-transaction.js ===
declare export function sendAndConfirmRawTransaction(
connection: Connection,
wireTransaction: Buffer,
confirmations: ?number,
options: ?ConfirmOptions,
): Promise<TransactionSignature>;
// === src/util/cluster.js ===

View File

@ -31,6 +31,28 @@ type Context = {
slot: number,
};
/**
* Options for sending transactions
*
* @typedef {Object} SendOptions
* @property {boolean} skipPreflight disable transaction verification step
*/
export type SendOptions = {
skipPreflight: boolean,
};
/**
* Options for confirming transactions
*
* @typedef {Object} ConfirmOptions
* @property {boolean} skipPreflight disable transaction verification step
* @property {number} confirmations desired number of cluster confirmations
*/
export type ConfirmOptions = {
confirmations: number,
skipPreflight: boolean,
};
/**
* RPC Response with extra contextual information
*
@ -1663,6 +1685,7 @@ export class Connection {
async sendTransaction(
transaction: Transaction,
signers: Array<Account>,
options?: SendOptions,
): Promise<TransactionSignature> {
if (transaction.nonceInfo) {
transaction.sign(...signers);
@ -1696,7 +1719,7 @@ export class Connection {
let attempts = 0;
const startTime = Date.now();
for (;;) {
const {blockhash} = await this.getRecentBlockhash();
const {blockhash} = await this.getRecentBlockhash('max');
if (this._blockhashInfo.recentBlockhash != blockhash) {
this._blockhashInfo = {
@ -1723,7 +1746,7 @@ export class Connection {
}
const wireTransaction = transaction.serialize();
return await this.sendRawTransaction(wireTransaction);
return await this.sendRawTransaction(wireTransaction, options);
}
/**
@ -1745,9 +1768,13 @@ export class Connection {
*/
async sendRawTransaction(
rawTransaction: Buffer | Uint8Array | Array<number>,
options: ?SendOptions,
): Promise<TransactionSignature> {
const encodedTransaction = bs58.encode(toBuffer(rawTransaction));
const result = await this.sendEncodedTransaction(encodedTransaction);
const result = await this.sendEncodedTransaction(
encodedTransaction,
options,
);
return result;
}
@ -1757,10 +1784,12 @@ export class Connection {
*/
async sendEncodedTransaction(
encodedTransaction: string,
options: ?SendOptions,
): Promise<TransactionSignature> {
const unsafeRes = await this._rpcRequest('sendTransaction', [
encodedTransaction,
]);
const args = [encodedTransaction];
const skipPreflight = options && options.skipPreflight;
if (skipPreflight) args.push({skipPreflight});
const unsafeRes = await this._rpcRequest('sendTransaction', args);
const res = SendTransactionRpcResult(unsafeRes);
if (res.error) {
throw new Error('failed to send transaction: ' + res.error.message);

View File

@ -69,7 +69,10 @@ export class Loader {
connection,
transaction,
[payer, program],
1,
{
confirmations: 1,
skipPreflight: true,
},
);
}
@ -107,7 +110,10 @@ export class Loader {
data,
});
transactions.push(
sendAndConfirmTransaction(connection, transaction, [payer, program], 1),
sendAndConfirmTransaction(connection, transaction, [payer, program], {
confirmations: 1,
skipPreflight: true,
}),
);
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
@ -152,7 +158,10 @@ export class Loader {
connection,
transaction,
[payer, program],
1,
{
confirmations: 1,
skipPreflight: true,
},
);
}
}

View File

@ -2,6 +2,7 @@
import {Connection} from '../connection';
import type {TransactionSignature} from '../transaction';
import type {ConfirmOptions} from '../connection';
/**
* Send and confirm a raw transaction
@ -9,12 +10,19 @@ import type {TransactionSignature} from '../transaction';
export async function sendAndConfirmRawTransaction(
connection: Connection,
rawTransaction: Buffer,
confirmations: ?number,
options?: ConfirmOptions,
): Promise<TransactionSignature> {
const start = Date.now();
const signature = await connection.sendRawTransaction(rawTransaction);
const status = (await connection.confirmTransaction(signature, confirmations))
.value;
const signature = await connection.sendRawTransaction(
rawTransaction,
options,
);
const status = (
await connection.confirmTransaction(
signature,
options && options.confirmations,
)
).value;
if (status) {
if (status.err) {

View File

@ -4,6 +4,7 @@ import {Connection} from '../connection';
import {Transaction} from '../transaction';
import {sleep} from './sleep';
import type {Account} from '../account';
import type {ConfirmOptions} from '../connection';
import type {TransactionSignature} from '../transaction';
const NUM_SEND_RETRIES = 10;
@ -17,15 +18,22 @@ export async function sendAndConfirmTransaction(
connection: Connection,
transaction: Transaction,
signers: Array<Account>,
confirmations: ?number,
options?: ConfirmOptions,
): Promise<TransactionSignature> {
const start = Date.now();
let sendRetries = NUM_SEND_RETRIES;
for (;;) {
const signature = await connection.sendTransaction(transaction, signers);
const signature = await connection.sendTransaction(
transaction,
signers,
options,
);
const status = (
await connection.confirmTransaction(signature, confirmations)
await connection.confirmTransaction(
signature,
options && options.confirmations,
)
).value;
if (status) {

View File

@ -44,7 +44,10 @@ test('load BPF C program', async () => {
keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}],
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [from], 1);
await sendAndConfirmTransaction(connection, transaction, [from], {
confirmations: 1,
skipPreflight: true,
});
});
test('load BPF Rust program', async () => {
@ -73,5 +76,8 @@ test('load BPF Rust program', async () => {
keys: [{pubkey: from.publicKey, isSigner: true, isWritable: true}],
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [from], 1);
await sendAndConfirmTransaction(connection, transaction, [from], {
confirmations: 1,
skipPreflight: true,
});
});

View File

@ -117,7 +117,7 @@ test('get program accounts', async () => {
await connection.requestAirdrop(account0.publicKey, LAMPORTS_PER_SOL);
await connection.requestAirdrop(account1.publicKey, 0.5 * LAMPORTS_PER_SOL);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -160,7 +160,10 @@ test('get program accounts', async () => {
accountPubkey: account0.publicKey,
programId: programId.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [account0], 1);
await sendAndConfirmTransaction(connection, transaction, [account0], {
confirmations: 1,
skipPreflight: true,
});
mockRpc.push([
url,
@ -205,7 +208,10 @@ test('get program accounts', async () => {
programId: programId.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [account1], 1);
await sendAndConfirmTransaction(connection, transaction, [account1], {
confirmations: 1,
skipPreflight: true,
});
mockGetRecentBlockhash('recent');
const {feeCalculator} = await connection.getRecentBlockhash();
@ -1056,7 +1062,13 @@ test('get confirmed block', async () => {
test('get recent blockhash', async () => {
const connection = new Connection(url);
for (const commitment of ['max', 'recent', 'root', 'single', 'singleGossip']) {
for (const commitment of [
'max',
'recent',
'root',
'single',
'singleGossip',
]) {
mockGetRecentBlockhash(commitment);
const {blockhash, feeCalculator} = await connection.getRecentBlockhash(
@ -1471,7 +1483,7 @@ test('transaction failure', async () => {
minimumAmount + 100010,
);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -1484,14 +1496,66 @@ test('transaction failure', async () => {
},
]);
const transaction = SystemProgram.transfer({
fromPubkey: account.publicKey,
toPubkey: account.publicKey,
lamports: 10,
});
const signature = await connection.sendTransaction(transaction, [account]);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 1,
err: null,
},
],
},
},
]);
const expectedErr = {InstructionError: [0, 'AccountBorrowFailed']};
const newAccount = new Account();
let transaction = SystemProgram.createAccount({
fromPubkey: account.publicKey,
newAccountPubkey: newAccount.publicKey,
lamports: 1000,
space: 0,
programId: SystemProgram.programId
});
await sendAndConfirmTransaction(connection, transaction, [account, newAccount], {confirmations: 1, skipPreflight: true});
mockRpc.push([
url,
{
method: 'sendTransaction',
},
{
error: null,
result:
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
// This should fail because the account is already created
transaction = SystemProgram.createAccount({
fromPubkey: account.publicKey,
newAccountPubkey: newAccount.publicKey,
lamports: 10,
space: 0,
programId: SystemProgram.programId
});
const signature = await connection.sendTransaction(transaction, [account, newAccount], {skipPreflight: true});
const expectedErr = {InstructionError: [0, {Custom: 0}]};
mockRpc.push([
url,
{
@ -1656,7 +1720,7 @@ test('transaction', async () => {
minimumAmount + 21,
);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -1676,7 +1740,7 @@ test('transaction', async () => {
});
const signature = await connection.sendTransaction(transaction, [
accountFrom,
]);
], {skipPreflight: true});
mockRpc.push([
url,
@ -1881,7 +1945,7 @@ test('multi-instruction transaction', async () => {
const signature = await connection.sendTransaction(transaction, [
accountFrom,
accountTo,
]);
], {skipPreflight: true});
await connection.confirmTransaction(signature, 1);
@ -1934,7 +1998,10 @@ test('account change notification', async () => {
toPubkey: programAccount.publicKey,
lamports: balanceNeeded,
});
await sendAndConfirmTransaction(connection, transaction, [owner], 1);
await sendAndConfirmTransaction(connection, transaction, [owner], {
confirmations: 1,
skipPreflight: true,
});
} catch (err) {
await connection.removeAccountChangeListener(subscriptionId);
throw err;
@ -1998,7 +2065,10 @@ test('program account change notification', async () => {
toPubkey: programAccount.publicKey,
lamports: balanceNeeded,
});
await sendAndConfirmTransaction(connection, transaction, [owner], 1);
await sendAndConfirmTransaction(connection, transaction, [owner], {
confirmations: 1,
skipPreflight: true,
});
} catch (err) {
await connection.removeProgramAccountChangeListener(subscriptionId);
throw err;

View File

@ -84,7 +84,7 @@ test('create and query nonce account', async () => {
const balance = await connection.getBalance(from.publicKey);
expect(balance).toBe(minimumAmount * 2);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -103,7 +103,7 @@ test('create and query nonce account', async () => {
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
});
await connection.sendTransaction(transaction, [from, nonceAccount]);
await connection.sendTransaction(transaction, [from, nonceAccount], {skipPreflight: true});
mockRpc.push([
url,
@ -201,7 +201,7 @@ test('create and query nonce account with seed', async () => {
const balance = await connection.getBalance(from.publicKey);
expect(balance).toBe(minimumAmount * 2);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -222,7 +222,7 @@ test('create and query nonce account with seed', async () => {
authorizedPubkey: from.publicKey,
lamports: minimumAmount,
});
await connection.sendTransaction(transaction, [from]);
await connection.sendTransaction(transaction, [from], {skipPreflight: true});
mockRpc.push([
url,

View File

@ -272,7 +272,7 @@ test('live staking actions', async () => {
connection,
createAndInitialize,
[from, newStakeAccount],
0,
{confirmations: 0, skipPreflight: true},
);
expect(await connection.getBalance(newStakeAccount.publicKey)).toEqual(
minimumAmount + 42,
@ -283,7 +283,10 @@ test('live staking actions', async () => {
authorizedPubkey: authorized.publicKey,
votePubkey,
});
await sendAndConfirmTransaction(connection, delegation, [authorized], 0);
await sendAndConfirmTransaction(connection, delegation, [authorized], {
confirmations: 0,
skipPreflight: true,
});
}
// Create Stake account with seed
@ -308,7 +311,7 @@ test('live staking actions', async () => {
connection,
createAndInitializeWithSeed,
[from],
0,
{confirmations: 0, skipPreflight: true},
);
let originalStakeBalance = await connection.getBalance(newAccountPubkey);
expect(originalStakeBalance).toEqual(3 * minimumAmount + 42);
@ -318,7 +321,10 @@ test('live staking actions', async () => {
authorizedPubkey: authorized.publicKey,
votePubkey,
});
await sendAndConfirmTransaction(connection, delegation, [authorized], 0);
await sendAndConfirmTransaction(connection, delegation, [authorized], {
confirmations: 0,
skipPreflight: true,
});
// Test that withdraw fails before deactivation
const recipient = new Account();
@ -329,7 +335,10 @@ test('live staking actions', async () => {
lamports: 1000,
});
await expect(
sendAndConfirmTransaction(connection, withdraw, [authorized], 0),
sendAndConfirmTransaction(connection, withdraw, [authorized], {
confirmations: 0,
skipPreflight: true,
}),
).rejects.toThrow();
// Split stake
@ -340,7 +349,10 @@ test('live staking actions', async () => {
splitStakePubkey: newStake.publicKey,
lamports: minimumAmount + 20,
});
await sendAndConfirmTransaction(connection, split, [authorized, newStake], 0);
await sendAndConfirmTransaction(connection, split, [authorized, newStake], {
confirmations: 0,
skipPreflight: true,
});
// Authorize to new account
const newAuthorized = new Account();
@ -352,14 +364,20 @@ test('live staking actions', async () => {
newAuthorizedPubkey: newAuthorized.publicKey,
stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
});
await sendAndConfirmTransaction(connection, authorize, [authorized], 0);
await sendAndConfirmTransaction(connection, authorize, [authorized], {
confirmations: 0,
skipPreflight: true,
});
authorize = StakeProgram.authorize({
stakePubkey: newAccountPubkey,
authorizedPubkey: authorized.publicKey,
newAuthorizedPubkey: newAuthorized.publicKey,
stakeAuthorizationType: StakeAuthorizationLayout.Staker,
});
await sendAndConfirmTransaction(connection, authorize, [authorized], 0);
await sendAndConfirmTransaction(connection, authorize, [authorized], {
confirmations: 0,
skipPreflight: true,
});
// Test old authorized can't deactivate
let deactivateNotAuthorized = StakeProgram.deactivate({
@ -371,7 +389,7 @@ test('live staking actions', async () => {
connection,
deactivateNotAuthorized,
[authorized],
0,
{confirmations: 0, skipPreflight: true},
),
).rejects.toThrow();
@ -380,7 +398,10 @@ test('live staking actions', async () => {
stakePubkey: newAccountPubkey,
authorizedPubkey: newAuthorized.publicKey,
});
await sendAndConfirmTransaction(connection, deactivate, [newAuthorized], 0);
await sendAndConfirmTransaction(connection, deactivate, [newAuthorized], {
confirmations: 0,
skipPreflight: true,
});
// Test that withdraw succeeds after deactivation
withdraw = StakeProgram.withdraw({
@ -389,7 +410,10 @@ test('live staking actions', async () => {
toPubkey: recipient.publicKey,
lamports: minimumAmount + 20,
});
await sendAndConfirmTransaction(connection, withdraw, [newAuthorized], 0);
await sendAndConfirmTransaction(connection, withdraw, [newAuthorized], {
confirmations: 0,
skipPreflight: true,
});
const balance = await connection.getBalance(newAccountPubkey);
expect(balance).toEqual(minimumAmount + 2);
const recipientBalance = await connection.getBalance(recipient.publicKey);

View File

@ -297,7 +297,7 @@ test('live Nonce actions', async () => {
connection,
createNonceAccount,
[from, nonceAccount],
0,
{confirmations: 0, skipPreflight: true},
);
const nonceBalance = await connection.getBalance(nonceAccount.publicKey);
expect(nonceBalance).toEqual(minimumAmount);
@ -325,7 +325,10 @@ test('live Nonce actions', async () => {
authorizedPubkey: from.publicKey,
}),
);
await sendAndConfirmTransaction(connection, advanceNonce, [from], 0);
await sendAndConfirmTransaction(connection, advanceNonce, [from], {
confirmations: 0,
skipPreflight: true,
});
const nonceQuery3 = await connection.getNonce(nonceAccount.publicKey);
if (nonceQuery3 === null) {
expect(nonceQuery3).not.toBeNull();
@ -344,7 +347,10 @@ test('live Nonce actions', async () => {
newAuthorizedPubkey: newAuthority.publicKey,
}),
);
await sendAndConfirmTransaction(connection, authorizeNonce, [from], 0);
await sendAndConfirmTransaction(connection, authorizeNonce, [from], {
confirmations: 0,
skipPreflight: true,
});
let transfer = SystemProgram.transfer({
fromPubkey: from.publicKey,
@ -359,12 +365,10 @@ test('live Nonce actions', async () => {
}),
};
await sendAndConfirmTransaction(
connection,
transfer,
[from, newAuthority],
0,
);
await sendAndConfirmTransaction(connection, transfer, [from, newAuthority], {
confirmations: 0,
skipPreflight: true,
});
const toBalance = await connection.getBalance(to.publicKey);
expect(toBalance).toEqual(minimumAmount);
@ -380,7 +384,10 @@ test('live Nonce actions', async () => {
toPubkey: withdrawAccount.publicKey,
}),
);
await sendAndConfirmTransaction(connection, withdrawNonce, [newAuthority], 0);
await sendAndConfirmTransaction(connection, withdrawNonce, [newAuthority], {
confirmations: 0,
skipPreflight: true,
});
expect(await connection.getBalance(nonceAccount.publicKey)).toEqual(0);
const withdrawBalance = await connection.getBalance(
withdrawAccount.publicKey,

View File

@ -86,7 +86,7 @@ test('transaction-payer', async () => {
]);
await connection.requestAirdrop(accountTo.publicKey, minimumAmount + 21);
mockGetRecentBlockhash('recent');
mockGetRecentBlockhash('max');
mockRpc.push([
url,
{
@ -108,7 +108,7 @@ test('transaction-payer', async () => {
const signature = await connection.sendTransaction(transaction, [
accountPayer,
accountFrom,
]);
], {skipPreflight: true});
mockRpc.push([
url,