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. 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. 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: 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` | | 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) | | 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 | | 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 | | 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` | | 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. bech32m for taproot) are only read by the local `electrumx_rpc query` command.
- Palladium Core has no `getindexinfo`, `txospenderindex` (`gettxspendingprevout`) or `submitpackage` RPCs, - Palladium Core has no `getindexinfo`, `txospenderindex` (`gettxspendingprevout`) or `submitpackage` RPCs,
so the daemon version/index checks are disabled for `COIN=Palladium`, and so the daemon version/index checks are disabled for `COIN=Palladium`, and
`blockchain.outpoint.subscribe` and `blockchain.transaction.broadcast_package` return a daemon `blockchain.outpoint.subscribe` is refused with an explicit error (`Coin.DAEMON_HAS_TXO_SPENDER_INDEX`)
error to clients. and `blockchain.transaction.broadcast_package` returns the daemon error to clients.
- The daemon must run with `txindex=1`. - The daemon must run with `txindex=1`.
- Same genesis blocks as Bitcoin (mainnet, testnet3, regtest), 80-byte headers, sha256d PoW. - 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. - 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 * 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. 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 * Palladium Core has no ``getindexinfo``, ``txospenderindex`` or ``submitpackage`` RPCs: the daemon version/index
checks are disabled, ``txindex=1`` is required, and ``blockchain.outpoint.subscribe`` / checks are disabled, ``txindex=1`` is required, ``blockchain.outpoint.subscribe`` is refused with an explicit
``blockchain.transaction.broadcast_package`` return a daemon error to clients. 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 * new: integration tests (``tests/integration``) against a Palladium node in regtest mode, run in Docker, with
a ``integration`` job in CI. a ``integration`` job in CI.
* removed: all other coins, their transaction deserializers, session/daemon/block processor classes, tests, * 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 NET: str | None
MIN_REQUIRED_DAEMON_VERSION: Optional[str] = None MIN_REQUIRED_DAEMON_VERSION: Optional[str] = None
REQUIRED_DAEMON_INDEXES: Sequence[str] = tuple() 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: # only used for initial db sync ETAs:
TX_COUNT_HEIGHT: int # at a given snapshot of the chain, 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). so no special handling is needed for either).
Its daemon has no 'txospenderindex', 'getindexinfo' or 'submitpackage', so Its daemon has no 'txospenderindex', 'getindexinfo' or 'submitpackage', so
'blockchain.outpoint.subscribe' and 'blockchain.transaction.broadcast_package' will 'blockchain.outpoint.subscribe' is refused (DAEMON_HAS_TXO_SPENDER_INDEX) and
return daemon errors to clients. The daemon needs 'txindex=1'. 'blockchain.transaction.broadcast_package' will return a daemon error to clients.
The daemon needs 'txindex=1'.
""" """
NAME = "Palladium" NAME = "Palladium"
SHORTNAME = "PLM" SHORTNAME = "PLM"
@@ -282,6 +285,7 @@ class Palladium(Coin):
'4ff763ae46a2a6c172b3f1b60a8ce26f') '4ff763ae46a2a6c172b3f1b60a8ce26f')
MEMPOOL_HISTOGRAM_REFRESH_SECS = 120 MEMPOOL_HISTOGRAM_REFRESH_SECS = 120
DESERIALIZER = lib_tx.DeserializerSegWit DESERIALIZER = lib_tx.DeserializerSegWit
DAEMON_HAS_TXO_SPENDER_INDEX = False
RPC_PORT = 2332 RPC_PORT = 2332
# snapshot (UpdateTip tx= count), only used for sync ETAs # snapshot (UpdateTip tx= count), only used for sync ETAs
TX_COUNT = 661_421 TX_COUNT = 661_421
+5 -1
View File
@@ -336,6 +336,10 @@ class Daemon:
async def mempool_info(self) -> dict[str, float]: async def mempool_info(self) -> dict[str, float]:
mempool_info = await self.getmempoolinfo() 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 { return {
# Dynamic minimum fee rate in PLM/kvB for tx to be accepted. # Dynamic minimum fee rate in PLM/kvB for tx to be accepted.
# Is the maximum of minrelaytxfee and minimum mempool fee # Is the maximum of minrelaytxfee and minimum mempool fee
@@ -344,7 +348,7 @@ class Daemon:
'minrelaytxfee': mempool_info['minrelaytxfee'], 'minrelaytxfee': mempool_info['minrelaytxfee'],
# Minimum fee rate increment for mempool limiting or replacement in PLM/kvB, # Minimum fee rate increment for mempool limiting or replacement in PLM/kvB,
# operator-configurable, static. # operator-configurable, static.
'incrementalrelayfee': mempool_info['incrementalrelayfee'], 'incrementalrelayfee': incrementalrelayfee,
} }
async def getrawtransaction(self, txid_hum: str, verbose=False): 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 spk_hint: scriptPubKey corresponding to the outpoint. Might be used by
other servers, but we don't need and hence ignore it. 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) txid_rev = assert_txid_hum(tx_hash)
txout_idx = non_negative_integer(txout_idx) txout_idx = non_negative_integer(txout_idx)
assert_hex_str(spk_hint) assert_hex_str(spk_hint)
@@ -2273,10 +2278,9 @@ class ElectrumX(SessionBase):
response: list[dict] = [] response: list[dict] = []
for orig_item in daemon_result: # one item for each tx for orig_item in daemon_result: # one item for each tx
new_item = { new_item = {"txid": orig_item["txid"]}
"txid": orig_item["txid"], if "wtxid" in orig_item: # not reported by older daemons (Palladium Core)
"wtxid": orig_item["wtxid"], new_item["wtxid"] = orig_item["wtxid"]
}
# optional: "allowed" field # optional: "allowed" field
if orig_item.get("allowed") in (True, False): if orig_item.get("allowed") in (True, False):
new_item["allowed"] = orig_item["allowed"] new_item["allowed"] = orig_item["allowed"]
+1
View File
@@ -46,6 +46,7 @@ def test_daemon_requirements_disabled():
for coin, *_ in PARAMS: for coin, *_ in PARAMS:
assert coin.MIN_REQUIRED_DAEMON_VERSION is None assert coin.MIN_REQUIRED_DAEMON_VERSION is None
assert not coin.REQUIRED_DAEMON_INDEXES assert not coin.REQUIRED_DAEMON_INDEXES
assert coin.DAEMON_HAS_TXO_SPENDER_INDEX is False
def bech32_encode_v0(hrp, program): 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 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 @pytest.mark.asyncio
async def test_mempool_txids_hum(daemon): async def test_mempool_txids_hum(daemon):
hashes = ['hex_hash1', 'hex_hash2'] hashes = ['hex_hash1', 'hex_hash2']