From 17e1f5684a5170d24706fa7fd4bb11c35162d7d8 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Sun, 30 Mar 2025 13:19:44 +0200 Subject: [PATCH] Remove ws_resubscribe default value making testing easier --- Readme.md | 2 +- ethers/providers/jsonrpc/subscriptions.nim | 113 ++++++++++----------- testmodule/providers/testJsonRpc.nim | 3 +- testmodule/test.nimble | 2 +- 4 files changed, 58 insertions(+), 62 deletions(-) diff --git a/Readme.md b/Readme.md index 2c053ce..bf64fa3 100644 --- a/Readme.md +++ b/Readme.md @@ -209,7 +209,7 @@ Hardhat websockets workaround If you're working with Hardhat, you might encounter an issue where [websocket subscriptions stop working after 5 minutes](https://github.com/NomicFoundation/hardhat/issues/2053). -This library provides a workaround using the `--define:ws_resubscribe` option. When this symbol is defined, the subscriptions will automatically resubscribe after 240 seconds (4 minutes) by default. You can change this value using `--define:ws_resubscribe=180`. +This library provides a workaround using the `--define:ws_resubscribe=240` option. When this symbol is defined, the subscriptions will automatically resubscribe after 240 seconds (4 minutes). Contribution ------------ diff --git a/ethers/providers/jsonrpc/subscriptions.nim b/ethers/providers/jsonrpc/subscriptions.nim index 2f74013..bc6052d 100644 --- a/ethers/providers/jsonrpc/subscriptions.nim +++ b/ethers/providers/jsonrpc/subscriptions.nim @@ -14,9 +14,6 @@ import ./conversions export serde -# Default re-subscription period is 240 seconds (4 minutes) -const WsResubscribe {.intdefine.}: int = 240 - type JsonRpcSubscriptions* = ref object of RootObj client: RpcClient @@ -101,31 +98,14 @@ proc getCallback(subscriptions: JsonRpcSubscriptions, # Web sockets +# Default re-subscription period is seconds +const WsResubscribe {.intdefine.}: int = 0 + type WebSocketSubscriptions = ref object of JsonRpcSubscriptions logFiltersLock: AsyncLock - when defined(ws_resubscribe): - resubscribeFut: Future[void] - resubscribeInterval: int - -proc new*(_: type JsonRpcSubscriptions, - client: RpcWebSocketClient, - resubscribeInterval = WsResubscribe): JsonRpcSubscriptions = - when defined(ws_resubscribe): - let subscriptions = WebSocketSubscriptions(client: client, resubscribeInterval: resubscribeInterval) - else: - let subscriptions = WebSocketSubscriptions(client: client) - - proc subscriptionHandler(arguments: JsonNode) {.raises:[].} = - let id = arguments{"subscription"} or newJString("") - if callback =? subscriptions.getCallback(id): - callback(id, success(arguments)) - subscriptions.setMethodHandler("eth_subscription", subscriptionHandler) - - when defined(ws_resubscribe): - subscriptions.resubscribeFut = resubscribeWebsocketEventsOnTimeout(subscriptions) - - subscriptions + resubscribeFut: Future[void] + resubscribeInterval: int template withLock*(subscriptions: WebSocketSubscriptions, body: untyped) = if subscriptions.logFiltersLock.isNil: @@ -137,6 +117,54 @@ template withLock*(subscriptions: WebSocketSubscriptions, body: untyped) = finally: subscriptions.logFiltersLock.release() +# This is a workaround to manage the 5 minutes limit due to hardhat. +# See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064 +proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} = + if subscriptions.resubscribeInterval <= 0: + info "Skipping the resubscription because the interval is zero or negative", period = subscriptions.resubscribeInterval + else: + while true: + await sleepAsync(subscriptions.resubscribeInterval.seconds) + try: + withLock(subscriptions): + for id, callback in subscriptions.callbacks: + + var newId: JsonNode + if id in subscriptions.logFilters: + let filter = subscriptions.logFilters[id] + newId = await subscriptions.client.eth_subscribe("logs", filter) + subscriptions.logFilters[newId] = filter + subscriptions.logFilters.del(id) + else: + newId = await subscriptions.client.eth_subscribe("newHeads") + + subscriptions.callbacks[newId] = callback + subscriptions.callbacks.del(id) + discard await subscriptions.client.eth_unsubscribe(id) + except CancelledError as e: + raise e + except CatchableError as e: + error "WS resubscription failed" , error = e.msg + +proc new*(_: type JsonRpcSubscriptions, + client: RpcWebSocketClient, + resubscribeInterval = WsResubscribe): JsonRpcSubscriptions = + let subscriptions = WebSocketSubscriptions(client: client, resubscribeInterval: resubscribeInterval) + + proc subscriptionHandler(arguments: JsonNode) {.raises:[].} = + let id = arguments{"subscription"} or newJString("") + if callback =? subscriptions.getCallback(id): + callback(id, success(arguments)) + subscriptions.setMethodHandler("eth_subscription", subscriptionHandler) + + if resubscribeInterval > 0: + if resubscribeInterval >= 300: + info "Resubscription interval greater than 300 seconds is useless for hardhat workaround", resubscribeInterval = resubscribeInterval + + subscriptions.resubscribeFut = resubscribeWebsocketEventsOnTimeout(subscriptions) + + subscriptions + method subscribeBlocks(subscriptions: WebSocketSubscriptions, onBlock: BlockHandler): Future[JsonNode] @@ -190,39 +218,8 @@ method unsubscribe*(subscriptions: WebSocketSubscriptions, method close*(subscriptions: WebSocketSubscriptions) {.async: (raises: [CancelledError, SubscriptionError]).} = await procCall JsonRpcSubscriptions(subscriptions).close() - when defined(ws_resubscribe): - if not subscriptions.resubscribeFut.isNil: - await subscriptions.resubscribeFut.cancelAndWait() - -when defined(ws_resubscribe): - # This is a workaround to manage the 5 minutes limit due to hardhat. - # See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064 - proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} = - if subscriptions.resubscribeInterval <= 0: - info "Skipping the resubscription because the interval is zero or negative", period = subscriptions.resubscribeInterval - else: - while true: - await sleepAsync(subscriptions.resubscribeInterval.seconds) - try: - withLock(subscriptions): - for id, callback in subscriptions.callbacks: - - var newId: JsonNode - if id in subscriptions.logFilters: - let filter = subscriptions.logFilters[id] - newId = await subscriptions.client.eth_subscribe("logs", filter) - subscriptions.logFilters[newId] = filter - subscriptions.logFilters.del(id) - else: - newId = await subscriptions.client.eth_subscribe("newHeads") - - subscriptions.callbacks[newId] = callback - subscriptions.callbacks.del(id) - discard await subscriptions.client.eth_unsubscribe(id) - except CancelledError as e: - raise e - except CatchableError as e: - error "WS resubscription failed" , error = e.msg + if not subscriptions.resubscribeFut.isNil: + await subscriptions.resubscribeFut.cancelAndWait() # Polling diff --git a/testmodule/providers/testJsonRpc.nim b/testmodule/providers/testJsonRpc.nim index 9cac33c..660c921 100644 --- a/testmodule/providers/testJsonRpc.nim +++ b/testmodule/providers/testJsonRpc.nim @@ -1,8 +1,7 @@ import ./jsonrpc/testJsonRpcProvider import ./jsonrpc/testJsonRpcSigner import ./jsonrpc/testJsonRpcSubscriptions -when defined(ws_resubscribe): - import ./jsonrpc/testWsResubscription +import ./jsonrpc/testWsResubscription import ./jsonrpc/testConversions import ./jsonrpc/testErrors diff --git a/testmodule/test.nimble b/testmodule/test.nimble index a3e9037..b0b7f02 100644 --- a/testmodule/test.nimble +++ b/testmodule/test.nimble @@ -7,4 +7,4 @@ requires "asynctest >= 0.4.0 & < 0.5.0" task test, "Run the test suite": exec "nimble install -d -y" - exec "nim c --define:ws_resubscribe=0 -r test" \ No newline at end of file + exec "nim c -r test"