tomusdrw / rust-web3

Ethereum JSON-RPC multi-transport client. Rust implementation of web3 library. ENS address: rust-web3.eth
MIT License
1.45k stars 465 forks source link

`confirm::wait_for_confirmations` wait too long unnecessarily #640

Open catflyflyfly opened 2 years ago

catflyflyfly commented 2 years ago

I'm currently trying to build a listener for events on finalized blocks, and I found the handy confirm utils which I tried to use.

Consider this code:

{
    .
    .
    .
    async fn get_block_number(_log: &Log) -> web3::error::Result<Option<U64>> {
        // supposed that our log is really far from the chain current block number

        Ok(Some(U64::from_str("0").unwrap()))
    }

    wait_for_confirmations(
        web3.eth(),
        web3.eth_filter(),
        time::Duration::from_millis(100),
        15,
        || get_block_number(&log),
    )
    .await?;

    process_log(log).await?;
}

Now, let's see how wait_for_confirmations works

{
    let filter = eth_filter.create_blocks_filter().await?;

    let filter_stream = filter.stream(poll_interval).skip(confirmations);
    futures::pin_mut!(filter_stream);
    loop {
        let _ = filter_stream.next().await;
        if let Some(confirmation_block_number) = check.check().await? {
            let block_number = eth.block_number().await?;
            if confirmation_block_number.low_u64() + confirmations as u64 <= block_number.low_u64() {
                return Ok(());
            }
        }
    }
}

Problems:

  1. Block number from check() in this case obviously is finalized. wait_for_confirmation should not even bother to create the stream and get the latest block.

    This will cause delay, especially if you use high confirmations (I used 15 on bsc, waiting for minutes for each event was rather annoying)

  2. Supposed that check() return a block number that is close to being finalized (for example, the log currently has 13 confirmations, but it needs 15 confirmations)

    The create_blocks_filter() stream should just .skip(2) instead of doing .skip(15) as it is right now. This will optimized the waiting time even more.

Solutions:

  1. If confirmations == 0, do return Ok(()); right away.
  2. Computed confirmations_needed by (check() + confirmations).checked_sub(eth.block_number()).unwrap_or(0). For example:
    • (1000 + 15) - 1000 = 15
    • (1000 + 15) - 1013 = 2
    • (1000 + 15) - 1015 = 0
    • (1000 + 15) - 2000 = 0 (checked_sub & unwrap_or will ensure no arithmatic error happens)
  3. if confirmations_needed == 0, do return Ok(());
  4. Otherwise, create the create_blocks_filter() stream with .skip(confirmations_needed)
  5. Loop fetching the stream and doing the check as usual

My example:

https://github.com/aofdev/event-watcher-contract-examples/pull/1/commits/d31413274331dee951b5660a177c26edd78787d8