diff --git a/lightningd/chaintopology.c b/lightningd/chaintopology.c index 7f08da9503c3..532c34cc6ead 100644 --- a/lightningd/chaintopology.c +++ b/lightningd/chaintopology.c @@ -59,8 +59,10 @@ static void filter_block_txs(struct chain_topology *topo, struct block *b) out.index = tx->input[j].index; txo = txowatch_hash_get(&topo->txowatches, &out); - if (txo) + if (txo) { + wallet_transaction_add(topo->wallet, tx, b->height, i); txowatch_fire(txo, tx, j, b); + } } satoshi_owned = 0; @@ -517,17 +519,18 @@ static void get_init_block(struct bitcoind *bitcoind, static void get_init_blockhash(struct bitcoind *bitcoind, u32 blockcount, struct chain_topology *topo) { - /* This can happen if bitcoind still syncing, or first_blocknum is MAX */ - if (blockcount < topo->first_blocknum) - topo->first_blocknum = blockcount; - - /* For fork protection (esp. because we don't handle our own first - * block getting reorged out), we always go 100 blocks further back - * than we need. */ - if (topo->first_blocknum < 100) - topo->first_blocknum = 0; - else - topo->first_blocknum -= 100; + /* If bitcoind's current blockheight is below the requested height, just + * go back to that height. This might be a new node catching up, or + * bitcoind is processing a reorg. */ + if (blockcount < topo->first_blocknum) { + if (bitcoind->ld->config.rescan < 0) { + /* Absolute blockheight, but bitcoind's blockheight isn't there yet */ + topo->first_blocknum = blockcount - 1; + } else if (topo->first_blocknum == UINT32_MAX) { + /* Relative rescan, but we didn't know the blockheight */ + topo->first_blocknum = blockcount - bitcoind->ld->config.rescan; + } + } /* Rollback to the given blockheight, so we start track * correctly again */ diff --git a/lightningd/channel.c b/lightningd/channel.c index 87325a885471..d43f5d19c6ce 100644 --- a/lightningd/channel.c +++ b/lightningd/channel.c @@ -265,6 +265,19 @@ struct channel *active_channel_by_id(struct lightningd *ld, return peer_active_channel(peer); } +struct channel *channel_by_dbid(struct lightningd *ld, const u64 dbid) +{ + struct peer *p; + struct channel *chan; + list_for_each(&ld->peers, p, list) { + list_for_each(&p->channels, chan, list) { + if (chan->dbid == dbid) + return chan; + } + } + return NULL; +} + void channel_set_last_tx(struct channel *channel, struct bitcoin_tx *tx, const secp256k1_ecdsa_signature *sig) diff --git a/lightningd/channel.h b/lightningd/channel.h index b178afbf80c8..291d037cd662 100644 --- a/lightningd/channel.h +++ b/lightningd/channel.h @@ -165,6 +165,8 @@ struct channel *active_channel_by_id(struct lightningd *ld, const struct pubkey *id, struct uncommitted_channel **uc); +struct channel *channel_by_dbid(struct lightningd *ld, const u64 dbid); + void channel_set_last_tx(struct channel *channel, struct bitcoin_tx *tx, const secp256k1_ecdsa_signature *sig); diff --git a/lightningd/lightningd.c b/lightningd/lightningd.c index d671cf712780..e7b4109cdb22 100644 --- a/lightningd/lightningd.c +++ b/lightningd/lightningd.c @@ -30,6 +30,7 @@ #include <lightningd/invoice.h> #include <lightningd/jsonrpc.h> #include <lightningd/log.h> +#include <lightningd/onchain_control.h> #include <lightningd/options.h> #include <onchaind/onchain_wire.h> #include <signal.h> @@ -275,7 +276,7 @@ int main(int argc, char *argv[]) { struct lightningd *ld; bool newdir; - u32 first_blocknum; + u32 blockheight; daemon_setup(argv[0], log_backtrace_print, log_backtrace_exit); ld = new_lightningd(NULL); @@ -350,10 +351,20 @@ int main(int argc, char *argv[]) if (!wallet_htlcs_reconnect(ld->wallet, &ld->htlcs_in, &ld->htlcs_out)) fatal("could not reconnect htlcs loaded from wallet, wallet may be inconsistent."); - /* Worst case, scan back to the first lightning deployment */ - first_blocknum = wallet_first_blocknum(ld->wallet, - get_chainparams(ld) - ->when_lightning_became_cool); + /* Get the blockheight we are currently at, UINT32_MAX is used to signal + * an unitialized wallet and that we should start off of bitcoind's + * current height */ + blockheight = wallet_blocks_height(ld->wallet, UINT32_MAX); + + /* If we were asked to rescan from an absolute height (--rescan < 0) + * then just go there. Otherwise take compute the diff to our current + * height, lowerbounded by 0. */ + if (ld->config.rescan < 0) + blockheight = -ld->config.rescan; + else if (blockheight < (u32)ld->config.rescan) + blockheight = 0; + else if (blockheight != UINT32_MAX) + blockheight -= ld->config.rescan; db_commit_transaction(ld->wallet->db); @@ -361,7 +372,10 @@ int main(int argc, char *argv[]) setup_topology(ld->topology, &ld->timers, ld->config.poll_time, - first_blocknum); + blockheight); + + /* Replay transactions for all running onchainds */ + onchaind_replay_channels(ld); /* Create RPC socket (if any) */ setup_jsonrpc(ld, ld->rpc_filename); diff --git a/lightningd/lightningd.h b/lightningd/lightningd.h index f1e9ebce912f..7f683677242d 100644 --- a/lightningd/lightningd.h +++ b/lightningd/lightningd.h @@ -72,6 +72,10 @@ struct config { /* Do we let the funder set any fee rate they want */ bool ignore_fee_limits; + + /* Number of blocks to rescan from the current head, or absolute + * blockheight if rescan >= 500'000 */ + s32 rescan; }; struct lightningd { diff --git a/lightningd/onchain_control.c b/lightningd/onchain_control.c index 293af7c8a43c..52ad3f4eb80b 100644 --- a/lightningd/onchain_control.c +++ b/lightningd/onchain_control.c @@ -3,13 +3,11 @@ #include <errno.h> #include <inttypes.h> #include <lightningd/chaintopology.h> -#include <lightningd/lightningd.h> #include <lightningd/log.h> #include <lightningd/onchain_control.h> #include <lightningd/peer_control.h> #include <lightningd/subd.h> #include <lightningd/watch.h> -#include <onchaind/gen_onchain_wire.h> #include <onchaind/onchain_wire.h> /* We dump all the known preimages when onchaind starts up. */ @@ -47,21 +45,32 @@ static void onchaind_tell_fulfill(struct channel *channel) } } -static void handle_onchain_init_reply(struct channel *channel, const u8 *msg) +static void handle_onchain_init_reply(struct channel *channel, const u8 *msg UNUSED) { /* FIXME: We may already be ONCHAIN state when we implement restart! */ channel_set_state(channel, FUNDING_SPEND_SEEN, ONCHAIN); +} - /* Tell it about any preimages we know. */ - onchaind_tell_fulfill(channel); +/** + * Notify onchaind about the depth change of the watched tx. + */ +static void onchain_tx_depth(struct channel *channel, + const struct bitcoin_txid *txid, + unsigned int depth) +{ + u8 *msg; + msg = towire_onchain_depth(channel, txid, depth); + subd_send_msg(channel->owner, take(msg)); } +/** + * Entrypoint for the txwatch callback, calls onchain_tx_depth. + */ static enum watch_result onchain_tx_watched(struct channel *channel, const struct bitcoin_txid *txid, unsigned int depth) { - u8 *msg; - + u32 blockheight = channel->peer->ld->topology->tip->height; if (depth == 0) { log_unusual(channel->log, "Chain reorganization!"); channel_set_owner(channel, NULL); @@ -74,25 +83,48 @@ static enum watch_result onchain_tx_watched(struct channel *channel, return KEEP_WATCHING; } - msg = towire_onchain_depth(channel, txid, depth); - subd_send_msg(channel->owner, take(msg)); + /* Store the channeltx so we can replay later */ + wallet_channeltxs_add(channel->peer->ld->wallet, channel, + WIRE_ONCHAIN_DEPTH, txid, 0, blockheight); + + onchain_tx_depth(channel, txid, depth); return KEEP_WATCHING; } static void watch_tx_and_outputs(struct channel *channel, const struct bitcoin_tx *tx); +/** + * Notify onchaind that an output was spent and register new watches. + */ +static void onchain_txo_spent(struct channel *channel, const struct bitcoin_tx *tx, size_t input_num, u32 blockheight) +{ + u8 *msg; + + watch_tx_and_outputs(channel, tx); + + msg = towire_onchain_spent(channel, tx, input_num, blockheight); + subd_send_msg(channel->owner, take(msg)); + +} + +/** + * Entrypoint for the txowatch callback, stores tx and calls onchain_txo_spent. + */ static enum watch_result onchain_txo_watched(struct channel *channel, const struct bitcoin_tx *tx, size_t input_num, const struct block *block) { - u8 *msg; + struct bitcoin_txid txid; + bitcoin_txid(tx, &txid); - watch_tx_and_outputs(channel, tx); + /* Store the channeltx so we can replay later */ + wallet_channeltxs_add(channel->peer->ld->wallet, channel, + WIRE_ONCHAIN_SPENT, &txid, input_num, + block->height); - msg = towire_onchain_spent(channel, tx, input_num, block->height); - subd_send_msg(channel->owner, take(msg)); + onchain_txo_spent(channel, tx, input_num, block->height); /* We don't need to keep watching: If this output is double-spent * (reorg), we'll get a zero depth cb to onchain_tx_watched, and @@ -201,7 +233,7 @@ static void handle_onchain_htlc_timeout(struct channel *channel, const u8 *msg) onchain_failed_our_htlc(channel, &htlc, "timed out"); } -static void handle_irrevocably_resolved(struct channel *channel, const u8 *msg) +static void handle_irrevocably_resolved(struct channel *channel, const u8 *msg UNUSED) { /* FIXME: Implement check_htlcs to ensure no dangling hout->in ptrs! */ free_htlcs(channel->peer->ld, channel); @@ -338,10 +370,9 @@ static void onchain_error(struct channel *channel, /* With a reorg, this can get called multiple times; each time we'll kill * onchaind (like any other owner), and restart */ -enum watch_result funding_spent(struct channel *channel, - const struct bitcoin_tx *tx, - size_t input_num UNUSED, - const struct block *block) +enum watch_result onchaind_funding_spent(struct channel *channel, + const struct bitcoin_tx *tx, + u32 blockheight) { u8 *msg; struct bitcoin_txid our_last_txid; @@ -411,7 +442,7 @@ enum watch_result funding_spent(struct channel *channel, &channel->channel_info.theirbase.htlc, &channel->channel_info.theirbase.delayed_payment, tx, - block->height, + blockheight, /* FIXME: config for 'reasonable depth' */ 3, channel->last_htlc_sigs, @@ -429,8 +460,55 @@ enum watch_result funding_spent(struct channel *channel, subd_send_msg(channel->owner, take(msg)); } + /* Tell it about any preimages we know. */ + onchaind_tell_fulfill(channel); + watch_tx_and_outputs(channel, tx); /* We keep watching until peer finally deleted, for reorgs. */ return KEEP_WATCHING; } + +void onchaind_replay_channels(struct lightningd *ld) +{ + u32 *onchaind_ids; + struct channeltx *txs; + struct channel *chan; + + db_begin_transaction(ld->wallet->db); + onchaind_ids = wallet_onchaind_channels(ld->wallet, ld); + + for (size_t i = 0; i < tal_count(onchaind_ids); i++) { + log_info(ld->log, "Restarting onchaind for channel %d", + onchaind_ids[i]); + + txs = wallet_channeltxs_get(ld->wallet, onchaind_ids, + onchaind_ids[i]); + chan = channel_by_dbid(ld, onchaind_ids[i]); + + for (size_t j = 0; j < tal_count(txs); j++) { + if (txs[j].type == WIRE_ONCHAIN_INIT) { + onchaind_funding_spent(chan, txs[j].tx, + txs[j].blockheight); + + } else if (txs[j].type == WIRE_ONCHAIN_SPENT) { + onchain_txo_spent(chan, txs[j].tx, + txs[j].input_num, + txs[j].blockheight); + + } else if (txs[j].type == WIRE_ONCHAIN_DEPTH) { + onchain_tx_depth(chan, &txs[j].txid, + txs[j].depth); + + } else { + fatal("unknown message of type %d during " + "onchaind replay", + txs[j].type); + } + } + tal_free(txs); + } + tal_free(onchaind_ids); + + db_commit_transaction(ld->wallet->db); +} diff --git a/lightningd/onchain_control.h b/lightningd/onchain_control.h index 8b1b20bcab6f..7ecfdf8a26fa 100644 --- a/lightningd/onchain_control.h +++ b/lightningd/onchain_control.h @@ -2,14 +2,17 @@ #define LIGHTNING_LIGHTNINGD_ONCHAIN_CONTROL_H #include "config.h" #include <ccan/short_types/short_types.h> +#include <lightningd/lightningd.h> +#include <onchaind/gen_onchain_wire.h> struct channel; struct bitcoin_tx; struct block; -enum watch_result funding_spent(struct channel *channel, - const struct bitcoin_tx *tx, - size_t input_num, - const struct block *block); +enum watch_result onchaind_funding_spent(struct channel *channel, + const struct bitcoin_tx *tx, + u32 blockheight); + +void onchaind_replay_channels(struct lightningd *ld); #endif /* LIGHTNING_LIGHTNINGD_ONCHAIN_CONTROL_H */ diff --git a/lightningd/options.c b/lightningd/options.c index ec13b20dc988..b35cb8ee3b72 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -309,6 +309,10 @@ static void config_register_opts(struct lightningd *ld) opt_register_arg("--fee-base", opt_set_u32, opt_show_u32, &ld->config.fee_base, "Millisatoshi minimum to charge for HTLC"); + opt_register_arg("--rescan", opt_set_s32, opt_show_s32, + &ld->config.rescan, + "Number of blocks to rescan from the current head, or " + "absolute blockheight if negative"); opt_register_arg("--fee-per-satoshi", opt_set_s32, opt_show_s32, &ld->config.fee_per_satoshi, "Microsatoshi fee for every satoshi in HTLC"); @@ -420,6 +424,9 @@ static const struct config testnet_config = { /* Testnet sucks */ .ignore_fee_limits = true, + + /* Rescan 5 hours of blocks on testnet, it's reorg happy */ + .rescan = 30, }; /* aka. "Dude, where's my coins?" */ @@ -481,6 +488,9 @@ static const struct config mainnet_config = { /* Mainnet should have more stable fees */ .ignore_fee_limits = false, + + /* Rescan 2.5 hours of blocks on startup, it's not so reorg happy */ + .rescan = 15, }; static void check_config(struct lightningd *ld) diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 820b4d3be6f6..a3a8aa0380c3 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -563,6 +563,20 @@ static enum watch_result funding_lockin_cb(struct channel *channel, return DELETE_WATCH; } +static enum watch_result funding_spent(struct channel *channel, + const struct bitcoin_tx *tx, + size_t inputnum UNUSED, + const struct block *block) +{ + struct bitcoin_txid txid; + bitcoin_txid(tx, &txid); + + wallet_channeltxs_add(channel->peer->ld->wallet, channel, + WIRE_ONCHAIN_INIT, &txid, 0, block->height); + + return onchaind_funding_spent(channel, tx, block->height); +} + void channel_watch_funding(struct lightningd *ld, struct channel *channel) { /* FIXME: Remove arg from cb? */ diff --git a/lightningd/test/run-find_my_path.c b/lightningd/test/run-find_my_path.c index 498a7aa54bc5..f10cddc54421 100644 --- a/lightningd/test/run-find_my_path.c +++ b/lightningd/test/run-find_my_path.c @@ -74,6 +74,9 @@ struct log_book *new_log_book(size_t max_mem UNNEEDED, /* Generated stub for new_topology */ struct chain_topology *new_topology(struct lightningd *ld UNNEEDED, struct log *log UNNEEDED) { fprintf(stderr, "new_topology called!\n"); abort(); } +/* Generated stub for onchaind_replay_channels */ +void onchaind_replay_channels(struct lightningd *ld UNNEEDED) +{ fprintf(stderr, "onchaind_replay_channels called!\n"); abort(); } /* Generated stub for register_opts */ void register_opts(struct lightningd *ld UNNEEDED) { fprintf(stderr, "register_opts called!\n"); abort(); } @@ -104,12 +107,12 @@ struct txfilter *txfilter_new(const tal_t *ctx UNNEEDED) /* Generated stub for version */ const char *version(void) { fprintf(stderr, "version called!\n"); abort(); } +/* Generated stub for wallet_blocks_height */ +u32 wallet_blocks_height(struct wallet *w UNNEEDED, u32 def UNNEEDED) +{ fprintf(stderr, "wallet_blocks_height called!\n"); abort(); } /* Generated stub for wallet_channels_load_active */ bool wallet_channels_load_active(const tal_t *ctx UNNEEDED, struct wallet *w UNNEEDED) { fprintf(stderr, "wallet_channels_load_active called!\n"); abort(); } -/* Generated stub for wallet_first_blocknum */ -u32 wallet_first_blocknum(struct wallet *w UNNEEDED, u32 first_possible UNNEEDED) -{ fprintf(stderr, "wallet_first_blocknum called!\n"); abort(); } /* Generated stub for wallet_htlcs_load_for_channel */ bool wallet_htlcs_load_for_channel(struct wallet *wallet UNNEEDED, struct channel *chan UNNEEDED, diff --git a/tests/test_lightningd.py b/tests/test_lightningd.py index ef5770a50471..f32acf59f48c 100644 --- a/tests/test_lightningd.py +++ b/tests/test_lightningd.py @@ -1448,12 +1448,14 @@ def test_onchain_unwatch(self): # 10 later, l1 should collect its to-self payment. bitcoind.generate_block(10) - l1.daemon.wait_for_log('Broadcasting OUR_DELAYED_RETURN_TO_WALLET .* to resolve OUR_UNILATERAL/DELAYED_OUTPUT_TO_US') + l1.daemon.wait_for_log('Broadcasting OUR_DELAYED_RETURN_TO_WALLET .* to resolve ' + 'OUR_UNILATERAL/DELAYED_OUTPUT_TO_US') l1.daemon.wait_for_log('sendrawtx exit 0') # First time it sees it, onchaind cares. bitcoind.generate_block(1) - l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal OUR_DELAYED_RETURN_TO_WALLET') + l1.daemon.wait_for_log('Resolved OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by our proposal ' + 'OUR_DELAYED_RETURN_TO_WALLET') # Now test unrelated onchain churn. # Daemon gets told about wallet; says it doesn't care. @@ -1478,6 +1480,57 @@ def test_onchain_unwatch(self): # Note: for this test we leave onchaind running, so we can detect # any leaks! + @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") + def test_onchaind_replay(self): + disconnects = ['+WIRE_REVOKE_AND_ACK', 'permfail'] + options = {'locktime-blocks': 201, 'cltv-delta': 101} + l1 = self.node_factory.get_node(options=options, disconnect=disconnects) + l2 = self.node_factory.get_node(options=options) + btc = l1.bitcoin + + l1.rpc.connect(l2.info['id'], 'localhost', l2.info['port']) + self.fund_channel(l1, l2, 10**6) + + rhash = l2.rpc.invoice(10**8, 'onchaind_replay', 'desc')['payment_hash'] + routestep = { + 'msatoshi': 10**8 - 1, + 'id': l2.info['id'], + 'delay': 101, + 'channel': '1:1:1' + } + l1.rpc.sendpay(to_json([routestep]), rhash) + l1.daemon.wait_for_log(r'Disabling channel') + btc.rpc.generate(1) + + # Wait for nodes to notice the failure, this seach needle is after the + # DB commit so we're sure the tx entries in onchaindtxs have been added + l1.daemon.wait_for_log("Deleting channel .* due to the funding outpoint being spent") + l2.daemon.wait_for_log("Deleting channel .* due to the funding outpoint being spent") + + # We should at least have the init tx now + assert len(l1.db_query("SELECT * FROM channeltxs;")) > 0 + assert len(l2.db_query("SELECT * FROM channeltxs;")) > 0 + + # Generate some blocks so we restart the onchaind from DB (we rescan + # last_height - 100) + btc.rpc.generate(100) + sync_blockheight([l1, l2]) + + # l1 should still have a running onchaind + assert len(l1.db_query("SELECT * FROM channeltxs;")) > 0 + + l2.rpc.stop() + l1.restart() + + # Can't wait for it, it's after the "Server started" wait in restart() + assert l1.daemon.is_in_log(r'Restarting onchaind for channel') + + # l1 should still notice that the funding was spent and that we should react to it + l1.daemon.wait_for_log("Propose handling OUR_UNILATERAL/DELAYED_OUTPUT_TO_US by OUR_DELAYED_RETURN_TO_WALLET") + sync_blockheight([l1]) + btc.rpc.generate(10) + sync_blockheight([l1]) + @unittest.skipIf(not DEVELOPER, "needs DEVELOPER=1") def test_onchain_dust_out(self): """Onchain handling of outgoing dust htlcs (they should fail)""" @@ -3433,6 +3486,7 @@ def test_lockin_between_restart(self): def test_funding_while_offline(self): l1 = self.node_factory.get_node() addr = l1.rpc.newaddr()['address'] + sync_blockheight([l1]) # l1 goes down. l1.stop() @@ -4287,6 +4341,40 @@ def test_disconnectpeer(self): self.assertRaisesRegex(ValueError, "Peer is not in gossip mode", l1.rpc.disconnect, l3.info['id']) + def test_rescan(self): + """Test the rescan option + """ + l1 = self.node_factory.get_node() + btc = l1.bitcoin + + # The first start should start at current_height - 30 = 71, make sure + # it's not earlier + l1.daemon.wait_for_log(r'Adding block 101') + assert not l1.daemon.is_in_log(r'Adding block 70') + + # Restarting with a higher rescan should go back further + l1.daemon.opts['rescan'] = 50 + l1.restart() + l1.daemon.wait_for_log(r'Adding block 101') + assert l1.daemon.is_in_log(r'Adding block 51') + assert not l1.daemon.is_in_log(r'Adding block 50') + + # Restarting with an absolute rescan should start from there + l1.daemon.opts['rescan'] = -31 + l1.restart() + l1.daemon.wait_for_log(r'Adding block 101') + assert l1.daemon.is_in_log(r'Adding block 31') + assert not l1.daemon.is_in_log(r'Adding block 30') + + # Restarting with a future absolute blockheight should just start with + # the current height + l1.daemon.opts['rescan'] = -500000 + l1.stop() + btc.rpc.generate(4) + l1.daemon.start() + l1.daemon.wait_for_log(r'Adding block 105') + assert not l1.daemon.is_in_log(r'Adding block 102') + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/tests/utils.py b/tests/utils.py index 0fe6d81ffc13..6d6cce60bac4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -23,6 +23,7 @@ "cltv-delta": 6, "cltv-final": 5, "locktime-blocks": 5, + "rescan": 1, } DEVELOPER = os.getenv("DEVELOPER", "0") == "1" diff --git a/wallet/db.c b/wallet/db.c index e5a7275d1376..f3383f833c31 100644 --- a/wallet/db.c +++ b/wallet/db.c @@ -306,6 +306,28 @@ char *dbmigrations[] = { " SET faildetail = 'unspecified payment failure reason'" " WHERE status = 2;", /* PAYMENT_FAILED */ /* -- Detailed payment faiure ends -- */ + "CREATE TABLE channeltxs (" + /* The id serves as insertion order and short ID */ + " id INTEGER" + ", channel_id INTEGER REFERENCES channels(id) ON DELETE CASCADE" + ", type INTEGER" + ", transaction_id BLOB REFERENCES transactions(id) ON DELETE CASCADE" + /* The input_num is only used by the txo_watch, 0 if txwatch */ + ", input_num INTEGER" + /* The height at which we sent the depth notice */ + ", blockheight INTEGER REFERENCES blocks(height) ON DELETE CASCADE" + ", PRIMARY KEY(id)" + ");", + /* -- Set the correct rescan height for PR #1398 -- */ + /* Delete blocks that are higher than our initial scan point, this is a + * no-op if we don't have a channel. */ + "DELETE FROM blocks WHERE height > (SELECT MIN(first_blocknum) FROM channels);", + /* Now make sure we have the lower bound block with the first_blocknum + * height. This may introduce a block with NULL height if we didn't have any + * blocks, remove that in the next. */ + "INSERT OR IGNORE INTO blocks (height) VALUES ((SELECT MIN(first_blocknum) FROM channels));", + "DELETE FROM blocks WHERE height IS NULL;", + /* -- End of PR #1398 -- */ NULL, }; diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index e132ea611240..cedcbb5d1e16 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -85,12 +85,6 @@ bool fromwire_gossip_getpeers_reply(const tal_t *ctx UNNEEDED, const void *p UNN /* Generated stub for fromwire_gossip_peer_connected */ bool fromwire_gossip_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct pubkey *id UNNEEDED, struct wireaddr *addr UNNEEDED, struct crypto_state *crypto_state UNNEEDED, u64 *gossip_index UNNEEDED, u8 **gfeatures UNNEEDED, u8 **lfeatures UNNEEDED) { fprintf(stderr, "fromwire_gossip_peer_connected called!\n"); abort(); } -/* Generated stub for funding_spent */ -enum watch_result funding_spent(struct channel *channel UNNEEDED, - const struct bitcoin_tx *tx UNNEEDED, - size_t input_num UNNEEDED, - const struct block *block UNNEEDED) -{ fprintf(stderr, "funding_spent called!\n"); abort(); } /* Generated stub for get_feerate */ u32 get_feerate(const struct chain_topology *topo UNNEEDED, enum feerate feerate UNNEEDED) { fprintf(stderr, "get_feerate called!\n"); abort(); } @@ -281,6 +275,11 @@ struct json_result *new_json_result(const tal_t *ctx UNNEEDED) /* Generated stub for null_response */ struct json_result *null_response(const tal_t *ctx UNNEEDED) { fprintf(stderr, "null_response called!\n"); abort(); } +/* Generated stub for onchaind_funding_spent */ +enum watch_result onchaind_funding_spent(struct channel *channel UNNEEDED, + const struct bitcoin_tx *tx UNNEEDED, + u32 blockheight UNNEEDED) +{ fprintf(stderr, "onchaind_funding_spent called!\n"); abort(); } /* Generated stub for outpointfilter_add */ void outpointfilter_add(struct outpointfilter *of UNNEEDED, const struct bitcoin_txid *txid UNNEEDED, const u32 outnum UNNEEDED) diff --git a/wallet/wallet.c b/wallet/wallet.c index 1125955457ca..1f1bfc124f8c 100644 --- a/wallet/wallet.c +++ b/wallet/wallet.c @@ -11,6 +11,7 @@ #include <lightningd/log.h> #include <lightningd/peer_control.h> #include <lightningd/peer_htlcs.h> +#include <onchaind/gen_onchain_wire.h> #include <string.h> #define SQLITE_MAX_UINT 0x7FFFFFFFFFFFFFFF @@ -766,75 +767,18 @@ void wallet_channel_stats_load(struct wallet *w, stats->out_msatoshi_fulfilled = sqlite3_column_int64(stmt, 7); } -#ifdef COMPAT_V052 -/* Upgrade of db (or initial create): do we have anything to scan for? */ -static bool wallet_ever_used(struct wallet *w) +u32 wallet_blocks_height(struct wallet *w, u32 def) { - sqlite3_stmt *stmt; - bool channel_utxos; - - /* If we ever handed out an address. */ - if (db_get_intvar(w->db, "bip32_max_index", 0) != 0) - return true; - - /* Or if they do a unilateral close, the output to us provides a UTXO. */ - stmt = db_query(__func__, w->db, - "SELECT COUNT(*) FROM outputs WHERE commitment_point IS NOT NULL;"); - int ret = sqlite3_step(stmt); - assert(ret == SQLITE_ROW); - channel_utxos = (sqlite3_column_int(stmt, 0) != 0); - sqlite3_finalize(stmt); - - return channel_utxos; -} -#endif - -/* We want the earlier of either: - * 1. The first channel we're still watching (it might have closed), - * 2. The last block we scanned for UTXO (might have new incoming payments) - * - * chaintopology actually subtracts another 100 blocks to make sure we - * catch chain forks. - */ -u32 wallet_first_blocknum(struct wallet *w, u32 first_possible) -{ - int err; - u32 first_channel, first_utxo; - sqlite3_stmt *stmt = - db_query(__func__, w->db, - "SELECT MIN(first_blocknum) FROM channels;"); - - /* If we ever opened a channel, this will give us the first block. */ - err = sqlite3_step(stmt); - if (err == SQLITE_ROW && sqlite3_column_type(stmt, 0) != SQLITE_NULL) - first_channel = sqlite3_column_int(stmt, 0); - else - first_channel = UINT32_MAX; - sqlite3_finalize(stmt); - -#ifdef COMPAT_V052 - /* This field was missing in older databases. */ - first_utxo = db_get_intvar(w->db, "last_processed_block", 0); - if (first_utxo == 0) { - /* Don't know? New db, or upgraded. */ - if (wallet_ever_used(w)) - /* Be conservative */ - first_utxo = first_possible; - else - first_utxo = UINT32_MAX; - } -#else - first_utxo = db_get_intvar(w->db, "last_processed_block", UINT32_MAX); -#endif - - /* Never go below the start of the Lightning Network */ - if (first_utxo < first_possible) - first_utxo = first_possible; + u32 blockheight; + sqlite3_stmt *stmt = db_prepare(w->db, "SELECT MAX(height) FROM blocks;"); - if (first_utxo < first_channel) - return first_utxo; - else - return first_channel; + /* If we ever processed a block we'll get the latest block in the chain */ + if (sqlite3_step(stmt) == SQLITE_ROW && sqlite3_column_type(stmt, 0) != SQLITE_NULL) { + blockheight = sqlite3_column_int(stmt, 0); + sqlite3_finalize(stmt); + return blockheight; + } else + return def; } static void wallet_channel_config_insert(struct wallet *w, @@ -2277,3 +2221,77 @@ struct bitcoin_txid *wallet_transactions_by_height(const tal_t *ctx, return txids; } +void wallet_channeltxs_add(struct wallet *w, struct channel *chan, + const int type, const struct bitcoin_txid *txid, + const u32 input_num, const u32 blockheight) +{ + sqlite3_stmt *stmt; + stmt = db_prepare(w->db, "INSERT INTO channeltxs (" + " channel_id" + ", type" + ", transaction_id" + ", input_num" + ", blockheight" + ") VALUES (?, ?, ?, ?, ?);"); + sqlite3_bind_int(stmt, 1, chan->dbid); + sqlite3_bind_int(stmt, 2, type); + sqlite3_bind_sha256(stmt, 3, &txid->shad.sha); + sqlite3_bind_int(stmt, 4, input_num); + sqlite3_bind_int(stmt, 5, blockheight); + + db_exec_prepared(w->db, stmt); +} + +u32 *wallet_onchaind_channels(struct wallet *w, + const tal_t *ctx) +{ + sqlite3_stmt *stmt; + size_t count = 0; + u32 *channel_ids = tal_arr(ctx, u32, 0); + stmt = db_prepare(w->db, "SELECT DISTINCT(channel_id) FROM channeltxs WHERE type = ?;"); + sqlite3_bind_int(stmt, 1, WIRE_ONCHAIN_INIT); + + while (sqlite3_step(stmt) == SQLITE_ROW) { + count++; + tal_resize(&channel_ids, count); + channel_ids[count-1] = sqlite3_column_int64(stmt, 0); + } + + return channel_ids; +} + +struct channeltx *wallet_channeltxs_get(struct wallet *w, const tal_t *ctx, + u32 channel_id) +{ + sqlite3_stmt *stmt; + size_t count = 0; + struct channeltx *res = tal_arr(ctx, struct channeltx, 0); + stmt = db_prepare(w->db, + "SELECT" + " c.type" + ", c.blockheight" + ", t.rawtx" + ", c.input_num" + ", c.blockheight - t.blockheight + 1 AS depth" + ", t.id as txid " + "FROM channeltxs c " + "JOIN transactions t ON t.id == c.transaction_id " + "WHERE channel_id = ? " + "ORDER BY c.id ASC;"); + sqlite3_bind_int(stmt, 1, channel_id); + + while (sqlite3_step(stmt) == SQLITE_ROW) { + count++; + tal_resize(&res, count); + + res[count-1].channel_id = channel_id; + res[count-1].type = sqlite3_column_int(stmt, 0); + res[count-1].blockheight = sqlite3_column_int(stmt, 1); + res[count-1].tx = sqlite3_column_tx(ctx, stmt, 2); + res[count-1].input_num = sqlite3_column_int(stmt, 3); + res[count-1].depth = sqlite3_column_int(stmt, 4); + sqlite3_column_sha256(stmt, 5, &res[count-1].txid.shad.sha); + } + + return res; +} diff --git a/wallet/wallet.h b/wallet/wallet.h index 0e41591f72e5..f754efad7e9b 100644 --- a/wallet/wallet.h +++ b/wallet/wallet.h @@ -128,6 +128,16 @@ struct channel_stats { u64 out_msatoshi_offered, out_msatoshi_fulfilled; }; +struct channeltx { + u32 channel_id; + int type; + u32 blockheight; + struct bitcoin_txid txid; + struct bitcoin_tx *tx; + u32 input_num; + u32 depth; +}; + /** * wallet_new - Constructor for a new sqlite3 based wallet * @@ -304,12 +314,15 @@ void wallet_channel_stats_incr_out_fulfilled(struct wallet *w, u64 cdbid, u64 ms void wallet_channel_stats_load(struct wallet *w, u64 cdbid, struct channel_stats *stats); /** - * wallet_first_blocknum - get first block we're interested in. + * Retrieve the blockheight of the last block processed by lightningd. + * + * Will return either the maximal blockheight or the default value if the wallet + * was never used before. * * @w: wallet to load from. - * @first_possible: when c-lightning may have been active from + * @def: the default value to return if we've never used the wallet before */ -u32 wallet_first_blocknum(struct wallet *w, u32 first_possible); +u32 wallet_blocks_height(struct wallet *w, u32 def); /** * wallet_extract_owned_outputs - given a tx, extract all of our outputs @@ -836,4 +849,23 @@ struct bitcoin_txid *wallet_transactions_by_height(const tal_t *ctx, struct wallet *w, const u32 blockheight); +/** + * Store transactions of interest in the database to replay on restart + */ +void wallet_channeltxs_add(struct wallet *w, struct channel *chan, + const int type, const struct bitcoin_txid *txid, + const u32 input_num, const u32 blockheight); + +/** + * List channels for which we had an onchaind running + */ +u32 *wallet_onchaind_channels(struct wallet *w, + const tal_t *ctx); + +/** + * Get transactions that we'd like to replay for a channel. + */ +struct channeltx *wallet_channeltxs_get(struct wallet *w, const tal_t *ctx, + u32 channel_id); + #endif /* LIGHTNING_WALLET_WALLET_H */