alloy-rs / alloy

Transports, Middleware, and Networks for the Alloy project
https://alloy.rs
Apache License 2.0
646 stars 231 forks source link

[Bug] Provider keeps spamming node after TX confirmed #1318

Open jgalat opened 1 month ago

jgalat commented 1 month ago

Component

provider, pubsub

What version of Alloy are you on?

0.3.6

Operating System

Linux

Describe the bug

I noticed my RPC provider getting spammed with eth_blockNumber and eth_getBlockByNumber calls after sending a tx. In my use case I am not even watching pending transactions, but still it starts to spam and doesn't stop unless I restart all over.

Here is a quick reproduction video and a repository. The repo uses 0.3.6, but I tested also with 0.2.1 and 0.1.4 and all behave the same. In this scenario I am watching and confirming that the transaction went through.

https://github.com/jgalat/alloy-repro-0

https://github.com/user-attachments/assets/7ff3a259-b06a-4175-8e02-543c20abc9b2

nhtyy commented 1 month ago

there is a check for is_local, maybe there could be a setter at the provider level but i don't think this is a bug

jgalat commented 1 month ago

@nhtyy Thanks for answering, but why is it pinging? Some RPC providers charge credits for this call, e.g. Alchemy uses 10 CU.

Also I see that this doesn't start unless I send a signed tx. eth_calls or eth_subscribe methods don't trigger the spam.

DaniPopes commented 1 month ago

This is the intended behavior. It's the background task that follows the chain for handling pending transactions. It's started when the first transaction is sent and closed when the last provider is dropped. I guess it could also be paused if there are no pending transactions? cc @klkvr @mattsse

jgalat commented 1 month ago

Thanks for taking a look @DaniPopes. I've been trying to find an alternative to at least increase the polling interval after reading nhtyy's comment, but seems to be impossible. Here's what I've been trying

    let ws = WsConnect::new("ws://localhost:8545");
    let transport = ws.into_service().await?;
    let client = ClientBuilder::default()
        .transport(transport, false)
        .with_poll_interval(std::time::Duration::from_secs(3_600));

    client
        .inner()
        .set_poll_interval(std::time::Duration::from_secs(3_600));

    let provider = ProviderBuilder::new()
        .with_recommended_fillers()
        .with_chain(NamedChain::Optimism)
        .on_client(client);

Neither attempt works. At least passing is_local = false to .transport increased the interval, but not to 7 seconds like it shows here, but rather to 1 second.

DaniPopes commented 1 month ago

I can't reproduce, I can set the interval in your repro and it works:

diff --git a/src/main.rs b/src/main.rs
index 3339d62..7113597 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,6 +5,7 @@ use alloy::{
     rpc::types::TransactionRequest,
 };
 use eyre::Result;
+use std::time::Duration;

 #[tokio::main]
 async fn main() -> Result<()> {
@@ -13,6 +14,7 @@ async fn main() -> Result<()> {
         .with_recommended_fillers()
         .on_ws(ws)
         .await?;
+    provider.client().set_poll_interval(Duration::from_secs(2));

     let wallet = provider.get_accounts().await?[0];

It won't work if you set it after sending the first transaction since it's loaded at the beginning then stays like that until shutdown

Alexangelj commented 1 month ago

This is the intended behavior. It's the background task that follows the chain for handling pending transactions. It's started when the first transaction is sent and closed when the last provider is dropped. I guess it could also be paused if there are no pending transactions? cc @klkvr @mattsse

That makes sense (didn't expect that tho), so after the first transaction is sent, this polling starts and will handle getting the receipts for any future transactions? And the provider needs to be completely dropped/reset to stop the polling?

How I expected it to work was to poll until we get the transaction receipt (at least, for get_receipt), then stop polling until a new transaction is being waited for. Being able to pause as you suggested would be good - where would this happen? If you point me to the place you think this logic should go I can try a pr