- 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>
123 lines
5.4 KiB
Python
123 lines
5.4 KiB
Python
# Tests of the Palladium coin classes (parameters of the Palladium Core chain params)
|
|
|
|
import pytest
|
|
|
|
from electrumx.lib import tx as lib_tx
|
|
from electrumx.lib.coins import Coin, Palladium, PalladiumTestnet, PalladiumRegtest
|
|
from electrumx.lib.hash import Base58Error, Bech32, Bech32Error
|
|
from electrumx.lib.script import ScriptPubKey
|
|
|
|
# (class, net, rpc port, P2PKH verbyte, P2SH verbyte, genesis hash)
|
|
PARAMS = [
|
|
(Palladium, "mainnet", 2332, "37", "05",
|
|
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
|
|
(PalladiumTestnet, "testnet", 12332, "7f", "73",
|
|
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"),
|
|
(PalladiumRegtest, "regtest", 28443, "7f", "73",
|
|
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"),
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("coin,net,rpc_port,p2pkh,p2sh,genesis", PARAMS)
|
|
def test_chain_params(coin, net, rpc_port, p2pkh, p2sh, genesis):
|
|
assert coin.NAME == "Palladium"
|
|
assert coin.NET == net
|
|
assert coin.RPC_PORT == rpc_port
|
|
assert coin.P2PKH_VERBYTE == bytes.fromhex(p2pkh)
|
|
assert coin.P2SH_VERBYTES == (bytes.fromhex(p2sh),)
|
|
assert coin.GENESIS_HASH == genesis
|
|
assert coin.BASIC_HEADER_SIZE == 80
|
|
assert coin.DESERIALIZER is lib_tx.DeserializerSegWit
|
|
|
|
|
|
@pytest.mark.parametrize("coin,shortname", [(Palladium, "PLM"), (PalladiumTestnet, "TPLM"), (PalladiumRegtest, "RPLM")])
|
|
def test_shortname(coin, shortname):
|
|
assert coin.SHORTNAME == shortname
|
|
|
|
|
|
@pytest.mark.parametrize("coin,net", [(c, n) for c, n, *_ in PARAMS])
|
|
def test_lookup_coin_class(coin, net):
|
|
assert Coin.lookup_coin_class("Palladium", net) is coin
|
|
assert Coin.lookup_coin_class("palladium", net.upper()) is coin
|
|
|
|
|
|
def test_daemon_requirements_disabled():
|
|
# Palladium Core has no getindexinfo/txospenderindex
|
|
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):
|
|
"""Minimal bech32 encoder of a witness v0 address, only to build test addresses"""
|
|
charset = Bech32.CHARSET
|
|
acc = bits = 0
|
|
data = [0]
|
|
for byte in program:
|
|
acc = (acc << 8) | byte
|
|
bits += 8
|
|
while bits >= 5:
|
|
bits -= 5
|
|
data.append((acc >> bits) & 31)
|
|
if bits:
|
|
data.append((acc << (5 - bits)) & 31)
|
|
values = [ord(c) >> 5 for c in hrp] + [0] + [ord(c) & 31 for c in hrp] + data
|
|
polymod = Bech32._polymod(values + [0] * 6) ^ Bech32.BECH32_CONST
|
|
checksum = [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]
|
|
return hrp + "1" + "".join(charset[d] for d in data + checksum)
|
|
|
|
|
|
# bech32 / bech32m: the valid and invalid segwit addresses of BIP 173 and BIP 350
|
|
BIP_VALID = [
|
|
("bc", "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", 0, "751e76e8199196d454941c45d1b3a323f1433bd6"),
|
|
("tb", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7", 0,
|
|
"1863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"),
|
|
("bc", "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y", 1,
|
|
"751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"),
|
|
("bc", "BC1SW50QGDZ25J", 16, "751e"),
|
|
]
|
|
BIP_INVALID = [
|
|
("bc", "tc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vq5zuyut"), # wrong hrp
|
|
("bc", "bc1p0xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqh2y7hd"), # bech32 for version 1
|
|
("bc", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kemeawh"), # bech32m for version 0
|
|
("bc", "BC1S0XLXVLHEMJA6C4DQV22UAPCTQUPFHLXM9H8Z3K2E72Q4K9HCZ7VQ54WELL"), # bad checksum
|
|
("bc", "Bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4"), # mixed case
|
|
("bc", "bc1gmk9yu"), # empty program
|
|
("bc", "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5"), # bad checksum
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("hrp,address,witver,program", BIP_VALID)
|
|
def test_bech32_decode_segwit(hrp, address, witver, program):
|
|
assert Bech32.decode_segwit(hrp, address) == (witver, bytes.fromhex(program))
|
|
|
|
|
|
@pytest.mark.parametrize("hrp,address", BIP_INVALID)
|
|
def test_bech32_decode_segwit_invalid(hrp, address):
|
|
with pytest.raises(Bech32Error):
|
|
Bech32.decode_segwit(hrp, address)
|
|
|
|
|
|
def test_segwit_script():
|
|
assert ScriptPubKey.segwit_script(0, bytes(20)) == b"\x00\x14" + bytes(20)
|
|
assert ScriptPubKey.segwit_script(1, bytes(32)) == b"\x51\x20" + bytes(32)
|
|
assert ScriptPubKey.segwit_script(16, b"\x75\x1e") == b"\x60\x02\x75\x1e"
|
|
|
|
|
|
@pytest.mark.parametrize("coin,hrp", [(Palladium, "plm"), (PalladiumTestnet, "tplm"), (PalladiumRegtest, "rplm")])
|
|
def test_bech32_address_to_script(coin, hrp):
|
|
assert coin.BECH32_HRP == hrp
|
|
# a v0 witness keyhash address built from a known hash160 (the checksum is computed with a constant)
|
|
h160 = bytes.fromhex("751e76e8199196d454941c45d1b3a323f1433bd6")
|
|
address = bech32_encode_v0(hrp, h160)
|
|
assert coin.pay_to_address_script(address) == b"\x00\x14" + h160
|
|
assert coin.pay_to_address_script(address.upper()) == b"\x00\x14" + h160
|
|
assert coin.address_to_hashX(address) == coin.hashX_from_script(b"\x00\x14" + h160)
|
|
|
|
|
|
def test_bech32_address_of_another_network_is_rejected():
|
|
address = bech32_encode_v0("tplm", bytes(20))
|
|
with pytest.raises((Base58Error, Bech32Error)): # not a mainnet bech32 address, so it is read as base58
|
|
Palladium.pay_to_address_script(address)
|