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 <noreply@anthropic.com>
This commit is contained in:
davideandClaude Sonnet 5.5 committed 2026-10-23 16:17:43 +02:00
1 parent 27ef7cbabf
commit b32548fd01
9 files changed
+41 -13

No files matched your search

+1 -1
View File
@@ -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.
+1 -1
View File
@@ -238,7 +238,7 @@ From another machine, `nc -zv <public IP> 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` |
+2 -2
View File
@@ -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.
+6 -2
View File
@@ -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,
+6 -2
View File
@@ -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
+5 -1
View File
@@ -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):
+8 -4
View File
@@ -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"]
+1
View File
@@ -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):
+11
View File
@@ -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']