Files
plm-electrumx/tests/integration/test_protocol.py
T

118 lines
5.6 KiB
Python
Raw Normal View History

'''Electrum protocol methods per protocol version, daemon limits and reorgs, against the regtest node (see conftest.py).'''
import pytest
from tests.integration.conftest import ElectrumClient, new_address, scripthash, wait_for
@pytest.fixture
def client(electrumx):
client = ElectrumClient(electrumx)
yield client
client.close()
def negotiate(client, version):
return client.call('server.version', 'plm-integration-test', version)
def spk_of(node, address):
return node.call('getaddressinfo', address)['scriptPubKey']
@pytest.mark.parametrize('version', ['1.4', '1.4.2', '1.5', '1.6', '1.7'])
def test_protocol_versions(client, version):
assert negotiate(client, version)[1] == version
def test_unsupported_protocol_versions(electrumx):
for version in ('1.3', '2.0'):
client = ElectrumClient(electrumx)
try:
with pytest.raises(RuntimeError, match='unsupported protocol version'):
negotiate(client, version)
finally:
client.close()
def test_server_features(node, client):
negotiate(client, '1.4')
features = client.call('server.features')
assert features['genesis_hash'] == node.call('getblockhash', 0)
assert features['hash_function'] == 'sha256'
assert features['protocol_min'] == '1.4'
def test_relayfee_and_estimatefee(client):
negotiate(client, '1.5') # blockchain.relayfee was removed in 1.6
assert client.call('blockchain.relayfee') > 0
assert isinstance(client.call('blockchain.estimatefee', 2), (int, float))
def test_mempool_info(client):
'''The daemon reports 'incrementalfee' in getnetworkinfo, not 'incrementalrelayfee' in getmempoolinfo'''
negotiate(client, '1.6')
info = client.call('mempool.get_info')
assert set(info) == {'mempoolminfee', 'minrelaytxfee', 'incrementalrelayfee'}
assert all(fee > 0 for fee in info.values())
def test_daemon_limits_are_reported_as_errors(client):
'''The Palladium daemon has no submitpackage and no gettxspendingprevout: clients get an error.
If a newer daemon gains them, update CLAUDE.md, README.md and DEPLOY.md and this test.'''
negotiate(client, '1.7')
with pytest.raises(RuntimeError, match='rejected by network rules'):
client.call('blockchain.transaction.broadcast_package', ['00'])
# a subscription would be accepted for an outpoint in the mempool and then close the session at the next block
with pytest.raises(RuntimeError, match='outpoint subscriptions are not supported'):
client.call('blockchain.outpoint.subscribe', '00' * 32, 0, '')
def test_protocol_1_7_scriptpubkey_methods(node, wallet, client):
negotiate(client, '1.7')
target = node.call('getnewaddress', '', 'bech32m', wallet=wallet)
spk = spk_of(node, target)
txid = node.call('sendtoaddress', target, 2, wallet=wallet)
wait_for(lambda: client.call('blockchain.scriptpubkey.get_mempool', spk)['history'], 'the tx in the mempool')
assert txid in [h['tx_hash'] for h in client.call('blockchain.scriptpubkey.get_history', spk)['history']]
assert client.call('blockchain.scriptpubkey.get_balance', spk)['unconfirmed'] == 200_000_000
assert client.call('blockchain.scriptpubkey.subscribe', spk) is not None
# mempool.recent lists it
assert txid in [t['txid'] for t in client.call('mempool.recent')]
node.mine(1, new_address(node, wallet))
wait_for(lambda: client.call('blockchain.scriptpubkey.get_balance', spk)['confirmed'] == 200_000_000, 'confirmation')
unspent = client.call('blockchain.scriptpubkey.listunspent', spk)['utxos']
assert [(u['tx_hash'], u['value']) for u in unspent] == [(txid, 200_000_000)]
assert client.call('blockchain.scriptpubkey.unsubscribe', spk) is True
def test_testmempoolaccept(node, wallet, client):
negotiate(client, '1.7')
target = new_address(node, wallet)
funded = node.call('fundrawtransaction', node.call('createrawtransaction', [], {target: 0.1}), wallet=wallet)
signed = node.call('signrawtransactionwithwallet', funded['hex'], wallet=wallet)['hex']
result, = client.call('blockchain.transaction.testmempoolaccept', [signed])
assert result['allowed'] is True
def test_reorg_replaces_blocks(node, wallet, electrumx, client):
'''The server follows a reorg: the block is replaced by a longer chain that confirms the tx again.
Like upstream ElectrumX it reorganises when the daemon's chain gets longer than its own, not before.'''
negotiate(client, '1.4.2')
miner = new_address(node, wallet)
target = node.call('getnewaddress', '', 'bech32', wallet=wallet)
sh = scripthash(node, target)
txid = node.call('sendtoaddress', target, 1, wallet=wallet)
block = node.mine(1, miner)[0]
height = node.call('getblockcount')
wait_for(lambda: client.call('blockchain.scripthash.get_balance', sh)['confirmed'] == 100_000_000, 'confirmation')
old_header = client.call('blockchain.block.header', height)
node.call('invalidateblock', block)
new_blocks = node.mine(2, new_address(node, wallet)) # other coinbase, else the same block; the tx goes back to the mempool and is mined again in the first one
assert block not in new_blocks
wait_for(lambda: client.call('blockchain.headers.subscribe')['height'] == height + 1, 'the server to follow the new chain')
assert client.call('blockchain.block.header', height) != old_header
wait_for(lambda: client.call('blockchain.scripthash.get_balance', sh) == {'confirmed': 100_000_000, 'unconfirmed': 0},
'the balance after the reorg')
history = client.call('blockchain.scripthash.get_history', sh)
assert [(h['tx_hash'], h['height']) for h in history] == [(txid, height)]