Move `confirm` from `jsonrpc` to `provider`
This commit is contained in:
parent
2653610b6b
commit
33df1e759d
|
@ -95,3 +95,114 @@ method subscribe*(provider: Provider,
|
||||||
|
|
||||||
method unsubscribe*(subscription: Subscription) {.base, async.} =
|
method unsubscribe*(subscription: Subscription) {.base, async.} =
|
||||||
doAssert false, "not implemented"
|
doAssert false, "not implemented"
|
||||||
|
|
||||||
|
import std/options # TODO
|
||||||
|
|
||||||
|
# Removed from `confirm` closure and exported so it can be tested.
|
||||||
|
# Likely there is a better way
|
||||||
|
func confirmations*(receiptBlk, atBlk: UInt256): UInt256 =
|
||||||
|
## Calculates the number of confirmations between two blocks
|
||||||
|
if atBlk < receiptBlk:
|
||||||
|
return 0.u256
|
||||||
|
else:
|
||||||
|
return (atBlk - receiptBlk) + 1 # add 1 for current block
|
||||||
|
|
||||||
|
# Removed from `confirm` closure and exported so it can be tested.
|
||||||
|
# Likely there is a better way
|
||||||
|
func hasBeenMined*(receipt: ?TransactionReceipt,
|
||||||
|
atBlock: UInt256,
|
||||||
|
wantedConfirms: int): bool =
|
||||||
|
## Returns true if the transaction receipt has been returned from the node
|
||||||
|
## with a valid block number and block hash and the specified number of
|
||||||
|
## blocks have passed since the tx was mined (confirmations)
|
||||||
|
|
||||||
|
if receipt.isSome and
|
||||||
|
receipt.get.blockNumber.isSome and
|
||||||
|
receipt.get.blockNumber.get > 0 and
|
||||||
|
# from ethers.js: "geth-etc" returns receipts before they are ready
|
||||||
|
receipt.get.blockHash.isSome:
|
||||||
|
|
||||||
|
let receiptBlock = receipt.get.blockNumber.get
|
||||||
|
return receiptBlock.confirmations(atBlock) >= wantedConfirms.u256
|
||||||
|
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc confirm*(tx: TransactionResponse,
|
||||||
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
|
Future[TransactionReceipt]
|
||||||
|
{.async, upraises: [EthersError].} = # raises for clarity
|
||||||
|
## Waits for a transaction to be mined and for the specified number of blocks
|
||||||
|
## to pass since it was mined (confirmations).
|
||||||
|
## A timeout, in blocks, can be specified that will raise an error if too many
|
||||||
|
## blocks have passed without the tx having been mined.
|
||||||
|
|
||||||
|
var subscription: Subscription
|
||||||
|
let
|
||||||
|
provider = tx.provider
|
||||||
|
retFut = newFuture[TransactionReceipt]("wait")
|
||||||
|
|
||||||
|
# used to check for block timeouts
|
||||||
|
let startBlock = await provider.getBlockNumber()
|
||||||
|
|
||||||
|
proc newBlock(blk: Block) {.async.} =
|
||||||
|
## subscription callback, called every time a new block event is sent from
|
||||||
|
## the node
|
||||||
|
|
||||||
|
# if ethereum node doesn't include blockNumber in the event
|
||||||
|
without blkNum =? blk.number:
|
||||||
|
return
|
||||||
|
|
||||||
|
let receipt = await provider.getTransactionReceipt(tx.hash)
|
||||||
|
if receipt.hasBeenMined(blkNum, wantedConfirms):
|
||||||
|
# fire and forget
|
||||||
|
discard subscription.unsubscribe()
|
||||||
|
if not retFut.finished:
|
||||||
|
retFut.complete(receipt.get)
|
||||||
|
|
||||||
|
elif timeoutInBlocks > 0:
|
||||||
|
let blocksPassed = (blkNum - startBlock) + 1
|
||||||
|
if blocksPassed >= timeoutInBlocks.u256:
|
||||||
|
discard subscription.unsubscribe()
|
||||||
|
if not retFut.finished:
|
||||||
|
let message =
|
||||||
|
"Transaction was not mined in " & $timeoutInBlocks & " blocks"
|
||||||
|
retFut.fail(newException(EthersError, message))
|
||||||
|
|
||||||
|
# If our tx is already mined, return the receipt. Otherwise, check each
|
||||||
|
# new block to see if the tx has been mined
|
||||||
|
let receipt = await provider.getTransactionReceipt(tx.hash)
|
||||||
|
if receipt.hasBeenMined(startBlock, wantedConfirms):
|
||||||
|
return receipt.get
|
||||||
|
else:
|
||||||
|
subscription = await provider.subscribe(newBlock)
|
||||||
|
return (await retFut)
|
||||||
|
|
||||||
|
proc confirm*(tx: Future[TransactionResponse],
|
||||||
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
|
Future[TransactionReceipt] {.async.} =
|
||||||
|
## Convenience method that allows wait to be chained to a sendTransaction
|
||||||
|
## call, eg:
|
||||||
|
## `await signer.sendTransaction(populated).confirm(3)`
|
||||||
|
|
||||||
|
let txResp = await tx
|
||||||
|
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
||||||
|
|
||||||
|
proc confirm*(tx: Future[?TransactionResponse],
|
||||||
|
wantedConfirms: Positive = EthersDefaultConfirmations,
|
||||||
|
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
||||||
|
Future[TransactionReceipt] {.async.} =
|
||||||
|
## Convenience method that allows wait to be chained to a contract
|
||||||
|
## transaction, eg:
|
||||||
|
## `await token.connect(signer0)
|
||||||
|
## .mint(accounts[1], 100.u256)
|
||||||
|
## .confirm(3)`
|
||||||
|
|
||||||
|
without txResp =? (await tx):
|
||||||
|
raise newException(
|
||||||
|
EthersError,
|
||||||
|
"Transaction hash required. Possibly was a call instead of a send?"
|
||||||
|
)
|
||||||
|
|
||||||
|
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
||||||
|
|
|
@ -197,117 +197,3 @@ method sendTransaction*(signer: JsonRpcSigner,
|
||||||
hash = await client.eth_sendTransaction(transaction)
|
hash = await client.eth_sendTransaction(transaction)
|
||||||
|
|
||||||
return TransactionResponse(hash: hash, provider: signer.provider)
|
return TransactionResponse(hash: hash, provider: signer.provider)
|
||||||
|
|
||||||
|
|
||||||
# Removed from `confirm` closure and exported so it can be tested.
|
|
||||||
# Likely there is a better way
|
|
||||||
func confirmations*(receiptBlk, atBlk: UInt256): UInt256 =
|
|
||||||
## Calculates the number of confirmations between two blocks
|
|
||||||
if atBlk < receiptBlk:
|
|
||||||
return 0.u256
|
|
||||||
else:
|
|
||||||
return (atBlk - receiptBlk) + 1 # add 1 for current block
|
|
||||||
|
|
||||||
# Removed from `confirm` closure and exported so it can be tested.
|
|
||||||
# Likely there is a better way
|
|
||||||
func hasBeenMined*(receipt: ?TransactionReceipt,
|
|
||||||
atBlock: UInt256,
|
|
||||||
wantedConfirms: int): bool =
|
|
||||||
## Returns true if the transaction receipt has been returned from the node
|
|
||||||
## with a valid block number and block hash and the specified number of
|
|
||||||
## blocks have passed since the tx was mined (confirmations)
|
|
||||||
|
|
||||||
if receipt.isSome and
|
|
||||||
receipt.get.blockNumber.isSome and
|
|
||||||
receipt.get.blockNumber.get > 0 and
|
|
||||||
# from ethers.js: "geth-etc" returns receipts before they are ready
|
|
||||||
receipt.get.blockHash.isSome:
|
|
||||||
|
|
||||||
let receiptBlock = receipt.get.blockNumber.get
|
|
||||||
return receiptBlock.confirmations(atBlock) >= wantedConfirms.u256
|
|
||||||
|
|
||||||
return false
|
|
||||||
|
|
||||||
proc confirm*(tx: TransactionResponse,
|
|
||||||
wantedConfirms: Positive = EthersDefaultConfirmations,
|
|
||||||
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
|
||||||
Future[TransactionReceipt]
|
|
||||||
{.async, upraises: [EthersError].} = # raises for clarity
|
|
||||||
## Waits for a transaction to be mined and for the specified number of blocks
|
|
||||||
## to pass since it was mined (confirmations).
|
|
||||||
## A timeout, in blocks, can be specified that will raise a
|
|
||||||
## JsonRpcProviderError if too many blocks have passed without the tx
|
|
||||||
## having been mined.
|
|
||||||
## Note: this method requires that the JsonRpcProvider client connects
|
|
||||||
## using RpcWebSocketClient, otherwise it will raise a Defect.
|
|
||||||
|
|
||||||
var subscription: JsonRpcSubscription
|
|
||||||
let
|
|
||||||
provider = JsonRpcProvider(tx.provider)
|
|
||||||
retFut = newFuture[TransactionReceipt]("wait")
|
|
||||||
|
|
||||||
# used to check for block timeouts
|
|
||||||
let startBlock = await provider.getBlockNumber()
|
|
||||||
|
|
||||||
proc newBlock(blk: Block) {.async.} =
|
|
||||||
## subscription callback, called every time a new block event is sent from
|
|
||||||
## the node
|
|
||||||
|
|
||||||
# if ethereum node doesn't include blockNumber in the event
|
|
||||||
without blkNum =? blk.number:
|
|
||||||
return
|
|
||||||
|
|
||||||
let receipt = await provider.getTransactionReceipt(tx.hash)
|
|
||||||
if receipt.hasBeenMined(blkNum, wantedConfirms):
|
|
||||||
# fire and forget
|
|
||||||
discard subscription.unsubscribe()
|
|
||||||
if not retFut.finished:
|
|
||||||
retFut.complete(receipt.get)
|
|
||||||
|
|
||||||
elif timeoutInBlocks > 0:
|
|
||||||
let blocksPassed = (blkNum - startBlock) + 1
|
|
||||||
if blocksPassed >= timeoutInBlocks.u256:
|
|
||||||
discard subscription.unsubscribe()
|
|
||||||
if not retFut.finished:
|
|
||||||
let message =
|
|
||||||
"Transaction was not mined in " & $timeoutInBlocks & " blocks"
|
|
||||||
retFut.fail(newException(EthersError, message))
|
|
||||||
|
|
||||||
# If our tx is already mined, return the receipt. Otherwise, check each
|
|
||||||
# new block to see if the tx has been mined
|
|
||||||
let receipt = await provider.getTransactionReceipt(tx.hash)
|
|
||||||
if receipt.hasBeenMined(startBlock, wantedConfirms):
|
|
||||||
return receipt.get
|
|
||||||
else:
|
|
||||||
let sub = await provider.subscribe(newBlock)
|
|
||||||
subscription = JsonRpcSubscription(sub)
|
|
||||||
return (await retFut)
|
|
||||||
|
|
||||||
proc confirm*(tx: Future[TransactionResponse],
|
|
||||||
wantedConfirms: Positive = EthersDefaultConfirmations,
|
|
||||||
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
|
||||||
Future[TransactionReceipt] {.async.} =
|
|
||||||
## Convenience method that allows wait to be chained to a sendTransaction
|
|
||||||
## call, eg:
|
|
||||||
## `await signer.sendTransaction(populated).confirm(3)`
|
|
||||||
|
|
||||||
let txResp = await tx
|
|
||||||
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
|
||||||
|
|
||||||
proc confirm*(tx: Future[?TransactionResponse],
|
|
||||||
wantedConfirms: Positive = EthersDefaultConfirmations,
|
|
||||||
timeoutInBlocks: Natural = EthersReceiptTimeoutBlks):
|
|
||||||
Future[TransactionReceipt] {.async.} =
|
|
||||||
## Convenience method that allows wait to be chained to a contract
|
|
||||||
## transaction, eg:
|
|
||||||
## `await token.connect(signer0)
|
|
||||||
## .mint(accounts[1], 100.u256)
|
|
||||||
## .confirm(3)`
|
|
||||||
|
|
||||||
without txResp =? (await tx):
|
|
||||||
raise newException(
|
|
||||||
EthersError,
|
|
||||||
"Transaction hash required. Possibly was a call instead of a send?"
|
|
||||||
)
|
|
||||||
|
|
||||||
return await txResp.confirm(wantedConfirms, timeoutInBlocks)
|
|
||||||
|
|
Loading…
Reference in New Issue