Skip to content

Commit 02583f8

Browse files
authored
fix(rpc): Fix issue with block number polling (#2712)
1 parent 14cecc9 commit 02583f8

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

.changeset/olive-adults-leave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
fix `watchBlockNumber` polling when fully unsubscribing and later re-subscribing
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
import { TEST_CLIENT } from "../../test/src/test-clients.js";
3+
import { baseSepolia } from "../chains/chain-definitions/base-sepolia.js";
4+
import { wait } from "../utils/promise/wait.js";
5+
import { watchBlockNumber } from "./watchBlockNumber.js";
6+
7+
describe.runIf(process.env.TW_SECRET_KEY)("watch block number", () => {
8+
const onNewBlockNumber = vi.fn();
9+
const onNewBlockNumber2 = vi.fn();
10+
11+
beforeEach(() => {
12+
onNewBlockNumber.mockClear();
13+
onNewBlockNumber2.mockClear();
14+
});
15+
16+
it("should set up the watcher", () => {
17+
const onNewBlockNumber = vi.fn();
18+
const unwatch = watchBlockNumber({
19+
client: TEST_CLIENT,
20+
chain: baseSepolia,
21+
onNewBlockNumber,
22+
});
23+
24+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
25+
expect(unwatch).toBeTypeOf("function");
26+
27+
unwatch();
28+
});
29+
30+
it("should call the onNewBlockNumber callback", async () => {
31+
const unwatch = watchBlockNumber({
32+
client: TEST_CLIENT,
33+
chain: baseSepolia,
34+
onNewBlockNumber,
35+
});
36+
37+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
38+
39+
// wait for 5 seconds which should always be sufficient for a new block to be mined
40+
await wait(5000);
41+
42+
expect(onNewBlockNumber).toHaveBeenCalled();
43+
44+
unwatch();
45+
});
46+
47+
it("should re-use the same watcher for multiple calls", async () => {
48+
const unwatch = watchBlockNumber({
49+
client: TEST_CLIENT,
50+
chain: baseSepolia,
51+
onNewBlockNumber,
52+
});
53+
const unwatch2 = watchBlockNumber({
54+
client: TEST_CLIENT,
55+
chain: baseSepolia,
56+
onNewBlockNumber: onNewBlockNumber2,
57+
});
58+
59+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
60+
expect(onNewBlockNumber2).toHaveBeenCalledTimes(0);
61+
62+
// wait for 5 seconds which should always be sufficient for a new block to be mined
63+
await wait(5000);
64+
65+
expect(onNewBlockNumber).toHaveBeenCalled();
66+
expect(onNewBlockNumber2).toHaveBeenCalled();
67+
68+
// TODO: ensure that unwatch and unwatch2 are acting ont he same poller
69+
70+
unwatch();
71+
unwatch2();
72+
});
73+
74+
it("should stop polling when unsubscribed", async () => {
75+
const unwatch = watchBlockNumber({
76+
client: TEST_CLIENT,
77+
chain: baseSepolia,
78+
onNewBlockNumber,
79+
});
80+
81+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
82+
83+
// wait for 5 seconds which should always be sufficient for a new block to be mined
84+
await wait(5000);
85+
86+
expect(onNewBlockNumber).toHaveBeenCalled();
87+
88+
onNewBlockNumber.mockClear();
89+
90+
unwatch();
91+
92+
// wait for 5 seconds which should always be sufficient for a new block to be mined
93+
await wait(5000);
94+
95+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
96+
});
97+
98+
it("should re-start from a fresh block when fully unsubscribed and re-subscribed", async () => {
99+
const unwatch = watchBlockNumber({
100+
client: TEST_CLIENT,
101+
chain: baseSepolia,
102+
onNewBlockNumber,
103+
});
104+
105+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
106+
107+
// wait for 5 seconds which should always be sufficient for a new block to be mined
108+
await wait(5000);
109+
110+
expect(onNewBlockNumber).toHaveBeenCalled();
111+
112+
const lastBlockNumber = onNewBlockNumber.mock.lastCall?.[0];
113+
114+
onNewBlockNumber.mockClear();
115+
116+
unwatch();
117+
118+
// wait for 5 seconds which should always be sufficient for a new block to be mined
119+
await wait(5000);
120+
121+
expect(onNewBlockNumber).toHaveBeenCalledTimes(0);
122+
123+
const unwatch2 = watchBlockNumber({
124+
client: TEST_CLIENT,
125+
chain: baseSepolia,
126+
onNewBlockNumber,
127+
});
128+
129+
// wait for 5 seconds which should always be sufficient for a new block to be mined
130+
await wait(5000);
131+
132+
expect(onNewBlockNumber).toHaveBeenCalled();
133+
134+
const firstBlockNumber = onNewBlockNumber.mock.calls[0]?.[0];
135+
136+
// we need to have skipped blocks here
137+
expect(firstBlockNumber).toBeGreaterThan(lastBlockNumber + 1n);
138+
139+
unwatch2();
140+
});
141+
});

packages/thirdweb/src/rpc/watchBlockNumber.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ function createBlockNumberPoller(
4949
async function poll() {
5050
// stop polling if there are no more subscriptions
5151
if (!isActive) {
52+
lastBlockNumber = undefined;
53+
lastBlockAt = undefined;
5254
return;
5355
}
5456
const blockNumber = await eth_blockNumber(rpcRequest);
@@ -112,6 +114,8 @@ function createBlockNumberPoller(
112114
subscribers = subscribers.filter((fn) => fn !== callBack);
113115
// if the new subscribers Array is empty (aka we were the last subscriber) -> stop polling
114116
if (subscribers.length === 0) {
117+
lastBlockNumber = undefined;
118+
lastBlockAt = undefined;
115119
isActive = false;
116120
}
117121
};

0 commit comments

Comments
 (0)