Skip to content
Merged
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
2 changes: 1 addition & 1 deletion channeld/channeld.c
Original file line number Diff line number Diff line change
Expand Up @@ -4449,7 +4449,7 @@ static void update_hsmd_with_splice(struct peer *peer, struct inflight *inflight
/*local_upfront_shutdown_script*/ NULL,
/*local_upfront_shutdown_wallet_index*/ NULL,
&peer->channel->basepoints[REMOTE],
&peer->channel->funding_pubkey[REMOTE],
&inflight->remote_funding,
peer->channel->config[REMOTE].to_self_delay,
/*remote_upfront_shutdown_script*/ NULL,
peer->channel->type);
Expand Down
41 changes: 29 additions & 12 deletions lightningd/peer_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,8 @@ u8 *p2tr_for_keyidx(const tal_t *ctx, struct lightningd *ld, u64 keyidx)
static struct bitcoin_tx *sign_last_tx(const tal_t *ctx,
const struct channel *channel,
const struct bitcoin_tx *last_tx,
const struct bitcoin_signature *last_sig)
const struct bitcoin_signature *last_sig,
const struct pubkey *remote_fundingkey)
{
struct lightningd *ld = channel->peer->ld;
struct bitcoin_signature sig;
Expand All @@ -262,8 +263,7 @@ static struct bitcoin_tx *sign_last_tx(const tal_t *ctx,
&channel->peer->id,
channel->dbid,
tx,
&channel->channel_info
.remote_fundingkey,
remote_fundingkey,
commit_index);

msg = hsm_sync_req(tmpctx, ld, take(msg));
Expand All @@ -273,13 +273,24 @@ static struct bitcoin_tx *sign_last_tx(const tal_t *ctx,

witness =
bitcoin_witness_2of2(tx, last_sig,
&sig, &channel->channel_info.remote_fundingkey,
&sig, remote_fundingkey,
&channel->local_funding_pubkey);

bitcoin_tx_input_set_witness(tx, 0, take(witness));
return tx;
}

/* A splice commitment spends the splice's funding output, which uses the
* funding key the peer gave in splice_init/splice_ack. */
static const struct pubkey *
inflight_remote_fundingkey(const struct channel *channel,
const struct channel_inflight *inflight)
{
if (inflight->funding->splice_remote_funding)
return inflight->funding->splice_remote_funding;
return &channel->channel_info.remote_fundingkey;
}

bool invalid_last_tx(const struct bitcoin_tx *tx)
{
/* This problem goes back further, but was discovered just before the
Expand Down Expand Up @@ -318,13 +329,14 @@ static struct bitcoin_tx *sign_and_send_last(const tal_t *ctx,
struct channel *channel,
const char *cmd_id,
const struct bitcoin_tx *last_tx,
const struct bitcoin_signature *last_sig)
const struct bitcoin_signature *last_sig,
const struct pubkey *remote_fundingkey)
{
struct bitcoin_txid txid;
struct anchor_details *adet;
struct bitcoin_tx *tx;

tx = sign_last_tx(ctx, channel, last_tx, last_sig);
tx = sign_last_tx(ctx, channel, last_tx, last_sig, remote_fundingkey);
bitcoin_txid(tx, &txid);
wallet_transaction_add(ld->wallet, tx->wtx, 0, 0);
wallet_extract_owned_outputs(ld->wallet, tx->wtx, false, NULL, NULL);
Expand Down Expand Up @@ -500,18 +512,21 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel,
channel,
cmd_id,
inflight->last_tx,
&inflight->last_sig));
&inflight->last_sig,
inflight_remote_fundingkey(channel, inflight)));
}
if (!have_current)
tal_arr_expand(&txs, sign_and_send_last(tmpctx, ld,
channel, cmd_id,
channel->last_tx,
&channel->last_sig));
&channel->last_sig,
&channel->channel_info.remote_fundingkey));
} else {
tal_arr_expand(&txs, sign_and_send_last(tmpctx, ld,
channel, cmd_id,
channel->last_tx,
&channel->last_sig));
&channel->last_sig,
&channel->channel_info.remote_fundingkey));
}

resolve_close_command(ld, channel, cooperative, txs);
Expand All @@ -529,7 +544,7 @@ void drop_to_chain(struct lightningd *ld, struct channel *channel,
}
const u8 *funding_wscript = bitcoin_redeem_2of2(tmpctx,
&channel->local_funding_pubkey,
inflight->funding->splice_remote_funding);
inflight_remote_fundingkey(channel, inflight));
watch_scriptpubkey(inflight, ld->topology,
take(scriptpubkey_p2wsh(NULL, funding_wscript)),
&inflight->funding->outpoint,
Expand Down Expand Up @@ -4114,7 +4129,8 @@ static struct command_result *json_sign_last_tx(struct command *cmd,
log_debug(channel->log, "dev-sign-last-tx: signing tx with %zu outputs",
channel->last_tx->wtx->num_outputs);

tx = sign_last_tx(cmd, channel, channel->last_tx, &channel->last_sig);
tx = sign_last_tx(cmd, channel, channel->last_tx, &channel->last_sig,
&channel->channel_info.remote_fundingkey);
json_add_tx(response, "tx", tx);

/* If we've got inflights, return them */
Expand All @@ -4126,7 +4142,8 @@ static struct command_result *json_sign_last_tx(struct command *cmd,
if (inflight->splice_locked_memonly)
continue;
tx = sign_last_tx(cmd, channel, inflight->last_tx,
&inflight->last_sig);
&inflight->last_sig,
inflight_remote_fundingkey(channel, inflight));
json_object_start(response, NULL);
json_add_txid(response, "funding_txid",
&inflight->funding->outpoint.txid);
Expand Down
33 changes: 33 additions & 0 deletions tests/test_opening.py
Original file line number Diff line number Diff line change
Expand Up @@ -4468,3 +4468,36 @@ def v0(psbt):
n.daemon.wait_for_log(' to CHANNELD_NORMAL')
# The bump is what confirmed.
assert only_one(l1.rpc.listpeerchannels()['channels'])['funding_txid'] != res['txid']


@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@pytest.mark.openchannel('v2')
def test_rbf_mutual_close_before_lockin(node_factory, bitcoind):
"""A mutual close of a dual-funded channel with an RBF candidate
pending watches every candidate's output."""
l1, l2 = node_factory.get_nodes(2)
l1.rpc.connect(l2.info['id'], 'localhost', l2.port)
chan_amount = 100000
l1.fundwallet(2**24)

res = l1.rpc.fundchannel(l2.info['id'], chan_amount)
chan_id = res['channel_id']
vin = only_one(bitcoind.rpc.decoderawtransaction(res['tx'])['vin'])
l1.daemon.wait_for_log(' to DUALOPEND_AWAITING_LOCKIN')

initpsbt = l1.rpc.utxopsbt(chan_amount, find_next_feerate(l1, l2), 42 + 172,
["{}:{}".format(vin['txid'], vin['vout'])],
reservedok=True, excess_as_change=True)['psbt']
bump = l1.rpc.openchannel_bump(chan_id, chan_amount, initpsbt)
update = l1.rpc.openchannel_update(chan_id, bump['psbt'])
assert update['commitments_secured']
l1.rpc.openchannel_signed(chan_id, l1.rpc.signpsbt(update['psbt'])['signed_psbt'])
wait_for(lambda: len(only_one(l1.rpc.listpeerchannels()['channels'])['inflight']) == 2)

l1.rpc.close(chan_id)
for n in (l1, l2):
wait_for(lambda: only_one(n.rpc.listpeerchannels()['channels'])['state'] == 'CLOSINGD_COMPLETE')

# Startup resends the close, which takes the same path.
l2.restart()
assert only_one(l2.rpc.listpeerchannels()['channels'])['state'] == 'CLOSINGD_COMPLETE'
50 changes: 50 additions & 0 deletions tests/test_splicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,56 @@ def test_splice_unconfirmed_force_close_publishes_current(node_factory, bitcoind
r'Broadcasting txid {}'.format(inflight['scratch_txid'])])


@pytest.mark.openchannel('v1')
@pytest.mark.openchannel('v2')
@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "modifies database, which is assumed sqlite3")
def test_splice_force_close_uses_splice_remote_fundingkey(node_factory, bitcoind):
"""A force close must sign a splice commitment with the splice's funding key.

A peer may give a new funding key in splice_init/splice_ack. Until the
splice locks, the channel still holds the peer's old key, and signing the
splice's commitment with it gives a witness that can't spend the splice
output: a peer that withholds splice_locked leaves us no valid close.
"""
l1, l2 = node_factory.line_graph(2, fundamount=1000000,
announce_channels=False,
opts={'may_reconnect': True,
'allow_warning': True})
chan_id = l1.get_channel_id(l2)

funds_result = l1.rpc.fundpsbt("109000sat", 0, 0, excess_as_change=True)
result = l1.rpc.splice_init(chan_id, 100000, funds_result['psbt'])
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert result['commitments_secured'] is False
result = l1.rpc.splice_update(chan_id, result['psbt'])
assert result['commitments_secured'] is True
result = l1.rpc.signpsbt(result['psbt'])
splice_txid = l1.rpc.splice_signed(chan_id, result['signed_psbt'])['txid']
l1.daemon.wait_for_log(' to CHANNELD_AWAITING_SPLICE')
l2.daemon.wait_for_log(' to CHANNELD_AWAITING_SPLICE')

# l2 withholds splice_locked while the splice confirms.
l2.stop()
bitcoind.generate_block(6, wait_for_mempool=splice_txid)

# CLN doesn't rotate its funding key, so make the channel's remote key
# differ from the splice's as if l2 had given a new one for the splice.
l1.stop()
l1.db_manip("UPDATE channels SET fundingkey_remote="
"X'0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'")
l1.start()

inflight = only_one(l1.rpc.dev_sign_last_tx(l2.info['id'])['inflights'])
assert inflight['funding_txid'] == splice_txid
commit_txid = bitcoind.rpc.decoderawtransaction(inflight['tx'])['txid']

l1.rpc.dev_fail(l2.info['id'])
l1.daemon.wait_for_log('Peer permanent failure')
bitcoind.generate_block(1, wait_for_mempool=commit_txid)
l1.daemon.wait_for_log(r'Resolved FUNDING_TRANSACTION/FUNDING_OUTPUT by OUR_UNILATERAL')


@unittest.skipIf(TEST_NETWORK != 'regtest', 'elementsd doesnt yet support PSBT features we need')
def test_splice_candidate_spent_before_lock(node_factory, bitcoind):
"""A confirmed splice output spent before splice_locked must be adopted.
Expand Down
Loading