From 11b199cccf6c7a553781a7f7e716705e562bd995 Mon Sep 17 00:00:00 2001 From: Justin Starry Date: Tue, 8 Sep 2020 13:12:47 +0800 Subject: [PATCH] feat: use pubsub to confirm transactions (#12095) --- web3.js/module.d.ts | 6 +- web3.js/module.flow.js | 6 +- web3.js/src/connection.js | 96 +++++-- web3.js/src/loader.js | 15 +- web3.js/src/util/promise-timeout.js | 16 ++ .../util/send-and-confirm-raw-transaction.js | 24 +- .../src/util/send-and-confirm-transaction.js | 24 +- web3.js/test/__mocks__/rpc-websockets.js | 74 ++++- web3.js/test/bpf-loader.test.js | 21 +- web3.js/test/connection.test.js | 256 ++++-------------- web3.js/test/mockrpc/confirm-transaction.js | 26 +- web3.js/test/new-account-with-lamports.js | 7 +- web3.js/test/nonce.test.js | 8 +- web3.js/test/stake-program.test.js | 22 +- web3.js/test/system-program.test.js | 10 +- web3.js/test/transaction-payer.test.js | 32 +-- 16 files changed, 276 insertions(+), 367 deletions(-) create mode 100644 web3.js/src/util/promise-timeout.js diff --git a/web3.js/module.d.ts b/web3.js/module.d.ts index 26a91d2f6f..cc6af5956e 100644 --- a/web3.js/module.d.ts +++ b/web3.js/module.d.ts @@ -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; confirmTransaction( signature: TransactionSignature, - confirmations?: number, - ): Promise>; + commitment?: Commitment, + ): Promise>; getSlot(commitment?: Commitment): Promise; getSlotLeader(commitment?: Commitment): Promise; getSignatureStatus( diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 3b72de2973..8af735579b 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -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; confirmTransaction( signature: TransactionSignature, - confirmations: ?number, - ): Promise>; + commitment: ?Commitment, + ): Promise>; getSlot(commitment: ?Commitment): Promise; getSlotLeader(commitment: ?Commitment): Promise; getSignatureStatus( diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index bcf15146bb..5efd1e857b 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -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> { - 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> { + 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 | 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; } /** diff --git a/web3.js/src/loader.js b/web3.js/src/loader.js index 213e4fa294..4cec43dabc 100644 --- a/web3.js/src/loader.js +++ b/web3.js/src/loader.js @@ -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, }, ); diff --git a/web3.js/src/util/promise-timeout.js b/web3.js/src/util/promise-timeout.js new file mode 100644 index 0000000000..8219b4e9bb --- /dev/null +++ b/web3.js/src/util/promise-timeout.js @@ -0,0 +1,16 @@ +// @flow + +export function promiseTimeout( + promise: Promise, + timeoutMs: number, +): Promise { + let timeoutId: TimeoutID; + const timeoutPromise = new Promise(resolve => { + timeoutId = setTimeout(() => resolve(null), timeoutMs); + }); + + return Promise.race([promise, timeoutPromise]).then(result => { + clearTimeout(timeoutId); + return result; + }); +} diff --git a/web3.js/src/util/send-and-confirm-raw-transaction.js b/web3.js/src/util/send-and-confirm-raw-transaction.js index 53ef551450..29b1000922 100644 --- a/web3.js/src/util/send-and-confirm-raw-transaction.js +++ b/web3.js/src/util/send-and-confirm-raw-transaction.js @@ -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 { - 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; } diff --git a/web3.js/src/util/send-and-confirm-transaction.js b/web3.js/src/util/send-and-confirm-transaction.js index 7a827b0dab..f60ffc06a3 100644 --- a/web3.js/src/util/send-and-confirm-transaction.js +++ b/web3.js/src/util/send-and-confirm-transaction.js @@ -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, options?: ConfirmOptions, ): Promise { - 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; } diff --git a/web3.js/test/__mocks__/rpc-websockets.js b/web3.js/test/__mocks__/rpc-websockets.js index 5619bcc1c5..fa2d9d1a7e 100644 --- a/web3.js/test/__mocks__/rpc-websockets.js +++ b/web3.js/test/__mocks__/rpc-websockets.js @@ -1,29 +1,75 @@ // @flow import {Client as LiveClient} from 'rpc-websockets'; +import EventEmitter from 'events'; + +type RpcRequest = { + method: string, + params?: Array, +}; + +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 { - throw new Error('call unsupported'); + close() { + if (this.mockOpen) { + this.mockOpen = false; + this.emit('close'); + } + } + + notify(): Promise { + return Promise.resolve(); + } + + on(event: string, callback: Function): this { + return super.on(event, callback); + } + + call(method: string, params: Array): Promise { + 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); } } diff --git a/web3.js/test/bpf-loader.test.js b/web3.js/test/bpf-loader.test.js index 9473f5a090..37fe081ed7 100644 --- a/web3.js/test/bpf-loader.test.js +++ b/web3.js/test/bpf-loader.test.js @@ -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 () => { diff --git a/web3.js/test/connection.test.js b/web3.js/test/connection.test.js index ce68b2c48b..3832172d9e 100644 --- a/web3.js/test/connection.test.js +++ b/web3.js/test/connection.test.js @@ -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) { diff --git a/web3.js/test/mockrpc/confirm-transaction.js b/web3.js/test/mockrpc/confirm-transaction.js index 873900e4f7..2f844397e9 100644 --- a/web3.js/test/mockrpc/confirm-transaction.js +++ b/web3.js/test/mockrpc/confirm-transaction.js @@ -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}, }, ]); } diff --git a/web3.js/test/new-account-with-lamports.js b/web3.js/test/new-account-with-lamports.js index babfdf51b1..414250c266 100644 --- a/web3.js/test/new-account-with-lamports.js +++ b/web3.js/test/new-account-with-lamports.js @@ -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; } diff --git a/web3.js/test/nonce.test.js b/web3.js/test/nonce.test.js index 735ab7454d..f45290dfbb 100644 --- a/web3.js/test/nonce.test.js +++ b/web3.js/test/nonce.test.js @@ -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, diff --git a/web3.js/test/stake-program.test.js b/web3.js/test/stake-program.test.js index 295d55a019..6c14577d33 100644 --- a/web3.js/test/stake-program.test.js +++ b/web3.js/test/stake-program.test.js @@ -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); diff --git a/web3.js/test/system-program.test.js b/web3.js/test/system-program.test.js index 741bd0ff6d..7472d6a465 100644 --- a/web3.js/test/system-program.test.js +++ b/web3.js/test/system-program.test.js @@ -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); diff --git a/web3.js/test/transaction-payer.test.js b/web3.js/test/transaction-payer.test.js index ea64c6c521..09f876e03c 100644 --- a/web3.js/test/transaction-payer.test.js +++ b/web3.js/test/transaction-payer.test.js @@ -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,