feat: use pubsub to confirm transactions (#12095)
This commit is contained in:
parent
9940870c89
commit
11b199cccf
|
@ -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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue