From f4e434d8e17df2400b1f5f8a8e08192801e00716 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Mon, 5 Aug 2019 18:33:08 +0200 Subject: [PATCH] bitcoind: Add a multi-step getfilteredblock method This will eventually replace the multi-step `getblockhash` + `getblock` + `gettxout` mechanism, and return entire filtered blocks which can be added to the DB, and represent the full set of P2WSH UTXOs. Signed-off-by: Christian Decker --- lightningd/bitcoind.c | 119 ++++++++++++++++++++++++++++++++++++++++++ lightningd/bitcoind.h | 31 +++++++++++ 2 files changed, 150 insertions(+) diff --git a/lightningd/bitcoind.c b/lightningd/bitcoind.c index 2dc85f072..722a1ef21 100644 --- a/lightningd/bitcoind.c +++ b/lightningd/bitcoind.c @@ -2,6 +2,7 @@ #include "bitcoin/base58.h" #include "bitcoin/block.h" #include "bitcoin/feerate.h" +#include "bitcoin/script.h" #include "bitcoin/shadouble.h" #include "bitcoind.h" #include "lightningd.h" @@ -784,6 +785,124 @@ void bitcoind_gettxout(struct bitcoind *bitcoind, NULL); } +/* Context for the getfilteredblock call. Wraps the actual arguments while we + * process the various steps. */ +struct filteredblock_call { + void (*cb)(struct bitcoind *bitcoind, struct filteredblock *fb, + void *arg); + void *arg; + + struct filteredblock *result; + struct filteredblock_outpoint **outpoints; + size_t current_outpoint; + struct timeabs start_time; +}; + +static void +process_getfilteredblock_step3(struct bitcoind *bitcoind, + const struct bitcoin_tx_output *output, + void *arg) +{ + struct filteredblock_call *call = (struct filteredblock_call *)arg; + struct filteredblock_outpoint *o = call->outpoints[call->current_outpoint]; + + /* If this output is unspent, add it to the filteredblock result. */ + if (output) + tal_arr_expand(&call->result->outpoints, tal_steal(call->result, o)); + + call->current_outpoint++; + if (call->current_outpoint < tal_count(call->outpoints)) { + o = call->outpoints[call->current_outpoint]; + bitcoind_gettxout(bitcoind, &o->txid, o->outnum, + process_getfilteredblock_step3, call); + } else { + /* If there were no more outpoints to check, we call the callback. */ + call->cb(bitcoind, call->result, call->arg); + tal_free(call); + } +} + +static void process_getfilteredblock_step2(struct bitcoind *bitcoind, + struct bitcoin_block *block, + struct filteredblock_call *call) +{ + struct filteredblock_outpoint *o; + struct bitcoin_tx *tx; + call->result->prev_hash = block->hdr.prev_hash; + + /* Allocate an array containing all the potentially interesting + * outpoints. We will later copy the ones we're interested in into the + * call->result if they are unspent. */ + + call->outpoints = tal_arr(call, struct filteredblock_outpoint *, 0); + for (size_t i = 0; i < tal_count(block->tx); i++) { + tx = block->tx[i]; + for (size_t j = 0; j < tx->wtx->num_outputs; j++) { + const u8 *script = bitcoin_tx_output_get_script(NULL, tx, j); + if (is_p2wsh(script, NULL)) { + /* This is an interesting output, remember it. */ + o = tal(call->outpoints, struct filteredblock_outpoint); + bitcoin_txid(tx, &o->txid); + o->satoshis = bitcoin_tx_output_get_amount(tx, j); + o->txindex = i; + o->outnum = j; + o->scriptPubKey = tal_steal(o, script); + tal_arr_expand(&call->outpoints, o); + } else { + tal_free(script); + } + } + } + + call->result->outpoints = tal_arr(call->result, struct filteredblock_outpoint *, 0); + call->current_outpoint = 0; + if (tal_count(call->outpoints) == 0) { + /* If there were no outpoints to check, we can short-circuit + * and just call the callback. */ + call->cb(bitcoind, call->result, call->arg); + tal_free(call); + } else { + + /* Otherwise we start iterating through call->outpoints and + * store the one's that are unspent in + * call->result->outpoints. */ + o = call->outpoints[call->current_outpoint]; + bitcoind_gettxout(bitcoind, &o->txid, o->outnum, + process_getfilteredblock_step3, call); + } +} + +static void process_getfilteredblock_step1(struct bitcoind *bitcoind, + const struct bitcoin_blkid *blkid, + struct filteredblock_call *call) +{ + /* So we have the first piece of the puzzle, the block hash */ + call->result->id = *blkid; + + /* Now get the raw block to get all outpoints that were created in + * this block. */ + bitcoind_getrawblock(bitcoind, blkid, process_getfilteredblock_step2, call); +} + +void bitcoind_getfilteredblock_(struct bitcoind *bitcoind, u32 height, + void (*cb)(struct bitcoind *bitcoind, + struct filteredblock *fb, + void *arg), + void *arg) +{ + /* Stash the call context for when we need to call the callback after + * all the bitcoind calls we need to perform. */ + struct filteredblock_call *call = tal(bitcoind, struct filteredblock_call); + call->cb = cb; + call->arg = arg; + call->result = tal(call, struct filteredblock); + assert(call->cb != NULL); + call->start_time = time_now(); + call->result->height = height; + + bitcoind_getblockhash(bitcoind, height, process_getfilteredblock_step1, call); +} + static bool extract_numeric_version(struct bitcoin_cli *bcli, const char *output, size_t output_bytes, u64 *version) diff --git a/lightningd/bitcoind.h b/lightningd/bitcoind.h index 53fa82459..6a71cf44b 100644 --- a/lightningd/bitcoind.h +++ b/lightningd/bitcoind.h @@ -67,6 +67,23 @@ struct bitcoind { char *rpcuser, *rpcpass, *rpcconnect, *rpcport; }; +/* A single outpoint in a filtered block */ +struct filteredblock_outpoint { + struct bitcoin_txid txid; + u32 outnum; + u32 txindex; + const u8 *scriptPubKey; + struct amount_sat satoshis; +}; + +/* A struct representing a block with most of the parts filtered out. */ +struct filteredblock { + struct bitcoin_blkid id; + u32 height; + struct bitcoin_blkid prev_hash; + struct filteredblock_outpoint **outpoints; +}; + struct bitcoind *new_bitcoind(const tal_t *ctx, struct lightningd *ld, struct log *log); @@ -132,6 +149,20 @@ void bitcoind_getblockhash_(struct bitcoind *bitcoind, const struct bitcoin_blkid *), \ (arg)) +void bitcoind_getfilteredblock_(struct bitcoind *bitcoind, u32 height, + void (*cb)(struct bitcoind *bitcoind, + struct filteredblock *fb, + void *arg), + void *arg); +#define bitcoind_getfilteredblock(bitcoind_, height, cb, arg) \ + bitcoind_getfilteredblock_((bitcoind_), \ + (height), \ + typesafe_cb_preargs(void, void *, \ + (cb), (arg), \ + struct bitcoind *, \ + const struct filteredblock *), \ + (arg)) + void bitcoind_getrawblock_(struct bitcoind *bitcoind, const struct bitcoin_blkid *blockid, void (*cb)(struct bitcoind *bitcoind,