Skip to content

Commit 4250046

Browse files
committed
update -- use method instead of timer, which is more performant
1 parent 043c20e commit 4250046

File tree

3 files changed

+192
-28
lines changed

3 files changed

+192
-28
lines changed
+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as web3 from '@solana/web3.js';
2+
3+
(async () => {
4+
// Connect to cluster
5+
var connection = new web3.Connection(
6+
web3.clusterApiUrl('devnet'),
7+
'confirmed',
8+
);
9+
10+
// Generate a new wallet keypair and airdrop SOL
11+
var wallet1 = web3.Keypair.generate();
12+
var airdropSignature = await connection.requestAirdrop(
13+
wallet1.publicKey,
14+
web3.LAMPORTS_PER_SOL,
15+
);
16+
17+
//wait for airdrop confirmation
18+
await connection.confirmTransaction(airdropSignature);
19+
20+
// Generate a new wallet keypair and airdrop SOL
21+
var wallet2 = web3.Keypair.generate();
22+
var airdropSignature2 = await connection.requestAirdrop(
23+
wallet2.publicKey,
24+
web3.LAMPORTS_PER_SOL,
25+
);
26+
27+
//wait for airdrop confirmation
28+
await connection.confirmTransaction(airdropSignature);
29+
30+
// get both accounts' info through a single JSON RPC batch transaction
31+
// account data is bytecode that needs to be deserialized
32+
// serialization and deserialization is program specific
33+
let [account1, account2] = await connection.performBatchRequest([
34+
() => connection.getAccountInfo(wallet1.publicKey),
35+
() => connection.getAccountInfo(wallet2.publicKey)
36+
]);
37+
console.log(account1, account2);
38+
})();

web3.js/src/connection.ts

+74-28
Original file line numberDiff line numberDiff line change
@@ -865,33 +865,18 @@ function createRpcRequest(
865865
connection: Connection,
866866
): RpcRequest {
867867
return (method, args) => {
868-
if (connection._autoBatch) {
868+
if (connection._numRequestsToBatch > 0) {
869869
return new Promise((resolve, reject) => {
870-
// Automatically batch requests every 100 ms.
871-
const BATCH_INTERVAL_MS = 100;
872-
873-
connection._batchRequests.push([
874-
client.request(method, args),
870+
// Automatically queue request to be processed in this batch.
871+
connection._batchedRequests.push({
872+
params: {methodName: method, args},
875873
resolve,
876874
reject,
877-
]);
878-
879-
if (!connection._pendingBatchTimer) {
880-
connection._pendingBatchTimer = setTimeout(() => {
881-
const batch = client.batchRequests.map((e: any) => e[0]);
882-
client.request(batch, (err: any, response: any) => {
883-
if (err) {
884-
// Call reject handler of each promise
885-
connection._batchRequests.map((e: any) => e[2](err));
886-
} else {
887-
// Call resolve handler of each promise
888-
connection._batchRequests.map((e: any, i: number) =>
889-
e[1](response[i]),
890-
);
891-
}
892-
connection._pendingBatchTimer = 0;
893-
});
894-
}, BATCH_INTERVAL_MS);
875+
});
876+
if (
877+
connection._batchedRequests.length === connection._numRequestsToBatch
878+
) {
879+
connection._resolvePendingBatchRequests();
895880
}
896881
});
897882
} else {
@@ -2119,9 +2104,14 @@ export class Connection {
21192104
/** @internal */ _confirmTransactionInitialTimeout?: number;
21202105
/** @internal */ _rpcEndpoint: string;
21212106
/** @internal */ _rpcWsEndpoint: string;
2122-
/** @internal */ _autoBatch?: boolean;
2123-
/** @internal */ _batchRequests: any[] = [];
2124-
/** @internal */ _pendingBatchTimer: number = 0;
2107+
/** @internal */ _numRequestsToBatch: number = 0;
2108+
/** @internal */ _resolvePendingBatchRequests: (value?: unknown) => void =
2109+
() => null;
2110+
/** @internal */ _batchedRequests: {
2111+
params: RpcParams;
2112+
resolve: (value?: unknown) => void;
2113+
reject: (reason?: any) => void;
2114+
}[] = [];
21252115
/** @internal */ _rpcClient: RpcClient;
21262116
/** @internal */ _rpcRequest: RpcRequest;
21272117
/** @internal */ _rpcBatchRequest: RpcBatchRequest;
@@ -2210,7 +2200,6 @@ export class Connection {
22102200
httpHeaders = commitmentOrConfig.httpHeaders;
22112201
fetchMiddleware = commitmentOrConfig.fetchMiddleware;
22122202
disableRetryOnRateLimit = commitmentOrConfig.disableRetryOnRateLimit;
2213-
this._autoBatch = commitmentOrConfig.autoBatch;
22142203
}
22152204

22162205
this._rpcEndpoint = endpoint;
@@ -4009,6 +3998,63 @@ export class Connection {
40093998
return res.result;
40103999
}
40114000

4001+
/**
4002+
* Perform the provided requests in a single batch JSON RPC request. Basically, this function allows you to
4003+
* replace the following code, which executes multiple JSON RPC requests in parallel:
4004+
*
4005+
* Promise.all(addresses.map(address => connection.getSignaturesForAddress(address, undefined, 'confirmed'))
4006+
*
4007+
* with the below code, which batches all requests into a single JSON RPC request:
4008+
*
4009+
* connection.performBatchRequest(addresses.map(address => () => connection.getSignaturesForAddress(address, undefined, 'confirmed'))
4010+
*
4011+
* @param deferredRequests an array of functions, each which returns a promise to be batched. Each promise should call a
4012+
* method on this Connection instance that performs a non-batched request. Note: only methods on
4013+
* the Connection class that call _rpcRequest are supported (most do).
4014+
* @return {Promise<Array<any>>} an array of responses that correspond to each request.
4015+
*/
4016+
async performBatchRequest(
4017+
deferredRequests: Array<() => Promise<any>>,
4018+
): Promise<Array<any>> {
4019+
this._numRequestsToBatch = deferredRequests.length;
4020+
let promises: Array<any> = [];
4021+
await new Promise((resolve, reject) => {
4022+
this._resolvePendingBatchRequests = resolve;
4023+
4024+
// Begin executing the promises.
4025+
promises = deferredRequests.map(e => e().catch(reject));
4026+
4027+
// Each promise generates an RPC payload, and it stores
4028+
// that payload, resolve function, and reject function
4029+
// in this._batchedRequests.
4030+
//
4031+
// This outer Promise is resolved only when all the entries
4032+
// in this._batchedRequests are created, at which
4033+
// point _resolvePendingBatchRequests is called.
4034+
});
4035+
4036+
assert(
4037+
this._batchedRequests.length === this._numRequestsToBatch,
4038+
'all requests were not properly batched',
4039+
);
4040+
4041+
// Now call the RPC batch request with the data.
4042+
try {
4043+
const unsafeRes = await this._rpcBatchRequest(
4044+
this._batchedRequests.map(e => e.params),
4045+
);
4046+
4047+
// Finally, resolve the promises created by deferredRequests with the appropriate data for each promise.
4048+
this._batchedRequests.forEach(({resolve}, i) => resolve(unsafeRes[i]));
4049+
} catch (err) {
4050+
// Propagate the error to the promises created by deferredRequests.
4051+
this._batchedRequests.forEach(({reject}) => reject(err));
4052+
}
4053+
4054+
// Await all promises so we return a list of the results from each one.
4055+
return Promise.all(promises);
4056+
}
4057+
40124058
/**
40134059
* @internal
40144060
*/

web3.js/test/connection.test.ts

+80
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,86 @@ describe('Connection', () => {
655655
expect(balance).to.be.at.least(0);
656656
});
657657

658+
it('get balance - batch a single request', async () => {
659+
const account = Keypair.generate();
660+
661+
await mockRpcBatchResponse({
662+
batch: [
663+
{
664+
methodName: 'getBalance',
665+
args: [account.publicKey.toBase58()],
666+
},
667+
],
668+
result: [
669+
{
670+
context: {
671+
slot: 11,
672+
},
673+
value: 5,
674+
},
675+
],
676+
});
677+
678+
const [balance] = await connection.performBatchRequest([
679+
() => connection.getBalance(account.publicKey),
680+
]);
681+
expect(balance).to.equal(5);
682+
});
683+
684+
it('get balance - batch multiple requests', async () => {
685+
const account1 = Keypair.generate();
686+
const account2 = Keypair.generate();
687+
const account3 = Keypair.generate();
688+
689+
await mockRpcBatchResponse({
690+
batch: [
691+
{
692+
methodName: 'getBalance',
693+
args: [account1.publicKey.toBase58()],
694+
},
695+
{
696+
methodName: 'getBalance',
697+
args: [account2.publicKey.toBase58()],
698+
},
699+
{
700+
methodName: 'getBalance',
701+
args: [account3.publicKey.toBase58()],
702+
},
703+
],
704+
result: [
705+
{
706+
context: {
707+
slot: 11,
708+
},
709+
value: 5,
710+
},
711+
{
712+
context: {
713+
slot: 11,
714+
},
715+
value: 10,
716+
},
717+
{
718+
context: {
719+
slot: 11,
720+
},
721+
value: 15,
722+
},
723+
],
724+
});
725+
726+
const [balance1, balance2, balance3] = await connection.performBatchRequest(
727+
[
728+
() => connection.getBalance(account1.publicKey),
729+
() => connection.getBalance(account2.publicKey),
730+
() => connection.getBalance(account3.publicKey),
731+
],
732+
);
733+
expect(balance1).to.equal(5);
734+
expect(balance2).to.equal(10);
735+
expect(balance3).to.equal(15);
736+
});
737+
658738
it('get inflation', async () => {
659739
await mockRpcResponse({
660740
method: 'getInflationGovernor',

0 commit comments

Comments
 (0)