From 69477f287f712c99e7ce74f7858842722c2bb417 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Fri, 10 Jul 2026 12:55:41 +0100 Subject: [PATCH] add blockchain.block.stats (uses getblockstats) --- src/electrumx/server/daemon.py | 4 ++++ src/electrumx/server/session.py | 10 ++++++++++ tests/server/test_api.py | 35 +++++++++++++++++++++++++++++++++ tests/server/test_daemon.py | 8 ++++++++ 4 files changed, 57 insertions(+) diff --git a/src/electrumx/server/daemon.py b/src/electrumx/server/daemon.py index e02be5c75..fdf09c7ba 100644 --- a/src/electrumx/server/daemon.py +++ b/src/electrumx/server/daemon.py @@ -358,6 +358,10 @@ async def getrawtransaction(self, txid_hum: str, verbose=False): return await self._send_single('getrawtransaction', (txid_hum, int(verbose))) + async def getblockstats(self, height: int): + '''Return per-block statistics from the daemon for the given height.''' + return await self._send_single('getblockstats', (height,)) + async def getrawtransactions(self, txids_hum: Iterable[str], replace_errs=True) -> Sequence[bytes | None]: '''Return the serialized raw transactions with the given hashes. diff --git a/src/electrumx/server/session.py b/src/electrumx/server/session.py index 6de279d4b..976cb7037 100644 --- a/src/electrumx/server/session.py +++ b/src/electrumx/server/session.py @@ -1924,6 +1924,15 @@ async def _merkle_proof(self, cp_height: int, height: int) -> dict[str, Any]: 'root': hash_to_hex_str(root), } + async def phandle_block_stats(self, height: int | Any): + '''Return per-block statistics from the daemon for the given height. + + height: the block height as a non-negative integer + ''' + height = non_negative_integer(height) + self.bump_cost(1.0) + return await self.daemon_request('getblockstats', height) + async def phandle_block_header(self, height: int, cp_height: int = 0) -> dict[str, Any]: '''Return a raw block header as a hexadecimal string, or as a dictionary with a merkle proof.''' @@ -2371,6 +2380,7 @@ def set_request_handlers(self, ptuple): handlers = { 'blockchain.block.header': self.phandle_block_header, 'blockchain.block.headers': self.phandle_block_headers, + 'blockchain.block.stats': self.phandle_block_stats, 'blockchain.estimatefee': self.phandle_estimatefee, 'blockchain.headers.subscribe': self.phandle_headers_subscribe, 'blockchain.transaction.broadcast': self.phandle_transaction_broadcast, diff --git a/tests/server/test_api.py b/tests/server/test_api.py index fcb4ab41b..b0def6096 100644 --- a/tests/server/test_api.py +++ b/tests/server/test_api.py @@ -1,8 +1,10 @@ import asyncio from unittest import mock +import pytest from aiorpcx import RPCError from electrumx import Controller, Env +from electrumx.server.session import ElectrumX loop = asyncio.new_event_loop() @@ -43,6 +45,39 @@ def ensure_text_exception(test, exception): def test_dummy(): assert True + +@pytest.mark.asyncio +async def test_phandle_block_stats(): + session = mock.Mock() + session.bump_cost = mock.Mock() + payload = {'height': 800000, 'avgfee': 1234} + session.daemon_request = mock.AsyncMock(return_value=payload) + + result = await ElectrumX.phandle_block_stats(session, 800000) + + assert result == payload + session.bump_cost.assert_called_once_with(1.0) + session.daemon_request.assert_awaited_once_with('getblockstats', 800000) + + +@pytest.mark.asyncio +async def test_phandle_block_stats_rejects_bad_height(): + session = mock.Mock() + session.bump_cost = mock.Mock() + session.daemon_request = mock.AsyncMock() + + with pytest.raises(RPCError): + await ElectrumX.phandle_block_stats(session, -1) + + session.daemon_request.assert_not_called() + + +def test_block_stats_handler_registered(): + session = ElectrumX.__new__(ElectrumX) + session.set_request_handlers((1, 4)) + assert session.request_handlers['blockchain.block.stats'] == session.phandle_block_stats + + def _test_transaction_get(): async def test_verbose_ignore_by_backend(): env = set_env() diff --git a/tests/server/test_daemon.py b/tests/server/test_daemon.py index 0afdbaada..e4dda9798 100644 --- a/tests/server/test_daemon.py +++ b/tests/server/test_daemon.py @@ -332,6 +332,14 @@ async def test_getrawtransaction(daemon): hex_hash, True) == verbose +@pytest.mark.asyncio +async def test_getblockstats(daemon): + height = 800000 + result = {'height': height, 'avgfee': 1234, 'total_size': 999} + daemon.session = ClientSessionGood(('getblockstats', [height], result)) + assert await daemon.getblockstats(height) == result + + @pytest.mark.asyncio async def test_protx(dash_daemon): protx_hash = 'deadbeaf'