fix: modify unsubscribe cleanup routine and tests (#84)
* fix: modify unsubscribe cleanup routine
Ignore exceptions (other than CancelledError) if uninstallation of the filter fails. If it's the last step in the subscription cleanup, then filter changes for this filter will no longer be polled so if the filter continues to live on in geth for whatever reason, then it doesn't matter.
This includes a number of fixes:
- `CancelledError` is now caught inside of `getChanges`. This was causing conditions during `subscriptions.close`, where the `CancelledError` would get consumed by the `except CatchableError`, if there was an ongoing `poll` happening at the time of close.
- After creating a new filter inside of `getChanges`, the new filter is polled for changes before returning.
- `getChanges` also does not swallow `CatchableError` by returning an empty array, and instead re-raises the error if it is not `filter not found`.
- The tests were simplified by accessing the private fields of `PollingSubscriptions`. That way, there wasn't a race condition for the `newFilterId` counter inside of the mock.
- The `MockRpcHttpServer` was simplified by keeping track of the active filters only, and invalidation simply removes the filter. The tests then only needed to rely on the fact that the filter id changed in the mapping.
- Because of the above changes, we no longer needed to sleep inside of the tests, so the sleeps were removed, and the polling interval could be changed to 1ms, which not only makes the tests faster, but would further highlight any race conditions if present.
* docs: rpc custom port documentation
---------
Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
2024-10-25 14:58:45 +11:00
|
|
|
import std/os
|
2022-01-18 12:10:20 +01:00
|
|
|
import pkg/asynctest
|
|
|
|
import pkg/chronos
|
feat: Allow contract transactions to be waited on
Allow waiting for a specified number of confirmations for contract transactions.
This change only requires an optional TransactionResponse return type to be added to the contract function. This allows the transaction hash to be passed to `.wait`.
For example, previously the `mint` method looked like this without a return value:
```
method mint(token: TestToken, holder: Address, amount: UInt256) {.base, contract.}
```
it still works without a return value, but if we want to wait for a 3 confirmations, we can now define it like this:
```
method mint(token: TestToken, holder: Address, amount: UInt256): ?TransactionResponse {.base, contract.}
```
and use like this:
```
let receipt = await token.connect(signer0)
.mint(accounts[1], 100.u256)
.wait(3) # wait for 3 confirmations
```
2022-05-17 14:57:18 +10:00
|
|
|
import pkg/ethers
|
2023-06-19 14:13:44 +02:00
|
|
|
import pkg/ethers/providers/jsonrpc/conversions
|
2022-05-17 12:34:22 +10:00
|
|
|
import pkg/stew/byteutils
|
2023-06-21 15:01:04 +02:00
|
|
|
import ../../examples
|
|
|
|
import ../../miner
|
2022-01-18 12:10:20 +01:00
|
|
|
|
fix: modify unsubscribe cleanup routine and tests (#84)
* fix: modify unsubscribe cleanup routine
Ignore exceptions (other than CancelledError) if uninstallation of the filter fails. If it's the last step in the subscription cleanup, then filter changes for this filter will no longer be polled so if the filter continues to live on in geth for whatever reason, then it doesn't matter.
This includes a number of fixes:
- `CancelledError` is now caught inside of `getChanges`. This was causing conditions during `subscriptions.close`, where the `CancelledError` would get consumed by the `except CatchableError`, if there was an ongoing `poll` happening at the time of close.
- After creating a new filter inside of `getChanges`, the new filter is polled for changes before returning.
- `getChanges` also does not swallow `CatchableError` by returning an empty array, and instead re-raises the error if it is not `filter not found`.
- The tests were simplified by accessing the private fields of `PollingSubscriptions`. That way, there wasn't a race condition for the `newFilterId` counter inside of the mock.
- The `MockRpcHttpServer` was simplified by keeping track of the active filters only, and invalidation simply removes the filter. The tests then only needed to rely on the fact that the filter id changed in the mapping.
- Because of the above changes, we no longer needed to sleep inside of the tests, so the sleeps were removed, and the polling interval could be changed to 1ms, which not only makes the tests faster, but would further highlight any race conditions if present.
* docs: rpc custom port documentation
---------
Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
2024-10-25 14:58:45 +11:00
|
|
|
let providerUrl = getEnv("ETHERS_TEST_PROVIDER", "localhost:8545")
|
|
|
|
for url in ["ws://" & providerUrl, "http://" & providerUrl]:
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
suite "JsonRpcProvider (" & url & ")":
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
var provider: JsonRpcProvider
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
setup:
|
2023-06-27 15:08:37 +02:00
|
|
|
provider = JsonRpcProvider.new(url, pollingInterval = 100.millis)
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 16:40:29 +02:00
|
|
|
|
|
|
|
teardown:
|
|
|
|
await provider.close()
|
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
test "can be instantiated with a default URL":
|
|
|
|
discard JsonRpcProvider.new()
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
test "lists all accounts":
|
|
|
|
let accounts = await provider.listAccounts()
|
|
|
|
check accounts.len > 0
|
2022-01-18 12:10:20 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
test "sends raw messages to the provider":
|
|
|
|
let response = await provider.send("evm_mine")
|
2024-05-13 11:51:43 +02:00
|
|
|
check response == %"0"
|
2022-01-18 14:24:46 +01:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
test "returns block number":
|
|
|
|
let blocknumber1 = await provider.getBlockNumber()
|
|
|
|
discard await provider.send("evm_mine")
|
|
|
|
let blocknumber2 = await provider.getBlockNumber()
|
|
|
|
check blocknumber2 > blocknumber1
|
|
|
|
|
|
|
|
test "returns block":
|
|
|
|
let block1 = !await provider.getBlock(BlockTag.earliest)
|
|
|
|
let block2 = !await provider.getBlock(BlockTag.latest)
|
|
|
|
check block1.hash != block2.hash
|
|
|
|
check !block1.number < !block2.number
|
|
|
|
check block1.timestamp < block2.timestamp
|
|
|
|
|
|
|
|
test "subscribes to new blocks":
|
|
|
|
let oldBlock = !await provider.getBlock(BlockTag.latest)
|
2023-06-27 15:24:01 +02:00
|
|
|
discard await provider.send("evm_mine")
|
2023-06-27 15:05:15 +02:00
|
|
|
var newBlock: Block
|
2024-11-28 14:48:10 +01:00
|
|
|
let blockHandler = proc(blck: ?!Block) {.raises:[].}= newBlock = blck.value
|
2023-06-27 15:05:15 +02:00
|
|
|
let subscription = await provider.subscribe(blockHandler)
|
|
|
|
discard await provider.send("evm_mine")
|
|
|
|
check eventually newBlock.number.isSome
|
|
|
|
check !newBlock.number > !oldBlock.number
|
|
|
|
check newBlock.timestamp > oldBlock.timestamp
|
|
|
|
check newBlock.hash != oldBlock.hash
|
|
|
|
await subscription.unsubscribe()
|
|
|
|
|
|
|
|
test "can send a transaction":
|
|
|
|
let signer = provider.getSigner()
|
|
|
|
let transaction = Transaction.example
|
|
|
|
let populated = await signer.populateTransaction(transaction)
|
|
|
|
|
|
|
|
let txResp = await signer.sendTransaction(populated)
|
|
|
|
check txResp.hash.len == 32
|
|
|
|
check UInt256.fromHex("0x" & txResp.hash.toHex) > 0
|
|
|
|
|
|
|
|
test "can wait for a transaction to be confirmed":
|
2024-11-11 12:26:36 +01:00
|
|
|
for confirmations in 1..3:
|
2023-06-29 09:59:48 +02:00
|
|
|
let signer = provider.getSigner()
|
|
|
|
let transaction = Transaction.example
|
|
|
|
let populated = await signer.populateTransaction(transaction)
|
|
|
|
let confirming = signer.sendTransaction(populated).confirm(confirmations)
|
|
|
|
await sleepAsync(100.millis) # wait for tx to be mined
|
2024-11-11 12:26:36 +01:00
|
|
|
await provider.mineBlocks(confirmations)
|
2023-06-29 09:59:48 +02:00
|
|
|
let receipt = await confirming
|
|
|
|
check receipt.blockNumber.isSome
|
|
|
|
|
|
|
|
test "confirmation times out":
|
|
|
|
let hash = TransactionHash.example
|
|
|
|
let tx = TransactionResponse(provider: provider, hash: hash)
|
|
|
|
let confirming = tx.confirm(confirmations = 2, timeout = 5)
|
2024-03-19 09:53:02 +01:00
|
|
|
await sleepAsync(100.millis) # wait for confirm to subscribe to new blocks
|
2023-06-29 09:59:48 +02:00
|
|
|
await provider.mineBlocks(5)
|
|
|
|
expect EthersError:
|
|
|
|
discard await confirming
|
2022-05-18 23:14:39 +10:00
|
|
|
|
2023-06-27 15:05:15 +02:00
|
|
|
test "raises JsonRpcProviderError when something goes wrong":
|
|
|
|
let provider = JsonRpcProvider.new("http://invalid.")
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.listAccounts()
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.send("evm_mine")
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.getBlockNumber()
|
|
|
|
expect JsonRpcProviderError:
|
|
|
|
discard await provider.getBlock(BlockTag.latest)
|
|
|
|
expect JsonRpcProviderError:
|
2024-11-28 14:48:10 +01:00
|
|
|
discard await provider.subscribe(proc(_: ?!Block) = discard)
|
2024-03-20 15:01:48 +01:00
|
|
|
expect JsonRpcProviderError:
|
2023-06-27 15:05:15 +02:00
|
|
|
discard await provider.getSigner().sendTransaction(Transaction.example)
|
2024-02-20 16:25:23 +01:00
|
|
|
|
|
|
|
test "syncing":
|
|
|
|
let isSyncing = await provider.isSyncing()
|
|
|
|
check not isSyncing
|
|
|
|
|