2020-09-24 21:33:09 +00:00
|
|
|
use crate::Middleware;
|
2020-06-21 08:09:19 +00:00
|
|
|
use crate::{
|
2020-06-22 08:44:08 +00:00
|
|
|
stream::{interval, DEFAULT_POLL_INTERVAL},
|
2020-09-23 08:04:54 +00:00
|
|
|
JsonRpcClient, PinBoxFut, Provider, ProviderError,
|
2020-06-21 08:09:19 +00:00
|
|
|
};
|
2021-07-06 08:06:18 +00:00
|
|
|
use ethers_core::types::{Transaction, TransactionReceipt, TxHash, U64};
|
2020-06-21 08:09:19 +00:00
|
|
|
use futures_core::stream::Stream;
|
2021-07-13 19:34:11 +00:00
|
|
|
use futures_timer::Delay;
|
2020-06-21 08:09:19 +00:00
|
|
|
use futures_util::stream::StreamExt;
|
2020-06-15 12:40:06 +00:00
|
|
|
use pin_project::pin_project;
|
|
|
|
use std::{
|
|
|
|
fmt,
|
|
|
|
future::Future,
|
|
|
|
ops::Deref,
|
|
|
|
pin::Pin,
|
|
|
|
task::{Context, Poll},
|
2020-06-21 08:09:19 +00:00
|
|
|
time::Duration,
|
2020-06-15 12:40:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
/// A pending transaction is a transaction which has been submitted but is not yet mined.
|
|
|
|
/// `await`'ing on a pending transaction will resolve to a transaction receipt
|
|
|
|
/// once the transaction has enough `confirmations`. The default number of confirmations
|
|
|
|
/// is 1, but may be adjusted with the `confirmations` method. If the transaction does not
|
|
|
|
/// have enough confirmations or is not mined, the future will stay in the pending state.
|
|
|
|
#[pin_project]
|
|
|
|
pub struct PendingTransaction<'a, P> {
|
|
|
|
tx_hash: TxHash,
|
|
|
|
confirmations: usize,
|
|
|
|
provider: &'a Provider<P>,
|
|
|
|
state: PendingTxState<'a>,
|
2020-06-21 08:09:19 +00:00
|
|
|
interval: Box<dyn Stream<Item = ()> + Send + Unpin>,
|
2020-06-15 12:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, P: JsonRpcClient> PendingTransaction<'a, P> {
|
|
|
|
/// Creates a new pending transaction poller from a hash and a provider
|
|
|
|
pub fn new(tx_hash: TxHash, provider: &'a Provider<P>) -> Self {
|
2021-07-13 19:34:11 +00:00
|
|
|
let delay = Box::pin(Delay::new(DEFAULT_POLL_INTERVAL));
|
2020-06-15 12:40:06 +00:00
|
|
|
Self {
|
|
|
|
tx_hash,
|
|
|
|
confirmations: 1,
|
|
|
|
provider,
|
2021-07-13 19:34:11 +00:00
|
|
|
state: PendingTxState::InitialDelay(delay),
|
2020-06-22 08:44:08 +00:00
|
|
|
interval: Box::new(interval(DEFAULT_POLL_INTERVAL)),
|
2020-06-15 12:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the number of confirmations for the pending transaction to resolve
|
|
|
|
/// to a receipt
|
|
|
|
pub fn confirmations(mut self, confs: usize) -> Self {
|
|
|
|
self.confirmations = confs;
|
|
|
|
self
|
|
|
|
}
|
2020-06-21 08:09:19 +00:00
|
|
|
|
|
|
|
/// Sets the polling interval
|
2020-06-22 08:44:08 +00:00
|
|
|
pub fn interval<T: Into<Duration>>(mut self, duration: T) -> Self {
|
2021-07-13 19:34:11 +00:00
|
|
|
let duration = duration.into();
|
|
|
|
|
|
|
|
self.interval = Box::new(interval(duration));
|
|
|
|
|
|
|
|
if matches!(self.state, PendingTxState::InitialDelay(_)) {
|
|
|
|
self.state = PendingTxState::InitialDelay(Box::pin(Delay::new(duration)))
|
|
|
|
}
|
|
|
|
|
2020-06-21 08:09:19 +00:00
|
|
|
self
|
|
|
|
}
|
2020-06-15 12:40:06 +00:00
|
|
|
}
|
|
|
|
|
2021-07-06 08:06:18 +00:00
|
|
|
macro_rules! rewake_with_new_state {
|
|
|
|
($ctx:ident, $this:ident, $new_state:expr) => {
|
|
|
|
*$this.state = $new_state;
|
|
|
|
$ctx.waker().wake_by_ref();
|
|
|
|
return Poll::Pending;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! rewake_with_new_state_if {
|
|
|
|
($condition:expr, $ctx:ident, $this:ident, $new_state:expr) => {
|
|
|
|
if $condition {
|
|
|
|
rewake_with_new_state!($ctx, $this, $new_state);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-06-15 12:40:06 +00:00
|
|
|
impl<'a, P: JsonRpcClient> Future for PendingTransaction<'a, P> {
|
2021-07-06 08:06:18 +00:00
|
|
|
type Output = Result<Option<TransactionReceipt>, ProviderError>;
|
2020-06-15 12:40:06 +00:00
|
|
|
|
|
|
|
fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
|
|
|
|
let this = self.project();
|
|
|
|
|
|
|
|
match this.state {
|
2021-07-13 19:34:11 +00:00
|
|
|
PendingTxState::InitialDelay(fut) => {
|
|
|
|
let _ready = futures_util::ready!(fut.as_mut().poll(ctx));
|
|
|
|
tracing::debug!("Starting to poll pending tx {:?}", *this.tx_hash);
|
|
|
|
let fut = Box::pin(this.provider.get_transaction(*this.tx_hash));
|
|
|
|
rewake_with_new_state!(ctx, this, PendingTxState::GettingTx(fut));
|
|
|
|
}
|
2021-07-06 08:06:18 +00:00
|
|
|
PendingTxState::PausedGettingTx => {
|
|
|
|
// Wait the polling period so that we do not spam the chain when no
|
|
|
|
// new block has been mined
|
|
|
|
let _ready = futures_util::ready!(this.interval.poll_next_unpin(ctx));
|
|
|
|
let fut = Box::pin(this.provider.get_transaction(*this.tx_hash));
|
|
|
|
*this.state = PendingTxState::GettingTx(fut);
|
|
|
|
ctx.waker().wake_by_ref();
|
|
|
|
}
|
|
|
|
PendingTxState::GettingTx(fut) => {
|
|
|
|
let tx_res = futures_util::ready!(fut.as_mut().poll(ctx));
|
|
|
|
// If the provider errors, just try again after the interval.
|
|
|
|
// nbd.
|
|
|
|
rewake_with_new_state_if!(
|
|
|
|
tx_res.is_err(),
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
PendingTxState::PausedGettingTx
|
|
|
|
);
|
|
|
|
|
|
|
|
let tx_opt = tx_res.unwrap();
|
|
|
|
// If the tx is no longer in the mempool, return Ok(None)
|
|
|
|
if tx_opt.is_none() {
|
2021-07-13 19:34:11 +00:00
|
|
|
tracing::debug!("Dropped from mempool, pending tx {:?}", *this.tx_hash);
|
2021-07-06 08:06:18 +00:00
|
|
|
*this.state = PendingTxState::Completed;
|
|
|
|
return Poll::Ready(Ok(None));
|
|
|
|
}
|
|
|
|
|
|
|
|
// If it hasn't confirmed yet, poll again later
|
|
|
|
let tx = tx_opt.unwrap();
|
|
|
|
rewake_with_new_state_if!(
|
|
|
|
tx.block_number.is_none(),
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
PendingTxState::PausedGettingTx
|
|
|
|
);
|
|
|
|
|
|
|
|
// Start polling for the receipt now
|
2021-07-13 19:34:11 +00:00
|
|
|
tracing::debug!("Getting receipt for pending tx {:?}", *this.tx_hash);
|
2021-07-06 08:06:18 +00:00
|
|
|
let fut = Box::pin(this.provider.get_transaction_receipt(*this.tx_hash));
|
2021-07-13 19:34:11 +00:00
|
|
|
rewake_with_new_state!(ctx, this, PendingTxState::GettingReceipt(fut));
|
2021-07-06 08:06:18 +00:00
|
|
|
}
|
2020-07-10 06:59:29 +00:00
|
|
|
PendingTxState::PausedGettingReceipt => {
|
2020-06-21 08:09:19 +00:00
|
|
|
// Wait the polling period so that we do not spam the chain when no
|
|
|
|
// new block has been mined
|
|
|
|
let _ready = futures_util::ready!(this.interval.poll_next_unpin(ctx));
|
2020-07-10 06:59:29 +00:00
|
|
|
let fut = Box::pin(this.provider.get_transaction_receipt(*this.tx_hash));
|
2020-07-13 17:52:33 +00:00
|
|
|
*this.state = PendingTxState::GettingReceipt(fut);
|
|
|
|
ctx.waker().wake_by_ref();
|
2020-07-10 06:59:29 +00:00
|
|
|
}
|
|
|
|
PendingTxState::GettingReceipt(fut) => {
|
2021-07-06 08:06:18 +00:00
|
|
|
if let Ok(receipt) = futures_util::ready!(fut.as_mut().poll(ctx)) {
|
2021-07-13 19:34:11 +00:00
|
|
|
tracing::debug!("Checking receipt for pending tx {:?}", *this.tx_hash);
|
2021-07-06 08:06:18 +00:00
|
|
|
*this.state = PendingTxState::CheckingReceipt(receipt)
|
2020-06-17 08:02:03 +00:00
|
|
|
} else {
|
2020-07-10 06:59:29 +00:00
|
|
|
*this.state = PendingTxState::PausedGettingReceipt
|
2020-06-17 08:02:03 +00:00
|
|
|
}
|
2020-12-17 09:23:10 +00:00
|
|
|
ctx.waker().wake_by_ref();
|
2020-06-15 12:40:06 +00:00
|
|
|
}
|
|
|
|
PendingTxState::CheckingReceipt(receipt) => {
|
2021-07-06 08:06:18 +00:00
|
|
|
rewake_with_new_state_if!(
|
|
|
|
receipt.is_none(),
|
|
|
|
ctx,
|
|
|
|
this,
|
|
|
|
PendingTxState::PausedGettingReceipt
|
|
|
|
);
|
|
|
|
|
2020-06-15 12:40:06 +00:00
|
|
|
// If we requested more than 1 confirmation, we need to compare the receipt's
|
|
|
|
// block number and the current block
|
|
|
|
if *this.confirmations > 1 {
|
2021-07-13 19:34:11 +00:00
|
|
|
tracing::debug!(
|
|
|
|
"Waiting on confirmations for pending tx {:?}",
|
|
|
|
*this.tx_hash
|
|
|
|
);
|
|
|
|
|
2020-06-15 12:40:06 +00:00
|
|
|
let fut = Box::pin(this.provider.get_block_number());
|
2021-07-06 08:06:18 +00:00
|
|
|
*this.state = PendingTxState::GettingBlockNumber(fut, receipt.take());
|
2020-06-17 08:02:03 +00:00
|
|
|
|
|
|
|
// Schedule the waker to poll again
|
|
|
|
ctx.waker().wake_by_ref();
|
2020-06-15 12:40:06 +00:00
|
|
|
} else {
|
2021-07-06 08:06:18 +00:00
|
|
|
let receipt = receipt.take();
|
2020-06-15 12:40:06 +00:00
|
|
|
*this.state = PendingTxState::Completed;
|
|
|
|
return Poll::Ready(Ok(receipt));
|
|
|
|
}
|
|
|
|
}
|
2020-07-10 06:59:29 +00:00
|
|
|
PendingTxState::PausedGettingBlockNumber(receipt) => {
|
2020-06-21 08:09:19 +00:00
|
|
|
// Wait the polling period so that we do not spam the chain when no
|
|
|
|
// new block has been mined
|
|
|
|
let _ready = futures_util::ready!(this.interval.poll_next_unpin(ctx));
|
|
|
|
|
2020-07-10 06:59:29 +00:00
|
|
|
// we need to re-instantiate the get_block_number future so that
|
|
|
|
// we poll again
|
|
|
|
let fut = Box::pin(this.provider.get_block_number());
|
2021-07-06 08:06:18 +00:00
|
|
|
*this.state = PendingTxState::GettingBlockNumber(fut, receipt.take());
|
2020-07-13 17:52:33 +00:00
|
|
|
ctx.waker().wake_by_ref();
|
2020-07-10 06:59:29 +00:00
|
|
|
}
|
|
|
|
PendingTxState::GettingBlockNumber(fut, receipt) => {
|
2021-07-06 08:06:18 +00:00
|
|
|
let current_block = futures_util::ready!(fut.as_mut().poll(ctx))?;
|
|
|
|
|
|
|
|
// This is safe so long as we only enter the `GettingBlock`
|
|
|
|
// loop from `CheckingReceipt`, which contains an explicit
|
|
|
|
// `is_none` check
|
|
|
|
let receipt = receipt.take().expect("GettingBlockNumber without receipt");
|
|
|
|
|
2020-06-21 08:09:19 +00:00
|
|
|
// Wait for the interval
|
2020-06-15 12:40:06 +00:00
|
|
|
let inclusion_block = receipt
|
|
|
|
.block_number
|
|
|
|
.expect("Receipt did not have a block number. This should never happen");
|
|
|
|
// if the transaction has at least K confirmations, return the receipt
|
|
|
|
// (subtract 1 since the tx already has 1 conf when it's mined)
|
2020-12-24 20:23:05 +00:00
|
|
|
if current_block > inclusion_block + *this.confirmations - 1 {
|
2021-07-06 08:06:18 +00:00
|
|
|
let receipt = Some(receipt);
|
2020-06-15 12:40:06 +00:00
|
|
|
*this.state = PendingTxState::Completed;
|
|
|
|
return Poll::Ready(Ok(receipt));
|
|
|
|
} else {
|
2020-12-24 20:23:05 +00:00
|
|
|
tracing::trace!(tx_hash = ?this.tx_hash, "confirmations {}/{}", current_block - inclusion_block + 1, this.confirmations);
|
2021-07-06 08:06:18 +00:00
|
|
|
*this.state = PendingTxState::PausedGettingBlockNumber(Some(receipt));
|
2020-12-24 16:33:22 +00:00
|
|
|
ctx.waker().wake_by_ref();
|
2020-06-15 12:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
PendingTxState::Completed => {
|
|
|
|
panic!("polled pending transaction future after completion")
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
Poll::Pending
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, P> fmt::Debug for PendingTransaction<'a, P> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.debug_struct("PendingTransaction")
|
|
|
|
.field("tx_hash", &self.tx_hash)
|
|
|
|
.field("confirmations", &self.confirmations)
|
|
|
|
.field("state", &self.state)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, P> PartialEq for PendingTransaction<'a, P> {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
self.tx_hash == other.tx_hash
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, P> PartialEq<TxHash> for PendingTransaction<'a, P> {
|
|
|
|
fn eq(&self, other: &TxHash) -> bool {
|
|
|
|
&self.tx_hash == other
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, P> Eq for PendingTransaction<'a, P> {}
|
|
|
|
|
|
|
|
impl<'a, P> Deref for PendingTransaction<'a, P> {
|
|
|
|
type Target = TxHash;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.tx_hash
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We box the TransactionReceipts to keep the enum small.
|
|
|
|
enum PendingTxState<'a> {
|
2021-07-13 19:34:11 +00:00
|
|
|
/// Initial delay to ensure the GettingTx loop doesn't immediately fail
|
|
|
|
InitialDelay(Pin<Box<futures_timer::Delay>>),
|
|
|
|
|
2020-07-10 06:59:29 +00:00
|
|
|
/// Waiting for interval to elapse before calling API again
|
2021-07-06 08:06:18 +00:00
|
|
|
PausedGettingTx,
|
2020-07-10 06:59:29 +00:00
|
|
|
|
2021-07-06 08:06:18 +00:00
|
|
|
/// Polling The blockchain to see if the Tx has confirmed or dropped
|
|
|
|
GettingTx(PinBoxFut<'a, Option<Transaction>>),
|
2020-06-15 12:40:06 +00:00
|
|
|
|
2020-07-10 06:59:29 +00:00
|
|
|
/// Waiting for interval to elapse before calling API again
|
2021-07-06 08:06:18 +00:00
|
|
|
PausedGettingReceipt,
|
2020-07-10 06:59:29 +00:00
|
|
|
|
2021-07-06 08:06:18 +00:00
|
|
|
/// Polling the blockchain for the receipt
|
|
|
|
GettingReceipt(PinBoxFut<'a, Option<TransactionReceipt>>),
|
2020-06-15 12:40:06 +00:00
|
|
|
|
|
|
|
/// If the pending tx required only 1 conf, it will return early. Otherwise it will
|
|
|
|
/// proceed to the next state which will poll the block number until there have been
|
|
|
|
/// enough confirmations
|
2021-07-06 08:06:18 +00:00
|
|
|
CheckingReceipt(Option<TransactionReceipt>),
|
|
|
|
|
|
|
|
/// Waiting for interval to elapse before calling API again
|
|
|
|
PausedGettingBlockNumber(Option<TransactionReceipt>),
|
|
|
|
|
|
|
|
/// Polling the blockchain for the current block number
|
|
|
|
GettingBlockNumber(PinBoxFut<'a, U64>, Option<TransactionReceipt>),
|
2020-06-15 12:40:06 +00:00
|
|
|
|
|
|
|
/// Future has completed and should panic if polled again
|
|
|
|
Completed,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> fmt::Debug for PendingTxState<'a> {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
let state = match self {
|
2021-07-13 19:34:11 +00:00
|
|
|
PendingTxState::InitialDelay(_) => "InitialDelay",
|
2021-07-06 08:06:18 +00:00
|
|
|
PendingTxState::PausedGettingTx => "PausedGettingTx",
|
|
|
|
PendingTxState::GettingTx(_) => "GettingTx",
|
2020-07-10 06:59:29 +00:00
|
|
|
PendingTxState::PausedGettingReceipt => "PausedGettingReceipt",
|
2021-07-06 08:06:18 +00:00
|
|
|
PendingTxState::GettingReceipt(_) => "GettingReceipt",
|
2020-06-15 12:40:06 +00:00
|
|
|
PendingTxState::GettingBlockNumber(_, _) => "GettingBlockNumber",
|
2020-07-10 06:59:29 +00:00
|
|
|
PendingTxState::PausedGettingBlockNumber(_) => "PausedGettingBlockNumber",
|
2020-06-15 12:40:06 +00:00
|
|
|
PendingTxState::CheckingReceipt(_) => "CheckingReceipt",
|
|
|
|
PendingTxState::Completed => "Completed",
|
|
|
|
};
|
|
|
|
|
|
|
|
f.debug_struct("PendingTxState")
|
|
|
|
.field("state", &state)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|