119 lines
5.6 KiB
Python
119 lines
5.6 KiB
Python
'''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)]
|