mirror of
https://github.com/ElementsProject/lightning.git
synced 2025-03-26 20:30:59 +01:00
coin_mvt: add integration tests for in-channel htlc movements
This commit is contained in:
parent
ce8bdfcc45
commit
9c4c0f10fb
3 changed files with 178 additions and 1 deletions
39
tests/plugins/coin_movements.py
Executable file
39
tests/plugins/coin_movements.py
Executable file
|
@ -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()
|
|
@ -6,7 +6,8 @@ from pyln.client import RpcError, Millisatoshi
|
||||||
from pyln.proto import Invoice
|
from pyln.proto import Invoice
|
||||||
from utils import (
|
from utils import (
|
||||||
DEVELOPER, only_one, sync_blockheight, TIMEOUT, wait_for, TEST_NETWORK,
|
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
|
import json
|
||||||
|
@ -1355,3 +1356,106 @@ def test_plugin_fail(node_factory):
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
# It should clean up!
|
# It should clean up!
|
||||||
assert 'failcmd' not in [h['command'] for h in l1.rpc.help()['help']]
|
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)
|
||||||
|
|
|
@ -27,3 +27,37 @@ def expected_channel_features():
|
||||||
return '80000000000000000000000000'
|
return '80000000000000000000000000'
|
||||||
else:
|
else:
|
||||||
return ''
|
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…
Add table
Reference in a new issue