onchaind: infrastructure to offload tx creation to lightningd.

Since we do both our own internal handling and handing it to
lightningd, we add to `proposed_resolution` to handle the lightningd
case.

Note, in particular, that we fix the blockheight calculation: it's out
by one, in that if we see a tx and our CSV lock is 5, we only need to
wait 4 more blocks, not 5.  This will matter as we start using it, and
convert the tests.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2023-04-06 09:03:24 +09:30
parent 956e6c4055
commit 86e044a9a8
3 changed files with 147 additions and 14 deletions

View file

@ -2,6 +2,7 @@
#include <bitcoin/feerate.h>
#include <bitcoin/script.h>
#include <ccan/asort/asort.h>
#include <ccan/cast/cast.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <common/htlc_tx.h>
@ -91,8 +92,13 @@ static u32 min_relay_feerate;
/* If we broadcast a tx, or need a delay to resolve the output. */
struct proposed_resolution {
/* This can be NULL if our proposal is to simply ignore it after depth */
/* flag indicating we are a modern resolution, sent to lightningd. */
bool via_lightningd;
/* Obsolete: if we created tx ourselves: */
const struct bitcoin_tx *tx;
/* Once we had lightningd create tx, here's what it told us
* witnesses were (we ignore sigs!). */
const struct onchain_witness_element **welements;
/* Non-zero if this is CSV-delayed. */
u32 depth_required;
enum tx_type tx_type;
@ -343,8 +349,13 @@ static void record_ignored_wallet_deposit(struct tracked_output *out)
{
struct bitcoin_outpoint outpoint;
/* FIXME: Would be clearer to omit the txid field, BUT the
* tests seem to assume it's there, and things break */
if (!out->proposal->tx)
memset(&outpoint.txid, 0, sizeof(outpoint.txid));
else
bitcoin_txid(out->proposal->tx, &outpoint.txid);
/* Every spend tx we construct has a single output. */
bitcoin_txid(out->proposal->tx, &outpoint.txid);
outpoint.n = 0;
enum mvt_tag tag = TO_WALLET;
@ -1134,6 +1145,24 @@ static void proposal_should_rbf(struct tracked_output *out)
}
}
static void handle_spend_created(struct tracked_output *out, const u8 *msg)
{
struct onchain_witness_element **witness;
bool worthwhile;
if (!fromwire_onchaind_spend_created(tmpctx, msg, &worthwhile, &witness))
master_badmsg(WIRE_ONCHAIND_SPEND_CREATED, msg);
out->proposal->welements
= cast_const2(const struct onchain_witness_element **,
tal_steal(out->proposal, witness));
/* Did it decide it's not worth it? Don't wait for it. */
if (!worthwhile)
ignore_output(out);
}
/* For old-style outputs where we've made our own txs. */
static void proposal_meets_depth(struct tracked_output *out)
{
assert(out->proposal);
@ -1173,7 +1202,7 @@ static void proposal_meets_depth(struct tracked_output *out)
}
static void propose_resolution(struct tracked_output *out,
const struct bitcoin_tx *tx,
const struct bitcoin_tx *tx STEALS,
unsigned int depth_required,
enum tx_type tx_type)
{
@ -1186,6 +1215,8 @@ static void propose_resolution(struct tracked_output *out,
out->proposal = tal(out, struct proposed_resolution);
out->proposal->tx = tal_steal(out->proposal, tx);
out->proposal->via_lightningd = false;
out->proposal->welements = NULL;
out->proposal->depth_required = depth_required;
out->proposal->tx_type = tx_type;
@ -1194,7 +1225,7 @@ static void propose_resolution(struct tracked_output *out,
}
static void propose_resolution_at_block(struct tracked_output *out,
const struct bitcoin_tx *tx,
const struct bitcoin_tx *tx STEALS,
unsigned int block_required,
enum tx_type tx_type)
{
@ -1208,6 +1239,36 @@ static void propose_resolution_at_block(struct tracked_output *out,
propose_resolution(out, tx, depth, tx_type);
}
/* Modern style: we don't create tx outselves, but tell lightningd. */
static void UNNEEDED propose_resolution_to_master(struct tracked_output *out,
const u8 *send_message TAKES,
unsigned int block_required,
enum tx_type tx_type)
{
/* i.e. we want this in @block_required, so it will be broadcast by
* lightningd after it sees @block_required - 1. */
status_debug("Telling lightningd about %s to resolve %s/%s"
" after block %u (%i more blocks)",
tx_type_name(tx_type),
tx_type_name(out->tx_type),
output_type_name(out->output_type),
block_required - 1, block_required - 1 - out->tx_blockheight);
out->proposal = tal(out, struct proposed_resolution);
out->proposal->via_lightningd = true;
out->proposal->tx = NULL;
out->proposal->welements = NULL;
out->proposal->tx_type = tx_type;
out->proposal->depth_required = block_required - out->tx_blockheight;
wire_sync_write(REQ_FD, send_message);
/* Get reply now: if we're replaying, tx could be included before we
* tell lightningd about it, so we need to recognize it! */
handle_spend_created(out,
queue_until_msg(tmpctx, WIRE_ONCHAIND_SPEND_CREATED));
}
static bool is_valid_sig(const u8 *e)
{
struct bitcoin_signature sig;
@ -1254,22 +1315,79 @@ static bool input_similar(const struct wally_tx_input *i1,
return true;
}
/* This simple case: true if this was resolved by our proposal. */
static bool resolved_by_proposal(struct tracked_output *out,
const struct tx_parts *tx_parts)
static bool resolved_by_our_tx(const struct bitcoin_tx *tx,
const struct tx_parts *tx_parts)
{
/* If there's no TX associated, it's not us. */
if (!out->proposal->tx)
return false;
/* Our proposal can change as feerates change. Input
* comparison (ignoring signatures) works pretty well. */
if (tal_count(tx_parts->inputs) != out->proposal->tx->wtx->num_inputs)
if (tal_count(tx_parts->inputs) != tx->wtx->num_inputs)
return false;
for (size_t i = 0; i < tal_count(tx_parts->inputs); i++) {
if (!input_similar(tx_parts->inputs[i],
&out->proposal->tx->wtx->inputs[i]))
&tx->wtx->inputs[i]))
return false;
}
return true;
}
/* Do any of these tx_parts spend this outpoint? If so, return it */
static const struct wally_tx_input *
which_input_spends(const struct tx_parts *tx_parts,
const struct bitcoin_outpoint *outpoint)
{
for (size_t i = 0; i < tal_count(tx_parts->inputs); i++) {
struct bitcoin_outpoint o;
if (!tx_parts->inputs[i])
continue;
wally_tx_input_get_outpoint(tx_parts->inputs[i], &o);
if (!bitcoin_outpoint_eq(&o, outpoint))
continue;
return tx_parts->inputs[i];
}
return NULL;
}
/* Does this tx input's witness match the witness we expected? */
static bool onchain_witness_element_matches(const struct onchain_witness_element **welements,
const struct wally_tx_input *input)
{
const struct wally_tx_witness_stack *stack = input->witness;
if (stack->num_items != tal_count(welements))
return false;
for (size_t i = 0; i < stack->num_items; i++) {
/* Don't compare signatures: they can change with
* other details */
if (welements[i]->is_signature)
continue;
if (!memeq(stack->items[i].witness,
stack->items[i].witness_len,
welements[i]->witness,
tal_bytelen(welements[i]->witness)))
return false;
}
return true;
}
/* This simple case: true if this was resolved by our proposal. */
static bool resolved_by_proposal(struct tracked_output *out,
const struct tx_parts *tx_parts)
{
/* Old case: we made the tx ourselves, so we compare that. */
if (out->proposal->tx) {
if (!resolved_by_our_tx(out->proposal->tx, tx_parts))
return false;
} else {
const struct wally_tx_input *input;
/* If there's no TX associated, it's not us. */
if (!out->proposal->welements)
return false;
input = which_input_spends(tx_parts, &out->outpoint);
if (!input)
return false;
if (!onchain_witness_element_matches(out->proposal->welements,
input))
return false;
}
@ -1399,9 +1517,17 @@ static size_t num_not_irrevocably_resolved(struct tracked_output **outs)
return num;
}
/* If a tx spends @out, and is CSV delayed by @delay, what's the first
* block it can get into? */
static u32 rel_blockheight(const struct tracked_output *out, u32 delay)
{
return out->tx_blockheight + delay;
}
/* What is the first block that the proposal can get into? */
static u32 prop_blockheight(const struct tracked_output *out)
{
return out->tx_blockheight + out->proposal->depth_required;
return rel_blockheight(out, out->proposal->depth_required);
}
static void billboard_update(struct tracked_output **outs)
@ -1925,6 +2051,7 @@ static void tx_new_depth(struct tracked_output **outs,
/* Otherwise, is this something we have a pending
* resolution for? */
if (outs[i]->proposal
&& !outs[i]->proposal->via_lightningd
&& bitcoin_txid_eq(&outs[i]->outpoint.txid, txid)
&& depth >= outs[i]->proposal->depth_required) {
proposal_meets_depth(outs[i]);

View file

@ -54,6 +54,9 @@ bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, s
/* Generated stub for fromwire_onchaind_known_preimage */
bool fromwire_onchaind_known_preimage(const void *p UNNEEDED, struct preimage *preimage UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_known_preimage called!\n"); abort(); }
/* Generated stub for fromwire_onchaind_spend_created */
bool fromwire_onchaind_spend_created(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, bool *expect_to_succeed UNNEEDED, struct onchain_witness_element ***witness UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_spend_created called!\n"); abort(); }
/* Generated stub for fromwire_onchaind_spent */
bool fromwire_onchaind_spent(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct tx_parts **tx UNNEEDED, u32 *input_num UNNEEDED, u32 *blockheight UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_spent called!\n"); abort(); }

View file

@ -59,6 +59,9 @@ bool fromwire_onchaind_init(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, s
/* Generated stub for fromwire_onchaind_known_preimage */
bool fromwire_onchaind_known_preimage(const void *p UNNEEDED, struct preimage *preimage UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_known_preimage called!\n"); abort(); }
/* Generated stub for fromwire_onchaind_spend_created */
bool fromwire_onchaind_spend_created(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, bool *expect_to_succeed UNNEEDED, struct onchain_witness_element ***witness UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_spend_created called!\n"); abort(); }
/* Generated stub for fromwire_onchaind_spent */
bool fromwire_onchaind_spent(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct tx_parts **tx UNNEEDED, u32 *input_num UNNEEDED, u32 *blockheight UNNEEDED)
{ fprintf(stderr, "fromwire_onchaind_spent called!\n"); abort(); }