Skip to content

Commit f4d2574

Browse files
committed
feat: paginated logs + batching client
1 parent 744a089 commit f4d2574

File tree

7 files changed

+134
-138
lines changed

7 files changed

+134
-138
lines changed

src/config/config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@ import { createClassFromType, detectNetwork } from "../utils/index.js";
55
import { envConfig } from "./env.js";
66
import { ConfigSchema } from "./schema.js";
77

8+
// These limits work for DRPC and Alchemy
9+
const PAGE_SIZE: Record<NetworkType, bigint> = {
10+
Mainnet: 100_000n,
11+
Optimism: 500_000n,
12+
Arbitrum: 500_000n,
13+
Base: 500_000n,
14+
Sonic: 500_000n,
15+
};
16+
817
interface DynamicConfig {
918
readonly network: NetworkType;
1019
readonly chainId: number;
1120
readonly startBlock: bigint;
21+
readonly logsPageSize: bigint;
1222
}
1323

1424
const ConfigClass = createClassFromType<ConfigSchema & DynamicConfig>();
@@ -32,6 +42,7 @@ export class Config extends ConfigClass {
3242
startBlock,
3343
chainId: Number(chainId),
3444
network,
45+
logsPageSize: PAGE_SIZE[network],
3546
});
3647
}
3748

src/services/AddressProviderService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { Config } from "../config/index.js";
77
import { DI } from "../di.js";
88
import { type ILogger, Logger } from "../log/index.js";
99
import { TxParser } from "../utils/ethers-6-temp/txparser/index.js";
10-
import { getLogsSafe } from "../utils/getLogsSafe.js";
10+
import { getLogsPaginated } from "../utils/getLogsPaginated.js";
1111
import type Client from "./Client.js";
1212

1313
const AP_SERVICES = [
@@ -52,13 +52,14 @@ export class AddressProviderService {
5252

5353
const toBlock = await this.client.pub.getBlockNumber();
5454

55-
const logs = await getLogsSafe(this.client.pub, {
55+
const logs = await getLogsPaginated(this.client.logs, {
5656
address,
5757
event: getAbiItem({ abi: iAddressProviderV3Abi, name: "SetAddress" }),
5858
args: { key: AP_SERVICES.map(s => stringToHex(s, { size: 32 })) },
5959
fromBlock: AP_BLOCK_BY_NETWORK[this.config.network],
6060
toBlock,
6161
strict: true,
62+
pageSize: this.config.logsPageSize,
6263
});
6364

6465
for (const { args } of logs) {

src/services/Client.ts

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export default class Client {
100100
#anvilInfo: AnvilNodeInfo | null = null;
101101

102102
#publicClient?: PublicClient;
103+
#logsClient?: PublicClient;
103104

104105
#walletClient?: WalletClient<Transport, Chain, PrivateKeyAccount, undefined>;
105106

@@ -113,15 +114,7 @@ export default class Client {
113114
>;
114115

115116
public async launch(): Promise<void> {
116-
const { ethProviderRpcs, chainId, network, optimistic, privateKey } =
117-
this.config;
118-
const rpcs = ethProviderRpcs.map(url =>
119-
http(url, {
120-
timeout: optimistic ? 240_000 : 10_000,
121-
retryCount: optimistic ? 3 : undefined,
122-
}),
123-
);
124-
const transport = rpcs.length > 1 && !optimistic ? fallback(rpcs) : rpcs[0];
117+
const { chainId, network, optimistic, privateKey } = this.config;
125118
const chain = defineChain({
126119
...CHAINS[network],
127120
id: chainId,
@@ -130,13 +123,19 @@ export default class Client {
130123
this.#publicClient = createPublicClient({
131124
cacheTime: 0,
132125
chain,
133-
transport,
126+
transport: this.#createTransport(),
127+
pollingInterval: optimistic ? 25 : undefined,
128+
});
129+
this.#logsClient = createPublicClient({
130+
cacheTime: 0,
131+
chain,
132+
transport: this.#createTransport(true),
134133
pollingInterval: optimistic ? 25 : undefined,
135134
});
136135
this.#walletClient = createWalletClient({
137136
account: privateKeyToAccount(privateKey),
138137
chain,
139-
transport,
138+
transport: this.#createTransport(),
140139
pollingInterval: optimistic ? 25 : undefined,
141140
});
142141
try {
@@ -148,7 +147,7 @@ export default class Client {
148147
AnvilRPCSchema
149148
>({
150149
mode: "anvil",
151-
transport,
150+
transport: this.#createTransport(),
152151
chain,
153152
pollingInterval: 25,
154153
});
@@ -310,6 +309,13 @@ export default class Client {
310309
return this.#publicClient;
311310
}
312311

312+
public get logs(): PublicClient {
313+
if (!this.#logsClient) {
314+
throw new Error("logs client not initialized");
315+
}
316+
return this.#logsClient;
317+
}
318+
313319
public get wallet(): WalletClient<
314320
Transport,
315321
Chain,
@@ -354,4 +360,16 @@ export default class Client {
354360
}
355361
return BigInt(n);
356362
}
363+
364+
#createTransport(batch = false): Transport {
365+
const { ethProviderRpcs, optimistic } = this.config;
366+
const rpcs = ethProviderRpcs.map(url =>
367+
http(url, {
368+
timeout: optimistic ? 240_000 : 10_000,
369+
retryCount: optimistic ? 3 : undefined,
370+
batch,
371+
}),
372+
);
373+
return rpcs.length > 1 && !optimistic ? fallback(rpcs) : rpcs[0];
374+
}
357375
}

src/services/OracleServiceV3.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { DI } from "../di.js";
1414
import type { ILogger } from "../log/index.js";
1515
import { Logger } from "../log/index.js";
1616
import { TxParser } from "../utils/ethers-6-temp/txparser/index.js";
17-
import { getLogsSafe } from "../utils/getLogsSafe.js";
17+
import { getLogsPaginated } from "../utils/getLogsPaginated.js";
1818
import type { AddressProviderService } from "./AddressProviderService.js";
1919
import type Client from "./Client.js";
2020

@@ -54,7 +54,7 @@ interface OracleEntry {
5454

5555
const ORACLE_START_BLOCK: Record<NetworkType, bigint> = {
5656
Mainnet: 18797638n,
57-
Optimism: 116864678n, // not deployed yet, arbitrary block here
57+
Optimism: 118413958n,
5858
Arbitrum: 184650373n,
5959
Base: 12299805n, // not deployed yet, arbitrary block here
6060
Sonic: 8897028n, // not deployed yet, arbitrary block here
@@ -192,12 +192,13 @@ export default class OracleServiceV3 {
192192
return;
193193
}
194194
this.log.debug(`updating price feeds in [${this.#lastBlock}, ${toBlock}]`);
195-
let logs = await getLogsSafe(this.client.pub, {
195+
let logs = await getLogsPaginated(this.client.logs, {
196196
address: this.oracle,
197197
events: iPriceOracleV3EventsAbi,
198198
fromBlock: BigInt(this.#lastBlock),
199199
toBlock: BigInt(toBlock),
200200
strict: true,
201+
pageSize: this.config.logsPageSize,
201202
});
202203
// sort logs by blockNumber ASC, logIndex ASC
203204
// on sonic sometimes events are not in order

src/utils/getLogsPaginated.ts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import type { AbiEvent } from "abitype";
2+
import type {
3+
BlockNumber,
4+
Chain,
5+
Client,
6+
GetLogsParameters,
7+
GetLogsReturnType,
8+
MaybeAbiEventName,
9+
Transport,
10+
} from "viem";
11+
import { getLogs } from "viem/actions";
12+
13+
export type GetLogsPaginatedParameters<
14+
abiEvent extends AbiEvent | undefined = undefined,
15+
abiEvents extends
16+
| readonly AbiEvent[]
17+
| readonly unknown[]
18+
| undefined = abiEvent extends AbiEvent ? [abiEvent] : undefined,
19+
strict extends boolean | undefined = undefined,
20+
//
21+
_eventName extends string | undefined = MaybeAbiEventName<abiEvent>,
22+
> = GetLogsParameters<
23+
abiEvent,
24+
abiEvents,
25+
strict,
26+
BlockNumber,
27+
BlockNumber,
28+
_eventName
29+
> & {
30+
pageSize: bigint;
31+
};
32+
33+
export async function getLogsPaginated<
34+
chain extends Chain | undefined,
35+
const abiEvent extends AbiEvent | undefined = undefined,
36+
const abiEvents extends
37+
| readonly AbiEvent[]
38+
| readonly unknown[]
39+
| undefined = abiEvent extends AbiEvent ? [abiEvent] : undefined,
40+
strict extends boolean | undefined = undefined,
41+
>(
42+
client: Client<Transport, chain>,
43+
params: GetLogsPaginatedParameters<abiEvent, abiEvents, strict>,
44+
): Promise<
45+
GetLogsReturnType<abiEvent, abiEvents, strict, BlockNumber, BlockNumber>
46+
> {
47+
const from_ = params.fromBlock as bigint;
48+
const to_ = params.toBlock as bigint;
49+
const pageSize = params.pageSize;
50+
const requests: GetLogsParameters<
51+
abiEvent,
52+
abiEvents,
53+
strict,
54+
BlockNumber,
55+
BlockNumber
56+
>[] = [];
57+
for (let fromBlock = from_; fromBlock < to_; fromBlock += pageSize) {
58+
let toBlock = fromBlock + pageSize - 1n;
59+
if (toBlock > to_) {
60+
toBlock = to_;
61+
}
62+
requests.push({
63+
...params,
64+
fromBlock,
65+
toBlock,
66+
} as GetLogsParameters<
67+
abiEvent,
68+
abiEvents,
69+
strict,
70+
BlockNumber,
71+
BlockNumber
72+
>);
73+
}
74+
const responses = await Promise.all(
75+
requests.map(r =>
76+
getLogs<chain, abiEvent, abiEvents, strict, BlockNumber, BlockNumber>(
77+
client,
78+
r,
79+
),
80+
),
81+
);
82+
const events = responses.flat();
83+
84+
return events;
85+
}

src/utils/getLogsSafe.ts

Lines changed: 0 additions & 120 deletions
This file was deleted.

src/utils/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export * from "./bigint-utils.js";
33
export * from "./detect-network.js";
44
export * from "./etherscan.js";
55
export * from "./formatters.js";
6-
export * from "./getLogsSafe.js";
6+
export * from "./getLogsPaginated.js";
77
export * from "./retry.js";
88
export * from "./simulateMulticall.js";
99
export * from "./types.js";

0 commit comments

Comments
 (0)