From b32548fd01dcd284db3fe02d79f0478254535066 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Fri, 23 Oct 2026 16:17:43 +0200 Subject: [PATCH] Fix mempool.get_info, testmempoolaccept and outpoint.subscribe with the Palladium daemon - mempool.get_info: the daemon has no incrementalrelayfee in getmempoolinfo, read incrementalfee from getnetworkinfo - testmempoolaccept: the daemon does not report wtxid - outpoint.subscribe: refuse the subscription (Coin.DAEMON_HAS_TXO_SPENDER_INDEX) instead of closing the session at the next block, when the daemon errors Co-Authored-By: Claude Sonnet 5.5 --- CLAUDE.md | 2 +- DEPLOY.md | 2 +- README.md | 4 ++-- docs/changelog.rst | 8 ++++++-- src/electrumx/lib/coins.py | 8 ++++++-- src/electrumx/server/daemon.py | 6 +++++- src/electrumx/server/session.py | 12 ++++++++---- tests/lib/test_palladium.py | 1 + tests/server/test_daemon.py | 11 +++++++++++ 9 files changed, 41 insertions(+), 13 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 29ad7f3..162b2f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ Pipeline (see `docs/architecture.rst`): `Env` → `Controller` (`server/controll Coin abstraction: `lib/coins.py` defines `Coin` and the `Palladium`, `PalladiumTestnet`, `PalladiumRegtest` subclasses. A coin class selects behavior through class attributes: `DESERIALIZER` (block/tx format, in `lib/tx.py`, only the segwit one remains), `DAEMON`, `BLOCK_PROCESSOR`, `SESSIONCLS`, address verbytes, `GENESIS_HASH`, `REORG_LIMIT`, `RPC_PORT`, `PEERS`, header handling, etc. Coins are discovered by walking `Coin` subclasses (`lookup_coin_class` matches `NAME` + `NET`; `COIN` defaults to `Palladium`). Concrete classes must define `TX_COUNT`, `TX_COUNT_HEIGHT`, `TX_PER_BLOCK` (used only for sync ETAs) or lookup raises. -Daemon: Palladium Core has segwit and taproot (BIP341/342, mainnet height 405000; the server is script-type agnostic, indexing by script hash) and bech32/bech32m addresses, but no `getindexinfo`, `txospenderindex` or `submitpackage` RPCs. So `MIN_REQUIRED_DAEMON_VERSION`/`REQUIRED_DAEMON_INDEXES` (see `Coin`) are unset, and `blockchain.outpoint.subscribe` / `blockchain.transaction.broadcast_package` return a daemon error to clients. The daemon needs `txindex=1`. +Daemon: Palladium Core has segwit and taproot (BIP341/342, mainnet height 405000; the server is script-type agnostic, indexing by script hash) and bech32/bech32m addresses, but no `getindexinfo`, `txospenderindex` or `submitpackage` RPCs. So `MIN_REQUIRED_DAEMON_VERSION`/`REQUIRED_DAEMON_INDEXES` (see `Coin`) are unset, and `blockchain.outpoint.subscribe` is refused with an explicit error (`Coin.DAEMON_HAS_TXO_SPENDER_INDEX = False`: otherwise the subscription is accepted and the daemon error at the next block closes the session) while `blockchain.transaction.broadcast_package` returns the daemon error to clients. The daemon needs `txindex=1`. DB schema changed in 2.0 with no migration; changing on-disk format requires a full resync. diff --git a/DEPLOY.md b/DEPLOY.md index 7031d16..e065dac 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -238,7 +238,7 @@ From another machine, `nc -zv 50001` must succeed if the server is m | log: connection refused / cannot connect to `palladiumd:2332` | the node is stopped or still starting; check that the node container is running | | log: `401`/authorization errors | wrong `PLM_RPC_USER` / `PLM_RPC_PASSWORD` in `.env`; the credentials must be the ones in the node's `palladium.conf` | | errors mentioning `txindex` | `txindex=1` is missing in `palladium.conf` (an index added later is built while the node runs, which takes time) | -| `blockchain.outpoint.subscribe` or package broadcast return a daemon error | expected: Palladium Core does not have the RPCs they need (`gettxspendingprevout`, `submitpackage`) | +| `blockchain.outpoint.subscribe` is refused, or package broadcast returns a daemon error | expected: Palladium Core does not have the RPCs they need (`gettxspendingprevout`, `submitpackage`) | | the server stays at an old height | the node is not synced; ElectrumX only follows the node | | clients cannot connect from outside | port not published/forwarded, firewall, or `SERVICES` does not list that protocol | | container stops at once, log says `ERROR: ... is not writable` | the data directory belongs to another user (or to root, if Docker created it): `chown -R $(id -u):$(id -g) ~/.plm-electrumx`, or set `PLM_UID`/`PLM_GID` | diff --git a/README.md b/README.md index 9f2d776..1575474 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,8 @@ When merging upstream, resolve modify/delete conflicts on these files by keeping bech32m for taproot) are only read by the local `electrumx_rpc query` command. - Palladium Core has no `getindexinfo`, `txospenderindex` (`gettxspendingprevout`) or `submitpackage` RPCs, so the daemon version/index checks are disabled for `COIN=Palladium`, and - `blockchain.outpoint.subscribe` and `blockchain.transaction.broadcast_package` return a daemon - error to clients. + `blockchain.outpoint.subscribe` is refused with an explicit error (`Coin.DAEMON_HAS_TXO_SPENDER_INDEX`) + and `blockchain.transaction.broadcast_package` returns the daemon error to clients. - The daemon must run with `txindex=1`. - Same genesis blocks as Bitcoin (mainnet, testnet3, regtest), 80-byte headers, sha256d PoW. - Parameters (address prefixes, ports, genesis hashes) were taken from the Palladium Core chain parameters. diff --git a/docs/changelog.rst b/docs/changelog.rst index df0518f..1f38c02 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,8 +13,12 @@ First release of the Palladium fork. Based on a snapshot of upstream ElectrumX 2 * Segwit and taproot outputs are supported (the server indexes by script hash); ``Coin.BECH32_HRP`` and a bech32/bech32m decoder let ``electrumx_rpc query`` read ``plm1``, ``tplm1`` and ``rplm1`` addresses. * Palladium Core has no ``getindexinfo``, ``txospenderindex`` or ``submitpackage`` RPCs: the daemon version/index - checks are disabled, ``txindex=1`` is required, and ``blockchain.outpoint.subscribe`` / - ``blockchain.transaction.broadcast_package`` return a daemon error to clients. + checks are disabled, ``txindex=1`` is required, ``blockchain.outpoint.subscribe`` is refused with an explicit + error (``Coin.DAEMON_HAS_TXO_SPENDER_INDEX``) and ``blockchain.transaction.broadcast_package`` returns the + daemon error to clients. +* fixed: ``mempool.get_info`` (protocol 1.6) failed with the Palladium daemon, whose ``getmempoolinfo`` has no + ``incrementalrelayfee`` (it is read from ``getnetworkinfo``), and ``blockchain.transaction.testmempoolaccept`` + failed because the daemon does not report ``wtxid``. * new: integration tests (``tests/integration``) against a Palladium node in regtest mode, run in Docker, with a ``integration`` job in CI. * removed: all other coins, their transaction deserializers, session/daemon/block processor classes, tests, diff --git a/src/electrumx/lib/coins.py b/src/electrumx/lib/coins.py index 32c656b..7789a9d 100644 --- a/src/electrumx/lib/coins.py +++ b/src/electrumx/lib/coins.py @@ -100,6 +100,8 @@ class Coin: NET: str | None MIN_REQUIRED_DAEMON_VERSION: Optional[str] = None REQUIRED_DAEMON_INDEXES: Sequence[str] = tuple() + # whether the daemon can tell which tx spends an outpoint (gettxspendingprevout); needed to subscribe to outpoints + DAEMON_HAS_TXO_SPENDER_INDEX = True # only used for initial db sync ETAs: TX_COUNT_HEIGHT: int # at a given snapshot of the chain, @@ -268,8 +270,9 @@ class Palladium(Coin): so no special handling is needed for either). Its daemon has no 'txospenderindex', 'getindexinfo' or 'submitpackage', so - 'blockchain.outpoint.subscribe' and 'blockchain.transaction.broadcast_package' will - return daemon errors to clients. The daemon needs 'txindex=1'. + 'blockchain.outpoint.subscribe' is refused (DAEMON_HAS_TXO_SPENDER_INDEX) and + 'blockchain.transaction.broadcast_package' will return a daemon error to clients. + The daemon needs 'txindex=1'. """ NAME = "Palladium" SHORTNAME = "PLM" @@ -282,6 +285,7 @@ class Palladium(Coin): '4ff763ae46a2a6c172b3f1b60a8ce26f') MEMPOOL_HISTOGRAM_REFRESH_SECS = 120 DESERIALIZER = lib_tx.DeserializerSegWit + DAEMON_HAS_TXO_SPENDER_INDEX = False RPC_PORT = 2332 # snapshot (UpdateTip tx= count), only used for sync ETAs TX_COUNT = 661_421 diff --git a/src/electrumx/server/daemon.py b/src/electrumx/server/daemon.py index 74a0f76..b4313e3 100644 --- a/src/electrumx/server/daemon.py +++ b/src/electrumx/server/daemon.py @@ -336,6 +336,10 @@ class Daemon: async def mempool_info(self) -> dict[str, float]: mempool_info = await self.getmempoolinfo() + incrementalrelayfee = mempool_info.get('incrementalrelayfee') + if incrementalrelayfee is None: + # the Palladium daemon only reports it in getnetworkinfo, as 'incrementalfee' + incrementalrelayfee = (await self.getnetworkinfo())['incrementalfee'] return { # Dynamic minimum fee rate in PLM/kvB for tx to be accepted. # Is the maximum of minrelaytxfee and minimum mempool fee @@ -344,7 +348,7 @@ class Daemon: 'minrelaytxfee': mempool_info['minrelaytxfee'], # Minimum fee rate increment for mempool limiting or replacement in PLM/kvB, # operator-configurable, static. - 'incrementalrelayfee': mempool_info['incrementalrelayfee'], + 'incrementalrelayfee': incrementalrelayfee, } async def getrawtransaction(self, txid_hum: str, verbose=False): diff --git a/src/electrumx/server/session.py b/src/electrumx/server/session.py index 588622b..9f9bdc6 100644 --- a/src/electrumx/server/session.py +++ b/src/electrumx/server/session.py @@ -1876,6 +1876,11 @@ class ElectrumX(SessionBase): spk_hint: scriptPubKey corresponding to the outpoint. Might be used by other servers, but we don't need and hence ignore it. ''' + if not self.env.coin.DAEMON_HAS_TXO_SPENDER_INDEX: + # the status of a subscribed outpoint is re-checked with the daemon at every block: without + # support the subscription would be accepted now and then close the session + raise RPCError(BAD_REQUEST, 'outpoint subscriptions are not supported: the daemon cannot look up ' + 'the spender of an outpoint') txid_rev = assert_txid_hum(tx_hash) txout_idx = non_negative_integer(txout_idx) assert_hex_str(spk_hint) @@ -2273,10 +2278,9 @@ class ElectrumX(SessionBase): response: list[dict] = [] for orig_item in daemon_result: # one item for each tx - new_item = { - "txid": orig_item["txid"], - "wtxid": orig_item["wtxid"], - } + new_item = {"txid": orig_item["txid"]} + if "wtxid" in orig_item: # not reported by older daemons (Palladium Core) + new_item["wtxid"] = orig_item["wtxid"] # optional: "allowed" field if orig_item.get("allowed") in (True, False): new_item["allowed"] = orig_item["allowed"] diff --git a/tests/lib/test_palladium.py b/tests/lib/test_palladium.py index da1dc74..28c5e00 100644 --- a/tests/lib/test_palladium.py +++ b/tests/lib/test_palladium.py @@ -46,6 +46,7 @@ def test_daemon_requirements_disabled(): for coin, *_ in PARAMS: assert coin.MIN_REQUIRED_DAEMON_VERSION is None assert not coin.REQUIRED_DAEMON_INDEXES + assert coin.DAEMON_HAS_TXO_SPENDER_INDEX is False def bech32_encode_v0(hrp, program): diff --git a/tests/server/test_daemon.py b/tests/server/test_daemon.py index ba50cdb..932d9ed 100644 --- a/tests/server/test_daemon.py +++ b/tests/server/test_daemon.py @@ -261,6 +261,17 @@ async def test_mempool_info(daemon): assert await daemon.mempool_info() == expected_result +@pytest.mark.asyncio +async def test_mempool_info_incrementalfee_from_networkinfo(daemon): + # Palladium Core: getmempoolinfo has no 'incrementalrelayfee', getnetworkinfo has 'incrementalfee' + daemon.session = ClientSessionGood( + ('getmempoolinfo', [], {'mempoolminfee': 0.00001, 'minrelaytxfee': 0.00002, 'other': 'cruft'}), + ('getnetworkinfo', [], {'incrementalfee': 0.00003, 'other': 'cruft'}), + ) + assert await daemon.mempool_info() == { + 'mempoolminfee': 0.00001, 'minrelaytxfee': 0.00002, 'incrementalrelayfee': 0.00003} + + @pytest.mark.asyncio async def test_mempool_txids_hum(daemon): hashes = ['hex_hash1', 'hex_hash2']