2023-06-22 10:47:19 +00:00
|
|
|
import std/tables
|
2023-06-27 12:10:12 +00:00
|
|
|
import std/sequtils
|
2023-06-22 10:47:19 +00:00
|
|
|
import pkg/chronos
|
|
|
|
import pkg/json_rpc/rpcclient
|
|
|
|
import ../../basics
|
|
|
|
import ../../provider
|
|
|
|
import ./rpccalls
|
|
|
|
import ./conversions
|
2023-06-27 12:44:03 +00:00
|
|
|
import ./looping
|
2023-06-22 10:47:19 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
JsonRpcSubscriptions* = ref object of RootObj
|
|
|
|
client: RpcClient
|
|
|
|
callbacks: Table[JsonNode, SubscriptionCallback]
|
|
|
|
JsonRpcSubscription = ref object of Subscription
|
|
|
|
subscriptions: JsonRpcSubscriptions
|
|
|
|
id: JsonNode
|
|
|
|
SubscriptionCallback = proc(id, arguments: JsonNode) {.gcsafe, upraises:[].}
|
|
|
|
|
|
|
|
method subscribeBlocks*(subscriptions: JsonRpcSubscriptions,
|
|
|
|
onBlock: BlockHandler):
|
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async, base.} =
|
|
|
|
raiseAssert "not implemented"
|
|
|
|
|
|
|
|
method subscribeLogs*(subscriptions: JsonRpcSubscriptions,
|
|
|
|
filter: Filter,
|
|
|
|
onLog: LogHandler):
|
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async, base.} =
|
|
|
|
raiseAssert "not implemented"
|
|
|
|
|
|
|
|
method unsubscribe(subscriptions: JsonRpcSubscriptions,
|
|
|
|
id: JsonNode)
|
|
|
|
{.async, base.} =
|
|
|
|
raiseAssert "not implemented"
|
|
|
|
|
2023-06-27 14:45:38 +00:00
|
|
|
method close*(subscriptions: JsonRpcSubscriptions) {.async, base.} =
|
2023-06-27 14:40:29 +00:00
|
|
|
let ids = toSeq subscriptions.callbacks.keys
|
|
|
|
for id in ids:
|
|
|
|
await subscriptions.unsubscribe(id)
|
|
|
|
|
2023-06-22 10:47:19 +00:00
|
|
|
method unsubscribe(subscription: JsonRpcSubscription) {.async.} =
|
2023-06-27 12:07:36 +00:00
|
|
|
let subscriptions = subscription.subscriptions
|
|
|
|
let id = subscription.id
|
|
|
|
await subscriptions.unsubscribe(id)
|
2023-06-22 10:47:19 +00:00
|
|
|
|
|
|
|
proc getCallback(subscriptions: JsonRpcSubscriptions,
|
|
|
|
id: JsonNode): ?SubscriptionCallback =
|
|
|
|
try:
|
|
|
|
if subscriptions.callbacks.hasKey(id):
|
|
|
|
subscriptions.callbacks[id].some
|
|
|
|
else:
|
|
|
|
SubscriptionCallback.none
|
|
|
|
except Exception:
|
|
|
|
SubscriptionCallback.none
|
|
|
|
|
|
|
|
# Web sockets
|
|
|
|
|
|
|
|
type
|
|
|
|
WebSocketSubscriptions = ref object of JsonRpcSubscriptions
|
|
|
|
|
2023-06-27 12:07:36 +00:00
|
|
|
proc new*(_: type JsonRpcSubscriptions,
|
|
|
|
client: RpcWebSocketClient): JsonRpcSubscriptions =
|
|
|
|
let subscriptions = WebSocketSubscriptions(client: client)
|
|
|
|
proc subscriptionHandler(arguments: JsonNode) {.upraises:[].} =
|
|
|
|
if id =? arguments["subscription"].catch and
|
|
|
|
callback =? subscriptions.getCallback(id):
|
|
|
|
callback(id, arguments)
|
|
|
|
client.setMethodHandler("eth_subscription", subscriptionHandler)
|
|
|
|
subscriptions
|
|
|
|
|
2023-06-22 10:47:19 +00:00
|
|
|
method subscribeBlocks(subscriptions: WebSocketSubscriptions,
|
|
|
|
onBlock: BlockHandler):
|
2023-06-27 12:07:36 +00:00
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async.} =
|
2023-06-22 10:47:19 +00:00
|
|
|
proc callback(id, arguments: JsonNode) =
|
|
|
|
if blck =? Block.fromJson(arguments["result"]).catch:
|
|
|
|
asyncSpawn onBlock(blck)
|
|
|
|
let id = await subscriptions.client.eth_subscribe("newHeads")
|
|
|
|
subscriptions.callbacks[id] = callback
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
|
|
|
|
method subscribeLogs(subscriptions: WebSocketSubscriptions,
|
|
|
|
filter: Filter,
|
|
|
|
onLog: LogHandler):
|
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async.} =
|
|
|
|
proc callback(id, arguments: JsonNode) =
|
|
|
|
if log =? Log.fromJson(arguments["result"]).catch:
|
|
|
|
onLog(log)
|
|
|
|
let id = await subscriptions.client.eth_subscribe("logs", filter)
|
|
|
|
subscriptions.callbacks[id] = callback
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
|
|
|
|
method unsubscribe(subscriptions: WebSocketSubscriptions,
|
|
|
|
id: JsonNode)
|
|
|
|
{.async.} =
|
|
|
|
subscriptions.callbacks.del(id)
|
|
|
|
discard await subscriptions.client.eth_unsubscribe(id)
|
|
|
|
|
|
|
|
# Polling
|
|
|
|
|
|
|
|
type
|
|
|
|
PollingSubscriptions = ref object of JsonRpcSubscriptions
|
2023-06-27 14:45:38 +00:00
|
|
|
polling: Future[void]
|
2023-06-22 10:47:19 +00:00
|
|
|
|
2023-06-27 12:10:12 +00:00
|
|
|
proc new*(_: type JsonRpcSubscriptions,
|
2023-06-27 12:33:14 +00:00
|
|
|
client: RpcHttpClient,
|
|
|
|
pollingInterval = 4.seconds): JsonRpcSubscriptions =
|
2023-06-27 12:10:12 +00:00
|
|
|
|
|
|
|
let subscriptions = PollingSubscriptions(client: client)
|
|
|
|
|
2023-06-27 12:56:45 +00:00
|
|
|
proc getChanges(id: JsonNode): Future[JsonNode] {.async.} =
|
|
|
|
try:
|
|
|
|
return await subscriptions.client.eth_getFilterChanges(id)
|
|
|
|
except CatchableError:
|
|
|
|
return newJArray()
|
|
|
|
|
2023-06-27 12:10:12 +00:00
|
|
|
proc poll(id: JsonNode) {.async.} =
|
2023-06-27 12:56:45 +00:00
|
|
|
for change in await getChanges(id):
|
2023-06-27 12:10:12 +00:00
|
|
|
if callback =? subscriptions.getCallback(id):
|
|
|
|
callback(id, change)
|
|
|
|
|
|
|
|
proc poll {.async.} =
|
2023-06-27 12:44:03 +00:00
|
|
|
untilCancelled:
|
|
|
|
for id in toSeq subscriptions.callbacks.keys:
|
|
|
|
await poll(id)
|
|
|
|
await sleepAsync(pollingInterval)
|
2023-06-27 12:10:12 +00:00
|
|
|
|
2023-06-27 14:45:38 +00:00
|
|
|
subscriptions.polling = poll()
|
2023-06-27 12:10:12 +00:00
|
|
|
subscriptions
|
|
|
|
|
2023-06-27 14:45:38 +00:00
|
|
|
method close*(subscriptions: PollingSubscriptions) {.async.} =
|
|
|
|
await subscriptions.polling.cancelAndWait()
|
|
|
|
await procCall JsonRpcSubscriptions(subscriptions).close()
|
|
|
|
|
2023-06-27 12:10:12 +00:00
|
|
|
method subscribeBlocks(subscriptions: PollingSubscriptions,
|
|
|
|
onBlock: BlockHandler):
|
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async.} =
|
|
|
|
|
|
|
|
proc getBlock(hash: BlockHash) {.async.} =
|
2023-06-27 13:24:29 +00:00
|
|
|
try:
|
|
|
|
if blck =? (await subscriptions.client.eth_getBlockByHash(hash, false)):
|
|
|
|
await onBlock(blck)
|
|
|
|
except CatchableError:
|
|
|
|
discard
|
2023-06-27 12:10:12 +00:00
|
|
|
|
|
|
|
proc callback(id, change: JsonNode) =
|
|
|
|
if hash =? BlockHash.fromJson(change).catch:
|
|
|
|
asyncSpawn getBlock(hash)
|
|
|
|
|
|
|
|
let id = await subscriptions.client.eth_newBlockFilter()
|
|
|
|
subscriptions.callbacks[id] = callback
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
|
2023-06-27 13:56:57 +00:00
|
|
|
method subscribeLogs(subscriptions: PollingSubscriptions,
|
|
|
|
filter: Filter,
|
|
|
|
onLog: LogHandler):
|
|
|
|
Future[JsonRpcSubscription]
|
|
|
|
{.async.} =
|
|
|
|
|
|
|
|
proc callback(id, change: JsonNode) =
|
|
|
|
if log =? Log.fromJson(change).catch:
|
|
|
|
onLog(log)
|
|
|
|
|
|
|
|
let id = await subscriptions.client.eth_newFilter(filter)
|
|
|
|
subscriptions.callbacks[id] = callback
|
|
|
|
return JsonRpcSubscription(subscriptions: subscriptions, id: id)
|
|
|
|
|
2023-06-27 12:10:12 +00:00
|
|
|
method unsubscribe(subscriptions: PollingSubscriptions,
|
|
|
|
id: JsonNode)
|
|
|
|
{.async.} =
|
|
|
|
subscriptions.callbacks.del(id)
|
|
|
|
discard await subscriptions.client.eth_uninstallFilter(id)
|