core-lightning/lightningd/watch.c
Rusty Russell a2b0212d50 lightningd: handle duplicate watches on the same thing correctly.
Our hash tables allow duplicate keys, and we use that in a few places.
However, the get() function only returns the first, so it's not a good
idea with such hash tables.

Another patch fixes this at a deeper level (using different hash table
types depending on whether this table can have duplicates), but this
is the minimal fix for existing code.

This may be the cause behind us occasionally missing onchain events:

Fixes: https://github.com/ElementsProject/lightning/issues/7460
Fixes: https://github.com/ElementsProject/lightning/issues/7377
Fixes: https://github.com/ElementsProject/lightning/issues/7118
Fixes: https://github.com/ElementsProject/lightning/issues/6951

This fixes them in future: fixing them now will require something else.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
Changelog-Fixed: lightningd: occasionally we could miss transaction outputs (not telling gossipd, or even onchaind)
2024-08-13 07:25:02 -07:00

319 lines
7.5 KiB
C

/* Code to talk to bitcoind to watch for various events.
*
* Here's what we want to know:
*
* - An anchor tx:
* - Reached given depth
* - Times out.
* - Is unspent after reaching given depth.
*
* - Our own commitment tx:
* - Reached a given depth.
*
* - HTLC spend tx:
* - Reached a given depth.
*
* - Anchor tx output:
* - Is spent by their current tx.
* - Is spent by a revoked tx.
*
* - Commitment tx HTLC outputs:
* - HTLC timed out
* - HTLC spent
*
* - Payments to invoice fallback addresses:
* - Reached a given depth.
*
* We do this by adding the P2SH address to the wallet, and then querying
* that using listtransactions.
*
* WE ASSUME NO MALLEABILITY! This requires segregated witness.
*/
#include "config.h"
#include <bitcoin/psbt.h>
#include <lightningd/chaintopology.h>
#include <lightningd/channel.h>
#include <lightningd/lightningd.h>
#include <lightningd/watch.h>
/* Watching an output */
struct txowatch {
struct chain_topology *topo;
/* Channel who owns us. */
struct channel *channel;
/* Output to watch. */
struct bitcoin_outpoint out;
/* A new tx. */
enum watch_result (*cb)(struct channel *channel,
const struct bitcoin_tx *tx,
size_t input_num,
const struct block *block);
};
struct txwatch {
struct chain_topology *topo;
/* Transaction to watch. */
struct bitcoin_txid txid;
/* May be NULL if we haven't seen it yet. */
const struct bitcoin_tx *tx;
int depth;
/* A new depth (0 if kicked out, otherwise 1 = tip, etc.) */
enum watch_result (*cb)(struct lightningd *ld,
const struct bitcoin_txid *txid,
const struct bitcoin_tx *tx,
unsigned int depth,
void *arg);
void *cbarg;
};
const struct bitcoin_outpoint *txowatch_keyof(const struct txowatch *w)
{
return &w->out;
}
size_t txo_hash(const struct bitcoin_outpoint *out)
{
/* This hash-in-one-go trick only works if they're consecutive. */
BUILD_ASSERT(offsetof(struct bitcoin_outpoint, n)
== sizeof(((struct bitcoin_outpoint *)NULL)->txid));
return siphash24(siphash_seed(), out,
sizeof(out->txid) + sizeof(out->n));
}
bool txowatch_eq(const struct txowatch *w, const struct bitcoin_outpoint *out)
{
return bitcoin_txid_eq(&w->out.txid, &out->txid)
&& w->out.n == out->n;
}
static void destroy_txowatch(struct txowatch *w)
{
txowatch_hash_del(w->topo->txowatches, w);
}
const struct bitcoin_txid *txwatch_keyof(const struct txwatch *w)
{
return &w->txid;
}
size_t txid_hash(const struct bitcoin_txid *txid)
{
return siphash24(siphash_seed(),
txid->shad.sha.u.u8, sizeof(txid->shad.sha.u.u8));
}
bool txwatch_eq(const struct txwatch *w, const struct bitcoin_txid *txid)
{
return bitcoin_txid_eq(&w->txid, txid);
}
static void destroy_txwatch(struct txwatch *w)
{
txwatch_hash_del(w->topo->txwatches, w);
}
struct txwatch *watch_txid_(const tal_t *ctx,
struct chain_topology *topo,
const struct bitcoin_txid *txid,
enum watch_result (*cb)(struct lightningd *ld,
const struct bitcoin_txid *,
const struct bitcoin_tx *,
unsigned int depth,
void *arg),
void *arg)
{
struct txwatch *w;
w = tal(ctx, struct txwatch);
w->topo = topo;
w->depth = -1;
w->txid = *txid;
w->tx = NULL;
w->cb = cb;
w->cbarg = arg;
txwatch_hash_add(w->topo->txwatches, w);
tal_add_destructor(w, destroy_txwatch);
return w;
}
struct txwatch *find_txwatch_(struct chain_topology *topo,
const struct bitcoin_txid *txid,
enum watch_result (*cb)(struct lightningd *ld,
const struct bitcoin_txid *,
const struct bitcoin_tx *,
unsigned int depth,
void *arg),
void *arg)
{
struct txwatch_hash_iter i;
struct txwatch *w;
/* We could have more than one channel watching same txid, though we
* don't for onchaind. */
for (w = txwatch_hash_getfirst(topo->txwatches, txid, &i);
w;
w = txwatch_hash_getnext(topo->txwatches, txid, &i)) {
if (w->cb == cb && w->cbarg == arg)
break;
}
return w;
}
bool watching_txid(const struct chain_topology *topo,
const struct bitcoin_txid *txid)
{
return txwatch_hash_get(topo->txwatches, txid) != NULL;
}
struct txowatch *watch_txo(const tal_t *ctx,
struct chain_topology *topo,
struct channel *channel,
const struct bitcoin_outpoint *outpoint,
enum watch_result (*cb)(struct channel *channel_,
const struct bitcoin_tx *tx,
size_t input_num,
const struct block *block))
{
struct txowatch *w = tal(ctx, struct txowatch);
w->topo = topo;
w->out = *outpoint;
w->channel = channel;
w->cb = cb;
txowatch_hash_add(w->topo->txowatches, w);
tal_add_destructor(w, destroy_txowatch);
return w;
}
/* Returns true if we fired a callback */
static bool txw_fire(struct txwatch *txw,
const struct bitcoin_txid *txid,
unsigned int depth)
{
enum watch_result r;
if (depth == txw->depth)
return false;
if (txw->depth == -1) {
log_debug(txw->topo->log,
"Got first depth change ->%u for %s",
txw->depth,
fmt_bitcoin_txid(tmpctx, &txw->txid));
} else {
/* zero depth signals a reorganization */
log_debug(txw->topo->log,
"Got depth change %u->%u for %s%s",
txw->depth, depth,
fmt_bitcoin_txid(tmpctx, &txw->txid),
depth ? "" : " REORG");
}
txw->depth = depth;
r = txw->cb(txw->topo->bitcoind->ld, txid, txw->tx, txw->depth,
txw->cbarg);
switch (r) {
case DELETE_WATCH:
tal_free(txw);
return true;
case KEEP_WATCHING:
return true;
}
fatal("txwatch callback %p returned %i\n", txw->cb, r);
}
void txwatch_fire(struct chain_topology *topo,
const struct bitcoin_txid *txid,
unsigned int depth)
{
struct txwatch_hash_iter it;
for (struct txwatch *txw = txwatch_hash_getfirst(topo->txwatches, txid, &it);
txw;
txw = txwatch_hash_getnext(topo->txwatches, txid, &it)) {
txw_fire(txw, txid, depth);
}
}
void txowatch_fire(const struct txowatch *txow,
const struct bitcoin_tx *tx,
size_t input_num,
const struct block *block)
{
struct bitcoin_txid txid;
enum watch_result r;
bitcoin_txid(tx, &txid);
log_debug(txow->channel->log,
"Got UTXO spend for %s:%u: %s",
fmt_bitcoin_txid(tmpctx, &txow->out.txid),
txow->out.n,
fmt_bitcoin_txid(tmpctx, &txid));
r = txow->cb(txow->channel, tx, input_num, block);
switch (r) {
case DELETE_WATCH:
tal_free(txow);
return;
case KEEP_WATCHING:
return;
}
fatal("txowatch callback %p returned %i", txow->cb, r);
}
void watch_topology_changed(struct chain_topology *topo)
{
struct txwatch_hash_iter i;
struct txwatch *w;
bool needs_rerun;
do {
/* Iterating a htable during deletes is safe, but might skip entries. */
needs_rerun = false;
for (w = txwatch_hash_first(topo->txwatches, &i);
w;
w = txwatch_hash_next(topo->txwatches, &i)) {
u32 depth;
depth = get_tx_depth(topo, &w->txid);
if (depth) {
if (!w->tx)
w->tx = wallet_transaction_get(w, topo->ld->wallet,
&w->txid);
needs_rerun |= txw_fire(w, &w->txid, depth);
}
}
} while (needs_rerun);
}
void txwatch_inform(const struct chain_topology *topo,
const struct bitcoin_txid *txid,
struct bitcoin_tx *tx TAKES)
{
struct txwatch_hash_iter it;
for (struct txwatch *txw = txwatch_hash_getfirst(topo->txwatches, txid, &it);
txw;
txw = txwatch_hash_getnext(topo->txwatches, txid, &it)) {
if (txw->tx)
continue;
/* FIXME: YUCK! These don't have PSBTs attached */
if (!tx->psbt)
tx->psbt = new_psbt(tx, tx->wtx);
txw->tx = clone_bitcoin_tx(txw, tx);
}
/* If we don't clone above, handle take() now */
if (taken(tx))
tal_free(tx);
}