pyrxd.network — ElectrumX + BTC sources¶
pyrxd.network — network layer for Radiant / Bitcoin SPV.
Re-exports the public surface of the sub-modules so callers can do:
from pyrxd.network import ElectrumXClient, ChainTracker, …
- class pyrxd.network.BitcoinCoreRpcSource[source]¶
Bases:
BtcDataSourceBtcDataSource backed by a Bitcoin Core JSON-RPC endpoint.
Credentials are stored as
SecretBytesand never logged.- Parameters:
url – RPC endpoint URL, e.g.
http://localhost:8332/.user – RPC username.
password – RPC password (stored securely as SecretBytes).
- async get_block_hash(height)[source]¶
Return the 32-byte block hash at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_block_header_hex(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_header_chain(start_height, count)[source]¶
Return count consecutive 80-byte headers starting at start_height.
- Parameters:
start_height (BlockHeight)
count (int)
- Return type:
- async get_merkle_proof(txid, height)[source]¶
Return
(branch_hashes_hex, leaf_position)for txid at height.
- async get_raw_tx(txid, min_confirmations=6)[source]¶
Return raw transaction bytes, enforcing min_confirmations.
- class pyrxd.network.BlockstreamSource[source]¶
Bases:
BtcDataSourceBtcDataSource backed by the blockstream.info HTTP API.
- __init__(base_url='https://blockstream.info/api')[source]¶
- Parameters:
base_url (str)
- Return type:
None
- async get_block_hash(height)[source]¶
Return the 32-byte block hash at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_block_header_hex(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_header_chain(start_height, count)[source]¶
Return count consecutive 80-byte headers starting at start_height.
- Parameters:
start_height (BlockHeight)
count (int)
- Return type:
- async get_merkle_proof(txid, height)[source]¶
Return
(branch_hashes_hex, leaf_position)for txid at height.
- async get_raw_tx(txid, min_confirmations=6)[source]¶
Return raw transaction bytes, enforcing min_confirmations.
- class pyrxd.network.BtcDataSource[source]¶
Bases:
ABCAbstract interface for blockchain data providers.
- abstractmethod async close()[source]¶
Close any underlying connections held by this source.
- Return type:
None
- abstractmethod async get_block_hash(height)[source]¶
Return the 32-byte block hash at height.
- Parameters:
height (BlockHeight)
- Return type:
- abstractmethod async get_block_header_hex(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- abstractmethod async get_header_chain(start_height, count)[source]¶
Return count consecutive 80-byte headers starting at start_height.
- Parameters:
start_height (BlockHeight)
count (int)
- Return type:
- abstractmethod async get_merkle_proof(txid, height)[source]¶
Return
(branch_hashes_hex, leaf_position)for txid at height.
- abstractmethod async get_raw_tx(txid, min_confirmations=6)[source]¶
Return raw transaction bytes, enforcing min_confirmations.
- abstractmethod async get_tip_height()[source]¶
Return the current chain tip block height.
- Return type:
- class pyrxd.network.ChainTracker[source]¶
Bases:
objectVerifies Merkle inclusion proofs against confirmed block headers.
- Bitcoin block header layout (80 bytes, all fields little-endian):
version : 4 bytes [0:4]
prev_hash : 32 bytes [4:36]
merkle_root : 32 bytes [36:68] ← compared here
time : 4 bytes [68:72]
bits : 4 bytes [72:76]
nonce : 4 bytes [76:80]
The
merkle_rootin the header is stored in little-endian byte order, matching the convention used byMerklePath.compute_root().- __init__(btc_source)[source]¶
- Parameters:
btc_source (BtcDataSource)
- Return type:
None
- async is_valid_root(merkle_root, height)[source]¶
Fetch the block header at height and check its Merkle root.
- Parameters:
merkle_root (Hex32) – The 32-byte Merkle root to verify (as
Hex32).height (BlockHeight) – Block height of the header to check against.
- Returns:
Trueif the header’s Merkle root matches merkle_root.- Return type:
- class pyrxd.network.ElectrumXClient[source]¶
Bases:
objectAsync ElectrumX JSON-RPC client.
- Parameters:
urls – One or more ElectrumX server URLs. The client uses the first URL; on disconnect it attempts one reconnect, then raises
NetworkError.allow_insecure – If
False(default)ws://URLs raiseNetworkErrorimmediately. Set toTrueonly for local testing.timeout – Per-request timeout in seconds (default 30).
- async call_extension(method, params=None)[source]¶
Call an arbitrary JSON-RPC method on the connected server.
Use this for indexer-extension RPCs that aren’t part of the base ElectrumX surface — e.g. RXinDexer’s
wave.resolve,glyph.get_token,swap.get_unconfirmed_orders. The underlying transport (connection, id correlation, error handling) is identical to the built-in methods.Returns the raw
resultfield from the JSON-RPC response. Server errors raiseNetworkError. The caller is responsible for validating the result shape.
- async close()[source]¶
Close the underlying WebSocket connection.
Cancels the reader task, fails any in-flight requests with NetworkError, and closes the socket.
- Return type:
None
- async get_balance(script_hash)[source]¶
Return the confirmed and unconfirmed balance for script_hash.
The
script_hashissha256(locking_script)with bytes reversed (ElectrumX little-endian convention). AcceptsHex32, rawbytes(length 32), or a hexstr(length 64).
- async get_block_header(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_history(script_hash)[source]¶
Return the transaction history for script_hash.
Returns a list of
{"tx_hash": str, "height": int}dicts. Unconfirmed transactions haveheightof 0 or negative.
- async get_tip_height()[source]¶
Return the current chain tip block height.
Uses
blockchain.headers.subscribe, whose INITIAL response is the current tip header —{"height": N, "hex": "..."}(standard ElectrumX). The call also installs a server-side header-push subscription, but that is harmless here: the reader loop drops every id-less server push (see_reader_loop()), so later header notifications never interfere with request/response matching.(The prior implementation called
blockchain.block.header [0, 0]expecting a{"height", ...}dict, but standard ElectrumX returns the bare genesis-header hex string for that call — so the tip read raised “Unexpected response type” against real servers, e.g. electrumx.radiant4people.com.)- Return type:
- async get_transaction_merkle(txid, height)[source]¶
Fetch the Merkle proof for txid at block height.
- Returns:
A parsed Merkle path object.
- Return type:
MerklePath
- Parameters:
txid (Txid)
height (BlockHeight)
- async get_transaction_verbose(txid)[source]¶
Fetch the verbose JSON-decoded form of a transaction.
Calls
blockchain.transaction.getwithverbose=Trueand returns the dict the server provides — includingconfirmations,blockhash,blocktime. Used by confirmation polling.Distinct from
get_transaction()(which returns raw bytes for cryptographic operations like merkle-proof checks). Callers polling for “is this tx confirmed yet?” want THIS one.
- class pyrxd.network.MempoolSpaceSource[source]¶
Bases:
BtcDataSourceBtcDataSource backed by the mempool.space HTTP API.
- Parameters:
base_url – Base URL of the API (default
https://mempool.space/api).
- __init__(base_url='https://mempool.space/api')[source]¶
- Parameters:
base_url (str)
- Return type:
None
- async get_block_hash(height)[source]¶
Return the 32-byte block hash at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_block_header_hex(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_header_chain(start_height, count)[source]¶
Return count consecutive 80-byte headers starting at start_height.
- Parameters:
start_height (BlockHeight)
count (int)
- Return type:
- async get_merkle_proof(txid, height)[source]¶
Return
(branch_hashes_hex, leaf_position)for txid at height.
- async get_raw_tx(txid, min_confirmations=6)[source]¶
Return raw transaction bytes, enforcing min_confirmations.
- class pyrxd.network.MultiSourceBtcDataSource[source]¶
Bases:
BtcDataSourceA quorum-based composite data source.
For read operations, all sources are queried concurrently and the result is returned only if at least quorum sources agree. For broadcast-style operations, sources are tried in order until one succeeds.
- Parameters:
sources – Two or more
BtcDataSourceinstances.quorum – Minimum number of agreeing sources required (default 2).
- __init__(sources, quorum=2)[source]¶
- Parameters:
sources (list[BtcDataSource])
quorum (int)
- Return type:
None
- async get_block_hash(height)[source]¶
Return the 32-byte block hash at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_block_header_hex(height)[source]¶
Return the raw 80-byte block header at height.
- Parameters:
height (BlockHeight)
- Return type:
- async get_header_chain(start_height, count)[source]¶
Return count consecutive 80-byte headers starting at start_height.
- Parameters:
start_height (BlockHeight)
count (int)
- Return type:
- async get_merkle_proof(txid, height)[source]¶
Return
(branch_hashes_hex, leaf_position)for txid at height.
- async get_raw_tx(txid, min_confirmations=6)[source]¶
Return raw transaction bytes, enforcing min_confirmations.
- class pyrxd.network.MultiSourceBtcFundingReader[source]¶
Bases:
objectQuorum
BtcFundingReaderover N independent Esplora-style providers.Audit 2026-05-29 F-17: mitigates the single-source confirmation-depth SPOF — a lone compromised/MITM’d source that OVER-reports depth (under-reports
block_height) can make an unburied/reorgable tx look final and trigger a premature release.- Operator policy (decided 2026-05-29):
quorum= 2 of 3 providers (majority): tolerates one source down or lying.dust_cap_sats= 10_000: at/below the cap a single successful read is accepted (the documented dust posture); ABOVE it the quorum is REQUIRED (fail-closed).confirmations()returns the MINIMUM depth across responding sources — a tx is only as buried as the most-pessimistic source, defeating an over-reporter.read_output_amount_sats()requires >=quorumsources to agree on the EXACT amount (a deterministic value; disagreement fails closed).
Satisfies the same duck-typed reader Protocol as
MempoolSpaceFundingReader, so it is a drop-in for the reorg gate / funding read-back on above-dust swaps. A failing source is simply dropped from the quorum (never fails the whole read).- DEFAULT_MAINNET_ENDPOINTS = ('https://mempool.space/api', 'https://blockstream.info/api', 'https://mempool.emzy.de/api')¶
Default independent mainnet Esplora endpoints (distinct operators).
- async confirmations(txid, *, value_sats=None)[source]¶
Quorum’d confirmation depth, returning the conservative MINIMUM.
value_satsselects the dust gate:None(the default, used by the reorg gate) or any value abovedust_cap_satsREQUIRES the quorum and fails closed otherwise; a value at/below the cap accepts a single source.
- classmethod default_mainnet(*, quorum=2, dust_cap_sats=10000)[source]¶
Wire the three default independent mainnet Esplora endpoints (2-of-3).
- Parameters:
- Return type:
- classmethod from_endpoints(urls, *, quorum=2, dust_cap_sats=10000)[source]¶
Build the reader from Esplora base URLs, clamping the effective quorum to the number of DISTINCT hosts. A quorum of same-host endpoints is false corroboration (one hostile/buggy host satisfies it), so e.g. two
mempool.spaceURLs can never form a 2-of-2 quorum. A clamp is logged loudly so the operator sees the real corroboration level.- Parameters:
- Return type:
- async list_address_utxos(address)[source]¶
UTXO discovery (not a value gate): return the first source that responds.
- pyrxd.network.choose_funding_reader(value_sats, *, single, multi, dust_cap_sats=10000)[source]¶
Route a funding-reader choice by swap value (audit 2026-05-29 F-17).
Returns the SINGLE-source reader for a value at/below
dust_cap_sats(the documented dust posture — a deliberate single-source SPOF the operator accepts for trivial value) and the MULTI-source quorum reader ABOVE it (fail-closed corroboration; seeMultiSourceBtcFundingReader). Inject the result as aBtcLeg’sfunding_reader.singleandmultimay each be a reader INSTANCE or a zero-argument FACTORY — a factory is invoked only for the chosen reader, so the unused one (e.g. the quorum reader’s three HTTP sessions on a dust swap) is never built.Use network-appropriate readers: the quorum reader’s
default_mainnet()endpoints are mainnet-only, so a signet/testnet above-dust path must supply its own endpoint set.