Skip to content

Commit 51f1f80

Browse files
committed
feat: subscriptions handlers get results
1 parent 1c2610a commit 51f1f80

File tree

5 files changed

+96
-42
lines changed

5 files changed

+96
-42
lines changed

ethers/contract.nim

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import std/macros
33
import std/sequtils
44
import pkg/chronicles
55
import pkg/chronos
6+
import pkg/questionable
67
import pkg/contractabi
78
import ./basics
89
import ./provider
@@ -35,11 +36,10 @@ type
3536
gasLimit*: ?UInt256
3637
CallOverrides* = ref object of TransactionOverrides
3738
blockTag*: ?BlockTag
38-
ContractError* = object of EthersError
3939
Confirmable* = object
4040
response*: ?TransactionResponse
4141
convert*: ConvertCustomErrors
42-
EventHandler*[E: Event] = proc(event: E) {.gcsafe, raises:[].}
42+
EventHandler*[E: Event] = proc(event: ?!E) {.gcsafe, raises:[].}
4343

4444
func new*(ContractType: type Contract,
4545
address: Address,
@@ -292,9 +292,13 @@ proc subscribe*[E: Event](contract: Contract,
292292
let topic = topic($E, E.fieldTypes).toArray
293293
let filter = EventFilter(address: contract.address, topics: @[topic])
294294

295-
proc logHandler(log: Log) {.raises: [].} =
295+
proc logHandler(logResult: ?!Log) {.raises: [].} =
296+
without log ?= logResult, err:
297+
handler(failure(e))
298+
return
299+
296300
if event =? E.decode(log.data, log.topics):
297-
handler(event)
301+
handler(success(event))
298302

299303
contract.provider.subscribe(filter, logHandler)
300304

ethers/errors.nim

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import ./basics
22

3-
type SolidityError* = object of EthersError
3+
type
4+
SolidityError* = object of EthersError
5+
ContractError* = object of EthersError
6+
SignerError* = object of EthersError
7+
SubscriptionError* = object of EthersError
8+
SubscriptionResult*[E] = Result[E, ref SubscriptionError]
9+
ProviderError* = object of EthersError
10+
data*: ?seq[byte]
11+
12+
template raiseSignerError*(message: string, parent: ref ProviderError = nil) =
13+
raise newException(SignerError, message, parent)
414

515
{.push raises:[].}
616

ethers/provider.nim

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pkg/chronicles
22
import pkg/serde
3+
import pkg/questionable
34
import ./basics
45
import ./transaction
56
import ./blocktag
@@ -13,8 +14,6 @@ export blocktag
1314

1415
type
1516
Provider* = ref object of RootObj
16-
ProviderError* = object of EthersError
17-
data*: ?seq[byte]
1817
EstimateGasError* = object of ProviderError
1918
transaction*: Transaction
2019
Subscription* = ref object of RootObj
@@ -56,8 +55,8 @@ type
5655
effectiveGasPrice*: ?UInt256
5756
status*: TransactionStatus
5857
transactionType* {.serialize("type"), deserialize("type").}: TransactionType
59-
LogHandler* = proc(log: Log) {.gcsafe, raises:[].}
60-
BlockHandler* = proc(blck: Block) {.gcsafe, raises:[].}
58+
LogHandler* = proc(log: SubscriptionResult[Log]) {.gcsafe, raises:[].}
59+
BlockHandler* = proc(blck: SubscriptionResult[Block]) {.gcsafe, raises:[].}
6160
Topic* = array[32, byte]
6261
Block* {.serialize.} = object
6362
number*: ?UInt256
@@ -227,21 +226,28 @@ proc confirm*(
227226
tx: TransactionResponse,
228227
confirmations = EthersDefaultConfirmations,
229228
timeout = EthersReceiptTimeoutBlks): Future[TransactionReceipt]
230-
{.async: (raises: [CancelledError, ProviderError, EthersError]).} =
229+
{.async: (raises: [CancelledError, ProviderError, SubscriptionError, EthersError]).} =
231230

232231
## Waits for a transaction to be mined and for the specified number of blocks
233232
## to pass since it was mined (confirmations).
234233
## A timeout, in blocks, can be specified that will raise an error if too many
235234
## blocks have passed without the tx having been mined.
236235

237236
var blockNumber: UInt256
237+
var blockSubscriptionError: ref SubscriptionError
238238
let blockEvent = newAsyncEvent()
239239

240240
proc onBlockNumber(number: UInt256) =
241241
blockNumber = number
242242
blockEvent.fire()
243243

244-
proc onBlock(blck: Block) =
244+
proc onBlock(blckResult: SubscriptionResult[Block]) =
245+
if blckResult.isErr:
246+
blockSubscriptionError = blckResult.error()
247+
blockEvent.fire()
248+
249+
let blck = blckResult.value
250+
245251
if number =? blck.number:
246252
onBlockNumber(number)
247253

@@ -255,6 +261,9 @@ proc confirm*(
255261
await blockEvent.wait()
256262
blockEvent.clear()
257263

264+
if not isNil(blockSubscriptionError):
265+
raise blockSubscriptionError
266+
258267
if blockNumber >= finish:
259268
await subscription.unsubscribe()
260269
raise newException(EthersError, "tx not mined before timeout")

ethers/providers/jsonrpc/subscriptions.nim

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import std/tables
22
import std/sequtils
33
import std/strutils
44
import pkg/chronos
5+
import pkg/questionable
56
import pkg/json_rpc/rpcclient
67
import ../../basics
8+
import ../../errors
79
import ../../provider
810
include ../../nimshims/hashes
911
import ./rpccalls
@@ -16,8 +18,7 @@ type
1618
callbacks: Table[JsonNode, SubscriptionCallback]
1719
methodHandlers: Table[string, MethodHandler]
1820
MethodHandler* = proc (j: JsonNode) {.gcsafe, raises: [].}
19-
SubscriptionCallback = proc(id, arguments: JsonNode) {.gcsafe, raises:[].}
20-
SubscriptionError* = object of EthersError
21+
SubscriptionCallback = proc(id, arguments: SubscriptionResult[JsonNode]) {.gcsafe, raises:[CancelledError].}
2122

2223
{.push raises:[].}
2324

@@ -53,7 +54,7 @@ proc setMethodHandler(
5354
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
5455
onBlock: BlockHandler):
5556
Future[JsonNode]
56-
{.async, base.} =
57+
{.async, base, raises: [CancelledError].} =
5758
raiseAssert "not implemented"
5859

5960
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
@@ -74,14 +75,16 @@ method close*(subscriptions: JsonRpcSubscriptions) {.async, base.} =
7475
await subscriptions.unsubscribe(id)
7576

7677
proc getCallback(subscriptions: JsonRpcSubscriptions,
77-
id: JsonNode): ?SubscriptionCallback =
78+
id: JsonNode): ?SubscriptionCallback {. raises:[].} =
7879
try:
7980
if not id.isNil and id in subscriptions.callbacks:
80-
subscriptions.callbacks[id].some
81+
try:
82+
return subscriptions.callbacks[id].some
83+
except: discard
8184
else:
82-
SubscriptionCallback.none
85+
return SubscriptionCallback.none
8386
except KeyError:
84-
SubscriptionCallback.none
87+
return SubscriptionCallback.none
8588

8689
# Web sockets
8790

@@ -95,17 +98,22 @@ proc new*(_: type JsonRpcSubscriptions,
9598
proc subscriptionHandler(arguments: JsonNode) {.raises:[].} =
9699
let id = arguments{"subscription"} or newJString("")
97100
if callback =? subscriptions.getCallback(id):
98-
callback(id, arguments)
101+
callback(id, success(arguments))
99102
subscriptions.setMethodHandler("eth_subscription", subscriptionHandler)
100103
subscriptions
101104

102105
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
103106
onBlock: BlockHandler):
104107
Future[JsonNode]
105-
{.async.} =
106-
proc callback(id, arguments: JsonNode) {.raises: [].} =
108+
{.async, raises: [].} =
109+
proc callback(id, argumentsResult: SubscriptionResult[JsonNode]) {.raises: [].} =
110+
if argumentsResult.isErr:
111+
onBlock(SubscriptionResult[Block].err(argumentsResult.error))
112+
return
113+
let arguments = argumentsResult.value
107114
if blck =? Block.fromJson(arguments{"result"}):
108-
onBlock(blck)
115+
onBlock(SubscriptionResult[Block].ok(blck))
116+
109117
let id = await subscriptions.client.eth_subscribe("newHeads")
110118
subscriptions.callbacks[id] = callback
111119
return id
@@ -115,9 +123,15 @@ method subscribeLogs(subscriptions: WebSocketSubscriptions,
115123
onLog: LogHandler):
116124
Future[JsonNode]
117125
{.async.} =
118-
proc callback(id, arguments: JsonNode) =
126+
proc callback(id, argumentsResult: SubscriptionResult[JsonNode]) =
127+
if argumentsResult.isErr:
128+
onLog(SubscriptionResult[Log].err(argumentsResult.error))
129+
return
130+
131+
let arguments = argumentsResult.value
119132
if log =? Log.fromJson(arguments{"result"}):
120-
onLog(log)
133+
onLog(SubscriptionResult[Log].ok(log))
134+
121135
let id = await subscriptions.client.eth_subscribe("logs", filter)
122136
subscriptions.callbacks[id] = callback
123137
return id
@@ -149,7 +163,7 @@ proc new*(_: type JsonRpcSubscriptions,
149163

150164
let subscriptions = PollingSubscriptions(client: client)
151165

152-
proc getChanges(originalId: JsonNode): Future[JsonNode] {.async.} =
166+
proc getChanges(originalId: JsonNode): Future[JsonNode] {.async, raises:[CancelledError, SubscriptionError].} =
153167
try:
154168
let mappedId = subscriptions.subscriptionMapping[originalId]
155169
let changes = await subscriptions.client.eth_getFilterChanges(mappedId)
@@ -175,12 +189,19 @@ proc new*(_: type JsonRpcSubscriptions,
175189
subscriptions.subscriptionMapping[originalId] = newId
176190
return await getChanges(originalId)
177191
else:
178-
raise e
192+
raise newException(SubscriptionError, "HTTP polling: There was an exception while getting subscription changes: " & e.msg, e)
179193

180-
proc poll(id: JsonNode) {.async.} =
181-
for change in await getChanges(id):
182-
if callback =? subscriptions.getCallback(id):
183-
callback(id, change)
194+
proc poll(id: JsonNode) {.async, raises: [CancelledError, SubscriptionError].} =
195+
without callback =? subscriptions.getCallback(id):
196+
return
197+
198+
try:
199+
for change in await getChanges(id):
200+
callback(id, success(change))
201+
except CancelledError as e:
202+
raise e
203+
except CatchableError as e:
204+
callback(id, failure(e))
184205

185206
proc poll {.async.} =
186207
untilCancelled:
@@ -198,16 +219,24 @@ method close*(subscriptions: PollingSubscriptions) {.async.} =
198219
method subscribeBlocks(subscriptions: PollingSubscriptions,
199220
onBlock: BlockHandler):
200221
Future[JsonNode]
201-
{.async.} =
222+
{.async, raises:[CancelledError].} =
202223

203-
proc getBlock(hash: BlockHash) {.async.} =
224+
proc getBlock(hash: BlockHash) {.async, raises:[CancelledError].} =
204225
try:
205226
if blck =? (await subscriptions.client.eth_getBlockByHash(hash, false)):
206-
onBlock(blck)
207-
except CatchableError:
208-
discard
227+
onBlock(SubscriptionResult[Block].ok(blck))
228+
except CancelledError as e:
229+
raise e
230+
except CatchableError as e:
231+
let wrappedErr = newException(SubscriptionError, "HTTP polling: There was an exception while getting subscription's block: " & e.msg, e)
232+
onBlock(SubscriptionResult[Block].err(wrappedErr))
233+
234+
proc callback(id, changeResult: SubscriptionResult[JsonNode]) {.raises:[CancelledError].} =
235+
if changeResult.isErr:
236+
onBlock(SubscriptionResult[Block].err(changeResult.error))
237+
return
209238

210-
proc callback(id, change: JsonNode) =
239+
let change = changeResult.value
211240
if hash =? BlockHash.fromJson(change):
212241
asyncSpawn getBlock(hash)
213242

@@ -222,9 +251,14 @@ method subscribeLogs(subscriptions: PollingSubscriptions,
222251
Future[JsonNode]
223252
{.async.} =
224253

225-
proc callback(id, change: JsonNode) =
254+
proc callback(id, changeResult: SubscriptionResult[JsonNode]) =
255+
if changeResult.isErr:
256+
onLog(SubscriptionResult[Log].err(changeResult.error))
257+
return
258+
259+
let change = changeResult.value
226260
if log =? Log.fromJson(change):
227-
onLog(log)
261+
onLog(SubscriptionResult[Log].ok(log))
228262

229263
let id = await subscriptions.client.eth_newFilter(filter)
230264
subscriptions.callbacks[id] = callback

ethers/signer.nim

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pkg/questionable
22
import ./basics
3+
import ./errors
34
import ./provider
45

56
export basics
@@ -9,10 +10,6 @@ export basics
910
type
1011
Signer* = ref object of RootObj
1112
populateLock: AsyncLock
12-
SignerError* = object of EthersError
13-
14-
template raiseSignerError(message: string, parent: ref ProviderError = nil) =
15-
raise newException(SignerError, message, parent)
1613

1714
template convertError(body) =
1815
try:

0 commit comments

Comments
 (0)