Files
palladum-lightning/tests/test_coinmoves.py
Rusty Russell 5bf5f3b3ae pytest: print useful information if we don't get our channelmoves/chainmoves
The equality check will fail, but it will show is what is missing, rather than:

FAILED tests/test_coinmoves.py::test_coinmoves_unilateral_htlc_fulfill - ValueError: Timeout while waiting for <function check_chain_moves.<locals>.<lambda> at 0x7f7800941ab0>

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2025-10-22 19:43:24 +10:30

2089 lines
103 KiB
Python

from fixtures import * # noqa: F401,F403
from fixtures import TEST_NETWORK
from utils import (
sync_blockheight, wait_for, only_one, TIMEOUT
)
import os
import unittest
import pytest
import re
import time
from pyln.testing.utils import EXPERIMENTAL_DUAL_FUND
# While we're doing this, check sql plugin's representation too.
def check_sql(node, kind, expected):
columns = only_one(node.rpc.listsqlschemas(kind)['schemas'])['columns']
ret = node.rpc.sql(f"SELECT * FROM {kind}")
assert len(ret['rows']) == len(expected)
for row, e in zip(ret['rows'], expected):
assert len(row) == len(columns)
for val, col in zip(row, columns):
if col['name'] in e:
assert val == e[col['name']], f"{col['name']} is {val} not {e[col['name']]}: ({row} vs {columns})"
elif col['name'] not in ('rowid', 'timestamp'):
assert val is None, f"{col['name']} is not None ({row} vs {columns})"
def check_moves(moves, expected):
# Can't predict timestamp
for m in moves:
del m['timestamp']
# But we can absolutely predict created_index.
for i, m in enumerate(expected, start=1):
m['created_index'] = i
assert moves == expected
def check_channel_moves(node, expected):
# If this times out, show the result anyway.
try:
wait_for(lambda: len(node.rpc.listchannelmoves()['channelmoves']) == len(expected))
except ValueError:
print("*** Didn't see enough channelmoves")
check_moves(node.rpc.listchannelmoves()['channelmoves'], expected)
check_sql(node, "channelmoves", expected)
def check_chain_moves(node, expected):
# If this times out, show the result anyway.
try:
wait_for(lambda: len(node.rpc.listchainmoves()['chainmoves']) == len(expected))
except ValueError:
print("*** Didn't see enough chainmoves")
check_moves(node.rpc.listchainmoves()['chainmoves'], expected)
check_sql(node, "chainmoves", expected)
# Check extra_tags.
for e in expected:
rows = node.rpc.sql(f"SELECT cet.extra_tags FROM chainmoves_extra_tags cet LEFT JOIN chainmoves cm ON cet.row = cm.rowid WHERE cm.created_index={e['created_index']} ORDER BY cm.created_index, cet.arrindex;")['rows']
extra_tags = [only_one(row) for row in rows]
assert extra_tags == e['extra_tags']
def account_balances(accounts):
"""Gather all the credits / debits for all accounts"""
balances = {}
for a in accounts:
if a['account_id'] not in balances:
balances[a['account_id']] = []
balances[a['account_id']].append(a['credit_msat'])
balances[a['account_id']].append(-a['debit_msat'])
return balances
def check_balances(l1, l2, channel_id, msats_sent_to_2):
channel1 = account_balances(l1.rpc.listchannelmoves()['channelmoves'])
channel2 = account_balances(l2.rpc.listchannelmoves()['channelmoves'])
chain1 = account_balances(l1.rpc.listchainmoves()['chainmoves'])
chain2 = account_balances(l2.rpc.listchainmoves()['chainmoves'])
# Our initial setup_channel sends 50000000000 msat
msats_sent_to_2 += 50000000000
# Channel balances should reflect sats transferred
assert sum(channel1[channel_id]) == -msats_sent_to_2
assert sum(channel2[channel_id]) == msats_sent_to_2
# Chain balances for the channels should be opposite the channel balances.
assert sum(chain1[channel_id]) == -sum(channel1[channel_id])
assert sum(chain2[channel_id]) == -sum(channel2[channel_id])
# Wallet balances should reflect reality
l1_wallet = sum([o['amount_msat'] for o in l1.rpc.listfunds()['outputs']])
l2_wallet = sum([o['amount_msat'] for o in l2.rpc.listfunds()['outputs']])
if sum(chain1['wallet']) != l1_wallet:
print(f"sum({chain1['wallet']}) != {l1_wallet}")
assert False
if sum(chain2['wallet']) != l2_wallet:
print(f"sum({chain2['wallet']}) != {l2_wallet}")
assert False
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves(node_factory, bitcoind):
l1, l2, l3 = node_factory.get_nodes(3)
# Empty
expected_channel1 = []
expected_channel2 = []
expected_chain1 = []
expected_chain2 = []
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_DEPOSIT
addr = l1.rpc.newaddr()['bech32']
txid_deposit = bitcoind.rpc.sendtoaddress(addr, 200000000 / 10**8)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1])
vout_deposit = only_one([out['n'] for out in bitcoind.rpc.gettransaction(txid_deposit, False, True)['decoded']['vout'] if out['scriptPubKey']['address'] == addr])
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 102,
'credit_msat': 200000000000,
'debit_msat': 0,
'output_msat': 200000000000,
'primary_tag': 'deposit',
'extra_tags': [],
'utxo': f"{txid_deposit}:{vout_deposit}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# Since sql uses pagination, rowids no longer change on each access!
first_rowid = only_one(only_one(l1.rpc.sql("SELECT rowid FROM chainmoves;")['rows']))
# MVT_WITHDRAWAL
addr = l3.rpc.newaddr()['bech32']
withdraw = l1.rpc.withdraw(addr, 100000000)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1])
vout_withdrawal = only_one([out['n'] for out in bitcoind.rpc.decoderawtransaction(withdraw['tx'])['vout'] if out['scriptPubKey']['address'] == addr])
expected_chain1 += [{'account_id': 'external',
'blockheight': 0,
'credit_msat': 100000000000,
'debit_msat': 0,
'output_msat': 100000000000,
'originating_account': 'wallet',
'primary_tag': 'deposit',
'extra_tags': [],
'utxo': f"{withdraw['txid']}:{vout_withdrawal}"},
# Spend
{'account_id': 'wallet',
'blockheight': 103,
'credit_msat': 0,
'debit_msat': 200000000000,
'primary_tag': 'withdrawal',
'output_msat': 200000000000,
'extra_tags': [],
'spending_txid': withdraw['txid'],
'utxo': f"{txid_deposit}:{vout_deposit}"},
# Change
{'account_id': 'wallet',
'blockheight': 103,
'credit_msat': 99995433000,
'debit_msat': 0,
'primary_tag': 'deposit',
'extra_tags': [],
'output_msat': 99995433000,
'utxo': f"{withdraw['txid']}:{vout_withdrawal ^ 1}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_CHANNEL_OPEN
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
fundchannel = l1.rpc.fundchannel(l2.info['id'], 'all')
bitcoind.generate_block(1, wait_for_mempool=fundchannel['txid'])
wait_for(lambda: all([c['state'] == 'CHANNELD_NORMAL' for c in l1.rpc.listpeerchannels(l2.info['id'])['channels']]))
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99995433000,
'output_msat': 99995433000,
'primary_tag': 'withdrawal',
'extra_tags': [],
'spending_txid': fundchannel['txid'],
'utxo': f"{withdraw['txid']}:{vout_withdrawal ^ 1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 25000000,
'debit_msat': 0,
'output_msat': 25000000,
'primary_tag': 'deposit',
'extra_tags': [],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum'] ^ 1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 99965813000,
'debit_msat': 0,
'output_msat': 99965813000,
'peer_id': l2.info['id'],
'primary_tag': 'channel_open',
'extra_tags': ['opener'],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 0,
'output_msat': 99965813000,
'peer_id': l1.info['id'],
'primary_tag': 'channel_open',
'extra_tags': [],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_INVOICE
inv = l2.rpc.invoice('any', 'test_coinmoves', 'test_coinmoves')
l1.rpc.xpay(inv['bolt11'], '1000sat')
# Make sure it's fully settled.
wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['htlcs'] == [])
# We cheat and extract group id.
group_id = l1.rpc.listchannelmoves()['channelmoves'][-1]['group_id']
expected_channel1 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 1000000,
'primary_tag': 'invoice',
'fees_msat': 0,
'payment_hash': inv['payment_hash'],
'group_id': group_id,
'part_id': 1}]
expected_channel2 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 1000000,
'debit_msat': 0,
'primary_tag': 'invoice',
'fees_msat': 0,
'payment_hash': inv['payment_hash']}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_PUSHED
l3.rpc.connect(l1.info['id'], 'localhost', l1.port)
if not EXPERIMENTAL_DUAL_FUND:
l3fundchannel = l3.rpc.fundchannel(l1.info['id'], 40000000, push_msat=100000)
else:
l3fundchannel = l3.rpc.fundchannel(l1.info['id'], 40000000)
bitcoind.generate_block(1, wait_for_mempool=1)
wait_for(lambda: all([c['state'] == 'CHANNELD_NORMAL' for c in l1.rpc.listpeerchannels(l3.info['id'])['channels']]))
expected_chain1 += [{'account_id': l3fundchannel['channel_id'],
'blockheight': 105,
'credit_msat': 0,
'debit_msat': 0,
'output_msat': 40000000000,
'peer_id': l3.info['id'],
'primary_tag': 'channel_open',
'extra_tags': [],
'utxo': f"{l3fundchannel['txid']}:{l3fundchannel['outnum']}"}]
if not EXPERIMENTAL_DUAL_FUND:
expected_channel1 += [{'account_id': l3fundchannel['channel_id'],
'credit_msat': 100000,
'debit_msat': 0,
'fees_msat': 0,
'primary_tag': 'pushed'}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_ROUTED
# Make sure l3 sees l2.
bitcoind.generate_block(5)
wait_for(lambda: len(l3.rpc.listchannels()['channels']) == 4)
inv = l2.rpc.invoice('any', 'test_coinmoves2', 'test_coinmoves2')
l3.rpc.xpay(inv['bolt11'], '10000000sat')
# Make sure it's fully settled.
wait_for(lambda: only_one(l3.rpc.listpeerchannels(l1.info['id'])['channels'])['htlcs'] == [])
expected_channel1 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 10000000000,
'fees_msat': 100001,
'payment_hash': inv['payment_hash'],
'primary_tag': 'routed'},
{'account_id': l3fundchannel['channel_id'],
'credit_msat': 10000100001,
'debit_msat': 0,
'fees_msat': 100001,
'payment_hash': inv['payment_hash'],
'primary_tag': 'routed'}]
expected_channel2 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 10000000000,
'debit_msat': 0,
'fees_msat': 0,
'payment_hash': inv['payment_hash'],
'primary_tag': 'invoice'}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# MVT_CHANNEL_CLOSE
close = l1.rpc.close(fundchannel['channel_id'])
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1, l2])
# Determining our own output is harder than you might think!
l1_addrs = [a['p2tr'] for a in l1.rpc.listaddresses()['addresses'] if 'p2tr' in a]
l1_vout_close = only_one([out['n'] for out in bitcoind.rpc.decoderawtransaction(only_one(close['txs']))['vout'] if out['scriptPubKey']['address'] in l1_addrs])
l2_addrs = [a['p2tr'] for a in l2.rpc.listaddresses()['addresses'] if 'p2tr' in a]
l2_vout_close = only_one([out['n'] for out in bitcoind.rpc.decoderawtransaction(only_one(close['txs']))['vout'] if out['scriptPubKey']['address'] in l2_addrs])
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 111,
'credit_msat': 89961918000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 89961918000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close['txids'])}:{l1_vout_close}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 111,
'credit_msat': 0,
'debit_msat': 89964813000,
'extra_tags': [],
'output_count': 2,
'output_msat': 99965813000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 111,
'credit_msat': 10001000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 10001000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close['txids'])}:{l1_vout_close ^ 1}"}]
expected_chain2 += [{'account_id': 'wallet',
'blockheight': 111,
'credit_msat': 10001000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 10001000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close['txids'])}:{l2_vout_close}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 111,
'credit_msat': 0,
'debit_msat': 10001000000,
'extra_tags': [],
'output_count': 2,
'output_msat': 99965813000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 111,
'credit_msat': 89961918000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 89961918000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close['txids'])}:{l2_vout_close ^ 1}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
final_first_rowid = only_one(only_one(l1.rpc.sql("SELECT rowid FROM chainmoves ORDER BY rowid LIMIT 1;")['rows']))
assert final_first_rowid == first_rowid
def setup_channel(bitcoind, l1, l2):
"""Set up a balanced l1->l2 channel, return:
l1's expected channel moves
l2's expected channel moves
l1's expected chain moves
l2's expected chain moves
The fundchannel return
"""
expected_channel1 = []
expected_channel2 = []
expected_chain1 = []
expected_chain2 = []
addr = l1.rpc.newaddr()['bech32']
txid_deposit = bitcoind.rpc.sendtoaddress(addr, 100000000 / 10**8)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1])
vout_deposit = only_one([out['n'] for out in bitcoind.rpc.gettransaction(txid_deposit, False, True)['decoded']['vout'] if out['scriptPubKey']['address'] == addr])
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 102,
'credit_msat': 100000000000,
'debit_msat': 0,
'output_msat': 100000000000,
'primary_tag': 'deposit',
'extra_tags': [],
'utxo': f"{txid_deposit}:{vout_deposit}"}]
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
fundchannel = l1.rpc.fundchannel(l2.info['id'], 'all')
bitcoind.generate_block(1, wait_for_mempool=fundchannel['txid'])
wait_for(lambda: all([c['state'] == 'CHANNELD_NORMAL' for c in l1.rpc.listpeerchannels(l2.info['id'])['channels']]))
expected_chain1 += [{'account_id': 'wallet', # Spent UTXO
'blockheight': 103,
'credit_msat': 0,
'debit_msat': 100000000000,
'output_msat': 100000000000,
'primary_tag': 'withdrawal',
'extra_tags': [],
'spending_txid': fundchannel['txid'],
'utxo': f"{txid_deposit}:{vout_deposit}"},
{'account_id': 'wallet', # Change
'blockheight': 103,
'credit_msat': 25000000,
'debit_msat': 0,
'output_msat': 25000000,
'primary_tag': 'deposit',
'extra_tags': [],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum'] ^ 1}"},
{'account_id': fundchannel['channel_id'], # Channel open
'blockheight': 103,
'credit_msat': 99970073000,
'debit_msat': 0,
'output_msat': 99970073000,
'peer_id': l2.info['id'],
'primary_tag': 'channel_open',
'extra_tags': ['opener'],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'], # Channel open
'blockheight': 103,
'credit_msat': 0,
'debit_msat': 0,
'output_msat': 99970073000,
'peer_id': l1.info['id'],
'primary_tag': 'channel_open',
'extra_tags': [],
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
inv = l2.rpc.invoice('any', 'setup_channel', 'setup_channel')
routestep = {
'amount_msat': 50000000000,
'id': l2.info['id'],
'delay': 5,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeerchannels(l2.info['id'])['channels'])['htlcs'] == [])
expected_channel1 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 50000000000,
'primary_tag': 'invoice',
'fees_msat': 0,
'payment_hash': inv['payment_hash'],
'group_id': 1,
'part_id': 0}]
expected_channel2 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 50000000000,
'debit_msat': 0,
'primary_tag': 'invoice',
'fees_msat': 0,
'payment_hash': inv['payment_hash']}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
return (expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel)
# There are many unilateral close variants to test:
# - HTLC not yet included in tx.
# - HTLC included in tx, times out.
# - HTLC included in tx, we fulfill.
# - HTLC not included in tx, because one side considers it fulfilled.
# - HTLC is too small to appear in tx, lost to fees.
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_before_included(node_factory, bitcoind):
# l2 includes it, but l1 doesn't get commitment, so it drops to chain without it.
if EXPERIMENTAL_DUAL_FUND:
disc = ['-WIRE_COMMITMENT_SIGNED*4']
else:
disc = ['-WIRE_COMMITMENT_SIGNED*3']
l1, l2 = node_factory.get_nodes(2, opts=[{}, {'disconnect': disc}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
# This HTLC doesn't make it to full confirmation.
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_in_before_included', 'test_coinmoves_unilateral_htlc_in_before_included')
routestep = {
# Too small to make it worth spending anchor
'amount_msat': 1000000,
'id': l2.info['id'],
'delay': 5,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
close_info = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
bitcoind.generate_block(1, wait_for_mempool=1)
# Make sure onchaind has digested it.
l1.daemon.wait_for_log('5 outputs unresolved: in 4 blocks will spend DELAYED_OUTPUT_TO_US')
l2.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/DELAYED_OUTPUT_TO_US')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUTPUT_TO_THEM')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
expected_chain1 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99970073000 - 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 49965193000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 49965193000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(4)
l1.daemon.wait_for_log('waiting confirmation that we spent DELAYED_OUTPUT_TO_US')
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
to_us_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 109,
'credit_msat': 49965059000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49965059000,
'primary_tag': 'deposit',
'utxo': f"{to_us_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49965193000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49965193000,
'primary_tag': 'delayed_to_us',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 109,
'credit_msat': 0,
'debit_msat': 49965193000,
'extra_tags': [],
'output_msat': 49965193000,
'primary_tag': 'to_wallet',
'spending_txid': to_us_txid,
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l1, l2])
time.sleep(5)
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# We didn't send any HTLCs
check_balances(l1, l2, fundchannel['channel_id'], 0)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_timeout(node_factory, bitcoind):
"""HTLC times out"""
l1, l2 = node_factory.get_nodes(2, opts=[{},
{'disconnect': ['-WIRE_UPDATE_FAIL_HTLC']}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_timeout', 'test_coinmoves_unilateral_htlc_timeout')
l2.rpc.delinvoice('test_coinmoves_unilateral_htlc_timeout', 'unpaid')
routestep = {
# We will spend anchor to make this confirm.
'amount_msat': 100000000,
'id': l2.info['id'],
'delay': 10,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
close_info = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
# We will spend anchor to confirm this.
line = l1.daemon.wait_for_log("Creating anchor spend for local commit tx ")
anchor_spend_txid = re.search(r'Creating anchor spend for local commit tx ([0-9a-f]{64})', line).group(1)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1, l2])
# Make sure onchaind has digested it.
l1.daemon.wait_for_log('6 outputs unresolved: in 4 blocks will spend DELAYED_OUTPUT_TO_US')
l2.daemon.wait_for_log('6 outputs unresolved')
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/DELAYED_OUTPUT_TO_US')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUTPUT_TO_THEM')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUR_HTLC')
htlc = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
# commitment tx weight can vary (DER sigs, FML) and so even though the feerate target
# is fixed, the amount of the child tx we create will vary, hence the change varies.
# So it's usually 15579000, but one in 128 it will be 15586000...
anchor_change_msats = bitcoind.rpc.gettxout(anchor_spend_txid, 0)['value'] * 100_000_000_000
expected_chain1 += [{'account_id': 'wallet', # Anchor spend from fundchannel change
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 25000000,
'extra_tags': [],
'output_msat': 25000000,
'primary_tag': 'withdrawal',
'spending_txid': anchor_spend_txid,
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum'] ^ 1}"},
{'account_id': 'wallet', # change from anchor spend
'blockheight': 104,
'credit_msat': anchor_change_msats,
'debit_msat': 0,
'extra_tags': [],
'output_msat': anchor_change_msats,
'primary_tag': 'deposit',
'utxo': f"{anchor_spend_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99970073000 - 50000000000,
'extra_tags': [],
'output_count': 5,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50000000000,
'extra_tags': [],
'output_count': 5,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 49864547000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 49864547000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(4)
l1.daemon.wait_for_log('waiting confirmation that we spent DELAYED_OUTPUT_TO_US')
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
to_l1_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 109,
'credit_msat': 49864413000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49864413000,
'primary_tag': 'deposit',
'utxo': f"{to_l1_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49864547000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'delayed_to_us',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 109,
'credit_msat': 0,
'debit_msat': 49864547000,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'to_wallet',
'spending_txid': to_l1_txid,
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
# When l1 spends the htlc_tx, it will grab a UTXO. Remove existing ones
# so it's deterministic.
l1.rpc.fundpsbt('all', 0, 0, reserve=100)
bitcoind.generate_block(5)
l1.daemon.wait_for_log('waiting confirmation that we spent OUR_HTLC')
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/OUR_HTLC by our proposal OUR_HTLC_TIMEOUT_TX')
htlc_timeout_txid = re.search(r'by our proposal OUR_HTLC_TIMEOUT_TX \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 115,
'credit_msat': 0,
'debit_msat': anchor_change_msats,
'extra_tags': [],
'output_msat': anchor_change_msats,
'primary_tag': 'withdrawal',
'spending_txid': htlc_timeout_txid,
'utxo': f"{anchor_spend_txid}:0"},
# Change
{'account_id': 'wallet',
'blockheight': 115,
'credit_msat': (15579000 + 6358000) - anchor_change_msats,
'debit_msat': 0,
'extra_tags': [],
'output_msat': (15579000 + 6358000) - anchor_change_msats,
'primary_tag': 'deposit',
'utxo': f"{htlc_timeout_txid}:1"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'htlc_timeout',
'utxo': f"{only_one(close_info['txids'])}:{htlc}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 115,
'credit_msat': 0,
'debit_msat': 100000000,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'htlc_timeout',
'spending_txid': htlc_timeout_txid,
'utxo': f"{only_one(close_info['txids'])}:{htlc}"}]
expected_chain2 += [{'account_id': 'external',
'blockheight': 104,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 100000000,
'primary_tag': 'htlc_timeout',
'utxo': f"{only_one(close_info['txids'])}:{htlc}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log("Telling lightningd about OUR_DELAYED_RETURN_TO_WALLET to resolve OUR_HTLC_TIMEOUT_TX/DELAYED_OUTPUT_TO_US after block 119")
bitcoind.generate_block(4)
l1.daemon.wait_for_log("waiting confirmation that we spent DELAYED_OUTPUT_TO_US .* using OUR_DELAYED_RETURN_TO_WALLET")
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_HTLC_TIMEOUT_TX/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
htlc_to_l1_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 120,
'credit_msat': 99866000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 99866000,
'primary_tag': 'deposit',
'utxo': f"{htlc_to_l1_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 115,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'htlc_tx',
'utxo': f"{htlc_timeout_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 120,
'credit_msat': 0,
'debit_msat': 100000000,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'to_wallet',
'spending_txid': htlc_to_l1_txid,
'utxo': f"{htlc_timeout_txid}:0"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l1, l2])
time.sleep(5)
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# We didn't send any HTLCs
check_balances(l1, l2, fundchannel['channel_id'], 0)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_dust(node_factory, bitcoind):
"""HTLC too small to appear in tx, lost to fees"""
l1, l2 = node_factory.get_nodes(2, opts=[{},
{'disconnect': ['-WIRE_UPDATE_FAIL_HTLC']}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_dust', 'test_coinmoves_unilateral_htlc_dust')
l2.rpc.delinvoice('test_coinmoves_unilateral_htlc_dust', 'unpaid')
routestep = {
'amount_msat': 10000,
'id': l2.info['id'],
'delay': 10,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
close_info = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1, l2])
# Make sure onchaind has digested it.
l1.daemon.wait_for_log('5 outputs unresolved: in 4 blocks will spend DELAYED_OUTPUT_TO_US')
l2.daemon.wait_for_log("All outputs resolved: waiting 100 more blocks before forgetting channel")
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/DELAYED_OUTPUT_TO_US')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUTPUT_TO_THEM')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
expected_chain1 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99970073000 - 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 49965183000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 49965183000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(4)
l1.daemon.wait_for_log("waiting confirmation that we spent DELAYED_OUTPUT_TO_US .* using OUR_DELAYED_RETURN_TO_WALLET")
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
to_l1_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 109,
'credit_msat': 49965049000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49965049000,
'primary_tag': 'deposit',
'utxo': f"{to_l1_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49965183000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49965183000,
'primary_tag': 'delayed_to_us',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 109,
'credit_msat': 0,
'debit_msat': 49965183000,
'extra_tags': [],
'output_msat': 49965183000,
'primary_tag': 'to_wallet',
'spending_txid': to_l1_txid,
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l1, l2])
time.sleep(5)
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# We send a HTLC but it didn't finalize.
check_balances(l1, l2, fundchannel['channel_id'], 0)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_fulfill(node_factory, bitcoind):
"""HTLC gets fulfilled (onchain)"""
l1, l2 = node_factory.get_nodes(2, opts=[{},
{'disconnect': ['-WIRE_UPDATE_FULFILL_HTLC*2']}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_fulfill', 'test_coinmoves_unilateral_htlc_fulfill')
routestep = {
# We will spend anchor to make this confirm.
'amount_msat': 100000000,
'id': l2.info['id'],
'delay': 10,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
close_info = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
# We will spend anchor to confirm this.
line = l1.daemon.wait_for_log("Creating anchor spend for local commit tx ")
anchor_spend_txid = re.search(r'Creating anchor spend for local commit tx ([0-9a-f]{64})', line).group(1)
bitcoind.generate_block(1, wait_for_mempool=1)
sync_blockheight(bitcoind, [l1, l2])
# Make sure onchaind has digested it.
l1.daemon.wait_for_log('6 outputs unresolved: in 4 blocks will spend DELAYED_OUTPUT_TO_US')
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/DELAYED_OUTPUT_TO_US')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUTPUT_TO_THEM')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUR_HTLC')
htlc = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
# commitment tx weight can vary (DER sigs, FML) and so even though the feerate target
# is fixed, the amount of the child tx we create will vary, hence the change varies.
# So it's usually 15579000, but one in 128 it will be 15586000...
anchor_change_msats = bitcoind.rpc.gettxout(anchor_spend_txid, 0)['value'] * 100_000_000_000
expected_chain1 += [{'account_id': 'wallet', # Anchor spend from fundchannel change
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 25000000,
'extra_tags': [],
'output_msat': 25000000,
'primary_tag': 'withdrawal',
'spending_txid': anchor_spend_txid,
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum'] ^ 1}"},
{'account_id': 'wallet', # Change from anchor spend
'blockheight': 104,
'credit_msat': anchor_change_msats,
'debit_msat': 0,
'extra_tags': [],
'output_msat': anchor_change_msats,
'primary_tag': 'deposit',
'utxo': f"{anchor_spend_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99970073000 - 50000000000,
'extra_tags': [],
'output_count': 5,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50000000000,
'extra_tags': [],
'output_count': 5,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 49864547000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 49864547000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(1, wait_for_mempool=1)
line = l2.daemon.wait_for_log('Resolved THEIR_UNILATERAL/THEIR_HTLC by our proposal THEIR_HTLC_FULFILL_TO_US')
htlc_success_txid = re.search(r'by our proposal THEIR_HTLC_FULFILL_TO_US \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'external',
'blockheight': 104,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 100000000,
'payment_hash': inv['payment_hash'],
'primary_tag': 'htlc_fulfill',
'utxo': f"{only_one(close_info['txids'])}:{htlc}"},
{'account_id': 'external',
'blockheight': 105,
'credit_msat': 0,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 100000000,
'spending_txid': htlc_success_txid,
'primary_tag': 'htlc_fulfill',
'utxo': f"{only_one(close_info['txids'])}:{htlc}"}]
# Note: the invoice is fulfilled in the *chain* moves, not *channel*.
expected_chain2 += [{'account_id': 'wallet',
'blockheight': 105,
'credit_msat': 94534000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 94534000,
'primary_tag': 'deposit',
'utxo': f"{htlc_success_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 100000000,
'payment_hash': inv['payment_hash'],
'primary_tag': 'htlc_fulfill',
'utxo': f"{only_one(close_info['txids'])}:{htlc}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 105,
'credit_msat': 0,
'debit_msat': 100000000,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'to_wallet',
'spending_txid': htlc_success_txid,
'utxo': f"{only_one(close_info['txids'])}:{htlc}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(3)
l1.daemon.wait_for_log('waiting confirmation that we spent DELAYED_OUTPUT_TO_US')
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
to_l1_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 109,
'credit_msat': 49864413000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49864413000,
'primary_tag': 'deposit',
'utxo': f"{to_l1_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49864547000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'delayed_to_us',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 109,
'credit_msat': 0,
'debit_msat': 49864547000,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'to_wallet',
'spending_txid': to_l1_txid,
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
l2.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l1, l2])
time.sleep(5)
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# We send an HTLC, but it's not accounted in channel.
check_balances(l1, l2, fundchannel['channel_id'], 0)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_fulfilled_oneside(node_factory, bitcoind):
"""l1 drops to chain with HTLC fulfilled (included in l2's output), l2 hasn't seen completion yet."""
if EXPERIMENTAL_DUAL_FUND:
disc = ['-WIRE_COMMITMENT_SIGNED*5']
else:
disc = ['-WIRE_COMMITMENT_SIGNED*4']
l1, l2 = node_factory.get_nodes(2, opts=[{'disconnect': disc}, {}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_fulfilled_oneside', 'test_coinmoves_unilateral_htlc_fulfilled_oneside')
routestep = {
# We will spend anchor to make this confirm.
'amount_msat': 100000000,
'id': l2.info['id'],
'delay': 10,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
close_info = l1.rpc.close(l2.info['id'], unilateraltimeout=1)
# We will spend anchor to confirm this.
line = l1.daemon.wait_for_log("Creating anchor spend for local commit tx ")
anchor_spend_txid = re.search(r'Creating anchor spend for local commit tx ([0-9a-f]{64})', line).group(1)
bitcoind.generate_block(1, wait_for_mempool=2)
sync_blockheight(bitcoind, [l1, l2])
# Make sure onchaind has digested it.
l2.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
l1.daemon.wait_for_log('5 outputs unresolved: in 5 blocks will spend DELAYED_OUTPUT_TO_US')
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/DELAYED_OUTPUT_TO_US')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l1.daemon.is_in_log('Tracking output.*/OUTPUT_TO_THEM')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
# Usually 16186000, but if we're lucky it's 16193000
anchor_change_msats = bitcoind.rpc.gettxout(anchor_spend_txid, 0)['value'] * 100_000_000_000
expected_chain1 += [{'account_id': 'wallet', # Anchor spend from fundchannel change
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 25000000,
'extra_tags': [],
'output_msat': 25000000,
'primary_tag': 'withdrawal',
'spending_txid': anchor_spend_txid,
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum'] ^ 1}"},
{'account_id': 'wallet', # Change from anchor spend
'blockheight': 104,
'credit_msat': anchor_change_msats,
'debit_msat': 0,
'extra_tags': [],
'output_msat': anchor_change_msats,
'primary_tag': 'deposit',
'utxo': f"{anchor_spend_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 99970073000 - 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 50100000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50100000000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50000000000,
'extra_tags': [],
'output_count': 4,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': only_one(close_info['txids']),
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l2}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{only_one(close_info['txids'])}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 49865193000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 49865193000,
'primary_tag': 'to_them',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50100000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50100000000,
'primary_tag': 'deposit',
'utxo': f"{only_one(close_info['txids'])}:{to_l2}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(4)
l1.daemon.wait_for_log('waiting confirmation that we spent DELAYED_OUTPUT_TO_US')
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
bitcoind.generate_block(1, wait_for_mempool=1)
line = l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET')
to_l1_txid = re.search(r'by our proposal OUR_DELAYED_RETURN_TO_WALLET \(([0-9a-f]{64})\)', line).group(1)
expected_chain1 += [{'account_id': 'wallet',
'blockheight': 109,
'credit_msat': 49865059000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49865059000,
'primary_tag': 'deposit',
'utxo': f"{to_l1_txid}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49865193000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49865193000,
'primary_tag': 'delayed_to_us',
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 109,
'credit_msat': 0,
'debit_msat': 49865193000,
'extra_tags': [],
'output_msat': 49865193000,
'primary_tag': 'to_wallet',
'spending_txid': to_l1_txid,
'utxo': f"{only_one(close_info['txids'])}:{to_l1}"}]
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l1, l2])
time.sleep(5)
check_channel_moves(l1, expected_channel1)
check_chain_moves(l1, expected_chain1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l1.daemon.wait_for_log('onchaind complete, forgetting peer')
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
# We send no in-channel HTLCs
check_balances(l1, l2, fundchannel['channel_id'], 0)
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', "Amounts are for regtest.")
def test_coinmoves_unilateral_htlc_penalty(node_factory, bitcoind):
"""l2 drops to old commitment to chain with HTLC."""
if EXPERIMENTAL_DUAL_FUND:
disc = ['-WIRE_COMMITMENT_SIGNED*5']
else:
disc = ['-WIRE_COMMITMENT_SIGNED*4']
l1, l2 = node_factory.get_nodes(2, opts=[{'may_reconnect': True,
'dev-no-reconnect': None},
{'disconnect': disc,
'may_reconnect': True,
'dev-no-reconnect': None}])
expected_channel1, expected_channel2, expected_chain1, expected_chain2, fundchannel = setup_channel(bitcoind, l1, l2)
inv = l2.rpc.invoice('any', 'test_coinmoves_unilateral_htlc_fulfilled_oneside', 'test_coinmoves_unilateral_htlc_fulfilled_oneside')
routestep = {
# We will spend anchor to make this confirm.
'amount_msat': 100000000,
'id': l2.info['id'],
'delay': 10,
'channel': l1.get_channel_scid(l2),
}
l1.rpc.sendpay([routestep], inv['payment_hash'], payment_secret=inv['payment_secret'], bolt11=inv['bolt11'])
wait_for(lambda: only_one(l1.rpc.listpeers(l2.info['id'])['peers'])['connected'] is False)
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
cheattx = l1.rpc.dev_sign_last_tx(l2.info['id'])['tx']
cheattxid = bitcoind.rpc.decoderawtransaction(cheattx)['txid']
# Reconnect, HTLC will settle.
l2.rpc.connect(l1.info['id'], 'localhost', l1.port)
wait_for(lambda: only_one(l1.rpc.listpeerchannels()['channels'])['htlcs'] == [])
expected_channel1 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 100000000,
'fees_msat': 0,
'group_id': 1,
'part_id': 0,
'payment_hash': inv['payment_hash'],
'primary_tag': 'invoice'}]
expected_channel2 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 100000000,
'debit_msat': 0,
'fees_msat': 0,
'payment_hash': inv['payment_hash'],
'primary_tag': 'invoice'}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
# Don't interfere, l1, we're going to *cheat*
l1.stop()
bitcoind.rpc.sendrawtransaction(cheattx)
bitcoind.generate_block(1)
# We spend all outputs at once.
l2.daemon.wait_for_log("6 outputs unresolved: waiting confirmation")
# Which outputs are anchors, and which are to us and which to them?
# Use onchaind's logs, eg:
# Tracking output 0e1cfbc2be0aada02222a163a1a413fd0b06bae8017c3626cbf8816499dadc09:0: OUR_UNILATERAL/ANCHOR_TO_THEM
line = l2.daemon.is_in_log('Tracking output.*/ANCHOR_TO_THEM')
anch_to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l2.daemon.is_in_log('Tracking output.*/ANCHOR_TO_US')
anch_to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l2.daemon.is_in_log('Tracking output.*/OUTPUT_TO_US')
to_l2 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l2.daemon.is_in_log('Tracking output.*/DELAYED_CHEAT_OUTPUT_TO_THEM')
to_l1 = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
line = l2.daemon.is_in_log('Tracking output.*/THEIR_HTLC')
htlc = int(re.search(r'output [0-9a-f]{64}:([0-9]):', line).group(1))
expected_chain2 += [{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 0,
'debit_msat': 50100000000,
'extra_tags': [],
'output_count': 5,
'output_msat': 99970073000,
'primary_tag': 'channel_close',
'spending_txid': cheattxid,
'utxo': f"{fundchannel['txid']}:{fundchannel['outnum']}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{cheattxid}:{anch_to_l1}"},
{'account_id': 'external',
'blockheight': 104,
'credit_msat': 330000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 330000,
'primary_tag': 'anchor',
'utxo': f"{cheattxid}:{anch_to_l2}"},
{'account_id': 'wallet',
'blockheight': 104,
'credit_msat': 50000000000,
'debit_msat': 0,
'extra_tags': [],
'originating_account': fundchannel['channel_id'],
'output_msat': 50000000000,
'primary_tag': 'deposit',
'utxo': f"{cheattxid}:{to_l2}"}]
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
# Generates all the penalties
bitcoind.generate_block(1, wait_for_mempool=2)
line = l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/DELAYED_CHEAT_OUTPUT_TO_THEM by our proposal OUR_PENALTY_TX')
to_l1_penalty = re.search(r'by our proposal OUR_PENALTY_TX \(([0-9a-f]{64})\)', line).group(1)
line = l2.daemon.wait_for_log('Resolved THEIR_REVOKED_UNILATERAL/THEIR_HTLC by our proposal OUR_PENALTY_TX')
htlc_penalty = re.search(r'by our proposal OUR_PENALTY_TX \(([0-9a-f]{64})\)', line).group(1)
expected_chain2 += [{'account_id': 'wallet',
'blockheight': 105,
'credit_msat': 49858187000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49858187000,
'primary_tag': 'deposit',
'utxo': f"{to_l1_penalty}:0"},
{'account_id': 'wallet',
'blockheight': 105,
'credit_msat': 92908000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 92908000,
'primary_tag': 'deposit',
'utxo': f"{htlc_penalty}:0"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 49864547000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'penalty',
'utxo': f"{cheattxid}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 105,
'credit_msat': 0,
'debit_msat': 49864547000,
'extra_tags': [],
'output_msat': 49864547000,
'primary_tag': 'to_wallet',
'spending_txid': to_l1_penalty,
'utxo': f"{cheattxid}:{to_l1}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 104,
'credit_msat': 100000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'penalty',
'utxo': f"{cheattxid}:{htlc}"},
{'account_id': fundchannel['channel_id'],
'blockheight': 105,
'credit_msat': 0,
'debit_msat': 100000000,
'extra_tags': [],
'output_msat': 100000000,
'primary_tag': 'to_wallet',
'spending_txid': htlc_penalty,
'utxo': f"{cheattxid}:{htlc}"}]
expected_channel2 += [{'account_id': fundchannel['channel_id'],
'credit_msat': 49864547000,
'debit_msat': 0,
'fees_msat': 0,
'primary_tag': 'penalty_adj'},
{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 49864547000,
'fees_msat': 0,
'primary_tag': 'penalty_adj'},
{'account_id': fundchannel['channel_id'],
'credit_msat': 100000000,
'debit_msat': 0,
'fees_msat': 0,
'primary_tag': 'penalty_adj'},
{'account_id': fundchannel['channel_id'],
'credit_msat': 0,
'debit_msat': 100000000,
'fees_msat': 0,
'primary_tag': 'penalty_adj'}]
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l2.daemon.wait_for_log('All outputs resolved: waiting 100 more blocks before forgetting channel')
# Make sure it's stable!
bitcoind.generate_block(100)
sync_blockheight(bitcoind, [l2])
time.sleep(5)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l2, expected_chain2)
l2.daemon.wait_for_log('onchaind complete, forgetting peer')
channel2 = account_balances(l2.rpc.listchannelmoves()['channelmoves'])
chain2 = account_balances(l2.rpc.listchainmoves()['chainmoves'])
# Channel balances should reflect sats transferred
# FIXME: Should probably include penalty.
assert sum(channel2[fundchannel['channel_id']]) == 50000000000 + 100000000
# Wallet balances should reflect reality
l2_wallet = sum([o['amount_msat'] for o in l2.rpc.listfunds()['outputs']])
if sum(chain2['wallet']) != l2_wallet:
print(f"sum({chain2['wallet']}) != {l2_wallet}")
assert False
# FIXME:
# MVT_PENALIZED,
# MVT_STOLEN,
# MVT_TO_MINER,
# MVT_LEASE_FEE,
# MVT_CHANNEL_PROPOSED,
# Extra tags
# MVT_SPLICE,
# MVT_LEASED,
# MVT_STEALABLE,
@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
def test_wait(node_factory, bitcoind, executor):
l1, l2 = node_factory.get_nodes(2)
fut = executor.submit(l1.rpc.wait, subsystem='chainmoves', indexname='created', nextvalue=1)
addr = l1.rpc.newaddr()['bech32']
bitcoind.rpc.sendtoaddress(addr, 200000000 / 10**8)
bitcoind.generate_block(1, wait_for_mempool=1)
out = fut.result(TIMEOUT)
assert out == {'subsystem': 'chainmoves',
'created': 1,
'chainmoves': {'account': 'wallet',
'credit_msat': 200000000000,
'debit_msat': 0}}
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
fund = l1.rpc.fundchannel(l2.info['id'], 10000000)
bitcoind.generate_block(1, wait_for_mempool=fund['txid'])
wait_for(lambda: all([c['state'] == 'CHANNELD_NORMAL' for c in l1.rpc.listpeerchannels(l2.info['id'])['channels']]))
fut = executor.submit(l1.rpc.wait, subsystem='channelmoves', indexname='created', nextvalue=1)
inv = l2.rpc.invoice('any', 'test_wait', 'test_wait')
l1.rpc.xpay(inv['bolt11'], '1000000sat')
out = fut.result(TIMEOUT)
assert out == {'subsystem': 'channelmoves',
'created': 1,
'channelmoves': {'account': fund['channel_id'],
'debit_msat': 1000000000,
'credit_msat': 0}}
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "uses snapshots")
@unittest.skipIf(TEST_NETWORK != 'regtest', "Snapshots are bitcoin regtest.")
def test_migration(node_factory, bitcoind):
"""These nodes import coinmoves from the old bookkeeper account.db"""
bitcoind.generate_block(1)
l1 = node_factory.get_node(dbfile="l1-before-moves-in-db.sqlite3.xz",
bkpr_dbfile="l1-bkpr-accounts.sqlite3.xz",
options={'database-upgrade': True})
l2 = node_factory.get_node(dbfile="l2-before-moves-in-db.sqlite3.xz",
bkpr_dbfile="l2-bkpr-accounts.sqlite3.xz",
options={'database-upgrade': True})
chan = only_one(l1.rpc.listpeerchannels()['channels'])
payment = only_one(l1.rpc.listsendpays()['payments'])
expected_channel1 = [{'account_id': chan['channel_id'],
'created_index': 1,
'credit_msat': 0,
'debit_msat': 12345678,
'fees_msat': 0,
'payment_hash': payment['payment_hash'],
'primary_tag': 'invoice'}]
expected_channel2 = [{'account_id': chan['channel_id'],
'created_index': 1,
'credit_msat': 12345678,
'debit_msat': 0,
'fees_msat': 0,
'payment_hash': payment['payment_hash'],
'primary_tag': 'invoice'}]
expected_chain1 = [{'account_id': 'wallet',
'blockheight': 102,
'created_index': 1,
'credit_msat': 2000000000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 2000000000,
'primary_tag': 'deposit',
'utxo': '63c59b312976320528552c258ae51563498dfd042b95bb0c842696614d59bb89:1'},
{'account_id': 'wallet',
'blockheight': 103,
'created_index': 2,
'credit_msat': 0,
'debit_msat': 2000000000,
'extra_tags': [],
'output_msat': 2000000000,
'primary_tag': 'withdrawal',
'spending_txid': chan['funding_txid'],
'utxo': '63c59b312976320528552c258ae51563498dfd042b95bb0c842696614d59bb89:1'},
{'account_id': 'wallet',
'blockheight': 103,
'created_index': 3,
'credit_msat': 995073000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 995073000,
'primary_tag': 'deposit',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum'] ^ 1}"},
{'account_id': chan['channel_id'],
'blockheight': 103,
'created_index': 4,
'credit_msat': 1000000000,
'debit_msat': 0,
'extra_tags': ['opener'],
'output_msat': 1000000000,
'peer_id': l2.info['id'],
'primary_tag': 'channel_open',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum']}"}]
expected_chain2 = [{'account_id': chan['channel_id'],
'blockheight': 103,
'created_index': 1,
'credit_msat': 0,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 1000000000,
'peer_id': l1.info['id'],
'primary_tag': 'channel_open',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum']}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "uses snapshots")
@unittest.skipIf(TEST_NETWORK != 'regtest', "Snapshots are for regtest.")
def test_migration_no_bkpr(node_factory, bitcoind):
"""These nodes need to invent coinmoves to make the balances work"""
bitcoind.generate_block(1)
l1 = node_factory.get_node(dbfile="l1-before-moves-in-db.sqlite3.xz",
options={'database-upgrade': True})
l2 = node_factory.get_node(dbfile="l2-before-moves-in-db.sqlite3.xz",
options={'database-upgrade': True})
chan = only_one(l1.rpc.listpeerchannels()['channels'])
expected_channel1 = [{'account_id': chan['channel_id'],
'created_index': 1,
'credit_msat': 0,
'debit_msat': 12345678,
'fees_msat': 0,
'primary_tag': 'journal_entry',
}]
expected_channel2 = [{'account_id': chan['channel_id'],
'created_index': 1,
'credit_msat': 12345678,
'debit_msat': 0,
'fees_msat': 0,
'primary_tag': 'journal_entry',
}]
expected_chain1 = [{'account_id': 'wallet',
'blockheight': 103,
'created_index': 1,
'credit_msat': 995073000,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 995073000,
'primary_tag': 'deposit',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum'] ^ 1}"},
{'account_id': chan['channel_id'],
'blockheight': 103,
'created_index': 2,
'credit_msat': 1000000000,
'debit_msat': 0,
'extra_tags': ['opener'],
'output_msat': 1000000000,
'peer_id': l2.info['id'],
'primary_tag': 'channel_open',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum']}"}]
expected_chain2 = [{'account_id': chan['channel_id'],
'blockheight': 103,
'created_index': 1,
'credit_msat': 0,
'debit_msat': 0,
'extra_tags': [],
'output_msat': 1000000000,
'peer_id': l1.info['id'],
'primary_tag': 'channel_open',
'utxo': f"{chan['funding_txid']}:{chan['funding_outnum']}"}]
check_channel_moves(l1, expected_channel1)
check_channel_moves(l2, expected_channel2)
check_chain_moves(l1, expected_chain1)
check_chain_moves(l2, expected_chain2)