feat: use pubsub to confirm transactions (#12095)

This commit is contained in:
Justin Starry 2020-09-08 13:12:47 +08:00 committed by GitHub
parent 9940870c89
commit 11b199cccf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 276 additions and 367 deletions

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

@ -51,7 +51,7 @@ declare module '@solana/web3.js' {
};
export type ConfirmOptions = {
confirmations?: number;
commitment?: Commitment;
skipPreflight?: boolean;
};
@ -384,8 +384,8 @@ declare module '@solana/web3.js' {
getVoteAccounts(commitment?: Commitment): Promise<VoteAccountStatus>;
confirmTransaction(
signature: TransactionSignature,
confirmations?: number,
): Promise<RpcResponseAndContext<SignatureStatus | null>>;
commitment?: Commitment,
): Promise<RpcResponseAndContext<SignatureResult>>;
getSlot(commitment?: Commitment): Promise<number>;
getSlotLeader(commitment?: Commitment): Promise<string>;
getSignatureStatus(

View File

@ -64,7 +64,7 @@ declare module '@solana/web3.js' {
};
declare export type ConfirmOptions = {
confirmations: ?number,
commitment: ?Commitment,
skipPreflight: ?boolean,
};
@ -388,8 +388,8 @@ declare module '@solana/web3.js' {
getVoteAccounts(commitment: ?Commitment): Promise<VoteAccountStatus>;
confirmTransaction(
signature: TransactionSignature,
confirmations: ?number,
): Promise<RpcResponseAndContext<SignatureStatus | null>>;
commitment: ?Commitment,
): Promise<RpcResponseAndContext<SignatureResult>>;
getSlot(commitment: ?Commitment): Promise<number>;
getSlotLeader(commitment: ?Commitment): Promise<string>;
getSignatureStatus(

View File

@ -14,6 +14,7 @@ import {MS_PER_SLOT} from './timing';
import {Transaction} from './transaction';
import {Message} from './message';
import {sleep} from './util/sleep';
import {promiseTimeout} from './util/promise-timeout';
import {toBuffer} from './util/to-buffer';
import type {Blockhash} from './blockhash';
import type {FeeCalculator} from './fee-calculator';
@ -57,11 +58,11 @@ export type SendOptions = {
*
* @typedef {Object} ConfirmOptions
* @property {boolean | undefined} skipPreflight disable transaction verification step
* @property {number | undefined} confirmations desired number of cluster confirmations
* @property {Commitment | undefined} commitment desired commitment level
*/
export type ConfirmOptions = {
skipPreflight?: boolean,
confirmations?: number,
commitment?: Commitment,
};
/**
@ -1951,39 +1952,74 @@ export class Connection {
/**
* Confirm the transaction identified by the specified signature.
*
* If `confirmations` count is not specified, wait for transaction to be finalized.
*
* If `commitment` is not specified, default to 'max'.
*/
async confirmTransaction(
signature: TransactionSignature,
confirmations: ?number,
): Promise<RpcResponseAndContext<SignatureStatus | null>> {
const start = Date.now();
const WAIT_TIMEOUT_MS = 60 * 1000;
let statusResponse = await this.getSignatureStatus(signature);
for (;;) {
const status = statusResponse.value;
if (status) {
// 'status.confirmations === null' implies that the tx has been finalized
if (
status.err ||
status.confirmations === null ||
(typeof confirmations === 'number' &&
status.confirmations >= confirmations)
) {
break;
}
} else if (Date.now() - start >= WAIT_TIMEOUT_MS) {
break;
}
// Sleep for approximately one slot
await sleep(MS_PER_SLOT);
statusResponse = await this.getSignatureStatus(signature);
commitment: ?Commitment,
): Promise<RpcResponseAndContext<SignatureResult>> {
let decodedSignature;
try {
decodedSignature = bs58.decode(signature);
} catch (err) {
throw new Error('signature must be base58 encoded: ' + signature);
}
return statusResponse;
assert(decodedSignature.length === 64, 'signature has invalid length');
const start = Date.now();
const subscriptionCommitment: Commitment = commitment || 'max';
let subscriptionId;
let response: RpcResponseAndContext<SignatureResult> | null = null;
const confirmPromise = new Promise((resolve, reject) => {
try {
subscriptionId = this.onSignature(
signature,
(result, context) => {
subscriptionId = undefined;
response = {
context,
value: result,
};
resolve();
},
subscriptionCommitment,
);
} catch (err) {
reject(err);
}
});
let timeoutMs = 60 * 1000;
switch (subscriptionCommitment) {
case 'recent':
case 'single':
case 'singleGossip': {
timeoutMs = 10 * 1000;
break;
}
// exhaust enums to ensure full coverage
case 'max':
case 'root':
}
try {
await promiseTimeout(confirmPromise, timeoutMs);
} finally {
if (subscriptionId) {
this.removeSignatureListener(subscriptionId);
}
}
if (response === null) {
const duration = (Date.now() - start) / 1000;
throw new Error(
`Transaction was not confirmed in ${duration.toFixed(2)} seconds`,
);
}
return response;
}
/**

View File

@ -4,7 +4,6 @@ import * as BufferLayout from 'buffer-layout';
import {Account} from './account';
import {PublicKey} from './publickey';
import {NUM_TICKS_PER_SECOND} from './timing';
import {Transaction, PACKET_DATA_SIZE} from './transaction';
import {SYSVAR_RENT_PUBKEY} from './sysvar';
import {sendAndConfirmTransaction} from './util/send-and-confirm-transaction';
@ -70,7 +69,7 @@ export class Loader {
transaction,
[payer, program],
{
confirmations: 1,
commitment: 'single',
skipPreflight: true,
},
);
@ -111,17 +110,17 @@ export class Loader {
});
transactions.push(
sendAndConfirmTransaction(connection, transaction, [payer, program], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
}),
);
// Delay ~1 tick between write transactions in an attempt to reduce AccountInUse errors
// since all the write transactions modify the same program account
await sleep(1000 / NUM_TICKS_PER_SECOND);
// Delay between sends in an attempt to reduce rate limit errors
const REQUESTS_PER_SECOND = 4;
await sleep(1000 / REQUESTS_PER_SECOND);
// Run up to 8 Loads in parallel to prevent too many parallel transactions from
// getting rejected with AccountInUse.
// getting retried due to AccountInUse errors.
//
// TODO: 8 was selected empirically and should probably be revisited
if (transactions.length === 8) {
@ -159,7 +158,7 @@ export class Loader {
transaction,
[payer, program],
{
confirmations: 1,
commitment: 'single',
skipPreflight: true,
},
);

View File

@ -0,0 +1,16 @@
// @flow
export function promiseTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
): Promise<T | null> {
let timeoutId: TimeoutID;
const timeoutPromise = new Promise(resolve => {
timeoutId = setTimeout(() => resolve(null), timeoutMs);
});
return Promise.race([promise, timeoutPromise]).then(result => {
clearTimeout(timeoutId);
return result;
});
}

View File

@ -7,7 +7,7 @@ import type {ConfirmOptions} from '../connection';
/**
* Send and confirm a raw transaction
*
* If `confirmations` count is not specified, wait for transaction to be finalized.
* If `commitment` option is not specified, defaults to 'max' commitment.
*
* @param {Connection} connection
* @param {Buffer} rawTransaction
@ -19,31 +19,23 @@ export async function sendAndConfirmRawTransaction(
rawTransaction: Buffer,
options?: ConfirmOptions,
): Promise<TransactionSignature> {
const start = Date.now();
const signature = await connection.sendRawTransaction(
rawTransaction,
options,
);
const status = (
await connection.confirmTransaction(
signature,
options && options.confirmations,
options && options.commitment,
)
).value;
if (status) {
if (status.err) {
throw new Error(
`Raw transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
return signature;
if (status.err) {
throw new Error(
`Raw transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
const duration = (Date.now() - start) / 1000;
throw new Error(
`Raw transaction '${signature}' was not confirmed in ${duration.toFixed(
2,
)} seconds`,
);
return signature;
}

View File

@ -9,7 +9,7 @@ import type {TransactionSignature} from '../transaction';
/**
* Sign, send and confirm a transaction.
*
* If `confirmations` count is not specified, wait for transaction to be finalized.
* If `commitment` option is not specified, defaults to 'max' commitment.
*
* @param {Connection} connection
* @param {Transaction} transaction
@ -23,32 +23,24 @@ export async function sendAndConfirmTransaction(
signers: Array<Account>,
options?: ConfirmOptions,
): Promise<TransactionSignature> {
const start = Date.now();
const signature = await connection.sendTransaction(
transaction,
signers,
options,
);
const status = (
await connection.confirmTransaction(
signature,
options && options.confirmations,
options && options.commitment,
)
).value;
if (status) {
if (status.err) {
throw new Error(
`Transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
return signature;
if (status.err) {
throw new Error(
`Transaction ${signature} failed (${JSON.stringify(status)})`,
);
}
const duration = (Date.now() - start) / 1000;
throw new Error(
`Transaction was not confirmed in ${duration.toFixed(
2,
)} seconds (${JSON.stringify(status)})`,
);
return signature;
}

View File

@ -1,29 +1,75 @@
// @flow
import {Client as LiveClient} from 'rpc-websockets';
import EventEmitter from 'events';
type RpcRequest = {
method: string,
params?: Array<any>,
};
type RpcResponse = {
context: {
slot: number,
},
value: any,
};
// Define TEST_LIVE in the environment to test against the real full node
// identified by `url` instead of using the mock
export const mockRpcEnabled = !process.env.TEST_LIVE;
let mockNotice = true;
export const mockRpcSocket: Array<[RpcRequest, RpcResponse]> = [];
class MockClient {
constructor(url: string) {
if (mockNotice) {
console.log(
'Note: rpc-websockets mock is disabled, testing live against',
url,
);
mockNotice = false;
class MockClient extends EventEmitter {
mockOpen = false;
subscriptionCounter = 0;
constructor() {
super();
}
connect() {
if (!this.mockOpen) {
this.mockOpen = true;
this.emit('open');
}
}
connect() {}
close() {}
on() {}
call(): Promise<Object> {
throw new Error('call unsupported');
close() {
if (this.mockOpen) {
this.mockOpen = false;
this.emit('close');
}
}
notify(): Promise<any> {
return Promise.resolve();
}
on(event: string, callback: Function): this {
return super.on(event, callback);
}
call(method: string, params: Array<any>): Promise<Object> {
expect(mockRpcSocket.length).toBeGreaterThanOrEqual(1);
const [mockRequest, mockResponse] = mockRpcSocket.shift();
expect(method).toBe(mockRequest.method);
expect(params).toMatchObject(mockRequest.params);
let id = this.subscriptionCounter++;
const response = {
subscription: id,
result: mockResponse,
};
setImmediate(() => {
const eventName = method.replace('Subscribe', 'Notification');
this.emit(eventName, response);
});
return Promise.resolve(id);
}
}

View File

@ -1,6 +1,5 @@
// @flow
import bs58 from 'bs58';
import fs from 'mz/fs';
import {
@ -47,7 +46,7 @@ test('load BPF C program', async () => {
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [from], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
});
});
@ -90,22 +89,22 @@ describe('load BPF Rust program', () => {
data,
BPF_LOADER_PROGRAM_ID,
);
const transaction = new Transaction().add({
keys: [
{pubkey: payerAccount.publicKey, isSigner: true, isWritable: true},
],
programId: program.publicKey,
});
await sendAndConfirmTransaction(connection, transaction, [payerAccount], {
skipPreflight: true,
});
if (transaction.signature === null) {
expect(transaction.signature).not.toBeNull();
return;
}
signature = bs58.encode(transaction.signature);
signature = await sendAndConfirmTransaction(
connection,
transaction,
[payerAccount],
{
skipPreflight: true,
},
);
});
test('get confirmed transaction', async () => {

View File

@ -19,8 +19,9 @@ import {BLOCKHASH_CACHE_TIMEOUT_MS} from '../src/connection';
import type {TransactionSignature} from '../src/transaction';
import type {SignatureStatus, TransactionError} from '../src/connection';
import {mockConfirmTransaction} from './mockrpc/confirm-transaction';
import {mockRpcSocket} from './__mocks__/rpc-websockets';
// Testing blockhash cache takes around 30s to complete
// Testing tokens and blockhash cache each take around 30s to complete
jest.setTimeout(40000);
const errorMessage = 'Invalid';
@ -99,7 +100,7 @@ test('get program accounts', async () => {
{
error: null,
result:
'0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
'2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
mockRpc.push([
@ -111,7 +112,7 @@ test('get program accounts', async () => {
{
error: null,
result:
'0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
'2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
await connection.requestAirdrop(account0.publicKey, LAMPORTS_PER_SOL);
@ -129,39 +130,17 @@ test('get program accounts', async () => {
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 11,
status: {Ok: null},
err: null,
},
],
},
},
]);
let transaction = SystemProgram.assign({
accountPubkey: account0.publicKey,
programId: programId.publicKey,
});
mockConfirmTransaction(
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
);
await sendAndConfirmTransaction(connection, transaction, [account0], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
});
@ -176,41 +155,17 @@ test('get program accounts', async () => {
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 11,
status: {Ok: null},
err: null,
},
],
},
},
]);
transaction = SystemProgram.assign({
accountPubkey: account1.publicKey,
programId: programId.publicKey,
});
mockConfirmTransaction(
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
);
await sendAndConfirmTransaction(connection, transaction, [account1], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
});
@ -607,18 +562,9 @@ test('confirm transaction - error', async () => {
const badTransactionSignature = 'bad transaction signature';
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [[badTransactionSignature]],
},
errorResponse,
]);
await expect(
connection.confirmTransaction(badTransactionSignature),
).rejects.toThrow(errorMessage);
).rejects.toThrow('signature must be base58 encoded');
mockRpc.push([
url,
@ -1348,7 +1294,7 @@ describe('token methods', () => {
const payerAccount = new Account();
await connection.confirmTransaction(
await connection.requestAirdrop(payerAccount.publicKey, 100000000000),
0,
'single',
);
const mintOwner = new Account();
@ -1628,35 +1574,8 @@ test('request airdrop', async () => {
minimumAmount + 42,
);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: null,
status: {Ok: null},
err: null,
},
],
},
},
]);
await connection.confirmTransaction(signature, 0);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'single');
mockRpc.push([
url,
@ -1782,7 +1701,7 @@ test('transaction failure', async () => {
{
error: null,
result:
'0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
'2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const airdropSignature = await connection.requestAirdrop(
@ -1791,7 +1710,7 @@ test('transaction failure', async () => {
);
mockConfirmTransaction(airdropSignature);
await connection.confirmTransaction(airdropSignature, 0);
await connection.confirmTransaction(airdropSignature, 'single');
mockRpc.push([
url,
@ -1826,33 +1745,6 @@ test('transaction failure', async () => {
},
]);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 1,
err: null,
},
],
},
},
]);
const newAccount = new Account();
let transaction = SystemProgram.createAccount({
fromPubkey: account.publicKey,
@ -1861,11 +1753,15 @@ test('transaction failure', async () => {
space: 0,
programId: SystemProgram.programId,
});
mockConfirmTransaction(
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
);
await sendAndConfirmTransaction(
connection,
transaction,
[account, newAccount],
{confirmations: 1, skipPreflight: true},
{commitment: 'single', skipPreflight: true},
);
mockRpc.push([
@ -1895,38 +1791,24 @@ test('transaction failure', async () => {
);
const expectedErr = {InstructionError: [0, {Custom: 0}]};
mockRpc.push([
url,
mockRpcSocket.push([
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
method: 'signatureSubscribe',
params: [signature, {commitment: 'single'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 1,
status: {Err: expectedErr},
err: expectedErr,
},
],
context: {
slot: 11,
},
value: {err: expectedErr},
},
]);
// Wait for one confirmation
const confirmResult = (await connection.confirmTransaction(signature, 1))
.value;
verifySignatureStatus(confirmResult, expectedErr);
const confirmResult = (
await connection.confirmTransaction(signature, 'single')
).value;
expect(confirmResult.err).toEqual(expectedErr);
mockRpc.push([
url,
@ -1991,15 +1873,16 @@ test('transaction', async () => {
{
error: null,
result:
'0WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
'2WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
const airdropFromSig = await connection.requestAirdrop(
accountFrom.publicKey,
minimumAmount + 100010,
);
mockConfirmTransaction(airdropFromSig);
await connection.confirmTransaction(airdropFromSig, 0);
await connection.confirmTransaction(airdropFromSig, 'single');
mockRpc.push([
url,
@ -2033,33 +1916,15 @@ test('transaction', async () => {
'8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
},
]);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'8WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 0,
status: {Ok: null},
err: null,
},
],
},
},
]);
const airdropToSig = await connection.requestAirdrop(
accountTo.publicKey,
minimumAmount,
);
mockConfirmTransaction(airdropToSig);
await connection.confirmTransaction(airdropToSig, 'single');
mockRpc.push([
url,
{
@ -2076,11 +1941,7 @@ test('transaction', async () => {
},
},
]);
const airdropToSig = await connection.requestAirdrop(
accountTo.publicKey,
minimumAmount,
);
await connection.confirmTransaction(airdropToSig, 0);
expect(await connection.getBalance(accountTo.publicKey)).toBe(minimumAmount);
mockGetRecentBlockhash('max');
@ -2108,8 +1969,9 @@ test('transaction', async () => {
);
mockConfirmTransaction(signature);
let confirmResult = (await connection.confirmTransaction(signature, 0)).value;
verifySignatureStatus(confirmResult);
let confirmResult = (await connection.confirmTransaction(signature, 'single'))
.value;
expect(confirmResult.err).toBeNull();
mockGetRecentBlockhash('max');
mockRpc.push([
@ -2140,7 +2002,7 @@ test('transaction', async () => {
expect(transaction.recentBlockhash).not.toEqual(transaction2.recentBlockhash);
mockConfirmTransaction(signature2);
await connection.confirmTransaction(signature2, 0);
await connection.confirmTransaction(signature2, 'single');
mockRpc.push([
url,
@ -2170,7 +2032,7 @@ test('transaction', async () => {
expect(transaction2.recentBlockhash).toEqual(transaction3.recentBlockhash);
mockConfirmTransaction(signature3);
await connection.confirmTransaction(signature3, 0);
await connection.confirmTransaction(signature3, 'single');
// Sleep until blockhash cache times out
await sleep(
@ -2204,7 +2066,7 @@ test('transaction', async () => {
},
);
mockConfirmTransaction(signature4);
await connection.confirmTransaction(signature4, 0);
await connection.confirmTransaction(signature4, 'single');
expect(transaction4.recentBlockhash).not.toEqual(
transaction3.recentBlockhash,
@ -2267,7 +2129,7 @@ test('multi-instruction transaction', async () => {
accountFrom.publicKey,
LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(signature, 0);
await connection.confirmTransaction(signature, 'single');
expect(await connection.getBalance(accountFrom.publicKey)).toBe(
LAMPORTS_PER_SOL,
);
@ -2281,7 +2143,7 @@ test('multi-instruction transaction', async () => {
accountTo.publicKey,
minimumAmount + 21,
);
await connection.confirmTransaction(signature, 0);
await connection.confirmTransaction(signature, 'single');
expect(await connection.getBalance(accountTo.publicKey)).toBe(
minimumAmount + 21,
);
@ -2305,7 +2167,7 @@ test('multi-instruction transaction', async () => {
{skipPreflight: true},
);
await connection.confirmTransaction(signature, 1);
await connection.confirmTransaction(signature, 'single');
const response = (await connection.getSignatureStatus(signature)).value;
if (response !== null) {
@ -2357,7 +2219,7 @@ test('account change notification', async () => {
lamports: balanceNeeded,
});
await sendAndConfirmTransaction(connection, transaction, [owner], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
});
} catch (err) {
@ -2424,7 +2286,7 @@ test('program account change notification', async () => {
lamports: balanceNeeded,
});
await sendAndConfirmTransaction(connection, transaction, [owner], {
confirmations: 1,
commitment: 'single',
skipPreflight: true,
});
} catch (err) {

View File

@ -1,31 +1,19 @@
// @flow
import type {TransactionSignature} from '../../src/transaction';
import {url} from '../url';
import {mockRpc} from '../__mocks__/node-fetch';
import {mockRpcSocket} from '../__mocks__/rpc-websockets';
export function mockConfirmTransaction(signature: TransactionSignature) {
mockRpc.push([
url,
mockRpcSocket.push([
{
method: 'getSignatureStatuses',
params: [[signature]],
method: 'signatureSubscribe',
params: [signature, {commitment: 'single'}],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: null,
status: {Ok: null},
err: null,
},
],
context: {
slot: 11,
},
value: {err: null},
},
]);
}

View File

@ -26,6 +26,11 @@ export async function newAccountWithLamports(
]);
}
await connection.requestAirdrop(account.publicKey, lamports);
const signature = await connection.requestAirdrop(
account.publicKey,
lamports,
);
await connection.confirmTransaction(signature, 'single');
return account;
}

View File

@ -64,7 +64,7 @@ test('create and query nonce account', async () => {
minimumAmount * 2,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 0);
await connection.confirmTransaction(signature, 'single');
mockRpc.push([
url,
@ -113,7 +113,7 @@ test('create and query nonce account', async () => {
},
);
mockConfirmTransaction(nonceSignature);
await connection.confirmTransaction(nonceSignature, 0);
await connection.confirmTransaction(nonceSignature, 'single');
mockRpc.push([
url,
@ -193,7 +193,7 @@ test('create and query nonce account with seed', async () => {
minimumAmount * 2,
);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 0);
await connection.confirmTransaction(signature, 'single');
mockRpc.push([
url,
@ -240,7 +240,7 @@ test('create and query nonce account with seed', async () => {
skipPreflight: true,
});
mockConfirmTransaction(nonceSignature);
await connection.confirmTransaction(nonceSignature, 0);
await connection.confirmTransaction(nonceSignature, 'single');
mockRpc.push([
url,

View File

@ -295,7 +295,7 @@ test('live staking actions', async () => {
connection,
createAndInitialize,
[from, newStakeAccount],
{confirmations: 0, skipPreflight: true},
{commitment: 'single', skipPreflight: true},
);
expect(await connection.getBalance(newStakeAccount.publicKey)).toEqual(
minimumAmount + 42,
@ -307,7 +307,7 @@ test('live staking actions', async () => {
votePubkey,
});
await sendAndConfirmTransaction(connection, delegation, [authorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
}
@ -334,7 +334,7 @@ test('live staking actions', async () => {
connection,
createAndInitializeWithSeed,
[from],
{confirmations: 0, skipPreflight: true},
{commitment: 'single', skipPreflight: true},
);
let originalStakeBalance = await connection.getBalance(newAccountPubkey);
expect(originalStakeBalance).toEqual(3 * minimumAmount + 42);
@ -345,7 +345,7 @@ test('live staking actions', async () => {
votePubkey,
});
await sendAndConfirmTransaction(connection, delegation, [authorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
@ -359,7 +359,7 @@ test('live staking actions', async () => {
});
await expect(
sendAndConfirmTransaction(connection, withdraw, [authorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
}),
).rejects.toThrow();
@ -373,7 +373,7 @@ test('live staking actions', async () => {
lamports: minimumAmount + 20,
});
await sendAndConfirmTransaction(connection, split, [authorized, newStake], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
@ -388,7 +388,7 @@ test('live staking actions', async () => {
stakeAuthorizationType: StakeAuthorizationLayout.Withdrawer,
});
await sendAndConfirmTransaction(connection, authorize, [authorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
authorize = StakeProgram.authorize({
@ -398,7 +398,7 @@ test('live staking actions', async () => {
stakeAuthorizationType: StakeAuthorizationLayout.Staker,
});
await sendAndConfirmTransaction(connection, authorize, [authorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
@ -412,7 +412,7 @@ test('live staking actions', async () => {
connection,
deactivateNotAuthorized,
[authorized],
{confirmations: 0, skipPreflight: true},
{commitment: 'single', skipPreflight: true},
),
).rejects.toThrow();
@ -422,7 +422,7 @@ test('live staking actions', async () => {
authorizedPubkey: newAuthorized.publicKey,
});
await sendAndConfirmTransaction(connection, deactivate, [newAuthorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
@ -434,7 +434,7 @@ test('live staking actions', async () => {
lamports: minimumAmount + 20,
});
await sendAndConfirmTransaction(connection, withdraw, [newAuthorized], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
const balance = await connection.getBalance(newAccountPubkey);

View File

@ -290,7 +290,7 @@ test('live Nonce actions', async () => {
connection,
createNonceAccount,
[from, nonceAccount],
{confirmations: 0, skipPreflight: true},
{commitment: 'single', skipPreflight: true},
);
const nonceBalance = await connection.getBalance(nonceAccount.publicKey);
expect(nonceBalance).toEqual(minimumAmount);
@ -319,7 +319,7 @@ test('live Nonce actions', async () => {
}),
);
await sendAndConfirmTransaction(connection, advanceNonce, [from], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
const nonceQuery3 = await connection.getNonce(nonceAccount.publicKey);
@ -341,7 +341,7 @@ test('live Nonce actions', async () => {
}),
);
await sendAndConfirmTransaction(connection, authorizeNonce, [from], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
@ -359,7 +359,7 @@ test('live Nonce actions', async () => {
};
await sendAndConfirmTransaction(connection, transfer, [from, newAuthority], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
const toBalance = await connection.getBalance(to.publicKey);
@ -378,7 +378,7 @@ test('live Nonce actions', async () => {
}),
);
await sendAndConfirmTransaction(connection, withdrawNonce, [newAuthority], {
confirmations: 0,
commitment: 'single',
skipPreflight: true,
});
expect(await connection.getBalance(nonceAccount.publicKey)).toEqual(0);

View File

@ -3,6 +3,7 @@ import {Account, Connection, SystemProgram, LAMPORTS_PER_SOL} from '../src';
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash';
import {url} from './url';
import {mockConfirmTransaction} from './mockrpc/confirm-transaction';
if (!mockRpcEnabled) {
// The default of 5 seconds is too slow for live testing sometimes
@ -99,35 +100,8 @@ test('transaction-payer', async () => {
{skipPreflight: true},
);
mockRpc.push([
url,
{
method: 'getSignatureStatuses',
params: [
[
'3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk',
],
],
},
{
error: null,
result: {
context: {
slot: 11,
},
value: [
{
slot: 0,
confirmations: 1,
status: {Ok: null},
err: null,
},
],
},
},
]);
await connection.confirmTransaction(signature, 1);
mockConfirmTransaction(signature);
await connection.confirmTransaction(signature, 'single');
mockRpc.push([
url,