From 882a9533c2f3e63decf4962546c97472cf29145b Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 16 Jun 2025 13:50:58 -0400 Subject: [PATCH 1/5] splice: Add `start_batch` and an internal wire type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We add `start_batch` to match t-bast’s splicing spec and we add a new internal wire type `WIRE_PROTOCOL_BATCH_ELEMENT` using the type number 0 Changelog-Added: support for `start_batch` --- channeld/channeld.c | 3 +++ common/gossmap.c | 2 ++ common/interactivetx.c | 4 ++++ connectd/gossip_rcvd_filter.c | 2 ++ connectd/gossip_store.c | 2 ++ connectd/multiplex.c | 2 ++ gossipd/gossipd.c | 2 ++ openingd/dualopend.c | 8 ++++++++ wire/extracted_peer_12_splice_update.patch | 24 ++++++++++++++++++++++ wire/peer_wire.c | 10 +++++++++ wire/peer_wire.csv | 9 ++++++++ 11 files changed, 68 insertions(+) create mode 100644 wire/extracted_peer_12_splice_update.patch diff --git a/channeld/channeld.c b/channeld/channeld.c index a94cd887b3af..287cda709bae 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -4906,6 +4906,8 @@ static void peer_in(struct peer *peer, const u8 *msg) case WIRE_UPDATE_ADD_HTLC: handle_peer_add_htlc(peer, msg); return; + case WIRE_START_BATCH: + return; case WIRE_COMMITMENT_SIGNED: handle_peer_commit_sig_batch(peer, msg, 0, peer->channel->funding_pubkey[REMOTE], @@ -4976,6 +4978,7 @@ static void peer_in(struct peer *peer, const u8 *msg) return; /* These are all swallowed by connectd */ + case WIRE_PROTOCOL_BATCH_ELEMENT: case WIRE_CHANNEL_ANNOUNCEMENT: case WIRE_CHANNEL_UPDATE: case WIRE_NODE_ANNOUNCEMENT: diff --git a/common/gossmap.c b/common/gossmap.c index 576acd0e7cd6..1fbd72f9497d 100644 --- a/common/gossmap.c +++ b/common/gossmap.c @@ -1799,6 +1799,8 @@ const void *gossmap_stream_next(const tal_t *ctx, case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: diff --git a/common/interactivetx.c b/common/interactivetx.c index 03ceb9d1d9aa..6e1aa1c916d5 100644 --- a/common/interactivetx.c +++ b/common/interactivetx.c @@ -167,6 +167,8 @@ static u8 *read_next_msg(const tal_t *ctx, case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: @@ -821,6 +823,8 @@ char *process_interactivetx_updates(const tal_t *ctx, case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: diff --git a/connectd/gossip_rcvd_filter.c b/connectd/gossip_rcvd_filter.c index 2337b48d4322..c8097a4d6cb3 100644 --- a/connectd/gossip_rcvd_filter.c +++ b/connectd/gossip_rcvd_filter.c @@ -73,6 +73,8 @@ static bool is_msg_gossip_broadcast(const u8 *cursor) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: diff --git a/connectd/gossip_store.c b/connectd/gossip_store.c index 561387eda5fe..7a6bb61dc05d 100644 --- a/connectd/gossip_store.c +++ b/connectd/gossip_store.c @@ -82,6 +82,8 @@ static bool public_msg_type(enum peer_wire type) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: diff --git a/connectd/multiplex.c b/connectd/multiplex.c index 49eccbb3d4cc..f493b6a944e5 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -382,6 +382,8 @@ static bool is_urgent(enum peer_wire type) /* These are time-sensitive, and so send without delay. */ case WIRE_PING: case WIRE_PONG: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: return true; diff --git a/gossipd/gossipd.c b/gossipd/gossipd.c index 4f773658f78b..41807050806f 100644 --- a/gossipd/gossipd.c +++ b/gossipd/gossipd.c @@ -244,6 +244,8 @@ static void handle_recv_gossip(struct daemon *daemon, const u8 *outermsg) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: diff --git a/openingd/dualopend.c b/openingd/dualopend.c index f6c183a917d2..357ba93935e1 100644 --- a/openingd/dualopend.c +++ b/openingd/dualopend.c @@ -1673,6 +1673,8 @@ static u8 *opening_negotiate_msg(const tal_t *ctx, struct state *state) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: @@ -2057,6 +2059,8 @@ static bool run_tx_interactive(struct state *state, case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: @@ -4196,6 +4200,9 @@ static u8 *handle_peer_in(struct state *state) } accepter_start(state, msg); return NULL; + case WIRE_START_BATCH: + /* We ignore batch messages as we dont need them */ + return NULL; case WIRE_COMMITMENT_SIGNED: handle_commit_signed(state, msg); return NULL; @@ -4257,6 +4264,7 @@ static u8 *handle_peer_in(struct state *state) case WIRE_SPLICE: case WIRE_SPLICE_ACK: case WIRE_SPLICE_LOCKED: + case WIRE_PROTOCOL_BATCH_ELEMENT: break; } diff --git a/wire/extracted_peer_12_splice_update.patch b/wire/extracted_peer_12_splice_update.patch new file mode 100644 index 000000000000..e47ba33fa763 --- /dev/null +++ b/wire/extracted_peer_12_splice_update.patch @@ -0,0 +1,24 @@ +diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv +index 9abcb0e64..e2aae8efb 100644 +--- a/wire/peer_wire.csv ++++ b/wire/peer_wire.csv +@@ -1,3 +1,6 @@ ++msgtype,protocol_batch_element,0 ++msgdata,protocol_batch_element,channel_id,channel_id, ++msgdata,protocol_batch_element,element_size,u16, + msgtype,init,16 + msgdata,init,gflen,u16, + msgdata,init,globalfeatures,byte,gflen +@@ -293,6 +296,12 @@ msgdata,update_fail_malformed_htlc,channel_id,channel_id, + msgdata,update_fail_malformed_htlc,id,u64, + msgdata,update_fail_malformed_htlc,sha256_of_onion,sha256, + msgdata,update_fail_malformed_htlc,failure_code,u16, ++msgtype,start_batch,127 ++msgdata,start_batch,channel_id,channel_id, ++msgdata,start_batch,batch_size,u16, ++msgdata,start_batch,batch_info,start_batch_tlvs, ++tlvtype,start_batch_tlvs,batch_info,1 ++tlvdata,start_batch_tlvs,batch_info,message_type,u16, + msgtype,commitment_signed,132 + msgdata,commitment_signed,channel_id,channel_id, + msgdata,commitment_signed,signature,signature, diff --git a/wire/peer_wire.c b/wire/peer_wire.c index fc2e1c075b6f..12c019b3b14b 100644 --- a/wire/peer_wire.c +++ b/wire/peer_wire.c @@ -21,6 +21,8 @@ static bool unknown_type(enum peer_wire t) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: @@ -89,6 +91,8 @@ bool is_msg_for_gossipd(const u8 *cursor) case WIRE_UPDATE_FULFILL_HTLC: case WIRE_UPDATE_FAIL_HTLC: case WIRE_UPDATE_FAIL_MALFORMED_HTLC: + case WIRE_PROTOCOL_BATCH_ELEMENT: + case WIRE_START_BATCH: case WIRE_COMMITMENT_SIGNED: case WIRE_REVOKE_AND_ACK: case WIRE_UPDATE_FEE: @@ -166,6 +170,7 @@ bool extract_channel_id(const u8 *in_pkt, struct channel_id *channel_id) case WIRE_ONION_MESSAGE: case WIRE_PEER_STORAGE: case WIRE_PEER_STORAGE_RETRIEVAL: + case WIRE_PROTOCOL_BATCH_ELEMENT: return false; /* Special cases: */ @@ -345,6 +350,11 @@ bool extract_channel_id(const u8 *in_pkt, struct channel_id *channel_id) * 2. data: * * [`channel_id`:`channel_id`] */ + case WIRE_START_BATCH: + /* 1. type: 127 (`start_batch`) + * 2. data: + * * [`channel_id`:`channel_id`] + */ case WIRE_COMMITMENT_SIGNED: /* BOLT #2: * 1. type: 132 (`commitment_signed`) diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv index 9abcb0e64308..2b7d17c3025c 100644 --- a/wire/peer_wire.csv +++ b/wire/peer_wire.csv @@ -1,3 +1,6 @@ +msgtype,protocol_batch_element,0 +msgdata,protocol_batch_element,channel_id,channel_id, +msgdata,protocol_batch_element,element_size,u16, msgtype,init,16 msgdata,init,gflen,u16, msgdata,init,globalfeatures,byte,gflen @@ -293,6 +296,12 @@ msgdata,update_fail_malformed_htlc,channel_id,channel_id, msgdata,update_fail_malformed_htlc,id,u64, msgdata,update_fail_malformed_htlc,sha256_of_onion,sha256, msgdata,update_fail_malformed_htlc,failure_code,u16, +msgtype,start_batch,127 +msgdata,start_batch,channel_id,channel_id, +msgdata,start_batch,batch_size,u16, +msgdata,start_batch,batch_info,start_batch_tlvs, +tlvtype,start_batch_tlvs,batch_info,1 +tlvdata,start_batch_tlvs,batch_info,message_type,u16, msgtype,commitment_signed,132 msgdata,commitment_signed,channel_id,channel_id, msgdata,commitment_signed,signature,signature, From b63f9c2556c2b324ce7a064f252578e88b2263d0 Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 16 Jun 2025 14:00:22 -0400 Subject: [PATCH 2/5] splice: Remove `batch_size` from `commitment_signed` The new spec sends `batch_size` in `start_batch` and removes it from `commitment_signed` so we need to stop processing it in `commitment_signed`. Since the tlv is now reduced to one element and that automagically turns it into a direct use TLV so we have to update the code everywhere it is referenced. --- channeld/channeld.c | 44 ++++++---------------- wire/extracted_peer_12_splice_update.patch | 8 ++++ wire/peer_wire.csv | 1 - wire/test/run-peer-wire.c | 16 ++------ 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index 287cda709bae..2c1697cdaf53 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -1160,12 +1160,7 @@ static u8 *send_commit_part(const tal_t *ctx, (int)splice_amnt, (int)remote_splice_amnt, remote_index); - if (batch_size > 1) { - cs_tlv->splice_info = tal(cs_tlv, struct tlv_commitment_signed_tlvs_splice_info); - - cs_tlv->splice_info->batch_size = batch_size; - cs_tlv->splice_info->funding_txid = funding->txid; - } + cs_tlv->splice_info = tal_dup(cs_tlv, struct bitcoin_txid, &funding->txid); } txs = channel_txs(tmpctx, funding, funding_sats, &htlc_map, @@ -1926,11 +1921,11 @@ static struct commitsig_info *handle_peer_commit_sig(struct peer *peer, if (peer->splice_state->await_commitment_succcess && !tal_count(peer->splice_state->inflights) && cs_tlv && cs_tlv->splice_info) { if (!bitcoin_txid_eq(&peer->channel->funding.txid, - &cs_tlv->splice_info->funding_txid)) { + cs_tlv->splice_info)) { status_info("Ignoring stale commit_sig for channel_id" " %s, as %s is locked in now.", fmt_bitcoin_txid(tmpctx, - &cs_tlv->splice_info->funding_txid), + cs_tlv->splice_info), fmt_bitcoin_txid(tmpctx, &peer->channel->funding.txid)); return NULL; @@ -1980,22 +1975,17 @@ static struct commitsig_info *handle_peer_commit_sig(struct peer *peer, outpoint = peer->splice_state->inflights[commit_index - 1]->outpoint; funding_sats = peer->splice_state->inflights[commit_index - 1]->amnt; - if (cs_tlv && cs_tlv->splice_info - && cs_tlv->splice_info->batch_size == 1) - peer_failed_err(peer->pps, &peer->channel_id, - "batch_size can never be 1"); - status_debug("handle_peer_commit_sig for inflight outpoint %s", fmt_bitcoin_txid(tmpctx, &outpoint.txid)); if (cs_tlv->splice_info && !bitcoin_txid_eq(&outpoint.txid, - &cs_tlv->splice_info->funding_txid)) + cs_tlv->splice_info)) peer_failed_err(peer->pps, &peer->channel_id, "Expected commit sig message for %s but" " got %s", fmt_bitcoin_txid(tmpctx, &outpoint.txid), - fmt_bitcoin_txid(tmpctx, &cs_tlv->splice_info->funding_txid)); + fmt_bitcoin_txid(tmpctx, cs_tlv->splice_info)); } else { outpoint = peer->channel->funding; @@ -2052,7 +2042,7 @@ static struct commitsig_info *handle_peer_commit_sig(struct peer *peer, fmt_amount_sat(tmpctx, funding_sats), cs_tlv && cs_tlv->splice_info ? fmt_bitcoin_txid(tmpctx, - &cs_tlv->splice_info->funding_txid) + cs_tlv->splice_info) : "N/A", peer->splice_state->await_commitment_succcess ? "yes" : "no", @@ -2216,7 +2206,7 @@ static int commit_index_from_msg(const u8 *msg, struct peer *peer) if (!cs_tlv || !cs_tlv->splice_info) return -1; - funding_txid = cs_tlv->splice_info->funding_txid; + funding_txid = *cs_tlv->splice_info; if (bitcoin_txid_eq(&funding_txid, &peer->channel->funding.txid)) return 0; @@ -2269,12 +2259,12 @@ static struct commitsig_info *handle_peer_commit_sig_batch(struct peer *peer, s64 remote_splice_amnt, u64 local_index, const struct pubkey *local_per_commit, - bool allow_empty_commit) + bool allow_empty_commit, + u16 batch_size) { struct channel_id channel_id; struct bitcoin_signature commit_sig; secp256k1_ecdsa_signature *raw_sigs; - u16 batch_size; const u8 **msg_batch; enum peer_wire type; struct tlv_commitment_signed_tlvs *cs_tlv @@ -2286,11 +2276,6 @@ static struct commitsig_info *handle_peer_commit_sig_batch(struct peer *peer, peer_failed_warn(peer->pps, &peer->channel_id, "Bad commit_sig %s", tal_hex(msg, msg)); - /* Default batch_size is 1 */ - batch_size = 1; - if (cs_tlv->splice_info && cs_tlv->splice_info->batch_size) - batch_size = cs_tlv->splice_info->batch_size; - msg_batch = tal_arr(tmpctx, const u8*, batch_size); msg_batch[0] = msg; status_debug("msg_batch[0]: %p", msg_batch[0]); @@ -2326,14 +2311,6 @@ static struct commitsig_info *handle_peer_commit_sig_batch(struct peer *peer, " splice_info", tal_hex(sub_msg, sub_msg), i, batch_size); - if (!sub_cs_tlv->splice_info - || sub_cs_tlv->splice_info->batch_size != batch_size) - peer_failed_err(peer->pps, &peer->channel_id, - "batch_size value mismatch in" - " commit_sig bundle, item [%"PRIu16 - "/%"PRIu16"] %s", i, batch_size, - tal_hex(sub_msg, sub_msg)); - msg_batch[i] = sub_msg; status_debug("msg_batch[%d]: %p", (int)i, msg_batch[i]); } @@ -4914,7 +4891,8 @@ static void peer_in(struct peer *peer, const u8 *msg) NULL, 0, 0, peer->next_index[LOCAL], &peer->next_local_per_commit, - false); + false, + 1); /* Batch size default is 1 */ return; case WIRE_UPDATE_FEE: handle_peer_feechange(peer, msg); diff --git a/wire/extracted_peer_12_splice_update.patch b/wire/extracted_peer_12_splice_update.patch index e47ba33fa763..817acd8ce03e 100644 --- a/wire/extracted_peer_12_splice_update.patch +++ b/wire/extracted_peer_12_splice_update.patch @@ -22,3 +22,11 @@ index 9abcb0e64..e2aae8efb 100644 msgtype,commitment_signed,132 msgdata,commitment_signed,channel_id,channel_id, msgdata,commitment_signed,signature,signature, +@@ -309,6 +309,5 @@ msgdata,commitment_signed,num_htlcs,u16, + msgdata,commitment_signed,htlc_signature,signature,num_htlcs + msgdata,commitment_signed,splice_channel_id,commitment_signed_tlvs, + tlvtype,commitment_signed_tlvs,splice_info,0 +-tlvdata,commitment_signed_tlvs,splice_info,batch_size,u16, + tlvdata,commitment_signed_tlvs,splice_info,funding_txid,sha256, + msgtype,revoke_and_ack,133 + msgdata,revoke_and_ack,channel_id,channel_id, diff --git a/wire/peer_wire.csv b/wire/peer_wire.csv index 2b7d17c3025c..e2aae8efb6bf 100644 --- a/wire/peer_wire.csv +++ b/wire/peer_wire.csv @@ -309,7 +309,6 @@ msgdata,commitment_signed,num_htlcs,u16, msgdata,commitment_signed,htlc_signature,signature,num_htlcs msgdata,commitment_signed,splice_channel_id,commitment_signed_tlvs, tlvtype,commitment_signed_tlvs,splice_info,0 -tlvdata,commitment_signed_tlvs,splice_info,batch_size,u16, tlvdata,commitment_signed_tlvs,splice_info,funding_txid,sha256, msgtype,revoke_and_ack,133 msgdata,revoke_and_ack,channel_id,channel_id, diff --git a/wire/test/run-peer-wire.c b/wire/test/run-peer-wire.c index cbb457ac27bb..894920440a88 100644 --- a/wire/test/run-peer-wire.c +++ b/wire/test/run-peer-wire.c @@ -792,19 +792,12 @@ static bool update_fail_htlc_eq(const struct msg_update_fail_htlc *a, && eq_var(a, b, reason); } -static bool tlv_splice_info_eq(const struct tlv_commitment_signed_tlvs_splice_info *a, - const struct tlv_commitment_signed_tlvs_splice_info *b) -{ - return eq_field(a, b, batch_size) - && eq_field(a, b, funding_txid); -} - static bool commitment_signed_eq(const struct msg_commitment_signed *a, - const struct msg_commitment_signed *b) + const struct msg_commitment_signed *b) { return eq_upto(a, b, htlc_signature) && eq_var(a, b, htlc_signature) - && eq_tlv(a, b, splice_info, tlv_splice_info_eq); + && eq_tlv(a, b, splice_info, bitcoin_txid_eq); } static bool funding_signed_eq(const struct msg_funding_signed *a, @@ -1026,9 +1019,8 @@ int main(int argc, char *argv[]) cs.htlc_signature = tal_arr(ctx, secp256k1_ecdsa_signature, 2); memset(cs.htlc_signature, 2, sizeof(secp256k1_ecdsa_signature)*2); cs.tlvs = tlv_commitment_signed_tlvs_new(tmpctx); - cs.tlvs->splice_info = tal(ctx, struct tlv_commitment_signed_tlvs_splice_info); - cs.tlvs->splice_info->batch_size = 1; - set_bitcoin_txid(&cs.tlvs->splice_info->funding_txid); + cs.tlvs->splice_info = tal(ctx, struct bitcoin_txid); + set_bitcoin_txid(cs.tlvs->splice_info); msg = towire_struct_commitment_signed(ctx, &cs); cs2 = fromwire_struct_commitment_signed(ctx, msg); From 7c5d56b1367925798bf83563e40ed1f63cec0f54 Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 16 Jun 2025 14:03:00 -0400 Subject: [PATCH 3/5] channeld: Implement receiving of `start_batch` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since `batch_size` has moved into this new message, we can’t ignore it anymore and have to process it --- channeld/channeld.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/channeld/channeld.c b/channeld/channeld.c index 2c1697cdaf53..fde5e382190b 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -2325,6 +2325,35 @@ static struct commitsig_info *handle_peer_commit_sig_batch(struct peer *peer, allow_empty_commit, msg_batch); } +static void handle_peer_start_batch(struct peer *peer, const u8 *msg) +{ + u16 batch_size; + struct channel_id channel_id; + struct tlv_start_batch_tlvs *tlvs; + if (!fromwire_start_batch(tmpctx, msg, &channel_id, &batch_size, &tlvs)) + peer_failed_warn(peer->pps, &peer->channel_id, + "Bad start_batch %s", tal_hex(msg, msg)); + + if (!tlvs || !tlvs->batch_info + || *tlvs->batch_info != WIRE_COMMITMENT_SIGNED) { + status_unusual("Ignoring Unrecognized start_batch message type" + " %s, expected WIRE_COMMITMENT_SIGNED.", + tlvs && tlvs->batch_info + ? peer_wire_name(*tlvs->batch_info) + : "N/A"); + return; + } + + handle_peer_commit_sig_batch(peer, peer_read(tmpctx, peer->pps), 0, + peer->channel->funding_pubkey[REMOTE], + NULL, 0, 0, + peer->next_index[LOCAL], + &peer->next_local_per_commit, + false, + batch_size); +} + + /* Pops the penalty base for the given commitnum from our internal list. There * may not be one, in which case we return NULL and leave the list * unmodified. */ @@ -4884,6 +4913,7 @@ static void peer_in(struct peer *peer, const u8 *msg) handle_peer_add_htlc(peer, msg); return; case WIRE_START_BATCH: + handle_peer_start_batch(peer, msg); return; case WIRE_COMMITMENT_SIGNED: handle_peer_commit_sig_batch(peer, msg, 0, From 28ecf25adb4efb795471e70f09c138e4fe43f271 Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 16 Jun 2025 14:04:13 -0400 Subject: [PATCH 4/5] channeld: Cleaner error messages Since handling commit sig batches is coming for multiple locations now, add more explicity error handling so log messages are more useful. --- channeld/channeld.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/channeld/channeld.c b/channeld/channeld.c index fde5e382190b..efbf48003428 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -2270,6 +2270,12 @@ static struct commitsig_info *handle_peer_commit_sig_batch(struct peer *peer, struct tlv_commitment_signed_tlvs *cs_tlv = tlv_commitment_signed_tlvs_new(tmpctx); status_debug("fromwire_commitment_signed(%p) primary", msg); + check_tx_abort(peer, msg, NULL); + type = fromwire_peektype(msg); + if (type != WIRE_COMMITMENT_SIGNED) + peer_failed_err(peer->pps, &peer->channel_id, + "Expected WIRE_COMMITMENT_SIGNED but got %s.", + peer_wire_name(type)); if (!fromwire_commitment_signed(tmpctx, msg, &channel_id, &commit_sig.s, &raw_sigs, &cs_tlv)) From 6e3101a9ab9950f828adc11bcd627fe68432aeda Mon Sep 17 00:00:00 2001 From: Dusty Daemon Date: Mon, 16 Jun 2025 14:07:45 -0400 Subject: [PATCH 5/5] connectd: Implement sending of `start_batch` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement the sending of `start_batch` and `protocol_batch_element` from `channeld` to `connectd`. Each real peer wire message is prefixed with `protocol_batch_element` so connectd can know the size of the message that were batched together. `connectd` intercepts `protocol_batch_element` messages and eats them (doesn’t forward them to peer) to get individual messages out of the batch. It needs this to be able to encrypt them individiaully. Afterwards it recombines the now encrypted messages into a single message to send over the wire to the peer. `channeld` remains responsible for making `start_batch` the first message of the message bundle. --- channeld/channeld.c | 78 +++++++++++++++++++++++++++++++++++++++++--- connectd/multiplex.c | 74 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 147 insertions(+), 5 deletions(-) diff --git a/channeld/channeld.c b/channeld/channeld.c index efbf48003428..98729dc6a216 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -1232,6 +1232,78 @@ static s64 sats_diff(struct amount_sat a, struct amount_sat b) return (s64)a.satoshis - (s64)b.satoshis; /* Raw: splicing numbers can wrap! */ } +static void send_message_batch(struct peer *peer, u8 **msgs) +{ + size_t size; + size_t hdr_size = tal_bytelen(towire_protocol_batch_element(tmpctx, + &peer->channel_id, + 0)); + u8 *batch_msg, *final_msg, *final_msg_ptr; + struct tlv_start_batch_tlvs *tlvs; + + assert(tal_count(msgs) > 0); + + /* When sending one message, no batching is required */ + if (tal_count(msgs) == 1) { + peer_write(peer->pps, msgs[0]); + return; + } + + /* We prefix each message with an interal wire type, + * protocol_batch_element. connectd will eat each message so they don't + * actually go out to the peer. It's just so connectd can chop up the + * message batch back out into individual messages. */ + + /* We start by calculating the total size */ + size = 0; + + /* Build the `start_batch` msg now so know it's size */ + tlvs = tlv_start_batch_tlvs_new(tmpctx); + tlvs->batch_info = tal(tlvs, u16); + *tlvs->batch_info = WIRE_COMMITMENT_SIGNED; + batch_msg = towire_start_batch(tmpctx, &peer->channel_id, + tal_count(msgs), tlvs); + size += tal_bytelen(batch_msg) + hdr_size; + + /* Count the size of all the messages in the batch */ + for(u32 i = 0; i < tal_count(msgs); i++) + size += tal_bytelen(msgs[i]) + hdr_size; + + /* Now we know the size of our `final_msg` so we allocate */ + final_msg = tal_arr(tmpctx, u8, size); + final_msg_ptr = final_msg; + + status_debug("proto_batch Building batch with %zu bytes, msgs: %zu", + size, tal_count(msgs)); + + /* Copy the bytes for `start_batch` prefix */ + memcpy(final_msg_ptr, + towire_protocol_batch_element(tmpctx, + &peer->channel_id, + tal_bytelen(batch_msg)), + hdr_size); + final_msg_ptr += hdr_size; + + memcpy(final_msg_ptr, batch_msg, tal_bytelen(batch_msg)); + final_msg_ptr += tal_bytelen(batch_msg); + + /* Now copy the bytes from all messages in `msgs` */ + for(u32 i = 0; i < tal_count(msgs); i++) { + memcpy(final_msg_ptr, + towire_protocol_batch_element(tmpctx, + &peer->channel_id, + tal_bytelen(msgs[i])), + hdr_size); + final_msg_ptr += hdr_size; + + memcpy(final_msg_ptr, msgs[i], tal_bytelen(msgs[i])); + final_msg_ptr += tal_bytelen(msgs[i]); + } + + assert(final_msg + size == final_msg_ptr); + peer_write(peer->pps, take(final_msg)); +} + static void send_commit(struct peer *peer) { const struct htlc **changed_htlcs; @@ -1398,8 +1470,7 @@ static void send_commit(struct peer *peer) peer->next_index[REMOTE]++; - for(u32 i = 0; i < tal_count(msgs); i++) - peer_write(peer->pps, take(msgs[i])); + send_message_batch(peer, msgs); maybe_send_shutdown(peer); @@ -5195,8 +5266,7 @@ static void resend_commitment(struct peer *peer, struct changed_htlc *last) peer->splice_state->inflights[i]->remote_funding)); } - for(i = 0; i < tal_count(msgs); i++) - peer_write(peer->pps, take(msgs[i])); + send_message_batch(peer, msgs); /* If we have already received the revocation for the previous, the * other side shouldn't be asking for a retransmit! */ diff --git a/connectd/multiplex.c b/connectd/multiplex.c index f493b6a944e5..a6e418d33eb8 100644 --- a/connectd/multiplex.c +++ b/connectd/multiplex.c @@ -399,6 +399,69 @@ static struct io_plan *io_sock_shutdown_cb(struct io_conn *conn, struct peer *un return io_sock_shutdown(conn); } +/* Process and eat protocol_batch_element messages, encrypt each element message + * and return the encrypted messages as one long byte array. */ +static u8 *process_batch_elements(struct peer *peer, const u8 *msg TAKES) +{ + u8 *ret = tal_arr(peer, u8, 0); + size_t ret_size = 0; + const u8 *cursor = msg; + size_t plen = tal_count(msg); + + status_debug("Processing batch elements of %zu bytes. %s", plen, + tal_hex(tmpctx, msg)); + + do { + u8 *element_bytes; + u16 element_size; + struct channel_id channel_id; + u8 *enc_msg; + + if (fromwire_u16(&cursor, &plen) != WIRE_PROTOCOL_BATCH_ELEMENT) { + status_broken("process_batch_elements on msg that is" + " not WIRE_PROTOCOL_BATCH_ELEMENT. %s", + tal_hexstr(tmpctx, cursor, plen)); + return tal_free(ret); + } + + fromwire_channel_id(&cursor, &plen, &channel_id); + + element_size = fromwire_u16(&cursor, &plen); + if (!element_size) { + status_broken("process_batch_elements cannot have zero" + " length elements. %s", + tal_hexstr(tmpctx, cursor, plen)); + return tal_free(ret); + } + + element_bytes = fromwire_tal_arrn(NULL, &cursor, &plen, + element_size); + if (!element_bytes) { + status_broken("process_batch_elements fromwire_tal_arrn" + " %s", + tal_hexstr(tmpctx, cursor, plen)); + return tal_free(ret); + } + + status_debug("Processing batch extracted item %s. %s", + peer_wire_name(fromwire_peektype(element_bytes)), + tal_hex(tmpctx, element_bytes)); + + enc_msg = cryptomsg_encrypt_msg(tmpctx, &peer->cs, + take(element_bytes)); + + tal_resize(&ret, ret_size + tal_bytelen(enc_msg)); + memcpy(&ret[ret_size], enc_msg, tal_bytelen(enc_msg)); + ret_size += tal_bytelen(enc_msg); + + } while(plen); + + if (taken(msg)) + tal_free(msg); + + return ret; +} + static struct io_plan *encrypt_and_send(struct peer *peer, const u8 *msg TAKES, struct io_plan *(*next) @@ -442,8 +505,17 @@ static struct io_plan *encrypt_and_send(struct peer *peer, set_urgent_flag(peer, is_urgent(type)); + /* Special message type directing us to process batch items. */ + if (type == WIRE_PROTOCOL_BATCH_ELEMENT) { + peer->sent_to_peer = process_batch_elements(peer, msg); + if (!peer->sent_to_peer) + return io_close(peer->to_peer); + } + else { + peer->sent_to_peer = cryptomsg_encrypt_msg(peer, &peer->cs, msg); + } /* We free this and the encrypted version in next write_to_peer */ - peer->sent_to_peer = cryptomsg_encrypt_msg(peer, &peer->cs, msg); + return io_write(peer->to_peer, peer->sent_to_peer, tal_bytelen(peer->sent_to_peer),