cculianu / Fulcrum

A fast & nimble SPV Server for BCH, BTC, and LTC
Other
325 stars 73 forks source link

Electrum 4.4.6 wallet error for Fulcrum 1.9.2 #201

Closed hMsats closed 9 months ago

hMsats commented 9 months ago

I upgraded Fulcrum from 1.9.1 to 1.9.2 and got the error message below from Electrum 4.4.6. Returned to Fulcrum 1.9.1 and the error disappeared and everything was normal like before. Running bitcoind 25.0.0 on Ubuntu Linux. The error seems to be with estimatefee and to be honest I don't understand how that could work with Fulcrum 1.9.1 in the first place as bitcoind only knows about estimatesmartfee.

The error message:

 0.47 | W | i/interface.[electrum.bitcoinserver.nl:50514] | disconnecting due to RPCError(2, 'Method not found')
  2.59 | W | i/interface.[electrum.bitcoinserver.nl:50514] | disconnecting due to RPCError(2, 'Method not found')
  9.30 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-142' coro=<Interface.get_estimatefee() done, defined at /home/user/Electrum-4.4.6/electrum/interface.py:1125> exception=RPCError(2, 'Method not found')>
Traceback (most recent call last):
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 1134, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 171, in send_request
    response = await asyncio.wait_for(
  File "/usr/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (2, 'Method not found')
  9.30 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-144' coro=<Interface.get_estimatefee() done, defined at /home/user/Electrum-4.4.6/electrum/interface.py:1125> exception=RPCError(2, 'Method not found')>
Traceback (most recent call last):
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 1134, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 171, in send_request
    response = await asyncio.wait_for(
  File "/usr/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (2, 'Method not found')
  9.30 | E | asyncio | Task was destroyed but it is pending!
task: <Task pending name='Task-227' coro=<NetworkJobOnDefaultServer._start.<locals>.run_tasks_wrapper() running at /home/user/Electrum-4.4.6/electrum/util.py:1582> wait_for=<Future pending cb=[Task.task_wakeup()]> cb=[TaskGroup._on_done(), TaskGroup._cancel_tasks.<locals>.pop_task() at /home/user/Electrum-4.4.6/packages/aiorpcx/curio.py:264]>
  9.30 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-217' coro=<Interface.get_estimatefee() done, defined at /home/user/Electrum-4.4.6/electrum/interface.py:1125> exception=RPCError(2, 'Method not found')>
Traceback (most recent call last):
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 1134, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 171, in send_request
    response = await asyncio.wait_for(
  File "/usr/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (2, 'Method not found')
  9.30 | E | asyncio | Task exception was never retrieved
future: <Task finished name='Task-218' coro=<Interface.get_estimatefee() done, defined at /home/user/Electrum-4.4.6/electrum/interface.py:1125> exception=RPCError(2, 'Method not found')>
Traceback (most recent call last):
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 1134, in get_estimatefee
    res = await self.session.send_request('blockchain.estimatefee', [num_blocks])
  File "/home/user/Electrum-4.4.6/electrum/interface.py", line 171, in send_request
    response = await asyncio.wait_for(
  File "/usr/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 540, in send_request
    return await self._send_concurrent(message, future, 1)
  File "/home/user/Electrum-4.4.6/packages/aiorpcx/session.py", line 512, in _send_concurrent
    return await future
aiorpcx.jsonrpc.RPCError: (2, 'Method not found')
cculianu commented 9 months ago

Wow thanks for the bug report.

So the strange thing is Fulcrum should be sending bitcoind estimatesmartfee, and it should figure out that it's talking to Core, and send it that RPC command instead of estimatefee.

I don't see any changes from v1.9.1 -> v1.9.2 where the logic that it uses to decide this remapping.. would have been affected!

Can you definitely confirm that's the issue? Is it sending bitcoind estimatefee in Fulcrum 1.9.2 and estimatesmartfee in Fulcrum 1.9.1? Can you look at your logs and really isolate it down to this?

I actually am traveling now and don't have my Core server running, so I can't even test this here right now. Any help in troubleshooting this would be appreciated.

EDIT: Also follow-up -- is the error 100% reproducible or sporadic?

cculianu commented 9 months ago

Update: I just tried Fulcrum v1.9.2 vs bitcoin core 25.0.0 release, on regtest however (again, traveling, and my real server is down). blockchain.estimatefee uses the correct RPC on the bitcoind side: estimatesmartfee.

See if you can provide more information as to what is really happening. Worst case you can run Fulcrum with -d -d to get "double debug", or HTTP trace in your logs. It's super verbose but at least maybe you can see what RPCs fail or what is going on.

hMsats commented 9 months ago

Ah, indeed Fulcrum translates estimatefee to estimatesmartfee. Went back to 1.9.2 and I don't see the error message anymore. Very strange. I restarted Electrum many times and each time got that error message but now it's gone. Sorry for that! I will close this issue so you can enjoy your travels!

cculianu commented 9 months ago

Ok. If you do figure it out, do let me know what is happening. Very strange.

hMsats commented 9 months ago

I investigated a little more. I commented out the if statement at line 1328 in src/Servers.cpp. This means that instead that bitcoind is called with estimatesmartfee, bitcoind is called with estimatefee (because it's called as Bitcoin Cash) and this gives the same Electrum error message as before.

So I think that for some reason the statement:

   if ((bitcoindmgr->isCoreLike())
            && bitcoindmgr->getBitcoinDVersion() >= Version{0,17,0}) {
        // Bitcoin Core removed the "estimatefee" RPC method entirely in version 0.17.0, in favor of "estimatesmartfee"
        generic_async_to_bitcoind(c, batchId, m.id, "estimatesmartfee", params, [](const RPC::Message &response){
            // We don't validate what bitcoind returns. Sometimes if it has not enough information, it may
            // return no "feerate" but instead return an "errors" entry in the dict. This is fine.
            // ElectrumX just returns -1 in that case here, so we do the same.
            return response.result().toMap().value("feerate", -1.0);
        });
        return;
    }

failed and the next line was executed instead:

   // regular Bitcoin Cash daemons
    generic_async_to_bitcoind(c, batchId, m.id, "estimatefee", params, [](const RPC::Message &response){
        return response.result();
    });

So I think there should be a similar if statement for Bitcoin Cash (that should return true if the previous one returned false) and if both return false an appropriate error message should be generated. So I guess that in my case, for some reason, Bitcoin Core wasn't detected which caused my issue. So whenever you find the time maybe this could be improved.

cculianu commented 9 months ago

Yeah that was my hypothesis internally as well but there is no way that bitcoin core wouldn't be detected. As soon as it comes online, the daemon's user agent string is analyzed and read. That detection happens before any TCP/SSL/WSS ports are even opened and bound to.. even before the "Server" class is constructed...

I'll look at it again though, for possible ways it "can go wrong"... since that is the only hypothesis that makes sense.

Thanks for the investigation..!