mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-07 16:13:06 +00:00
Convert retryable RPC/HTTP errors to RpcNetworkError type in ethers
Converts specific errors to RpcNetworkError, which can be bubbled to applications at a higher level and retried on the network (eg with exponential backoff) until resolved or timed out.
This commit is contained in:
parent
bbced46733
commit
af35395ace
@ -69,7 +69,7 @@ func addOverridesParameter*(procedure: var NimNode) =
|
|||||||
func addAsyncPragma*(procedure: var NimNode) =
|
func addAsyncPragma*(procedure: var NimNode) =
|
||||||
procedure.addPragma nnkExprColonExpr.newTree(
|
procedure.addPragma nnkExprColonExpr.newTree(
|
||||||
quote do: async,
|
quote do: async,
|
||||||
quote do: (raises: [CancelledError, ProviderError, EthersError])
|
quote do: (raises: [CancelledError, ProviderError, EthersError, RpcNetworkError])
|
||||||
)
|
)
|
||||||
|
|
||||||
func addUsedPragma*(procedure: var NimNode) =
|
func addUsedPragma*(procedure: var NimNode) =
|
||||||
|
|||||||
@ -34,13 +34,15 @@ proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
|||||||
|
|
||||||
proc call(
|
proc call(
|
||||||
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
provider: Provider, transaction: Transaction, overrides: TransactionOverrides
|
||||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
if overrides of CallOverrides and blockTag =? CallOverrides(overrides).blockTag:
|
||||||
await provider.call(transaction, blockTag)
|
await provider.call(transaction, blockTag)
|
||||||
else:
|
else:
|
||||||
await provider.call(transaction)
|
await provider.call(transaction)
|
||||||
|
|
||||||
proc callTransaction*(call: ContractCall) {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
proc callTransaction*(
|
||||||
|
call: ContractCall
|
||||||
|
) {.async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
var transaction = createTransaction(call)
|
var transaction = createTransaction(call)
|
||||||
|
|
||||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
if signer =? call.contract.signer and transaction.sender.isNone:
|
||||||
@ -48,7 +50,10 @@ proc callTransaction*(call: ContractCall) {.async: (raises: [ProviderError, Sign
|
|||||||
|
|
||||||
discard await call.contract.provider.call(transaction, call.overrides)
|
discard await call.contract.provider.call(transaction, call.overrides)
|
||||||
|
|
||||||
proc callTransaction*(call: ContractCall, ReturnType: type): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError, CancelledError]).} =
|
proc callTransaction*(
|
||||||
|
call: ContractCall,
|
||||||
|
ReturnType: type
|
||||||
|
): Future[ReturnType] {.async: (raises: [ProviderError, SignerError, ContractError, CancelledError, RpcNetworkError]).} =
|
||||||
var transaction = createTransaction(call)
|
var transaction = createTransaction(call)
|
||||||
|
|
||||||
if signer =? call.contract.signer and transaction.sender.isNone:
|
if signer =? call.contract.signer and transaction.sender.isNone:
|
||||||
@ -57,7 +62,9 @@ proc callTransaction*(call: ContractCall, ReturnType: type): Future[ReturnType]
|
|||||||
let response = await call.contract.provider.call(transaction, call.overrides)
|
let response = await call.contract.provider.call(transaction, call.overrides)
|
||||||
return decodeResponse(ReturnType, response)
|
return decodeResponse(ReturnType, response)
|
||||||
|
|
||||||
proc sendTransaction*(call: ContractCall): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError]).} =
|
proc sendTransaction*(
|
||||||
|
call: ContractCall
|
||||||
|
): Future[?TransactionResponse] {.async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
if signer =? call.contract.signer:
|
if signer =? call.contract.signer:
|
||||||
withLock(signer):
|
withLock(signer):
|
||||||
let transaction = createTransaction(call)
|
let transaction = createTransaction(call)
|
||||||
|
|||||||
@ -7,6 +7,10 @@ type
|
|||||||
SubscriptionError* = object of EthersError
|
SubscriptionError* = object of EthersError
|
||||||
ProviderError* = object of EthersError
|
ProviderError* = object of EthersError
|
||||||
data*: ?seq[byte]
|
data*: ?seq[byte]
|
||||||
|
RpcNetworkError* = object of EthersError
|
||||||
|
RpcHttpErrorResponse* = object of RpcNetworkError
|
||||||
|
RequestLimitError* = object of RpcHttpErrorResponse
|
||||||
|
RequestTimeoutError* = object of RpcHttpErrorResponse
|
||||||
|
|
||||||
{.push raises:[].}
|
{.push raises:[].}
|
||||||
|
|
||||||
@ -16,3 +20,7 @@ proc toErr*[E1: ref CatchableError, E2: EthersError](
|
|||||||
msg: string = e1.msg): ref E2 =
|
msg: string = e1.msg): ref E2 =
|
||||||
|
|
||||||
return newException(E2, msg, e1)
|
return newException(E2, msg, e1)
|
||||||
|
|
||||||
|
proc raiseNetworkError*(
|
||||||
|
error: ref CatchableError) {.raises: [RpcNetworkError].} =
|
||||||
|
raise newException(RpcNetworkError, error.msg, error)
|
||||||
|
|||||||
@ -103,84 +103,86 @@ func toTransaction*(past: PastTransaction): Transaction =
|
|||||||
|
|
||||||
method getBlockNumber*(
|
method getBlockNumber*(
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getBlock*(
|
method getBlock*(
|
||||||
provider: Provider, tag: BlockTag
|
provider: Provider, tag: BlockTag
|
||||||
): Future[?Block] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[?Block] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method call*(
|
method call*(
|
||||||
provider: Provider, tx: Transaction, blockTag = BlockTag.latest
|
provider: Provider, tx: Transaction, blockTag = BlockTag.latest
|
||||||
): Future[seq[byte]] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[seq[byte]] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: Provider, address: Address, blockTag = BlockTag.latest
|
provider: Provider, address: Address, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransaction*(
|
method getTransaction*(
|
||||||
provider: Provider, txHash: TransactionHash
|
provider: Provider, txHash: TransactionHash
|
||||||
): Future[?PastTransaction] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[?PastTransaction] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getTransactionReceipt*(
|
method getTransactionReceipt*(
|
||||||
provider: Provider, txHash: TransactionHash
|
provider: Provider, txHash: TransactionHash
|
||||||
): Future[?TransactionReceipt] {.
|
): Future[?TransactionReceipt] {.
|
||||||
base, async: (raises: [ProviderError, CancelledError])
|
base, async: (raises: [ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
provider: Provider, rawTransaction: seq[byte]
|
provider: Provider, rawTransaction: seq[byte]
|
||||||
): Future[TransactionResponse] {.
|
): Future[TransactionResponse] {.
|
||||||
base, async: (raises: [ProviderError, CancelledError])
|
base, async: (raises: [ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getLogs*(
|
method getLogs*(
|
||||||
provider: Provider, filter: EventFilter
|
provider: Provider, filter: EventFilter
|
||||||
): Future[seq[Log]] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[seq[Log]] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method estimateGas*(
|
method estimateGas*(
|
||||||
provider: Provider, transaction: Transaction, blockTag = BlockTag.latest
|
provider: Provider, transaction: Transaction, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getChainId*(
|
method getChainId*(
|
||||||
provider: Provider
|
provider: Provider
|
||||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: Provider, filter: EventFilter, callback: LogHandler
|
provider: Provider, filter: EventFilter, callback: LogHandler
|
||||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: Provider, callback: BlockHandler
|
provider: Provider, callback: BlockHandler
|
||||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method unsubscribe*(
|
method unsubscribe*(
|
||||||
subscription: Subscription
|
subscription: Subscription
|
||||||
) {.base, async: (raises: [ProviderError, CancelledError]).} =
|
) {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method isSyncing*(provider: Provider): Future[bool] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
method isSyncing*(
|
||||||
|
provider: Provider
|
||||||
|
): Future[bool] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
proc replay*(
|
proc replay*(
|
||||||
provider: Provider, tx: Transaction, blockNumber: UInt256
|
provider: Provider, tx: Transaction, blockNumber: UInt256
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
# Replay transaction at block. Useful for fetching revert reasons, which will
|
# Replay transaction at block. Useful for fetching revert reasons, which will
|
||||||
# be present in the raised error message. The replayed block number should
|
# be present in the raised error message. The replayed block number should
|
||||||
# include the state of the chain in the block previous to the block in which
|
# include the state of the chain in the block previous to the block in which
|
||||||
@ -193,7 +195,7 @@ proc replay*(
|
|||||||
|
|
||||||
proc ensureSuccess(
|
proc ensureSuccess(
|
||||||
provider: Provider, receipt: TransactionReceipt
|
provider: Provider, receipt: TransactionReceipt
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
## If the receipt.status is Failed, the tx is replayed to obtain a revert
|
||||||
## reason, after which a ProviderError with the revert reason is raised.
|
## reason, after which a ProviderError with the revert reason is raised.
|
||||||
## If no revert reason was obtained
|
## If no revert reason was obtained
|
||||||
@ -241,7 +243,7 @@ proc confirm*(
|
|||||||
if number > blockNumber:
|
if number > blockNumber:
|
||||||
blockNumber = number
|
blockNumber = number
|
||||||
blockEvent.fire()
|
blockEvent.fire()
|
||||||
except ProviderError, CancelledError:
|
except ProviderError, CancelledError, RpcNetworkError:
|
||||||
# there's nothing we can do here
|
# there's nothing we can do here
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|||||||
@ -56,7 +56,7 @@ proc new*(
|
|||||||
var client: RpcClient
|
var client: RpcClient
|
||||||
var subscriptions: JsonRpcSubscriptions
|
var subscriptions: JsonRpcSubscriptions
|
||||||
|
|
||||||
proc initialize() {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
proc initialize() {.async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
case parseUri(url).scheme
|
case parseUri(url).scheme
|
||||||
of "ws", "wss":
|
of "ws", "wss":
|
||||||
@ -65,7 +65,7 @@ proc new*(
|
|||||||
client = websocket
|
client = websocket
|
||||||
subscriptions = JsonRpcSubscriptions.new(websocket)
|
subscriptions = JsonRpcSubscriptions.new(websocket)
|
||||||
else:
|
else:
|
||||||
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
let http = newRpcHttpClient()
|
||||||
await http.connect(url)
|
await http.connect(url)
|
||||||
client = http
|
client = http
|
||||||
subscriptions = JsonRpcSubscriptions.new(http,
|
subscriptions = JsonRpcSubscriptions.new(http,
|
||||||
@ -73,14 +73,14 @@ proc new*(
|
|||||||
subscriptions.start()
|
subscriptions.start()
|
||||||
|
|
||||||
proc awaitClient(): Future[RpcClient] {.
|
proc awaitClient(): Future[RpcClient] {.
|
||||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
return client
|
return client
|
||||||
|
|
||||||
proc awaitSubscriptions(): Future[JsonRpcSubscriptions] {.
|
proc awaitSubscriptions(): Future[JsonRpcSubscriptions] {.
|
||||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
convertError:
|
convertError:
|
||||||
await initialized
|
await initialized
|
||||||
@ -91,28 +91,30 @@ proc new*(
|
|||||||
|
|
||||||
proc callImpl(
|
proc callImpl(
|
||||||
client: RpcClient, call: string, args: JsonNode
|
client: RpcClient, call: string, args: JsonNode
|
||||||
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError, JsonRpcError]).} =
|
||||||
try:
|
try:
|
||||||
let response = await client.call(call, %args)
|
let response = await client.call(call, %args)
|
||||||
without json =? JsonNode.fromJson(response.string), error:
|
without json =? JsonNode.fromJson(response.string), error:
|
||||||
raiseJsonRpcProviderError "Failed to parse response '" & response.string & "': " &
|
raiseJsonRpcProviderError error, "Failed to parse response '" & response.string & "': " &
|
||||||
error.msg
|
error.msg
|
||||||
return json
|
return json
|
||||||
except CancelledError as error:
|
except CancelledError as error:
|
||||||
raise error
|
raise error
|
||||||
|
except JsonRpcError as error:
|
||||||
|
raise error
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
raiseJsonRpcProviderError error.msg
|
raiseJsonRpcProviderError error
|
||||||
|
|
||||||
proc send*(
|
proc send*(
|
||||||
provider: JsonRpcProvider, call: string, arguments: seq[JsonNode] = @[]
|
provider: JsonRpcProvider, call: string, arguments: seq[JsonNode] = @[]
|
||||||
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.callImpl(call, %arguments)
|
return await client.callImpl(call, %arguments)
|
||||||
|
|
||||||
proc listAccounts*(
|
proc listAccounts*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_accounts()
|
return await client.eth_accounts()
|
||||||
@ -125,56 +127,56 @@ proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
|||||||
|
|
||||||
method getBlockNumber*(
|
method getBlockNumber*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_blockNumber()
|
return await client.eth_blockNumber()
|
||||||
|
|
||||||
method getBlock*(
|
method getBlock*(
|
||||||
provider: JsonRpcProvider, tag: BlockTag
|
provider: JsonRpcProvider, tag: BlockTag
|
||||||
): Future[?Block] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[?Block] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getBlockByNumber(tag, false)
|
return await client.eth_getBlockByNumber(tag, false)
|
||||||
|
|
||||||
method call*(
|
method call*(
|
||||||
provider: JsonRpcProvider, tx: Transaction, blockTag = BlockTag.latest
|
provider: JsonRpcProvider, tx: Transaction, blockTag = BlockTag.latest
|
||||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_call(tx, blockTag)
|
return await client.eth_call(tx, blockTag)
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_gasPrice()
|
return await client.eth_gasPrice()
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionCount(address, blockTag)
|
return await client.eth_getTransactionCount(address, blockTag)
|
||||||
|
|
||||||
method getTransaction*(
|
method getTransaction*(
|
||||||
provider: JsonRpcProvider, txHash: TransactionHash
|
provider: JsonRpcProvider, txHash: TransactionHash
|
||||||
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionByHash(txHash)
|
return await client.eth_getTransactionByHash(txHash)
|
||||||
|
|
||||||
method getTransactionReceipt*(
|
method getTransactionReceipt*(
|
||||||
provider: JsonRpcProvider, txHash: TransactionHash
|
provider: JsonRpcProvider, txHash: TransactionHash
|
||||||
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
return await client.eth_getTransactionReceipt(txHash)
|
return await client.eth_getTransactionReceipt(txHash)
|
||||||
|
|
||||||
method getLogs*(
|
method getLogs*(
|
||||||
provider: JsonRpcProvider, filter: EventFilter
|
provider: JsonRpcProvider, filter: EventFilter
|
||||||
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let logsJson =
|
let logsJson =
|
||||||
@ -196,7 +198,7 @@ method estimateGas*(
|
|||||||
provider: JsonRpcProvider,
|
provider: JsonRpcProvider,
|
||||||
transaction: Transaction,
|
transaction: Transaction,
|
||||||
blockTag = BlockTag.latest,
|
blockTag = BlockTag.latest,
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
try:
|
try:
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
@ -211,7 +213,7 @@ method estimateGas*(
|
|||||||
|
|
||||||
method getChainId*(
|
method getChainId*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
try:
|
try:
|
||||||
@ -223,7 +225,7 @@ method getChainId*(
|
|||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
provider: JsonRpcProvider, rawTransaction: seq[byte]
|
provider: JsonRpcProvider, rawTransaction: seq[byte]
|
||||||
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let
|
let
|
||||||
client = await provider.client
|
client = await provider.client
|
||||||
@ -233,7 +235,7 @@ method sendTransaction*(
|
|||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: JsonRpcProvider, filter: EventFilter, onLog: LogHandler
|
provider: JsonRpcProvider, filter: EventFilter, onLog: LogHandler
|
||||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeLogs(filter, onLog)
|
let id = await subscriptions.subscribeLogs(filter, onLog)
|
||||||
@ -241,7 +243,7 @@ method subscribe*(
|
|||||||
|
|
||||||
method subscribe*(
|
method subscribe*(
|
||||||
provider: JsonRpcProvider, onBlock: BlockHandler
|
provider: JsonRpcProvider, onBlock: BlockHandler
|
||||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
let id = await subscriptions.subscribeBlocks(onBlock)
|
let id = await subscriptions.subscribeBlocks(onBlock)
|
||||||
@ -249,7 +251,7 @@ method subscribe*(
|
|||||||
|
|
||||||
method unsubscribe*(
|
method unsubscribe*(
|
||||||
subscription: JsonRpcSubscription
|
subscription: JsonRpcSubscription
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let subscriptions = subscription.subscriptions
|
let subscriptions = subscription.subscriptions
|
||||||
let id = subscription.id
|
let id = subscription.id
|
||||||
@ -257,7 +259,7 @@ method unsubscribe*(
|
|||||||
|
|
||||||
method isSyncing*(
|
method isSyncing*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
): Future[bool] {.async: (raises: [ProviderError, CancelledError]).} =
|
): Future[bool] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
let response = await provider.send("eth_syncing")
|
let response = await provider.send("eth_syncing")
|
||||||
if response.kind == JsonNodeKind.JObject:
|
if response.kind == JsonNodeKind.JObject:
|
||||||
return true
|
return true
|
||||||
@ -265,7 +267,7 @@ method isSyncing*(
|
|||||||
|
|
||||||
method close*(
|
method close*(
|
||||||
provider: JsonRpcProvider
|
provider: JsonRpcProvider
|
||||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
convertError:
|
convertError:
|
||||||
let client = await provider.client
|
let client = await provider.client
|
||||||
let subscriptions = await provider.subscriptions
|
let subscriptions = await provider.subscriptions
|
||||||
@ -274,24 +276,13 @@ method close*(
|
|||||||
|
|
||||||
# Signer
|
# Signer
|
||||||
|
|
||||||
proc raiseJsonRpcSignerError(
|
|
||||||
message: string) {.raises: [JsonRpcSignerError].} =
|
|
||||||
|
|
||||||
var message = message
|
|
||||||
if json =? JsonNode.fromJson(message):
|
|
||||||
if "message" in json:
|
|
||||||
message = json{"message"}.getStr
|
|
||||||
raise newException(JsonRpcSignerError, message)
|
|
||||||
|
|
||||||
template convertSignerError(body) =
|
template convertSignerError(body) =
|
||||||
try:
|
try:
|
||||||
body
|
body
|
||||||
except CancelledError as error:
|
except CancelledError as error:
|
||||||
raise error
|
raise error
|
||||||
except JsonRpcError as error:
|
|
||||||
raiseJsonRpcSignerError(error.msg)
|
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
raise newException(JsonRpcSignerError, error.msg)
|
raise newException(JsonRpcSignerError, error.msg, error)
|
||||||
|
|
||||||
method provider*(signer: JsonRpcSigner): Provider
|
method provider*(signer: JsonRpcSigner): Provider
|
||||||
{.gcsafe, raises: [SignerError].} =
|
{.gcsafe, raises: [SignerError].} =
|
||||||
@ -300,7 +291,7 @@ method provider*(signer: JsonRpcSigner): Provider
|
|||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: JsonRpcSigner
|
signer: JsonRpcSigner
|
||||||
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
if address =? signer.address:
|
if address =? signer.address:
|
||||||
return address
|
return address
|
||||||
|
|
||||||
@ -308,11 +299,11 @@ method getAddress*(
|
|||||||
if accounts.len > 0:
|
if accounts.len > 0:
|
||||||
return accounts[0]
|
return accounts[0]
|
||||||
|
|
||||||
raiseJsonRpcSignerError "no address found"
|
raise newException(SignerError, "no address found")
|
||||||
|
|
||||||
method signMessage*(
|
method signMessage*(
|
||||||
signer: JsonRpcSigner, message: seq[byte]
|
signer: JsonRpcSigner, message: seq[byte]
|
||||||
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError]).} =
|
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
convertSignerError:
|
convertSignerError:
|
||||||
let client = await signer.provider.client
|
let client = await signer.provider.client
|
||||||
let address = await signer.getAddress()
|
let address = await signer.getAddress()
|
||||||
@ -321,7 +312,7 @@ method signMessage*(
|
|||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: JsonRpcSigner, transaction: Transaction
|
signer: JsonRpcSigner, transaction: Transaction
|
||||||
): Future[TransactionResponse] {.
|
): Future[TransactionResponse] {.
|
||||||
async: (raises: [SignerError, ProviderError, CancelledError])
|
async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
convertError:
|
convertError:
|
||||||
let
|
let
|
||||||
|
|||||||
@ -31,19 +31,53 @@ func new*(_: type JsonRpcProviderError, json: JsonNode): ref JsonRpcProviderErro
|
|||||||
error
|
error
|
||||||
|
|
||||||
proc raiseJsonRpcProviderError*(
|
proc raiseJsonRpcProviderError*(
|
||||||
message: string) {.raises: [JsonRpcProviderError].} =
|
error: ref CatchableError, message = error.msg) {.raises: [JsonRpcProviderError].} =
|
||||||
if json =? JsonNode.fromJson(message):
|
if json =? JsonNode.fromJson(error.msg):
|
||||||
raise JsonRpcProviderError.new(json)
|
raise JsonRpcProviderError.new(json)
|
||||||
else:
|
else:
|
||||||
raise newException(JsonRpcProviderError, message)
|
raise newException(JsonRpcProviderError, message)
|
||||||
|
|
||||||
|
proc underlyingErrorOf(e: ref Exception, T: type CatchableError): (ref CatchableError) =
|
||||||
|
if e of (ref T):
|
||||||
|
return (ref T)(e)
|
||||||
|
elif not e.parent.isNil:
|
||||||
|
return e.parent.underlyingErrorOf T
|
||||||
|
else:
|
||||||
|
return nil
|
||||||
|
|
||||||
template convertError*(body) =
|
template convertError*(body) =
|
||||||
try:
|
try:
|
||||||
body
|
try:
|
||||||
|
body
|
||||||
|
# Inspect SubscriptionErrors and re-raise underlying JsonRpcErrors so that
|
||||||
|
# exception inspection and resolution only needs to happen once. All
|
||||||
|
# CatchableErrors that occur in the Subscription module are converted to
|
||||||
|
# SubscriptionError, with the original error preserved as the exception's
|
||||||
|
# parent.
|
||||||
|
except SubscriptionError, SignerError:
|
||||||
|
let e = getCurrentException()
|
||||||
|
let parent = e.underlyingErrorOf(JsonRpcError)
|
||||||
|
if not parent.isNil:
|
||||||
|
raise parent
|
||||||
except CancelledError as error:
|
except CancelledError as error:
|
||||||
raise error
|
raise error
|
||||||
|
except RpcPostError as error:
|
||||||
|
raiseNetworkError(error)
|
||||||
|
except FailedHttpResponse as error:
|
||||||
|
raiseNetworkError(error)
|
||||||
|
except ErrorResponse as error:
|
||||||
|
if error.status == 429:
|
||||||
|
raise newException(RequestLimitError, error.msg, error)
|
||||||
|
elif error.status == 408:
|
||||||
|
raise newException(RequestTimeoutError, error.msg, error)
|
||||||
|
else:
|
||||||
|
raiseJsonRpcProviderError(error)
|
||||||
except JsonRpcError as error:
|
except JsonRpcError as error:
|
||||||
raiseJsonRpcProviderError(error.msg)
|
var message = error.msg
|
||||||
|
if jsn =? JsonNode.fromJson(message):
|
||||||
|
if "message" in jsn:
|
||||||
|
message = jsn{"message"}.getStr
|
||||||
|
raiseJsonRpcProviderError(error, message)
|
||||||
except CatchableError as error:
|
except CatchableError as error:
|
||||||
raiseJsonRpcProviderError(error.msg)
|
raiseJsonRpcProviderError(error)
|
||||||
|
|
||||||
|
|||||||
@ -122,7 +122,7 @@ template withLock*(subscriptions: WebSocketSubscriptions, body: untyped) =
|
|||||||
|
|
||||||
# This is a workaround to manage the 5 minutes limit due to hardhat.
|
# This is a workaround to manage the 5 minutes limit due to hardhat.
|
||||||
# See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064
|
# See https://github.com/NomicFoundation/hardhat/issues/2053#issuecomment-1061374064
|
||||||
proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebsocketSubscriptions) {.async: (raises: [CancelledError]).} =
|
proc resubscribeWebsocketEventsOnTimeout*(subscriptions: WebSocketSubscriptions) {.async: (raises: [CancelledError]).} =
|
||||||
while true:
|
while true:
|
||||||
await sleepAsync(subscriptions.resubscribeInterval.seconds)
|
await sleepAsync(subscriptions.resubscribeInterval.seconds)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
import pkg/json_rpc/errors
|
||||||
import ./basics
|
import ./basics
|
||||||
import ./errors
|
import ./errors
|
||||||
import ./provider
|
import ./provider
|
||||||
@ -15,16 +16,6 @@ type
|
|||||||
template raiseSignerError*(message: string, parent: ref CatchableError = nil) =
|
template raiseSignerError*(message: string, parent: ref CatchableError = nil) =
|
||||||
raise newException(SignerError, message, parent)
|
raise newException(SignerError, message, parent)
|
||||||
|
|
||||||
template convertError(body) =
|
|
||||||
try:
|
|
||||||
body
|
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except ProviderError as error:
|
|
||||||
raise error # do not convert provider errors
|
|
||||||
except CatchableError as error:
|
|
||||||
raiseSignerError(error.msg)
|
|
||||||
|
|
||||||
method provider*(
|
method provider*(
|
||||||
signer: Signer): Provider {.base, gcsafe, raises: [SignerError].} =
|
signer: Signer): Provider {.base, gcsafe, raises: [SignerError].} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
@ -32,42 +23,41 @@ method provider*(
|
|||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: Signer
|
signer: Signer
|
||||||
): Future[Address] {.
|
): Future[Address] {.
|
||||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
base, async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method signMessage*(
|
method signMessage*(
|
||||||
signer: Signer, message: seq[byte]
|
signer: Signer, message: seq[byte]
|
||||||
): Future[seq[byte]] {.base, async: (raises: [SignerError, CancelledError]).} =
|
): Future[seq[byte]] {.base, async: (raises: [SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: Signer, transaction: Transaction
|
signer: Signer, transaction: Transaction
|
||||||
): Future[TransactionResponse] {.
|
): Future[TransactionResponse] {.
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
method getGasPrice*(
|
method getGasPrice*(
|
||||||
signer: Signer
|
signer: Signer
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
base, async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
return await signer.provider.getGasPrice()
|
return await signer.provider.getGasPrice()
|
||||||
|
|
||||||
method getTransactionCount*(
|
method getTransactionCount*(
|
||||||
signer: Signer, blockTag = BlockTag.latest
|
signer: Signer, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
convertError:
|
let address = await signer.getAddress()
|
||||||
let address = await signer.getAddress()
|
return await signer.provider.getTransactionCount(address, blockTag)
|
||||||
return await signer.provider.getTransactionCount(address, blockTag)
|
|
||||||
|
|
||||||
method estimateGas*(
|
method estimateGas*(
|
||||||
signer: Signer, transaction: Transaction, blockTag = BlockTag.latest
|
signer: Signer, transaction: Transaction, blockTag = BlockTag.latest
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
var transaction = transaction
|
var transaction = transaction
|
||||||
transaction.sender = some(await signer.getAddress())
|
transaction.sender = some(await signer.getAddress())
|
||||||
@ -76,14 +66,14 @@ method estimateGas*(
|
|||||||
method getChainId*(
|
method getChainId*(
|
||||||
signer: Signer
|
signer: Signer
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
return await signer.provider.getChainId()
|
return await signer.provider.getChainId()
|
||||||
|
|
||||||
method getNonce(
|
method getNonce(
|
||||||
signer: Signer
|
signer: Signer
|
||||||
): Future[UInt256] {.
|
): Future[UInt256] {.
|
||||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||||
.} =
|
.} =
|
||||||
return await signer.getTransactionCount(BlockTag.pending)
|
return await signer.getTransactionCount(BlockTag.pending)
|
||||||
|
|
||||||
@ -103,15 +93,14 @@ template withLock*(signer: Signer, body: untyped) =
|
|||||||
method populateTransaction*(
|
method populateTransaction*(
|
||||||
signer: Signer,
|
signer: Signer,
|
||||||
transaction: Transaction): Future[Transaction]
|
transaction: Transaction): Future[Transaction]
|
||||||
{.base, async: (raises: [CancelledError, ProviderError, SignerError]).} =
|
{.base, async: (raises: [CancelledError, ProviderError, SignerError, RpcNetworkError]).} =
|
||||||
## Populates a transaction with sender, chainId, gasPrice, nonce, and gasLimit.
|
## Populates a transaction with sender, chainId, gasPrice, nonce, and gasLimit.
|
||||||
## NOTE: to avoid async concurrency issues, this routine should be called with
|
## NOTE: to avoid async concurrency issues, this routine should be called with
|
||||||
## a lock if it is followed by sendTransaction. For reference, see the `send`
|
## a lock if it is followed by sendTransaction. For reference, see the `send`
|
||||||
## function in contract.nim.
|
## function in contract.nim.
|
||||||
|
|
||||||
var address: Address
|
var address: Address
|
||||||
convertError:
|
address = await signer.getAddress()
|
||||||
address = await signer.getAddress()
|
|
||||||
|
|
||||||
if sender =? transaction.sender and sender != address:
|
if sender =? transaction.sender and sender != address:
|
||||||
raiseSignerError("from address mismatch")
|
raiseSignerError("from address mismatch")
|
||||||
@ -154,7 +143,7 @@ method populateTransaction*(
|
|||||||
method cancelTransaction*(
|
method cancelTransaction*(
|
||||||
signer: Signer,
|
signer: Signer,
|
||||||
tx: Transaction
|
tx: Transaction
|
||||||
): Future[TransactionResponse] {.base, async: (raises: [SignerError, CancelledError, ProviderError]).} =
|
): Future[TransactionResponse] {.base, async: (raises: [SignerError, CancelledError, ProviderError, RpcNetworkError]).} =
|
||||||
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
# cancels a transaction by sending with a 0-valued transaction to ourselves
|
||||||
# with the failed tx's nonce
|
# with the failed tx's nonce
|
||||||
|
|
||||||
@ -164,7 +153,7 @@ method cancelTransaction*(
|
|||||||
raiseSignerError "transaction must have nonce"
|
raiseSignerError "transaction must have nonce"
|
||||||
|
|
||||||
withLock(signer):
|
withLock(signer):
|
||||||
convertError:
|
# convertError:
|
||||||
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
||||||
cancelTx = await signer.populateTransaction(cancelTx)
|
cancelTx = await signer.populateTransaction(cancelTx)
|
||||||
return await signer.sendTransaction(cancelTx)
|
return await signer.sendTransaction(cancelTx)
|
||||||
|
|||||||
@ -69,7 +69,7 @@ method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
|||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
wallet: Wallet): Future[Address]
|
wallet: Wallet): Future[Address]
|
||||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
{.async: (raises:[ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
|
|
||||||
return wallet.address
|
return wallet.address
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ proc signTransaction*(wallet: Wallet,
|
|||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
wallet: Wallet,
|
wallet: Wallet,
|
||||||
transaction: Transaction): Future[TransactionResponse]
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
{.async: (raises:[SignerError, ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
|
|
||||||
let signed = await signTransaction(wallet, transaction)
|
let signed = await signTransaction(wallet, transaction)
|
||||||
return await provider(wallet).sendTransaction(signed)
|
return await provider(wallet).sendTransaction(signed)
|
||||||
|
|||||||
@ -6,10 +6,3 @@ type
|
|||||||
func raiseWalletError*(message: string) {.raises: [WalletError].}=
|
func raiseWalletError*(message: string) {.raises: [WalletError].}=
|
||||||
raise newException(WalletError, message)
|
raise newException(WalletError, message)
|
||||||
|
|
||||||
template convertError*(body) =
|
|
||||||
try:
|
|
||||||
body
|
|
||||||
except CancelledError as error:
|
|
||||||
raise error
|
|
||||||
except CatchableError as error:
|
|
||||||
raiseWalletError(error.msg)
|
|
||||||
|
|||||||
@ -13,13 +13,13 @@ method provider*(signer: MockSigner): Provider =
|
|||||||
|
|
||||||
method getAddress*(
|
method getAddress*(
|
||||||
signer: MockSigner): Future[Address]
|
signer: MockSigner): Future[Address]
|
||||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
{.async: (raises:[ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||||
|
|
||||||
return signer.address
|
return signer.address
|
||||||
|
|
||||||
method sendTransaction*(
|
method sendTransaction*(
|
||||||
signer: MockSigner,
|
signer: MockSigner,
|
||||||
transaction: Transaction): Future[TransactionResponse]
|
transaction: Transaction): Future[TransactionResponse]
|
||||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
{.async: (raises:[SignerError, ProviderError, CancelledError, RpcNetworkError]).} =
|
||||||
|
|
||||||
signer.transactions.add(transaction)
|
signer.transactions.add(transaction)
|
||||||
|
|||||||
80
testmodule/providers/jsonrpc/mocks/mockHttpServer.nim
Normal file
80
testmodule/providers/jsonrpc/mocks/mockHttpServer.nim
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import std/tables
|
||||||
|
import pkg/serde
|
||||||
|
import pkg/chronos/apps/http/httpclient
|
||||||
|
import pkg/chronos/apps/http/httpserver
|
||||||
|
import pkg/stew/byteutils
|
||||||
|
import pkg/questionable
|
||||||
|
|
||||||
|
export httpserver
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
type
|
||||||
|
RpcResponse* = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]), raises: [].}
|
||||||
|
|
||||||
|
MockHttpServer* = object
|
||||||
|
server: HttpServerRef
|
||||||
|
rpcResponses: ref Table[string, RpcResponse]
|
||||||
|
|
||||||
|
RequestRx {.deserialize.} = object
|
||||||
|
jsonrpc*: string
|
||||||
|
id* : int
|
||||||
|
`method`* : string
|
||||||
|
|
||||||
|
|
||||||
|
proc init*(_: type MockHttpServer, address: TransportAddress): MockHttpServer =
|
||||||
|
var server: MockHttpServer
|
||||||
|
|
||||||
|
proc processRequest(r: RequestFence): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||||
|
if r.isErr:
|
||||||
|
return defaultResponse()
|
||||||
|
|
||||||
|
let request = r.get()
|
||||||
|
try:
|
||||||
|
let body = string.fromBytes(await request.getBody())
|
||||||
|
echo "mockHttpServer.processRequest request: ", body
|
||||||
|
without req =? RequestRx.fromJson(body), error:
|
||||||
|
echo "failed to deserialize, error: ", error.msg
|
||||||
|
return await request.respond(Http400, "Invalid request, must be valid json rpc request")
|
||||||
|
|
||||||
|
echo "Received request with method: ", req.method
|
||||||
|
if not server.rpcResponses.contains(req.method):
|
||||||
|
return await request.respond(Http404, "Method not registered")
|
||||||
|
|
||||||
|
try:
|
||||||
|
let rpcResponseProc = server.rpcResponses[req.method]
|
||||||
|
return await rpcResponseProc(request)
|
||||||
|
except KeyError as e:
|
||||||
|
return await request.respond(Http500, "Method lookup error with key, error: " & e.msg)
|
||||||
|
|
||||||
|
except HttpProtocolError as e:
|
||||||
|
echo "HttpProtocolError encountered, error: ", e.msg
|
||||||
|
return defaultResponse(e)
|
||||||
|
except HttpTransportError as e:
|
||||||
|
echo "HttpTransportError encountered, error: ", e.msg
|
||||||
|
return defaultResponse(e)
|
||||||
|
except HttpWriteError as exc:
|
||||||
|
return defaultResponse(exc)
|
||||||
|
|
||||||
|
let
|
||||||
|
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||||
|
serverFlags = {HttpServerFlags.Http11Pipeline}
|
||||||
|
res = HttpServerRef.new(address, processRequest,
|
||||||
|
socketFlags = socketFlags,
|
||||||
|
serverFlags = serverFlags)
|
||||||
|
server = MockHttpServer(server: res.get(), rpcResponses: newTable[string, RpcResponse]())
|
||||||
|
return server
|
||||||
|
|
||||||
|
template registerRpcMethod*(server: MockHttpServer, `method`: string, rpcResponse: untyped): untyped =
|
||||||
|
server.rpcResponses[`method`] = rpcResponse
|
||||||
|
|
||||||
|
proc address*(server: MockHttpServer): TransportAddress =
|
||||||
|
server.server.instance.localAddress()
|
||||||
|
|
||||||
|
proc start*(server: MockHttpServer) =
|
||||||
|
server.server.start()
|
||||||
|
|
||||||
|
proc stop*(server: MockHttpServer) {.async: (raises: []).} =
|
||||||
|
await server.server.stop()
|
||||||
|
await server.server.closeWait()
|
||||||
|
|
||||||
31
testmodule/providers/jsonrpc/mocks/mockRpcHttpServer.nim
Normal file
31
testmodule/providers/jsonrpc/mocks/mockRpcHttpServer.nim
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import ../../../examples
|
||||||
|
import ../../../../ethers/provider
|
||||||
|
import ../../../../ethers/providers/jsonrpc/conversions
|
||||||
|
|
||||||
|
import std/sequtils
|
||||||
|
import pkg/stew/byteutils
|
||||||
|
import pkg/json_rpc/rpcserver except `%`, `%*`
|
||||||
|
import pkg/json_rpc/errors
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
type MockRpcHttpServer* = ref object of RootObj
|
||||||
|
srv: RpcHttpServer
|
||||||
|
|
||||||
|
proc new*(_: type MockRpcHttpServer): MockRpcHttpServer {.raises: [JsonRpcError].} =
|
||||||
|
let srv = newRpcHttpServer(["127.0.0.1:0"])
|
||||||
|
MockRpcHttpServer(srv: srv)
|
||||||
|
|
||||||
|
|
||||||
|
template registerRpcMethod*(server: MockRpcHttpServer, path: string, body: untyped): untyped =
|
||||||
|
server.srv.router.rpc(path, body)
|
||||||
|
|
||||||
|
method start*(server: MockRpcHttpServer) {.gcsafe, base.} =
|
||||||
|
server.srv.start()
|
||||||
|
|
||||||
|
proc stop*(server: MockRpcHttpServer) {.async.} =
|
||||||
|
await server.srv.stop()
|
||||||
|
await server.srv.closeWait()
|
||||||
|
|
||||||
|
proc localAddress*(server: MockRpcHttpServer): seq[TransportAddress] =
|
||||||
|
return server.srv.localAddress()
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import ../../../examples
|
||||||
|
import ../../../../ethers/provider
|
||||||
|
import ../../../../ethers/providers/jsonrpc/conversions
|
||||||
|
|
||||||
|
import std/sequtils
|
||||||
|
import pkg/stew/byteutils
|
||||||
|
import pkg/json_rpc/rpcserver except `%`, `%*`
|
||||||
|
import pkg/json_rpc/errors
|
||||||
|
import ./mockRpcHttpServer
|
||||||
|
|
||||||
|
export mockRpcHttpServer
|
||||||
|
|
||||||
|
{.push raises: [].}
|
||||||
|
|
||||||
|
type MockRpcHttpServerSubscriptions* = ref object of MockRpcHttpServer
|
||||||
|
filters*: seq[string]
|
||||||
|
nextGetChangesReturnsError*: bool
|
||||||
|
|
||||||
|
proc new*(_: type MockRpcHttpServerSubscriptions): MockRpcHttpServerSubscriptions {.raises: [JsonRpcError].} =
|
||||||
|
let srv = newRpcHttpServer(["127.0.0.1:0"])
|
||||||
|
MockRpcHttpServerSubscriptions(filters: @[], srv: srv, nextGetChangesReturnsError: false)
|
||||||
|
|
||||||
|
proc invalidateFilter*(server: MockRpcHttpServerSubscriptions, jsonId: JsonNode) =
|
||||||
|
server.filters.keepItIf it != jsonId.getStr
|
||||||
|
|
||||||
|
method start*(server: MockRpcHttpServerSubscriptions) =
|
||||||
|
server.registerRpcMethod("eth_newFilter") do(filter: EventFilter) -> string:
|
||||||
|
let filterId = "0x" & (array[16, byte].example).toHex
|
||||||
|
server.filters.add filterId
|
||||||
|
return filterId
|
||||||
|
|
||||||
|
server.registerRpcMethod("eth_newBlockFilter") do() -> string:
|
||||||
|
let filterId = "0x" & (array[16, byte].example).toHex
|
||||||
|
server.filters.add filterId
|
||||||
|
return filterId
|
||||||
|
|
||||||
|
server.registerRpcMethod("eth_getFilterChanges") do(id: string) -> seq[string]:
|
||||||
|
if server.nextGetChangesReturnsError:
|
||||||
|
raise (ref ApplicationError)(code: -32000, msg: "unknown error")
|
||||||
|
|
||||||
|
if id notin server.filters:
|
||||||
|
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
||||||
|
|
||||||
|
return @[]
|
||||||
|
|
||||||
|
server.registerRpcMethod("eth_uninstallFilter") do(id: string) -> bool:
|
||||||
|
if id notin server.filters:
|
||||||
|
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
||||||
|
|
||||||
|
server.invalidateFilter(%id)
|
||||||
|
return true
|
||||||
|
|
||||||
|
procCall MockRpcHttpServer(server).start()
|
||||||
@ -1,56 +0,0 @@
|
|||||||
import ../../examples
|
|
||||||
import ../../../ethers/provider
|
|
||||||
import ../../../ethers/providers/jsonrpc/conversions
|
|
||||||
|
|
||||||
import std/sequtils
|
|
||||||
import pkg/stew/byteutils
|
|
||||||
import pkg/json_rpc/rpcserver except `%`, `%*`
|
|
||||||
import pkg/json_rpc/errors
|
|
||||||
|
|
||||||
type MockRpcHttpServer* = ref object
|
|
||||||
filters*: seq[string]
|
|
||||||
nextGetChangesReturnsError*: bool
|
|
||||||
srv: RpcHttpServer
|
|
||||||
|
|
||||||
proc new*(_: type MockRpcHttpServer): MockRpcHttpServer =
|
|
||||||
let srv = newRpcHttpServer(["127.0.0.1:0"])
|
|
||||||
MockRpcHttpServer(filters: @[], srv: srv, nextGetChangesReturnsError: false)
|
|
||||||
|
|
||||||
proc invalidateFilter*(server: MockRpcHttpServer, jsonId: JsonNode) =
|
|
||||||
server.filters.keepItIf it != jsonId.getStr
|
|
||||||
|
|
||||||
proc start*(server: MockRpcHttpServer) =
|
|
||||||
server.srv.router.rpc("eth_newFilter") do(filter: EventFilter) -> string:
|
|
||||||
let filterId = "0x" & (array[16, byte].example).toHex
|
|
||||||
server.filters.add filterId
|
|
||||||
return filterId
|
|
||||||
|
|
||||||
server.srv.router.rpc("eth_newBlockFilter") do() -> string:
|
|
||||||
let filterId = "0x" & (array[16, byte].example).toHex
|
|
||||||
server.filters.add filterId
|
|
||||||
return filterId
|
|
||||||
|
|
||||||
server.srv.router.rpc("eth_getFilterChanges") do(id: string) -> seq[string]:
|
|
||||||
if server.nextGetChangesReturnsError:
|
|
||||||
raise (ref ApplicationError)(code: -32000, msg: "unknown error")
|
|
||||||
|
|
||||||
if id notin server.filters:
|
|
||||||
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
|
||||||
|
|
||||||
return @[]
|
|
||||||
|
|
||||||
server.srv.router.rpc("eth_uninstallFilter") do(id: string) -> bool:
|
|
||||||
if id notin server.filters:
|
|
||||||
raise (ref ApplicationError)(code: -32000, msg: "filter not found")
|
|
||||||
|
|
||||||
server.invalidateFilter(%id)
|
|
||||||
return true
|
|
||||||
|
|
||||||
server.srv.start()
|
|
||||||
|
|
||||||
proc stop*(server: MockRpcHttpServer) {.async.} =
|
|
||||||
await server.srv.stop()
|
|
||||||
await server.srv.closeWait()
|
|
||||||
|
|
||||||
proc localAddress*(server: MockRpcHttpServer): seq[TransportAddress] =
|
|
||||||
return server.srv.localAddress()
|
|
||||||
@ -1,7 +1,17 @@
|
|||||||
import std/unittest
|
import std/sequtils
|
||||||
|
import std/typetraits
|
||||||
|
|
||||||
|
import stew/byteutils
|
||||||
|
import pkg/asynctest/chronos/unittest
|
||||||
import pkg/serde
|
import pkg/serde
|
||||||
import pkg/questionable
|
import pkg/questionable
|
||||||
|
import pkg/ethers/providers/jsonrpc
|
||||||
import pkg/ethers/providers/jsonrpc/errors
|
import pkg/ethers/providers/jsonrpc/errors
|
||||||
|
import pkg/ethers/erc20
|
||||||
|
import pkg/json_rpc/clients/httpclient
|
||||||
|
import ./mocks/mockHttpServer
|
||||||
|
import ../../examples
|
||||||
|
import ../../hardhat
|
||||||
|
|
||||||
suite "JSON RPC errors":
|
suite "JSON RPC errors":
|
||||||
|
|
||||||
@ -25,3 +35,106 @@ suite "JSON RPC errors":
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
|
check JsonRpcProviderError.new(error).data == some @[0xab'u8, 0xcd'u8]
|
||||||
|
|
||||||
|
type
|
||||||
|
TestToken = ref object of Erc20Token
|
||||||
|
|
||||||
|
method mint(token: TestToken, holder: Address, amount: UInt256): Confirmable {.base, contract.}
|
||||||
|
|
||||||
|
suite "Network errors":
|
||||||
|
|
||||||
|
var provider: JsonRpcProvider
|
||||||
|
var mockServer: MockHttpServer
|
||||||
|
var token: TestToken
|
||||||
|
|
||||||
|
setup:
|
||||||
|
mockServer = MockHttpServer.init(initTAddress("127.0.0.1:0"))
|
||||||
|
mockServer.start()
|
||||||
|
provider = JsonRpcProvider.new("http://" & $mockServer.address)
|
||||||
|
|
||||||
|
let deployment = readDeployment()
|
||||||
|
token = TestToken.new(!deployment.address(TestToken), provider)
|
||||||
|
|
||||||
|
teardown:
|
||||||
|
await provider.close()
|
||||||
|
await mockServer.stop()
|
||||||
|
|
||||||
|
proc registerRpcMethods(response: RpcResponse) =
|
||||||
|
mockServer.registerRpcMethod("eth_accounts", response)
|
||||||
|
mockServer.registerRpcMethod("eth_call", response)
|
||||||
|
mockServer.registerRpcMethod("eth_sendTransaction", response)
|
||||||
|
mockServer.registerRpcMethod("eth_sendRawTransaction", response)
|
||||||
|
mockServer.registerRpcMethod("eth_newBlockFilter", response)
|
||||||
|
mockServer.registerRpcMethod("eth_newFilter", response)
|
||||||
|
# mockServer.registerRpcMethod("eth_subscribe", response) # TODO: handle
|
||||||
|
# eth_subscribe for websockets
|
||||||
|
|
||||||
|
proc testCustomResponse(errorName: string, responseHttpCode: HttpCode, responseText: string, errorType: type CatchableError) =
|
||||||
|
let response = proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
||||||
|
try:
|
||||||
|
return await request.respond(responseHttpCode, responseText)
|
||||||
|
except HttpWriteError as exc:
|
||||||
|
return defaultResponse(exc)
|
||||||
|
|
||||||
|
let testNamePrefix = errorName & " error response is converted to " & errorType.name & " for "
|
||||||
|
test testNamePrefix & "sending a manual RPC method request":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
discard await provider.send("eth_accounts")
|
||||||
|
|
||||||
|
test testNamePrefix & "calling a provider method that converts errors when calling a generated RPC request":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
discard await provider.listAccounts()
|
||||||
|
|
||||||
|
test testNamePrefix & "calling a view method of a contract":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
discard await token.balanceOf(Address.example)
|
||||||
|
|
||||||
|
test testNamePrefix & "calling a contract method that executes a transaction":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
token = TestToken.new(token.address, provider.getSigner())
|
||||||
|
discard await token.mint(
|
||||||
|
Address.example, 100.u256,
|
||||||
|
TransactionOverrides(gasLimit: 100.u256.some, chainId: 1.u256.some)
|
||||||
|
)
|
||||||
|
|
||||||
|
test testNamePrefix & "sending a manual transaction":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
let tx = Transaction.example
|
||||||
|
discard await provider.getSigner().sendTransaction(tx)
|
||||||
|
|
||||||
|
test testNamePrefix & "sending a raw transaction":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
const pk_with_funds = "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
||||||
|
let wallet = !Wallet.new(pk_with_funds)
|
||||||
|
let tx = Transaction(
|
||||||
|
to: wallet.address,
|
||||||
|
nonce: some 0.u256,
|
||||||
|
chainId: some 31337.u256,
|
||||||
|
gasPrice: some 1_000_000_000.u256,
|
||||||
|
gasLimit: some 21_000.u256,
|
||||||
|
)
|
||||||
|
let signedTx = await wallet.signTransaction(tx)
|
||||||
|
discard await provider.sendTransaction(signedTx)
|
||||||
|
|
||||||
|
test testNamePrefix & "subscribing to blocks":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
let emptyHandler = proc(blckResult: ?!Block) = discard
|
||||||
|
discard await provider.subscribe(emptyHandler)
|
||||||
|
|
||||||
|
test testNamePrefix & "subscribing to logs":
|
||||||
|
registerRpcMethods(response)
|
||||||
|
expect errorType:
|
||||||
|
let filter = EventFilter(address: Address.example, topics: @[array[32, byte].example])
|
||||||
|
let emptyHandler = proc(log: ?!Log) = discard
|
||||||
|
discard await provider.subscribe(filter, emptyHandler)
|
||||||
|
|
||||||
|
testCustomResponse("429", Http429, "Too many requests", RequestLimitError)
|
||||||
|
testCustomResponse("408", Http408, "Request timed out", RequestTimeoutError)
|
||||||
|
testCustomResponse("non-429", Http500, "Server error", JsonRpcProviderError)
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import ethers/provider
|
|||||||
import ethers/providers/jsonrpc/subscriptions
|
import ethers/providers/jsonrpc/subscriptions
|
||||||
|
|
||||||
import ../../examples
|
import ../../examples
|
||||||
import ./rpc_mock
|
import ./mocks/mockRpcHttpServerSubscriptions
|
||||||
|
|
||||||
suite "JsonRpcSubscriptions":
|
suite "JsonRpcSubscriptions":
|
||||||
|
|
||||||
@ -111,18 +111,18 @@ suite "HTTP polling subscriptions - mock tests":
|
|||||||
|
|
||||||
var subscriptions: PollingSubscriptions
|
var subscriptions: PollingSubscriptions
|
||||||
var client: RpcHttpClient
|
var client: RpcHttpClient
|
||||||
var mockServer: MockRpcHttpServer
|
var mockServer: MockRpcHttpServerSubscriptions
|
||||||
|
|
||||||
privateAccess(PollingSubscriptions)
|
privateAccess(PollingSubscriptions)
|
||||||
privateAccess(JsonRpcSubscriptions)
|
privateAccess(JsonRpcSubscriptions)
|
||||||
|
|
||||||
proc startServer() {.async.} =
|
proc startServer() {.async.} =
|
||||||
mockServer = MockRpcHttpServer.new()
|
mockServer = MockRpcHttpServerSubscriptions.new()
|
||||||
mockServer.start()
|
mockServer.start()
|
||||||
await client.connect("http://" & $mockServer.localAddress()[0])
|
await client.connect("http://" & $MockRpcHttpServer(mockServer).localAddress()[0])
|
||||||
|
|
||||||
proc stopServer() {.async.} =
|
proc stopServer() {.async.} =
|
||||||
await mockServer.stop()
|
await MockRpcHttpServer(mockServer).stop()
|
||||||
|
|
||||||
setup:
|
setup:
|
||||||
client = newRpcHttpClient()
|
client = newRpcHttpClient()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user