Skip to content

Commit

Permalink
Splice: Better balance checking
Browse files Browse the repository at this point in the history
* Regression test added for Issue #6572 (issuecomment-1730808863) w/stuck HTLC
* `check_balance` adjusted to calculate pending HTLCs explicitly
* Test confirmed to fail prior to PR #6713

ChangeLog-Fixed: Issue splicing with pending / stuck HTLCs fixed.
  • Loading branch information
ddustin committed Oct 14, 2023
1 parent 8f6a02c commit 9ef52ad
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 6 deletions.
52 changes: 46 additions & 6 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -2928,13 +2928,35 @@ static struct amount_sat check_balances(struct peer *peer,
funding_amount_res, min_multiplied;
struct amount_msat funding_amount,
initiator_fee, accepter_fee;
struct amount_msat in[NUM_TX_ROLES], out[NUM_TX_ROLES];
struct amount_msat in[NUM_TX_ROLES], out[NUM_TX_ROLES],
pending_htlcs[NUM_TX_ROLES];
struct htlc_map_iter it;
const struct htlc *htlc;
bool opener = our_role == TX_INITIATOR;
u8 *msg;

/* The channel funds less any pending htlcs */
in[TX_INITIATOR] = peer->channel->view->owed[opener ? LOCAL : REMOTE];
in[TX_ACCEPTER] = peer->channel->view->owed[opener ? REMOTE : LOCAL];

/* pending_htlcs holds the value of all pending htlcs for each side */
pending_htlcs[TX_INITIATOR] = AMOUNT_MSAT(0);
pending_htlcs[TX_ACCEPTER] = AMOUNT_MSAT(0);
for (htlc = htlc_map_first(peer->channel->htlcs, &it);
htlc;
htlc = htlc_map_next(peer->channel->htlcs, &it)) {
struct amount_msat *itr;

if (htlc_owner(htlc) == opener ? LOCAL : REMOTE)
itr = &pending_htlcs[TX_INITIATOR];
else
itr = &pending_htlcs[TX_ACCEPTER];

if (!amount_msat_add(itr, *itr, htlc->amount))
peer_failed_warn(peer->pps, &peer->channel_id,
"Unable to add HTLC balance");
}

for (size_t i = 0; i < psbt->num_inputs; i++)
if (i != chan_input_index)
add_amount_to_side(peer, in,
Expand All @@ -2952,12 +2974,22 @@ static struct amount_sat check_balances(struct peer *peer,
psbt_output_get_amount(psbt, i),
&psbt->outputs[i].unknowns);

/* Calculate total channel output amount */
/* Calculate original channel output amount */
if (!amount_msat_add(&funding_amount,
peer->channel->view->owed[LOCAL],
peer->channel->view->owed[REMOTE]))
peer_failed_warn(peer->pps, &peer->channel_id,
"Unable to calculate starting channel amount");
if (!amount_msat_add(&funding_amount,
funding_amount,
pending_htlcs[TX_INITIATOR]))
peer_failed_warn(peer->pps, &peer->channel_id,
"Unable to calculate starting channel amount");
if (!amount_msat_add(&funding_amount,
funding_amount,
pending_htlcs[TX_ACCEPTER]))
peer_failed_warn(peer->pps, &peer->channel_id,
"Unable to calculate starting channel amount");

/* Tasks:
* Add up total funding_amount
Expand Down Expand Up @@ -3001,9 +3033,13 @@ static struct amount_sat check_balances(struct peer *peer,
peer_failed_warn(peer->pps, &peer->channel_id,
"Initiator funding is less than commited"
" amount. Initiator contributing %s but they"
" committed to %s.",
" committed to %s. Pending offered HTLC"
" balance of %s is not available for this"
" operation.",
fmt_amount_msat(tmpctx, in[TX_INITIATOR]),
fmt_amount_msat(tmpctx, out[TX_INITIATOR]));
fmt_amount_msat(tmpctx, out[TX_INITIATOR]),
fmt_amount_msat(tmpctx,
pending_htlcs[TX_INITIATOR]));
}

if (!amount_msat_sub(&initiator_fee, in[TX_INITIATOR], out[TX_INITIATOR]))
Expand All @@ -3019,9 +3055,13 @@ static struct amount_sat check_balances(struct peer *peer,
peer_failed_warn(peer->pps, &peer->channel_id,
"Accepter funding is less than commited"
" amount. Accepter contributing %s but they"
" committed to %s.",
" committed to %s. Pending offered HTLC"
" balance of %s is not available for this"
" operation.",
fmt_amount_msat(tmpctx, in[TX_INITIATOR]),
fmt_amount_msat(tmpctx, out[TX_INITIATOR]));
fmt_amount_msat(tmpctx, out[TX_INITIATOR]),
fmt_amount_msat(tmpctx,
pending_htlcs[TX_INITIATOR]));
}

if (!amount_msat_sub(&accepter_fee, in[TX_ACCEPTER], out[TX_ACCEPTER]))
Expand Down
41 changes: 41 additions & 0 deletions tests/test_splicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,3 +276,44 @@ def test_commit_crash_splice(node_factory, bitcoind):
# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0


@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_stuck_htlc(node_factory, bitcoind, executor):
l1, l2, l3 = node_factory.line_graph(3, wait_for_announce=True, opts={'experimental-splicing': None})

l3.rpc.dev_ignore_htlcs(id=l2.info['id'], ignore=True)

inv = l3.rpc.invoice(10000000, '1', 'no_1')
executor.submit(l1.rpc.pay, inv['bolt11'])
l3.daemon.wait_for_log('their htlc 0 dev_ignore_htlcs')

# Now we should have a stuck invoice between l1 -> l2

chan_id = l1.get_channel_id(l2)

# add extra sats to pay fee
funds_result = l1.rpc.fundpsbt("109000sat", "slow", 166, excess_as_change=True)

result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
result = l1.rpc.signpsbt(result['psbt'])
result = l1.rpc.splice_signed(chan_id, result['signed_psbt'])

l2.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')
l1.daemon.wait_for_log(r'CHANNELD_NORMAL to CHANNELD_AWAITING_SPLICE')

mempool = bitcoind.rpc.getrawmempool(True)
assert len(list(mempool.keys())) == 1
assert result['txid'] in list(mempool.keys())

bitcoind.generate_block(6, wait_for_mempool=1)

l2.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')
l1.daemon.wait_for_log(r'CHANNELD_AWAITING_SPLICE to CHANNELD_NORMAL')

# Check that the splice doesn't generate a unilateral close transaction
time.sleep(5)
assert l1.db_query("SELECT count(*) as c FROM channeltxs;")[0]['c'] == 0

0 comments on commit 9ef52ad

Please sign in to comment.