Browse Source

coin_mvt: add integration tests for in-channel htlc movements

nifty/pset-pre
lisa neigut 5 years ago
committed by Rusty Russell
parent
commit
9c4c0f10fb
  1. 39
      tests/plugins/coin_movements.py
  2. 106
      tests/test_plugin.py
  3. 34
      tests/utils.py

39
tests/plugins/coin_movements.py

@ -0,0 +1,39 @@
#!/usr/bin/env python3
from pyln.client import Plugin
plugin = Plugin()
@plugin.init()
def init(configuration, options, plugin):
plugin.coin_moves = []
@plugin.subscribe("coin_movement")
def notify_coin_movement(plugin, coin_movement, **kwargs):
idx = coin_movement['movement_idx']
plugin.log("{} coins movement version: {}".format(idx, coin_movement['version']))
plugin.log("{} coins node: {}".format(idx, coin_movement['node_id']))
plugin.log("{} coins mvt_type: {}".format(idx, coin_movement['type']))
plugin.log("{} coins account: {}".format(idx, coin_movement['account_id']))
plugin.log("{} coins credit: {}".format(idx, coin_movement['credit']))
plugin.log("{} coins debit: {}".format(idx, coin_movement['debit']))
plugin.log("{} coins tag: {}".format(idx, coin_movement['tag']))
plugin.log("{} coins blockheight: {}".format(idx, coin_movement['blockheight']))
plugin.log("{} coins timestamp: {}".format(idx, coin_movement['timestamp']))
plugin.log("{} coins unit_of_account: {}".format(idx, coin_movement['unit_of_account']))
for f in ['payment_hash', 'utxo_txid', 'vout', 'txid', 'part_id']:
if f in coin_movement:
plugin.log("{} coins {}: {}".format(idx, f, coin_movement[f]))
plugin.coin_moves.append(coin_movement)
@plugin.method('listcoinmoves_plugin')
def return_moves(plugin):
return {'coin_moves': plugin.coin_moves}
plugin.run()

106
tests/test_plugin.py

@ -6,7 +6,8 @@ from pyln.client import RpcError, Millisatoshi
from pyln.proto import Invoice
from utils import (
DEVELOPER, only_one, sync_blockheight, TIMEOUT, wait_for, TEST_NETWORK,
DEPRECATED_APIS, expected_peer_features, expected_node_features
DEPRECATED_APIS, expected_peer_features, expected_node_features, account_balance,
check_coin_moves, first_channel_id
)
import json
@ -1355,3 +1356,106 @@ def test_plugin_fail(node_factory):
time.sleep(2)
# It should clean up!
assert 'failcmd' not in [h['command'] for h in l1.rpc.help()['help']]
@unittest.skipIf(not DEVELOPER, "without DEVELOPER=1, gossip v slow")
def test_coin_movement_notices(node_factory, bitcoind):
"""Verify that coin movements are triggered correctly.
"""
l1_l2_mvts = [
{'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'deposit'},
{'type': 'channel_mvt', 'credit': 100001001, 'debit': 0, 'tag': 'routed'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tag': 'routed'},
{'type': 'channel_mvt', 'credit': 100000000, 'debit': 0, 'tag': 'invoice'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 50000000, 'tag': 'invoice'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1, 'tag': 'chain_fees'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 100001000, 'tag': 'withdrawal'},
]
l2_l3_mvts = [
{'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'channel_mvt', 'credit': 0, 'debit': 100000000, 'tag': 'routed'},
{'type': 'channel_mvt', 'credit': 50000501, 'debit': 0, 'tag': 'routed'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 5430501, 'tag': 'chain_fees'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 944570000, 'tag': 'withdrawal'},
]
l2_wallet_mvts = [
{'type': 'chain_mvt', 'credit': 2000000000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 995418000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'withdrawal'},
{'type': 'chain_mvt', 'credit': 0, 'debit': 4582000, 'tag': 'chain_fees'},
{'type': 'chain_mvt', 'credit': 995418000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 100001000, 'debit': 0, 'tag': 'deposit'},
{'type': 'chain_mvt', 'credit': 944570000, 'debit': 0, 'tag': 'deposit'},
]
l1, l2, l3 = node_factory.line_graph(3, opts=[
{},
{'plugin': os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py')},
{}
], wait_for_announce=True)
bitcoind.generate_block(5)
wait_for(lambda: len(l1.rpc.listchannels()['channels']) == 4)
amount = 10**8
payment_hash13 = l3.rpc.invoice(amount, "first", "desc")['payment_hash']
route = l1.rpc.getroute(l3.info['id'], amount, 1)['route']
# status: offered -> settled
l1.rpc.sendpay(route, payment_hash13)
l1.rpc.waitsendpay(payment_hash13)
# status: offered -> failed
route = l1.rpc.getroute(l3.info['id'], amount, 1)['route']
payment_hash13 = "f" * 64
with pytest.raises(RpcError):
l1.rpc.sendpay(route, payment_hash13)
l1.rpc.waitsendpay(payment_hash13)
# go the other direction
payment_hash31 = l1.rpc.invoice(amount // 2, "first", "desc")['payment_hash']
route = l3.rpc.getroute(l1.info['id'], amount // 2, 1)['route']
l3.rpc.sendpay(route, payment_hash31)
l3.rpc.waitsendpay(payment_hash31)
# receive a payment (endpoint)
payment_hash12 = l2.rpc.invoice(amount, "first", "desc")['payment_hash']
route = l1.rpc.getroute(l2.info['id'], amount, 1)['route']
l1.rpc.sendpay(route, payment_hash12)
l1.rpc.waitsendpay(payment_hash12)
# send a payment (originator)
payment_hash21 = l1.rpc.invoice(amount // 2, "second", "desc")['payment_hash']
route = l2.rpc.getroute(l1.info['id'], amount // 2, 1)['route']
l2.rpc.sendpay(route, payment_hash21)
l2.rpc.waitsendpay(payment_hash21)
# close the channel down
chan1 = l2.get_channel_scid(l1)
chan3 = l2.get_channel_scid(l3)
chanid_1 = first_channel_id(l2, l1)
chanid_3 = first_channel_id(l2, l3)
l2.rpc.close(chan1)
l2.daemon.wait_for_log(' to CLOSINGD_SIGEXCHANGE')
assert account_balance(l2, chanid_1) == 100001001
bitcoind.generate_block(6)
sync_blockheight(bitcoind, [l2])
l2.daemon.wait_for_log('{}.*FUNDING_TRANSACTION/FUNDING_OUTPUT->MUTUAL_CLOSE depth'.format(l1.info['id']))
l2.rpc.close(chan3)
l2.daemon.wait_for_log(' to CLOSINGD_SIGEXCHANGE')
assert account_balance(l2, chanid_3) == 950000501
bitcoind.generate_block(6)
sync_blockheight(bitcoind, [l2])
l2.daemon.wait_for_log('{}.*FUNDING_TRANSACTION/FUNDING_OUTPUT->MUTUAL_CLOSE depth'.format(l3.info['id']))
# Ending channel balance should be zero
assert account_balance(l2, chanid_1) == 0
assert account_balance(l2, chanid_3) == 0
# Verify we recorded all the movements we expect
check_coin_moves(l2, chanid_1, l1_l2_mvts)
check_coin_moves(l2, chanid_3, l2_l3_mvts)
check_coin_moves(l2, 'wallet', l2_wallet_mvts)

34
tests/utils.py

@ -27,3 +27,37 @@ def expected_channel_features():
return '80000000000000000000000000'
else:
return ''
def check_coin_moves(n, account_id, expected_moves):
moves = n.rpc.call('listcoinmoves_plugin')['coin_moves']
node_id = n.info['id']
acct_moves = [m for m in moves if m['account_id'] == account_id]
assert len(acct_moves) == len(expected_moves)
for mv, exp in list(zip(acct_moves, expected_moves)):
assert mv['version'] == 1
assert mv['node_id'] == node_id
assert mv['type'] == exp['type']
assert mv['credit'] == "{}msat".format(exp['credit'])
assert mv['debit'] == "{}msat".format(exp['debit'])
assert mv['tag'] == exp['tag']
assert mv['timestamp'] > 0
assert mv['unit_of_account'] == 'btc'
# chain moves should have blockheights
if mv['type'] == 'chain_mvt':
assert mv['blockheight'] is not None
def account_balance(n, account_id):
moves = n.rpc.call('listcoinmoves_plugin')['coin_moves']
chan_moves = [m for m in moves if m['account_id'] == account_id]
assert len(chan_moves) > 0
m_sum = 0
for m in chan_moves:
m_sum += int(m['credit'][:-4])
m_sum -= int(m['debit'][:-4])
return m_sum
def first_channel_id(n1, n2):
return only_one(only_one(n1.rpc.listpeers(n2.info['id'])['peers'])['channels'])['channel_id']

Loading…
Cancel
Save