Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ struct peer {
/* After STFU mode is enabled, wait for a single message flag */
bool stfu_wait_single_msg;
/* Updates master asked, which we've deferred while quiescing */
struct msg_queue *update_queue;
struct msg_queue *stfu_pending_queue;
/* Callback for when when stfu is negotiated successfully */
void (*on_stfu_success)(struct peer*);

Expand Down Expand Up @@ -234,11 +234,21 @@ static bool is_entering_stfu(const struct peer *peer)

static void end_stfu_mode(struct peer *peer)
{
const u8 *msg;

peer->want_stfu = false;
peer->stfu_sent[LOCAL] = peer->stfu_sent[REMOTE] = false;
peer->stfu_wait_single_msg = false;
peer->on_stfu_success = NULL;

/* Move any pending messages onto from_master; the main
* loop drains that queue via req_in. */
while ((msg = msg_dequeue(peer->stfu_pending_queue))) {
status_debug("Requeueing quiescence-deferred %s onto from_master",
channeld_wire_name(fromwire_peektype(msg)));
msg_enqueue(peer->from_master, msg);
}

status_debug("Left STFU mode.");
}

Expand Down Expand Up @@ -367,7 +377,7 @@ static void handle_stfu(struct peer *peer, const u8 *stfu)
static bool handle_master_request_later(struct peer *peer, const u8 *msg)
{
if (is_entering_stfu(peer)) {
msg_enqueue(peer->update_queue, take(msg));
msg_enqueue(peer->stfu_pending_queue, take(msg));
return true;
}
return false;
Expand Down Expand Up @@ -7042,7 +7052,7 @@ int main(int argc, char *argv[])
peer->stfu_sent[LOCAL] = peer->stfu_sent[REMOTE] = false;
peer->stfu_wait_single_msg = false;
peer->on_stfu_success = NULL;
peer->update_queue = msg_queue_new(peer, false);
peer->stfu_pending_queue = msg_queue_new(peer, false);
peer->splice_state = splice_state_new(peer);
peer->splicing = NULL;

Expand Down
44 changes: 44 additions & 0 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -3811,6 +3811,50 @@ def test_quiescence(node_factory, executor):
pass


def test_quiescence_end_delivers_pending_fulfill(node_factory, bitcoind, executor):
"""While quiescent, a fulfill queued on stfu_pending_queue must be
delivered when quiescence ends (end_stfu_mode moves it to from_master).
"""
hold_plugin = str(Path(__file__).parent / "plugins" / "hold_htlcs.py")
l1, l2 = node_factory.line_graph(2, fundamount=1000000,
opts={'plugin': [hold_plugin], 'hold-time': 5})
# l2 needs wallet UTXOs for the splice; l1 opened the channel.
addr = l2.rpc.newaddr('bech32')['bech32']
bitcoind.rpc.sendtoaddress(addr, 0.002)
bitcoind.generate_block(1, wait_for_mempool=1)
wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == 1)

# Balance so l2 can send.
inv = l2.rpc.invoice(250000000, 'balance', 'balance')
l1.rpc.xpay(inv['bolt11'])

inv = l1.rpc.invoice(10000000, 'qend', 'qend')
fut = executor.submit(l2.rpc.xpay, inv['bolt11'])
l1.daemon.wait_for_log('Holding onto an incoming htlc')

# Start the splice (enters quiescence); the hold plugin delays the
# fulfill past quiescence-entry, so the fulfill lands on
# stfu_pending_queue while l1 is at loop top.
funds = l2.rpc.fundpsbt("111722sat", 0, 0, excess_as_change=True)
r = l2.rpc.splice_init(l2.get_channel_id(l1), 100000, funds['psbt'])
l1.daemon.wait_for_log('STFU complete: we are quiescent')
l2.daemon.wait_for_log('STFU complete: we are quiescent')

# Hold expires: lightningd resolves the invoice, but the fulfill is
# queued (quiescent). l1 marks the invoice paid.
wait_for(lambda: only_one(l1.rpc.listinvoices('qend')['invoices'])['status'] == 'paid')

# End quiescence by completing the in-flight splice; end_stfu_mode must
# move the queued fulfill onto from_master and deliver it.
r = l2.rpc.splice_update(l2.get_channel_id(l1), r['psbt'])
r = l2.rpc.splice_update(l2.get_channel_id(l1), r['psbt'])
r = l2.rpc.signpsbt(r['psbt'])
l2.rpc.splice_signed(l2.get_channel_id(l1), r['signed_psbt'])

# The payment completes: the queued fulfill was delivered.
fut.result(timeout=60)


def test_htlc_failed_noclose(node_factory):
"""Test a bug where the htlc timeout would kick in even if the HTLC failed"""
l1, l2 = node_factory.line_graph(2)
Expand Down
Loading