mirror of
https://github.com/logos-storage/nim-ethers.git
synced 2026-01-02 13:43: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) =
|
||||
procedure.addPragma nnkExprColonExpr.newTree(
|
||||
quote do: async,
|
||||
quote do: (raises: [CancelledError, ProviderError, EthersError])
|
||||
quote do: (raises: [CancelledError, ProviderError, EthersError, RpcNetworkError])
|
||||
)
|
||||
|
||||
func addUsedPragma*(procedure: var NimNode) =
|
||||
|
||||
@ -34,13 +34,15 @@ proc decodeResponse(T: type, bytes: seq[byte]): T {.raises: [ContractError].} =
|
||||
|
||||
proc call(
|
||||
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:
|
||||
await provider.call(transaction, blockTag)
|
||||
else:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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:
|
||||
withLock(signer):
|
||||
let transaction = createTransaction(call)
|
||||
|
||||
@ -7,6 +7,10 @@ type
|
||||
SubscriptionError* = object of EthersError
|
||||
ProviderError* = object of EthersError
|
||||
data*: ?seq[byte]
|
||||
RpcNetworkError* = object of EthersError
|
||||
RpcHttpErrorResponse* = object of RpcNetworkError
|
||||
RequestLimitError* = object of RpcHttpErrorResponse
|
||||
RequestTimeoutError* = object of RpcHttpErrorResponse
|
||||
|
||||
{.push raises:[].}
|
||||
|
||||
@ -16,3 +20,7 @@ proc toErr*[E1: ref CatchableError, E2: EthersError](
|
||||
msg: string = e1.msg): ref E2 =
|
||||
|
||||
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*(
|
||||
provider: Provider
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getBlock*(
|
||||
provider: Provider, tag: BlockTag
|
||||
): Future[?Block] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[?Block] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method call*(
|
||||
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"
|
||||
|
||||
method getGasPrice*(
|
||||
provider: Provider
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getTransactionCount*(
|
||||
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"
|
||||
|
||||
method getTransaction*(
|
||||
provider: Provider, txHash: TransactionHash
|
||||
): Future[?PastTransaction] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[?PastTransaction] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getTransactionReceipt*(
|
||||
provider: Provider, txHash: TransactionHash
|
||||
): Future[?TransactionReceipt] {.
|
||||
base, async: (raises: [ProviderError, CancelledError])
|
||||
base, async: (raises: [ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method sendTransaction*(
|
||||
provider: Provider, rawTransaction: seq[byte]
|
||||
): Future[TransactionResponse] {.
|
||||
base, async: (raises: [ProviderError, CancelledError])
|
||||
base, async: (raises: [ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getLogs*(
|
||||
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"
|
||||
|
||||
method estimateGas*(
|
||||
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"
|
||||
|
||||
method getChainId*(
|
||||
provider: Provider
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method subscribe*(
|
||||
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"
|
||||
|
||||
method subscribe*(
|
||||
provider: Provider, callback: BlockHandler
|
||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[Subscription] {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method unsubscribe*(
|
||||
subscription: Subscription
|
||||
) {.base, async: (raises: [ProviderError, CancelledError]).} =
|
||||
) {.base, async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
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"
|
||||
|
||||
proc replay*(
|
||||
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
|
||||
# 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
|
||||
@ -193,7 +195,7 @@ proc replay*(
|
||||
|
||||
proc ensureSuccess(
|
||||
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
|
||||
## reason, after which a ProviderError with the revert reason is raised.
|
||||
## If no revert reason was obtained
|
||||
@ -241,7 +243,7 @@ proc confirm*(
|
||||
if number > blockNumber:
|
||||
blockNumber = number
|
||||
blockEvent.fire()
|
||||
except ProviderError, CancelledError:
|
||||
except ProviderError, CancelledError, RpcNetworkError:
|
||||
# there's nothing we can do here
|
||||
discard
|
||||
|
||||
|
||||
@ -56,7 +56,7 @@ proc new*(
|
||||
var client: RpcClient
|
||||
var subscriptions: JsonRpcSubscriptions
|
||||
|
||||
proc initialize() {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
||||
proc initialize() {.async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
case parseUri(url).scheme
|
||||
of "ws", "wss":
|
||||
@ -65,7 +65,7 @@ proc new*(
|
||||
client = websocket
|
||||
subscriptions = JsonRpcSubscriptions.new(websocket)
|
||||
else:
|
||||
let http = newRpcHttpClient(getHeaders = jsonHeaders)
|
||||
let http = newRpcHttpClient()
|
||||
await http.connect(url)
|
||||
client = http
|
||||
subscriptions = JsonRpcSubscriptions.new(http,
|
||||
@ -73,14 +73,14 @@ proc new*(
|
||||
subscriptions.start()
|
||||
|
||||
proc awaitClient(): Future[RpcClient] {.
|
||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
||||
async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
convertError:
|
||||
await initialized
|
||||
return client
|
||||
|
||||
proc awaitSubscriptions(): Future[JsonRpcSubscriptions] {.
|
||||
async: (raises: [JsonRpcProviderError, CancelledError])
|
||||
async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
convertError:
|
||||
await initialized
|
||||
@ -91,28 +91,30 @@ proc new*(
|
||||
|
||||
proc callImpl(
|
||||
client: RpcClient, call: string, args: JsonNode
|
||||
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
||||
): Future[JsonNode] {.async: (raises: [JsonRpcProviderError, CancelledError, JsonRpcError]).} =
|
||||
try:
|
||||
let response = await client.call(call, %args)
|
||||
without json =? JsonNode.fromJson(response.string), error:
|
||||
raiseJsonRpcProviderError "Failed to parse response '" & response.string & "': " &
|
||||
raiseJsonRpcProviderError error, "Failed to parse response '" & response.string & "': " &
|
||||
error.msg
|
||||
return json
|
||||
except CancelledError as error:
|
||||
raise error
|
||||
except JsonRpcError as error:
|
||||
raise error
|
||||
except CatchableError as error:
|
||||
raiseJsonRpcProviderError error.msg
|
||||
raiseJsonRpcProviderError error
|
||||
|
||||
proc send*(
|
||||
provider: JsonRpcProvider, call: string, arguments: seq[JsonNode] = @[]
|
||||
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[JsonNode] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.callImpl(call, %arguments)
|
||||
|
||||
proc listAccounts*(
|
||||
provider: JsonRpcProvider
|
||||
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError]).} =
|
||||
): Future[seq[Address]] {.async: (raises: [JsonRpcProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_accounts()
|
||||
@ -125,56 +127,56 @@ proc getSigner*(provider: JsonRpcProvider, address: Address): JsonRpcSigner =
|
||||
|
||||
method getBlockNumber*(
|
||||
provider: JsonRpcProvider
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_blockNumber()
|
||||
|
||||
method getBlock*(
|
||||
provider: JsonRpcProvider, tag: BlockTag
|
||||
): Future[?Block] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[?Block] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_getBlockByNumber(tag, false)
|
||||
|
||||
method call*(
|
||||
provider: JsonRpcProvider, tx: Transaction, blockTag = BlockTag.latest
|
||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[seq[byte]] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_call(tx, blockTag)
|
||||
|
||||
method getGasPrice*(
|
||||
provider: JsonRpcProvider
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_gasPrice()
|
||||
|
||||
method getTransactionCount*(
|
||||
provider: JsonRpcProvider, address: Address, blockTag = BlockTag.latest
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_getTransactionCount(address, blockTag)
|
||||
|
||||
method getTransaction*(
|
||||
provider: JsonRpcProvider, txHash: TransactionHash
|
||||
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[?PastTransaction] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_getTransactionByHash(txHash)
|
||||
|
||||
method getTransactionReceipt*(
|
||||
provider: JsonRpcProvider, txHash: TransactionHash
|
||||
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[?TransactionReceipt] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
return await client.eth_getTransactionReceipt(txHash)
|
||||
|
||||
method getLogs*(
|
||||
provider: JsonRpcProvider, filter: EventFilter
|
||||
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[seq[Log]] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
let logsJson =
|
||||
@ -196,7 +198,7 @@ method estimateGas*(
|
||||
provider: JsonRpcProvider,
|
||||
transaction: Transaction,
|
||||
blockTag = BlockTag.latest,
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
try:
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
@ -211,7 +213,7 @@ method estimateGas*(
|
||||
|
||||
method getChainId*(
|
||||
provider: JsonRpcProvider
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[UInt256] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
try:
|
||||
@ -223,7 +225,7 @@ method getChainId*(
|
||||
|
||||
method sendTransaction*(
|
||||
provider: JsonRpcProvider, rawTransaction: seq[byte]
|
||||
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[TransactionResponse] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let
|
||||
client = await provider.client
|
||||
@ -233,7 +235,7 @@ method sendTransaction*(
|
||||
|
||||
method subscribe*(
|
||||
provider: JsonRpcProvider, filter: EventFilter, onLog: LogHandler
|
||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let subscriptions = await provider.subscriptions
|
||||
let id = await subscriptions.subscribeLogs(filter, onLog)
|
||||
@ -241,7 +243,7 @@ method subscribe*(
|
||||
|
||||
method subscribe*(
|
||||
provider: JsonRpcProvider, onBlock: BlockHandler
|
||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[Subscription] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let subscriptions = await provider.subscriptions
|
||||
let id = await subscriptions.subscribeBlocks(onBlock)
|
||||
@ -249,7 +251,7 @@ method subscribe*(
|
||||
|
||||
method unsubscribe*(
|
||||
subscription: JsonRpcSubscription
|
||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let subscriptions = subscription.subscriptions
|
||||
let id = subscription.id
|
||||
@ -257,7 +259,7 @@ method unsubscribe*(
|
||||
|
||||
method isSyncing*(
|
||||
provider: JsonRpcProvider
|
||||
): Future[bool] {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
): Future[bool] {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
let response = await provider.send("eth_syncing")
|
||||
if response.kind == JsonNodeKind.JObject:
|
||||
return true
|
||||
@ -265,7 +267,7 @@ method isSyncing*(
|
||||
|
||||
method close*(
|
||||
provider: JsonRpcProvider
|
||||
) {.async: (raises: [ProviderError, CancelledError]).} =
|
||||
) {.async: (raises: [ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
convertError:
|
||||
let client = await provider.client
|
||||
let subscriptions = await provider.subscriptions
|
||||
@ -274,24 +276,13 @@ method close*(
|
||||
|
||||
# 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) =
|
||||
try:
|
||||
body
|
||||
except CancelledError as error:
|
||||
raise error
|
||||
except JsonRpcError as error:
|
||||
raiseJsonRpcSignerError(error.msg)
|
||||
except CatchableError as error:
|
||||
raise newException(JsonRpcSignerError, error.msg)
|
||||
raise newException(JsonRpcSignerError, error.msg, error)
|
||||
|
||||
method provider*(signer: JsonRpcSigner): Provider
|
||||
{.gcsafe, raises: [SignerError].} =
|
||||
@ -300,7 +291,7 @@ method provider*(signer: JsonRpcSigner): Provider
|
||||
|
||||
method getAddress*(
|
||||
signer: JsonRpcSigner
|
||||
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError]).} =
|
||||
): Future[Address] {.async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||
if address =? signer.address:
|
||||
return address
|
||||
|
||||
@ -308,11 +299,11 @@ method getAddress*(
|
||||
if accounts.len > 0:
|
||||
return accounts[0]
|
||||
|
||||
raiseJsonRpcSignerError "no address found"
|
||||
raise newException(SignerError, "no address found")
|
||||
|
||||
method signMessage*(
|
||||
signer: JsonRpcSigner, message: seq[byte]
|
||||
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError]).} =
|
||||
): Future[seq[byte]] {.async: (raises: [SignerError, CancelledError, RpcNetworkError]).} =
|
||||
convertSignerError:
|
||||
let client = await signer.provider.client
|
||||
let address = await signer.getAddress()
|
||||
@ -321,7 +312,7 @@ method signMessage*(
|
||||
method sendTransaction*(
|
||||
signer: JsonRpcSigner, transaction: Transaction
|
||||
): Future[TransactionResponse] {.
|
||||
async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
convertError:
|
||||
let
|
||||
|
||||
@ -31,19 +31,53 @@ func new*(_: type JsonRpcProviderError, json: JsonNode): ref JsonRpcProviderErro
|
||||
error
|
||||
|
||||
proc raiseJsonRpcProviderError*(
|
||||
message: string) {.raises: [JsonRpcProviderError].} =
|
||||
if json =? JsonNode.fromJson(message):
|
||||
error: ref CatchableError, message = error.msg) {.raises: [JsonRpcProviderError].} =
|
||||
if json =? JsonNode.fromJson(error.msg):
|
||||
raise JsonRpcProviderError.new(json)
|
||||
else:
|
||||
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) =
|
||||
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:
|
||||
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:
|
||||
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:
|
||||
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.
|
||||
# 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:
|
||||
await sleepAsync(subscriptions.resubscribeInterval.seconds)
|
||||
try:
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import pkg/questionable
|
||||
import pkg/json_rpc/errors
|
||||
import ./basics
|
||||
import ./errors
|
||||
import ./provider
|
||||
@ -15,16 +16,6 @@ type
|
||||
template raiseSignerError*(message: string, parent: ref CatchableError = nil) =
|
||||
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*(
|
||||
signer: Signer): Provider {.base, gcsafe, raises: [SignerError].} =
|
||||
doAssert false, "not implemented"
|
||||
@ -32,42 +23,41 @@ method provider*(
|
||||
method getAddress*(
|
||||
signer: Signer
|
||||
): Future[Address] {.
|
||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
||||
base, async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method signMessage*(
|
||||
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"
|
||||
|
||||
method sendTransaction*(
|
||||
signer: Signer, transaction: Transaction
|
||||
): Future[TransactionResponse] {.
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
doAssert false, "not implemented"
|
||||
|
||||
method getGasPrice*(
|
||||
signer: Signer
|
||||
): Future[UInt256] {.
|
||||
base, async: (raises: [ProviderError, SignerError, CancelledError])
|
||||
base, async: (raises: [ProviderError, SignerError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
return await signer.provider.getGasPrice()
|
||||
|
||||
method getTransactionCount*(
|
||||
signer: Signer, blockTag = BlockTag.latest
|
||||
): Future[UInt256] {.
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
convertError:
|
||||
let address = await signer.getAddress()
|
||||
return await signer.provider.getTransactionCount(address, blockTag)
|
||||
let address = await signer.getAddress()
|
||||
return await signer.provider.getTransactionCount(address, blockTag)
|
||||
|
||||
method estimateGas*(
|
||||
signer: Signer, transaction: Transaction, blockTag = BlockTag.latest
|
||||
): Future[UInt256] {.
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
var transaction = transaction
|
||||
transaction.sender = some(await signer.getAddress())
|
||||
@ -76,14 +66,14 @@ method estimateGas*(
|
||||
method getChainId*(
|
||||
signer: Signer
|
||||
): Future[UInt256] {.
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
return await signer.provider.getChainId()
|
||||
|
||||
method getNonce(
|
||||
signer: Signer
|
||||
): Future[UInt256] {.
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError])
|
||||
base, async: (raises: [SignerError, ProviderError, CancelledError, RpcNetworkError])
|
||||
.} =
|
||||
return await signer.getTransactionCount(BlockTag.pending)
|
||||
|
||||
@ -103,15 +93,14 @@ template withLock*(signer: Signer, body: untyped) =
|
||||
method populateTransaction*(
|
||||
signer: Signer,
|
||||
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.
|
||||
## 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`
|
||||
## function in contract.nim.
|
||||
|
||||
var address: Address
|
||||
convertError:
|
||||
address = await signer.getAddress()
|
||||
address = await signer.getAddress()
|
||||
|
||||
if sender =? transaction.sender and sender != address:
|
||||
raiseSignerError("from address mismatch")
|
||||
@ -154,7 +143,7 @@ method populateTransaction*(
|
||||
method cancelTransaction*(
|
||||
signer: Signer,
|
||||
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
|
||||
# with the failed tx's nonce
|
||||
|
||||
@ -164,7 +153,7 @@ method cancelTransaction*(
|
||||
raiseSignerError "transaction must have nonce"
|
||||
|
||||
withLock(signer):
|
||||
convertError:
|
||||
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
||||
cancelTx = await signer.populateTransaction(cancelTx)
|
||||
return await signer.sendTransaction(cancelTx)
|
||||
# convertError:
|
||||
var cancelTx = Transaction(to: sender, value: 0.u256, nonce: some nonce)
|
||||
cancelTx = await signer.populateTransaction(cancelTx)
|
||||
return await signer.sendTransaction(cancelTx)
|
||||
|
||||
@ -69,7 +69,7 @@ method provider*(wallet: Wallet): Provider {.gcsafe, raises: [SignerError].} =
|
||||
|
||||
method getAddress*(
|
||||
wallet: Wallet): Future[Address]
|
||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
||||
{.async: (raises:[ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||
|
||||
return wallet.address
|
||||
|
||||
@ -83,7 +83,7 @@ proc signTransaction*(wallet: Wallet,
|
||||
method sendTransaction*(
|
||||
wallet: Wallet,
|
||||
transaction: Transaction): Future[TransactionResponse]
|
||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
||||
{.async: (raises:[SignerError, ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
|
||||
let signed = await signTransaction(wallet, transaction)
|
||||
return await provider(wallet).sendTransaction(signed)
|
||||
|
||||
@ -6,10 +6,3 @@ type
|
||||
func raiseWalletError*(message: string) {.raises: [WalletError].}=
|
||||
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*(
|
||||
signer: MockSigner): Future[Address]
|
||||
{.async: (raises:[ProviderError, SignerError, CancelledError]).} =
|
||||
{.async: (raises:[ProviderError, SignerError, CancelledError, RpcNetworkError]).} =
|
||||
|
||||
return signer.address
|
||||
|
||||
method sendTransaction*(
|
||||
signer: MockSigner,
|
||||
transaction: Transaction): Future[TransactionResponse]
|
||||
{.async: (raises:[SignerError, ProviderError, CancelledError]).} =
|
||||
{.async: (raises:[SignerError, ProviderError, CancelledError, RpcNetworkError]).} =
|
||||
|
||||
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/questionable
|
||||
import pkg/ethers/providers/jsonrpc
|
||||
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":
|
||||
|
||||
@ -25,3 +35,106 @@ suite "JSON RPC errors":
|
||||
}
|
||||
}
|
||||
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 ../../examples
|
||||
import ./rpc_mock
|
||||
import ./mocks/mockRpcHttpServerSubscriptions
|
||||
|
||||
suite "JsonRpcSubscriptions":
|
||||
|
||||
@ -111,18 +111,18 @@ suite "HTTP polling subscriptions - mock tests":
|
||||
|
||||
var subscriptions: PollingSubscriptions
|
||||
var client: RpcHttpClient
|
||||
var mockServer: MockRpcHttpServer
|
||||
var mockServer: MockRpcHttpServerSubscriptions
|
||||
|
||||
privateAccess(PollingSubscriptions)
|
||||
privateAccess(JsonRpcSubscriptions)
|
||||
|
||||
proc startServer() {.async.} =
|
||||
mockServer = MockRpcHttpServer.new()
|
||||
mockServer = MockRpcHttpServerSubscriptions.new()
|
||||
mockServer.start()
|
||||
await client.connect("http://" & $mockServer.localAddress()[0])
|
||||
await client.connect("http://" & $MockRpcHttpServer(mockServer).localAddress()[0])
|
||||
|
||||
proc stopServer() {.async.} =
|
||||
await mockServer.stop()
|
||||
await MockRpcHttpServer(mockServer).stop()
|
||||
|
||||
setup:
|
||||
client = newRpcHttpClient()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user