interactive-tx: Renaming for clarity and cleaning up

New daemon process means we don’t have to deal with gossip, so that gets removed along with error cleanup and a refactoring of how we calculating PDBT diffs.
This commit is contained in:
Dusty Daemon 2023-07-27 13:13:33 -07:00 committed by Rusty Russell
parent 6b81243f96
commit ebd0a3fd69
2 changed files with 110 additions and 81 deletions

View file

@ -8,8 +8,6 @@
#include <ccan/tal/str/str.h> #include <ccan/tal/str/str.h>
#include <common/billboard.h> #include <common/billboard.h>
#include <common/blockheight_states.h> #include <common/blockheight_states.h>
#include <common/crypto_sync.h>
#include <common/gossip_rcvd_filter.h>
#include <common/gossip_store.h> #include <common/gossip_store.h>
#include <common/initial_channel.h> #include <common/initial_channel.h>
#include <common/interactivetx.h> #include <common/interactivetx.h>
@ -17,6 +15,7 @@
#include <common/memleak.h> #include <common/memleak.h>
#include <common/peer_billboard.h> #include <common/peer_billboard.h>
#include <common/peer_failed.h> #include <common/peer_failed.h>
#include <common/peer_io.h>
#include <common/psbt_internal.h> #include <common/psbt_internal.h>
#include <common/psbt_open.h> #include <common/psbt_open.h>
#include <common/read_peer_msg.h> #include <common/read_peer_msg.h>
@ -24,7 +23,6 @@
#include <common/status.h> #include <common/status.h>
#include <common/subdaemon.h> #include <common/subdaemon.h>
#include <common/type_to_string.h> #include <common/type_to_string.h>
#include <common/wire_error.h>
/* /*
* BOLT-f53ca2301232db780843e894f55d95d512f297f9 #2: * BOLT-f53ca2301232db780843e894f55d95d512f297f9 #2:
@ -64,14 +62,13 @@ struct interactivetx_context *new_interactivetx_context(const tal_t *ctx,
{ {
struct interactivetx_context *ictx = tal(ctx, struct interactivetx_context); struct interactivetx_context *ictx = tal(ctx, struct interactivetx_context);
ictx->ctx = NULL;
ictx->our_role = our_role; ictx->our_role = our_role;
ictx->pps = pps; ictx->pps = pps;
ictx->channel_id = channel_id; ictx->channel_id = channel_id;
ictx->tx_add_input_count = 0; ictx->tx_add_input_count = 0;
ictx->tx_add_output_count = 0; ictx->tx_add_output_count = 0;
ictx->next_update = default_next_update; ictx->next_update_fn = default_next_update;
ictx->current_psbt = NULL; ictx->current_psbt = create_psbt(ictx, 0, 0, 0);
ictx->desired_psbt = NULL; ictx->desired_psbt = NULL;
ictx->pause_when_complete = false; ictx->pause_when_complete = false;
ictx->change_set = NULL; ictx->change_set = NULL;
@ -102,26 +99,14 @@ static u8 *read_next_msg(const tal_t *ctx,
for (;;) { for (;;) {
char *desc; char *desc;
bool warning; bool warning;
struct channel_id actual;
enum peer_wire t; enum peer_wire t;
bool from_gossipd;
/* Prevent runaway memory usage from many messages */ /* Prevent runaway memory usage from many messages */
if (msg) if (msg)
tal_free(msg); tal_free(msg);
/* This helper routine polls the peer. */ /* This helper routine polls the peer. */
msg = peer_or_gossip_sync_read(ctx, state->pps, &from_gossipd); msg = peer_read(ctx, state->pps);
/* Line should be in STFU mode and not receiving gossip */
if (from_gossipd) {
*error = tal_fmt(ctx, "interactivetx got gossip but"
" should be in STFU mode.");
tal_free(msg);
/* Return NULL so caller knows to stop negotiating. */
return NULL;
}
/* BOLT #1: /* BOLT #1:
* *
@ -140,7 +125,7 @@ static u8 *read_next_msg(const tal_t *ctx,
if (!desc) if (!desc)
continue; continue;
*error = tal_fmt(ctx, "They sent a %s: %s" *error = tal_fmt(ctx, "They sent a %s: %s",
warning ? "warning" : "error", warning ? "warning" : "error",
desc); desc);
@ -160,9 +145,11 @@ static u8 *read_next_msg(const tal_t *ctx,
case WIRE_TX_REMOVE_OUTPUT: case WIRE_TX_REMOVE_OUTPUT:
case WIRE_TX_COMPLETE: case WIRE_TX_COMPLETE:
return msg; return msg;
case WIRE_TX_ABORT:
/* TODO */
case WIRE_TX_SIGNATURES: case WIRE_TX_SIGNATURES:
case WIRE_FUNDING_LOCKED: case WIRE_CHANNEL_READY:
case WIRE_INIT_RBF: case WIRE_TX_INIT_RBF:
case WIRE_OPEN_CHANNEL2: case WIRE_OPEN_CHANNEL2:
case WIRE_INIT: case WIRE_INIT:
case WIRE_ERROR: case WIRE_ERROR:
@ -184,7 +171,7 @@ static u8 *read_next_msg(const tal_t *ctx,
case WIRE_GOSSIP_TIMESTAMP_FILTER: case WIRE_GOSSIP_TIMESTAMP_FILTER:
case WIRE_ONION_MESSAGE: case WIRE_ONION_MESSAGE:
case WIRE_ACCEPT_CHANNEL2: case WIRE_ACCEPT_CHANNEL2:
case WIRE_ACK_RBF: case WIRE_TX_ACK_RBF:
case WIRE_CHANNEL_ANNOUNCEMENT: case WIRE_CHANNEL_ANNOUNCEMENT:
case WIRE_CHANNEL_UPDATE: case WIRE_CHANNEL_UPDATE:
case WIRE_NODE_ANNOUNCEMENT: case WIRE_NODE_ANNOUNCEMENT:
@ -197,6 +184,11 @@ static u8 *read_next_msg(const tal_t *ctx,
case WIRE_PONG: case WIRE_PONG:
case WIRE_SHUTDOWN: case WIRE_SHUTDOWN:
case WIRE_STFU: case WIRE_STFU:
case WIRE_PEER_STORAGE:
case WIRE_YOUR_PEER_STORAGE:
case WIRE_SPLICE:
case WIRE_SPLICE_ACK:
case WIRE_SPLICE_LOCKED:
*error = tal_fmt(ctx, *error = tal_fmt(ctx,
"Received invalid message from peer: %d", t); "Received invalid message from peer: %d", t);
return NULL; return NULL;
@ -211,7 +203,7 @@ static char *send_next(const tal_t *ctx,
struct channel_id *cid = &ictx->channel_id; struct channel_id *cid = &ictx->channel_id;
struct psbt_changeset *set = ictx->change_set; struct psbt_changeset *set = ictx->change_set;
u64 serial_id; u64 serial_id;
u8 *msg = NULL; u8 *msg;
*finished = false; *finished = false;
if (!set) if (!set)
@ -219,35 +211,29 @@ static char *send_next(const tal_t *ctx,
if (tal_count(set->added_ins) != 0) { if (tal_count(set->added_ins) != 0) {
const struct input_set *in = &set->added_ins[0]; const struct input_set *in = &set->added_ins[0];
struct bitcoin_outpoint outpoint;
u8 *prevtx; u8 *prevtx;
if (!psbt_get_serial_id(&in->input.unknowns, &serial_id)) if (!psbt_get_serial_id(&in->input.unknowns, &serial_id))
return "interactivetx ADD_INPUT PSBT has invalid serial_id."; return "interactivetx ADD_INPUT PSBT has invalid"
" serial_id.";
if (in->input.utxo) if (in->input.utxo)
prevtx = linearize_wtx(ctx, prevtx = linearize_wtx(ctx, in->input.utxo);
in->input.utxo);
else else
return "interactivetx ADD_INPUT PSBT needs the previous transaction set."; return "interactivetx ADD_INPUT PSBT needs the previous"
" transaction set.";
memcpy(outpoint.txid.shad.sha.u.u8,
in->tx_input.txhash,
WALLY_TXHASH_LEN);
outpoint.n = in->tx_input.index;
msg = towire_tx_add_input(NULL, cid, serial_id, msg = towire_tx_add_input(NULL, cid, serial_id,
prevtx, in->tx_input.index, prevtx, in->input.index,
in->tx_input.sequence, in->input.sequence);
NULL);
tal_arr_remove(&set->added_ins, 0); tal_arr_remove(&set->added_ins, 0);
} }
else if (tal_count(set->rm_ins) != 0) { else if (tal_count(set->rm_ins) != 0) {
if (!psbt_get_serial_id(&set->rm_ins[0].input.unknowns, if (!psbt_get_serial_id(&set->rm_ins[0].input.unknowns,
&serial_id)) &serial_id))
return "interactivetx RM_INPUT PSBT has invalid serial_id."; return "interactivetx RM_INPUT PSBT has invalid"
" serial_id.";
msg = towire_tx_remove_input(NULL, cid, serial_id); msg = towire_tx_remove_input(NULL, cid, serial_id);
@ -262,11 +248,12 @@ static char *send_next(const tal_t *ctx,
out = &set->added_outs[0]; out = &set->added_outs[0];
if (!psbt_get_serial_id(&out->output.unknowns, &serial_id)) if (!psbt_get_serial_id(&out->output.unknowns, &serial_id))
return "interactivetx ADD_OUTPUT PSBT has invalid serial_id."; return "interactivetx ADD_OUTPUT PSBT has invalid"
" serial_id.";
asset_amt = wally_tx_output_get_amount(&out->tx_output); asset_amt = wally_psbt_output_get_amount(&out->output);
sats = amount_asset_to_sat(&asset_amt); sats = amount_asset_to_sat(&asset_amt);
script = wally_tx_output_get_script(ctx, &out->tx_output); script = wally_psbt_output_get_script(ctx, &out->output);
msg = towire_tx_add_output(NULL, msg = towire_tx_add_output(NULL,
cid, cid,
@ -290,33 +277,74 @@ static char *send_next(const tal_t *ctx,
if (!msg) if (!msg)
return "Interactivetx send_next failed to build a message"; return "Interactivetx send_next failed to build a message";
sync_crypto_write(ictx->pps, take(msg)); peer_write(ictx->pps, take(msg));
return NULL; return NULL;
tx_complete: tx_complete:
*finished = true;
if (!ictx->pause_when_complete) { if (!ictx->pause_when_complete) {
if (ictx->current_psbt->num_inputs > MAX_FUNDING_INPUTS) if (ictx->current_psbt->num_inputs > MAX_FUNDING_INPUTS)
return tal_fmt(ctx, "Humbly refusing to `tx_complete` " return tal_fmt(ctx, "Humbly refusing to `tx_complete` "
"because we have too many inputs (%zu). " "because we have too many inputs (%zu). "
"Limit is %zu." "Limit is %d.",
ictx->current_psbt->num_inputs, ictx->current_psbt->num_inputs,
MAX_FUNDING_INPUTS); MAX_FUNDING_INPUTS);
if (ictx->current_psbt->num_outputs > MAX_FUNDING_OUTPUTS) if (ictx->current_psbt->num_outputs > MAX_FUNDING_OUTPUTS)
return tal_fmt(ctx, "Humbly refusing to `tx_complete` " return tal_fmt(ctx, "Humbly refusing to `tx_complete` "
"because we have too many outputs (%zu). " "because we have too many outputs (%zu). "
"Limit is %zu." "Limit is %d.",
ictx->current_psbt->num_outputs, ictx->current_psbt->num_outputs,
MAX_FUNDING_OUTPUTS); MAX_FUNDING_OUTPUTS);
msg = towire_tx_complete(ctx, cid); msg = towire_tx_complete(NULL, cid);
sync_crypto_write(ictx->pps, msg); peer_write(ictx->pps, take(msg));
} }
*finished = true;
return NULL; return NULL;
} }
static struct psbt_changeset *get_changes(const tal_t *ctx,
struct interactivetx_context *ictx,
struct wally_psbt *next_psbt)
{
u64 serial_id;
struct psbt_changeset *set = psbt_get_changeset(tmpctx,
ictx->current_psbt,
next_psbt);
/* Remove duplicate serial_ids from the change set. */
for (int i = 0; i < tal_count(set->added_ins); i++) {
struct bitcoin_outpoint point;
wally_psbt_input_get_outpoint(&set->added_ins[i].input, &point);
if (psbt_get_serial_id(&set->added_ins[i].input.unknowns,
&serial_id)) {
if (psbt_find_serial_input(ictx->current_psbt,
serial_id) != -1)
tal_arr_remove(&set->added_ins, i--);
else if (psbt_has_input(ictx->current_psbt, &point))
tal_arr_remove(&set->added_ins, i--);
}
}
for (int i = 0; i < tal_count(set->added_outs); i++)
if (psbt_get_serial_id(&set->added_outs[i].output.unknowns,
&serial_id))
if (psbt_find_serial_output(ictx->current_psbt,
serial_id) != -1)
tal_arr_remove(&set->added_outs, i--);
return set;
}
bool interactivetx_has_changes(struct interactivetx_context *ictx,
struct wally_psbt *next_psbt)
{
struct psbt_changeset *set = get_changes(tmpctx, ictx, next_psbt);
return tal_count(set->added_ins) || tal_count(set->rm_ins)
|| tal_count(set->added_outs) || tal_count(set->rm_outs);
}
char *process_interactivetx_updates(const tal_t *ctx, char *process_interactivetx_updates(const tal_t *ctx,
struct interactivetx_context *ictx, struct interactivetx_context *ictx,
bool *received_tx_complete) bool *received_tx_complete)
@ -326,26 +354,21 @@ char *process_interactivetx_updates(const tal_t *ctx,
char *error = NULL; char *error = NULL;
struct wally_psbt *next_psbt; struct wally_psbt *next_psbt;
if (ictx->current_psbt == NULL)
ictx->current_psbt = create_psbt(ictx, 0, 0, 0);
if (received_tx_complete) if (received_tx_complete)
they_complete = *received_tx_complete; they_complete = *received_tx_complete;
/* Build change_set and handle PSBT variables */ /* Build change_set and handle PSBT variables */
ictx->change_set = tal_free(ictx->change_set); ictx->change_set = tal_free(ictx->change_set);
/* Call next_update or default to 'desired_psbt' */ /* Call next_update_fn or default to 'desired_psbt' */
next_psbt = ictx->next_update(ictx, ictx); next_psbt = ictx->next_update_fn(ictx, ictx);
/* Returning NULL from next_update is the same as using `current_psbt` /* Returning NULL from next_update_fn is the same as using `current_psbt`
* with no changes -- both indicate no changes */ * with no changes -- both indicate no changes */
if (!next_psbt) if (!next_psbt)
next_psbt = ictx->current_psbt; next_psbt = ictx->current_psbt;
ictx->change_set = psbt_get_changeset(ictx, ictx->change_set = get_changes(ctx, ictx, next_psbt);
ictx->current_psbt,
next_psbt);
/* If current_psbt and next_psbt are the same, dont double free it! /* If current_psbt and next_psbt are the same, dont double free it!
* Otherwise we advance `current_psbt` to `next_psbt` and begin * Otherwise we advance `current_psbt` to `next_psbt` and begin
@ -353,7 +376,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
if (ictx->current_psbt != next_psbt) { if (ictx->current_psbt != next_psbt) {
/* psbt_get_changeset requires we keep the current_psbt until /* psbt_get_changeset requires we keep the current_psbt until
* we're done withh change_set */ * we're done withh change_set */
tal_steal(ictx->change_set, current_psbt); tal_steal(ictx->change_set, ictx->current_psbt);
ictx->current_psbt = next_psbt; ictx->current_psbt = next_psbt;
} }
@ -389,7 +412,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
t = fromwire_peektype(msg); t = fromwire_peektype(msg);
switch (t) { switch (t) {
case WIRE_TX_ADD_INPUT: { case WIRE_TX_ADD_INPUT: {
const u8 *tx_bytes, *redeemscript; const u8 *tx_bytes;
u32 sequence; u32 sequence;
size_t len; size_t len;
struct bitcoin_tx *tx; struct bitcoin_tx *tx;
@ -399,9 +422,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
&serial_id, &serial_id,
cast_const2(u8 **, cast_const2(u8 **,
&tx_bytes), &tx_bytes),
&outpoint.n, &sequence, &outpoint.n, &sequence))
cast_const2(u8 **,
&redeemscript)))
return tal_fmt(ctx, return tal_fmt(ctx,
"Parsing tx_add_input %s", "Parsing tx_add_input %s",
tal_hex(ctx, msg)); tal_hex(ctx, msg));
@ -435,7 +456,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
* the transaction * the transaction
*/ */
if (psbt_find_serial_input(ictx->current_psbt, serial_id) != -1) if (psbt_find_serial_input(ictx->current_psbt, serial_id) != -1)
return tal_fmt(ctx, "Duplicate serial_id rcvd." return tal_fmt(ctx, "Duplicate serial_id rcvd"
" %"PRIu64, serial_id); " %"PRIu64, serial_id);
/* Convert tx_bytes to a tx! */ /* Convert tx_bytes to a tx! */
@ -459,7 +480,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
*/ */
if (!is_segwit_output(ctx, if (!is_segwit_output(ctx,
&tx->wtx->outputs[outpoint.n], &tx->wtx->outputs[outpoint.n],
redeemscript)) NULL))
return tal_fmt(ctx, return tal_fmt(ctx,
"Invalid tx sent. Not SegWit %s", "Invalid tx sent. Not SegWit %s",
type_to_string(ctx, type_to_string(ctx,
@ -491,7 +512,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
*/ */
if (ictx->current_psbt->num_inputs + 1 > MAX_FUNDING_INPUTS) if (ictx->current_psbt->num_inputs + 1 > MAX_FUNDING_INPUTS)
return tal_fmt(ctx, "Too many inputs. Have %zu," return tal_fmt(ctx, "Too many inputs. Have %zu,"
" Max allowed %zu", " Max allowed %d",
ictx->current_psbt->num_inputs + 1, ictx->current_psbt->num_inputs + 1,
MAX_FUNDING_INPUTS); MAX_FUNDING_INPUTS);
@ -618,7 +639,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
*/ */
if (ictx->current_psbt->num_outputs + 1 > MAX_FUNDING_OUTPUTS) if (ictx->current_psbt->num_outputs + 1 > MAX_FUNDING_OUTPUTS)
return tal_fmt(ctx, "Too many inputs. Have %zu," return tal_fmt(ctx, "Too many inputs. Have %zu,"
" Max allowed %zu", " Max allowed %d",
ictx->current_psbt->num_outputs + 1, ictx->current_psbt->num_outputs + 1,
MAX_FUNDING_OUTPUTS); MAX_FUNDING_OUTPUTS);
@ -676,6 +697,8 @@ char *process_interactivetx_updates(const tal_t *ctx,
if (received_tx_complete) if (received_tx_complete)
*received_tx_complete = true; *received_tx_complete = true;
break; break;
case WIRE_TX_ABORT:
/* Todo */
case WIRE_INIT: case WIRE_INIT:
case WIRE_ERROR: case WIRE_ERROR:
case WIRE_WARNING: case WIRE_WARNING:
@ -683,7 +706,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
case WIRE_ACCEPT_CHANNEL: case WIRE_ACCEPT_CHANNEL:
case WIRE_FUNDING_CREATED: case WIRE_FUNDING_CREATED:
case WIRE_FUNDING_SIGNED: case WIRE_FUNDING_SIGNED:
case WIRE_FUNDING_LOCKED: case WIRE_CHANNEL_READY:
case WIRE_SHUTDOWN: case WIRE_SHUTDOWN:
case WIRE_CLOSING_SIGNED: case WIRE_CLOSING_SIGNED:
case WIRE_UPDATE_ADD_HTLC: case WIRE_UPDATE_ADD_HTLC:
@ -697,13 +720,12 @@ char *process_interactivetx_updates(const tal_t *ctx,
case WIRE_CHANNEL_REESTABLISH: case WIRE_CHANNEL_REESTABLISH:
case WIRE_ANNOUNCEMENT_SIGNATURES: case WIRE_ANNOUNCEMENT_SIGNATURES:
case WIRE_GOSSIP_TIMESTAMP_FILTER: case WIRE_GOSSIP_TIMESTAMP_FILTER:
case WIRE_OBS2_ONION_MESSAGE:
case WIRE_ONION_MESSAGE: case WIRE_ONION_MESSAGE:
case WIRE_TX_SIGNATURES: case WIRE_TX_SIGNATURES:
case WIRE_OPEN_CHANNEL2: case WIRE_OPEN_CHANNEL2:
case WIRE_ACCEPT_CHANNEL2: case WIRE_ACCEPT_CHANNEL2:
case WIRE_INIT_RBF: case WIRE_TX_INIT_RBF:
case WIRE_ACK_RBF: case WIRE_TX_ACK_RBF:
case WIRE_CHANNEL_ANNOUNCEMENT: case WIRE_CHANNEL_ANNOUNCEMENT:
case WIRE_CHANNEL_UPDATE: case WIRE_CHANNEL_UPDATE:
case WIRE_NODE_ANNOUNCEMENT: case WIRE_NODE_ANNOUNCEMENT:
@ -713,9 +735,12 @@ char *process_interactivetx_updates(const tal_t *ctx,
case WIRE_REPLY_SHORT_CHANNEL_IDS_END: case WIRE_REPLY_SHORT_CHANNEL_IDS_END:
case WIRE_PING: case WIRE_PING:
case WIRE_PONG: case WIRE_PONG:
case WIRE_STFU: case WIRE_PEER_STORAGE:
case WIRE_YOUR_PEER_STORAGE:
case WIRE_SPLICE: case WIRE_SPLICE:
case WIRE_SPLICE_ACK: case WIRE_SPLICE_ACK:
case WIRE_STFU:
case WIRE_SPLICE_LOCKED:
return tal_fmt(ctx, "Unexpected wire message %s", return tal_fmt(ctx, "Unexpected wire message %s",
tal_hex(ctx, msg)); tal_hex(ctx, msg));
} }
@ -727,5 +752,7 @@ char *process_interactivetx_updates(const tal_t *ctx,
/* Sort psbt! */ /* Sort psbt! */
psbt_sort_by_serial_id(ictx->current_psbt); psbt_sort_by_serial_id(ictx->current_psbt);
tal_steal(ictx, ictx->current_psbt);
return NULL; return NULL;
} }

View file

@ -26,9 +26,6 @@ enum tx_msgs {
struct interactivetx_context { struct interactivetx_context {
/* Users can set this to their own context */
void *ctx;
enum tx_role our_role; enum tx_role our_role;
struct per_peer_state *pps; struct per_peer_state *pps;
struct channel_id channel_id; struct channel_id channel_id;
@ -45,16 +42,14 @@ struct interactivetx_context {
* If no more changes are demanded, return NULL or current_psbt * If no more changes are demanded, return NULL or current_psbt
* unchanged to signal completion. * unchanged to signal completion.
*/ */
struct wally_psbt *(*next_update)(const tal_t *ctx, struct wally_psbt *(*next_update_fn)(const tal_t *ctx,
struct interactivetx_context *ictx); struct interactivetx_context *ictx);
/* Set this to the intial psbt. If NULL will be filled with an empty /* Set this to the intial psbt. Defaults to an empty PSBT. */
* psbt.
*/
struct wally_psbt *current_psbt; struct wally_psbt *current_psbt;
/* Optional field for storing your side's desired psbt state, to be /* Optional field for storing your side's desired psbt state, to be
* used inside 'next_update'. * used inside 'next_update_fn'.
*/ */
struct wally_psbt *desired_psbt; struct wally_psbt *desired_psbt;
@ -74,8 +69,8 @@ struct interactivetx_context *new_interactivetx_context(const tal_t *ctx,
struct channel_id channel_id); struct channel_id channel_id);
/* Blocks the thread until we run out of changes (and we send tx_complete), /* Blocks the thread until we run out of changes (and we send tx_complete),
* or an error occurs. If 'pause_when_complete' is set, this behavior changes * or an error occurs. If 'pause_when_complete' on the `interactivetx_context`
* and we return without sending tx_complete. * is set, this behavior changes and we return without sending tx_complete.
* *
* If received_tx_complete is not NULL: * If received_tx_complete is not NULL:
* in -> true means we assume we've received tx_complete in a previous round. * in -> true means we assume we've received tx_complete in a previous round.
@ -87,4 +82,11 @@ char *process_interactivetx_updates(const tal_t *ctx,
struct interactivetx_context *ictx, struct interactivetx_context *ictx,
bool *received_tx_complete); bool *received_tx_complete);
/* If the given ictx would cause `process_interactivetx_updates to send tx
* changes when called. Returns true if an error occurs
* (call `process_interactivetx_updates` for a description of the error).
*/
bool interactivetx_has_changes(struct interactivetx_context *ictx,
struct wally_psbt *next_psbt);
#endif /* LIGHTNING_COMMON_INTERACTIVETX_H */ #endif /* LIGHTNING_COMMON_INTERACTIVETX_H */