Skip to content

Store onchaind state in the database and reduce rescan on startup #1398

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Apr 25, 2018
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions lightningd/chaintopology.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
Expand Down
13 changes: 13 additions & 0 deletions lightningd/channel.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions lightningd/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 20 additions & 6 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -350,18 +351,31 @@ 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);

/* Initialize block topology (does its own transaction) */
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);
Expand Down
4 changes: 4 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
116 changes: 97 additions & 19 deletions lightningd/onchain_control.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand 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);
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
11 changes: 7 additions & 4 deletions lightningd/onchain_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
10 changes: 10 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems more sensible, to use negative to mean "current blockheight minus absolute value" and to use positive for "absolute blockheight". And to use 0 to mean "current blockheight, no rescan". Similar to Python arrays/strings, with the "end" being the current known blockheight and a negative index being relative from the end.

But this comment is not a blocking one and I think the current standard is reasonable.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I preferred making the more complex case, i.e., choosing a fixed offset, the case with the added sign. Everybody should just use relative offsets from their current head, in particular if they run from a startup script, and only use the absolute offset (with negation) in a recovery scenario.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, nobody should be using this anyway, so I think -ve for relative is pretty common. Otherwise you want --rescan-absolute or something...

opt_register_arg("--fee-per-satoshi", opt_set_s32, opt_show_s32,
&ld->config.fee_per_satoshi,
"Microsatoshi fee for every satoshi in HTLC");
Expand Down Expand Up @@ -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?" */
Expand Down Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will rescan 15 blocks before the current block height? The comment seems not matching? (should it not be 0 if we just continue scanning at where we left off?)

Also, I remember we had an issue before that our block height would keep getting pushed back if we kept getting restarted, would that behavior occur again?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I had 0 in there initially and then I remembered that we don't handle reorgs below our in-memory chain all too well, so I made it 15 blocks. If we process less than 15 blocks before crashing, then yes, this could end up walking backwards, we could add the lower bound at the segwit activation back in, but that's also painful.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative could be to use last_processed_block and not decrement that if it is above our current blockheight. That would make sure that our offset if computed from a fixed point, rather than risking sliding back in time.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is still /* Do not rescan */ even though default is to rescan by 15 blocks back?

};

static void check_config(struct lightningd *ld)
Expand Down
Loading