fix: add lastId caching
This commit is contained in:
parent
aaf86ff2ba
commit
83d12f50df
|
@ -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]]);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue