fix: add lastId caching

This commit is contained in:
Michael Vines 2018-10-22 20:03:44 -07:00
parent aaf86ff2ba
commit 83d12f50df
2 changed files with 58 additions and 18 deletions

View File

@ -159,7 +159,13 @@ export type SignatureStatus = 'Confirmed' | 'SignatureNotFound' | 'ProgramRuntim
*/ */
export class Connection { export class Connection {
_rpcRequest: RpcRequest; _rpcRequest: RpcRequest;
_lastId: null | TransactionId;
_lastIdInfo: {
lastId: TransactionId | null,
seconds: number,
transactionSignatures: Array<string>,
};
_disableLastIdCaching: boolean = false
/** /**
* Establish a JSON RPC connection * Establish a JSON RPC connection
@ -171,7 +177,11 @@ 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; this._lastIdInfo = {
lastId: null,
seconds: -1,
transactionSignatures: [],
};
} }
/** /**
@ -301,27 +311,50 @@ 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> {
let attempts = 0;
for (;;) { for (;;) {
transaction.lastId = await this.getLastId(); // Attempt to use the previous last id for up to 1 second
const seconds = (new Date()).getSeconds();
if ( (this._lastIdInfo.lastId != null) &&
(this._lastIdInfo.seconds === seconds) ) {
// TODO: Waiting for the next lastId is really only necessary if a second transaction.lastId = this._lastIdInfo.lastId;
// transaction with the raw input bytes as an in-flight transaction transaction.sign(from);
// is issued. if (!transaction.signature) {
if (this._lastId != transaction.lastId) { throw new Error('!signature'); // should never happen
this._lastId = transaction.lastId; }
break;
// If the signature of this transaction has not been seen before with the
// current lastId, all done.
const signature = transaction.signature.toString();
if (!this._lastIdInfo.transactionSignatures.includes(signature)) {
this._lastIdInfo.transactionSignatures.push(signature);
if (this._disableLastIdCaching) {
this._lastIdInfo.seconds = -1;
}
break;
}
} }
if (attempts === 20) {
throw new Error('Unable to obtain new last id'); // Fetch a new last id
let attempts = 0;
for (;;) {
const lastId = await this.getLastId();
if (this._lastIdInfo.lastId != lastId) {
this._lastIdInfo = {
lastId,
seconds: (new Date()).getSeconds(),
transactionSignatures: [],
};
break;
}
if (attempts === 8) {
throw new Error('Unable to obtain new last id');
}
await sleep(250);
++attempts;
} }
// TODO: Add a pubsub notification for obtaining the next last id instead
// of polling?
await sleep(100);
++attempts;
} }
transaction.sign(from);
const wireTransaction = transaction.serialize(); const wireTransaction = transaction.serialize();
const unsafeRes = await this._rpcRequest('sendTransaction', [[...wireTransaction]]); const unsafeRes = await this._rpcRequest('sendTransaction', [[...wireTransaction]]);

View File

@ -52,6 +52,7 @@ let initialOwnerTokenAccount: PublicKey;
test('create new token', async () => { test('create new token', async () => {
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
initialOwner = await newAccountWithTokens(connection); initialOwner = await newAccountWithTokens(connection);
@ -168,6 +169,7 @@ test('create new token', async () => {
test('create new token account', async () => { test('create new token account', async () => {
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const destOwner = await newAccountWithTokens(connection); const destOwner = await newAccountWithTokens(connection);
{ {
@ -224,6 +226,7 @@ test('create new token account', async () => {
test('transfer', async () => { test('transfer', async () => {
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const destOwner = await newAccountWithTokens(connection); const destOwner = await newAccountWithTokens(connection);
{ {
@ -319,6 +322,7 @@ test('transfer', async () => {
test('approve/revoke', async () => { test('approve/revoke', async () => {
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const delegateOwner = await newAccountWithTokens(connection); const delegateOwner = await newAccountWithTokens(connection);
{ {
@ -453,6 +457,7 @@ test('invalid approve', async () => {
} }
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const owner = await newAccountWithTokens(connection); const owner = await newAccountWithTokens(connection);
const account1 = await testToken.newAccount(owner); const account1 = await testToken.newAccount(owner);
@ -488,6 +493,7 @@ test('fail on approve overspend', async () => {
} }
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const owner = await newAccountWithTokens(connection); const owner = await newAccountWithTokens(connection);
const account1 = await testToken.newAccount(owner); const account1 = await testToken.newAccount(owner);
@ -552,6 +558,7 @@ test('set owner', async () => {
} }
const connection = new Connection(url); const connection = new Connection(url);
connection._disableLastIdCaching = mockRpcEnabled;
const owner = await newAccountWithTokens(connection); const owner = await newAccountWithTokens(connection);
const newOwner = await newAccountWithTokens(connection); const newOwner = await newAccountWithTokens(connection);