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:
1 parent
27ef7cbabf
commit
b32548fd01
9 files changed
+41
-13
No files matched your search
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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` |
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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']
|
||||||
|
|||||||
Reference in new issue
Block a user