alloy-rs / alloy-rs/alloy

[Bug] PendingTransactionBuilder::get_receipt() can time out for >1 confirmations even when the receipt already exists

未关闭
#3,916 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
bug
主要语言
Rust
星标
1.3k
派生
668
平均合并
2 天 2 小时
30 天内合并 PR
29

描述

### Component

provider, pubsub

### What version of Alloy are you on?

alloy v1.8.3

### Operating System

macOS (Intel)

### Describe the bug

## Summary

`PendingTransactionBuilder::with_required_confirmations(...).get_receipt()` can return a timeout from the tx watcher even though:

- the transaction was already mined successfully
- `eth_getTransactionReceipt(tx_hash)` returns a receipt
- enough blocks have already passed to satisfy the requested confirmation count

I reproduced this against local Anvil with HTTP polling.

## Expected behavior

If the receipt exists and the latest block is already at or past:

`receipt.block_number + required_confirmations - 1`

then `get_receipt()` should resolve successfully.

## Actual behavior

`get_receipt()` sometimes returns:

- `Err(PendingTransactionError::TxWatcher(_))`
- with message similar to `transaction was not confirmed within the timeout`

but a direct receipt lookup right after that returns a valid successful receipt.

## Minimal repro

### Cargo.toml

```toml
[package]
name = "alloy-heartbeat-repro"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
alloy = { version = "1.0", features = ["node-bindings", "provider-anvil-api", "signer-local"] }

### src/main.rs

use alloy::network::{ReceiptResponse, TransactionBuilder};
use alloy::node_bindings::Anvil;
use alloy::primitives::U256;
use alloy::providers::ext::AnvilApi;
use alloy::providers::{PendingTransactionError, Provider, ProviderBuilder, WalletProvider};
use alloy::rpc::client::RpcClient;
use alloy::rpc::types::TransactionRequest;
use std::time::Duration;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let anvil = Anvil::new().block_time(1).spawn();
let wallet = anvil.wallet().expect("dev wallet");

let client = RpcClient::builder()
.connect(&anvil.endpoint())
.await?
.with_poll_interval(Duration::from_millis(100));

let provider = ProviderBuilder::new().wallet(wallet).connect_client(client);

provider.anvil_set_auto_mine(false).await?;

for attempt in 1..=50 {
let tx = TransactionRequest::default()
.with_to(provider.default_signer_address())
.with_value(U256::from(1_u64));

let pending = provider.send_transaction(tx).await?;
let tx_hash = *pending.tx_hash();

let watcher = tokio::spawn(async move {
pending
.with_required_confirmations(3)
.with_timeout(Some(Duration::from_secs(10)))
.get_receipt()
.await
});

tokio::time::sleep(Duration::from_millis(10)).await;
provider.anvil_mine(Some(3), None).await?;

let watcher_result = watcher.await.expect("watch task panicked");
let direct_receipt = provider.get_transaction_receipt(tx_hash).await?;
let latest_block = provider.get_block_number().await?;

match (&watcher_result, &direct_receipt) {
(Err(PendingTransactionError::TxWatcher(_)), Some(receipt)) => {
println!(
"BUG reproduced on attempt {attempt}: tx_hash={tx_hash:?}, latest_block={latest_block}, receipt_block={:?}, receipt_status={}",
receipt.block_number(),
receipt.status()
);
return Ok(());
}
_ => {
println!(
"attempt {attempt}: watcher_ok={}, receipt_present={}, receipt_block={:?}",
watcher_result.is_ok(),
direct_receipt.is_some(),
direct_receipt.as_ref().and_then(|r| r.block_number())
);
}
}
}

anyhow::bail!("did not reproduce")
}
```
Run:

cargo run

## Reproduced output

Example failure:

BUG reproduced on attempt 2: tx_hash=..., latest_block=16, receipt_block=Some(4), receipt_status=true

So in that failing run:

- the receipt already existed
- the tx was mined in block 4
- latest block was 16
- required confirmations was 3

That should already be well past the confirmation target.

## Notes

From tracing, the timeout appears to come from the internal watcher path rather than receipt absence on the RPC side.

This looks like a race in the pending transaction confirmation tracking path for required_confirmations > 1.

贡献指南

打开贡献指南

评估

这个 Issue 还没有评估数据。

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。