From 83d12f50df8f1d38a3963e4da9ef2eb6042dd190 Mon Sep 17 00:00:00 2001 From: Michael Vines Date: Mon, 22 Oct 2018 20:03:44 -0700 Subject: [PATCH] fix: add lastId caching --- web3.js/src/connection.js | 69 ++++++++++++++++++++++-------- web3.js/test/token-program.test.js | 7 +++ 2 files changed, 58 insertions(+), 18 deletions(-) diff --git a/web3.js/src/connection.js b/web3.js/src/connection.js index 8a2c86bd09..6a06bd5bbe 100644 --- a/web3.js/src/connection.js +++ b/web3.js/src/connection.js @@ -159,7 +159,13 @@ export type SignatureStatus = 'Confirmed' | 'SignatureNotFound' | 'ProgramRuntim */ export class Connection { _rpcRequest: RpcRequest; - _lastId: null | TransactionId; + + _lastIdInfo: { + lastId: TransactionId | null, + seconds: number, + transactionSignatures: Array, + }; + _disableLastIdCaching: boolean = false /** * Establish a JSON RPC connection @@ -171,7 +177,11 @@ export class Connection { throw new Error('Connection endpoint not specified'); } 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 */ async sendTransaction(from: Account, transaction: Transaction): Promise { - - let attempts = 0; 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 with the raw input bytes as an in-flight transaction - // is issued. - if (this._lastId != transaction.lastId) { - this._lastId = transaction.lastId; - break; + transaction.lastId = this._lastIdInfo.lastId; + transaction.sign(from); + if (!transaction.signature) { + throw new Error('!signature'); // should never happen + } + + // 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 unsafeRes = await this._rpcRequest('sendTransaction', [[...wireTransaction]]); diff --git a/web3.js/test/token-program.test.js b/web3.js/test/token-program.test.js index ad4aa6fe98..8ab3ecc2e9 100644 --- a/web3.js/test/token-program.test.js +++ b/web3.js/test/token-program.test.js @@ -52,6 +52,7 @@ let initialOwnerTokenAccount: PublicKey; test('create new token', async () => { const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; initialOwner = await newAccountWithTokens(connection); @@ -168,6 +169,7 @@ test('create new token', async () => { test('create new token account', async () => { const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const destOwner = await newAccountWithTokens(connection); { @@ -224,6 +226,7 @@ test('create new token account', async () => { test('transfer', async () => { const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const destOwner = await newAccountWithTokens(connection); { @@ -319,6 +322,7 @@ test('transfer', async () => { test('approve/revoke', async () => { const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const delegateOwner = await newAccountWithTokens(connection); { @@ -453,6 +457,7 @@ test('invalid approve', async () => { } const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const owner = await newAccountWithTokens(connection); const account1 = await testToken.newAccount(owner); @@ -488,6 +493,7 @@ test('fail on approve overspend', async () => { } const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const owner = await newAccountWithTokens(connection); const account1 = await testToken.newAccount(owner); @@ -552,6 +558,7 @@ test('set owner', async () => { } const connection = new Connection(url); + connection._disableLastIdCaching = mockRpcEnabled; const owner = await newAccountWithTokens(connection); const newOwner = await newAccountWithTokens(connection);