fix: wait for the next lastId before sending a new transaction
This commit is contained in:
@ -7,6 +7,7 @@ import {struct} from 'superstruct';
|
|||||||
|
|
||||||
import {Transaction} from './transaction';
|
import {Transaction} from './transaction';
|
||||||
import {PublicKey} from './publickey';
|
import {PublicKey} from './publickey';
|
||||||
|
import {sleep} from './util/sleep';
|
||||||
import type {Account} from './account';
|
import type {Account} from './account';
|
||||||
import type {TransactionSignature, TransactionId} from './transaction';
|
import type {TransactionSignature, TransactionId} from './transaction';
|
||||||
|
|
||||||
@ -158,6 +159,7 @@ export type SignatureStatus = 'Confirmed' | 'SignatureNotFound' | 'ProgramRuntim
|
|||||||
*/
|
*/
|
||||||
export class Connection {
|
export class Connection {
|
||||||
_rpcRequest: RpcRequest;
|
_rpcRequest: RpcRequest;
|
||||||
|
_lastId: null | TransactionId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Establish a JSON RPC connection
|
* Establish a JSON RPC connection
|
||||||
@ -169,6 +171,7 @@ export class Connection {
|
|||||||
throw new Error('Connection endpoint not specified');
|
throw new Error('Connection endpoint not specified');
|
||||||
}
|
}
|
||||||
this._rpcRequest = createRpcRequest(endpoint);
|
this._rpcRequest = createRpcRequest(endpoint);
|
||||||
|
this._lastId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -298,7 +301,26 @@ export class Connection {
|
|||||||
* Sign and send a transaction
|
* Sign and send a transaction
|
||||||
*/
|
*/
|
||||||
async sendTransaction(from: Account, transaction: Transaction): Promise<TransactionSignature> {
|
async sendTransaction(from: Account, transaction: Transaction): Promise<TransactionSignature> {
|
||||||
transaction.lastId = await this.getLastId();
|
|
||||||
|
let attempts = 0;
|
||||||
|
for (;;) {
|
||||||
|
transaction.lastId = await this.getLastId();
|
||||||
|
|
||||||
|
// TODO: Waiting for the next lastId is really only necessary if a second
|
||||||
|
// transaction with the raw input bytes as an in-flight transaction
|
||||||
|
// is issued.
|
||||||
|
if (this._lastId != transaction.lastId) {
|
||||||
|
this._lastId = transaction.lastId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (attempts === 20) {
|
||||||
|
throw new Error('Unable to obtain new last id');
|
||||||
|
}
|
||||||
|
// TODO: Add a pubsub notification for obtaining the next last id instead
|
||||||
|
// of polling?
|
||||||
|
await sleep(100);
|
||||||
|
++attempts;
|
||||||
|
}
|
||||||
transaction.sign(from);
|
transaction.sign(from);
|
||||||
|
|
||||||
const wireTransaction = transaction.serialize();
|
const wireTransaction = transaction.serialize();
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
SystemProgram,
|
SystemProgram,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
|
import {mockGetLastId} from './mockrpc/getlastid';
|
||||||
import {url} from './url';
|
import {url} from './url';
|
||||||
|
|
||||||
if (!mockRpcEnabled) {
|
if (!mockRpcEnabled) {
|
||||||
@ -117,18 +118,7 @@ test('get transaction count', async () => {
|
|||||||
test('get last Id', async () => {
|
test('get last Id', async () => {
|
||||||
const connection = new Connection(url);
|
const connection = new Connection(url);
|
||||||
|
|
||||||
mockRpc.push([
|
mockGetLastId();
|
||||||
url,
|
|
||||||
{
|
|
||||||
method: 'getLastId',
|
|
||||||
params: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: null,
|
|
||||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastId = await connection.getLastId();
|
const lastId = await connection.getLastId();
|
||||||
expect(lastId.length).toBeGreaterThanOrEqual(43);
|
expect(lastId.length).toBeGreaterThanOrEqual(43);
|
||||||
@ -283,18 +273,7 @@ test('transaction', async () => {
|
|||||||
await connection.requestAirdrop(accountTo.publicKey, 21);
|
await connection.requestAirdrop(accountTo.publicKey, 21);
|
||||||
expect(await connection.getBalance(accountTo.publicKey)).toBe(21);
|
expect(await connection.getBalance(accountTo.publicKey)).toBe(21);
|
||||||
|
|
||||||
mockRpc.push([
|
mockGetLastId();
|
||||||
url,
|
|
||||||
{
|
|
||||||
method: 'getLastId',
|
|
||||||
params: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: null,
|
|
||||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
);
|
|
||||||
mockRpc.push([
|
mockRpc.push([
|
||||||
url,
|
url,
|
||||||
{
|
{
|
||||||
|
23
web3.js/test/mockrpc/getlastid.js
Normal file
23
web3.js/test/mockrpc/getlastid.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// @flow
|
||||||
|
|
||||||
|
import {
|
||||||
|
Account,
|
||||||
|
} from '../../src';
|
||||||
|
import {url} from '../url';
|
||||||
|
import {mockRpc} from '../__mocks__/node-fetch';
|
||||||
|
|
||||||
|
export function mockGetLastId() {
|
||||||
|
const lastId = new Account();
|
||||||
|
|
||||||
|
mockRpc.push([
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'getLastId',
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: null,
|
||||||
|
result: lastId.publicKey.toBase58(),
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
@ -10,26 +10,13 @@ import {SYSTEM_TOKEN_PROGRAM_ID} from '../src/token-program';
|
|||||||
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
import {url} from './url';
|
import {url} from './url';
|
||||||
import {newAccountWithTokens} from './new-account-with-tokens';
|
import {newAccountWithTokens} from './new-account-with-tokens';
|
||||||
|
import {mockGetLastId} from './mockrpc/getlastid';
|
||||||
|
|
||||||
if (!mockRpcEnabled) {
|
if (!mockRpcEnabled) {
|
||||||
// The default of 5 seconds is too slow for live testing sometimes
|
// The default of 5 seconds is too slow for live testing sometimes
|
||||||
jest.setTimeout(10000);
|
jest.setTimeout(10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockGetLastId() {
|
|
||||||
mockRpc.push([
|
|
||||||
url,
|
|
||||||
{
|
|
||||||
method: 'getLastId',
|
|
||||||
params: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
error: null,
|
|
||||||
result: '2BjEqiiT43J6XskiHdz7aoocjPeWkCPiKD72SiFQsrA2',
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mockGetSignatureStatus(result: string = 'Confirmed') {
|
function mockGetSignatureStatus(result: string = 'Confirmed') {
|
||||||
mockRpc.push([
|
mockRpc.push([
|
||||||
url,
|
url,
|
||||||
|
Reference in New Issue
Block a user