-
Notifications
You must be signed in to change notification settings - Fork 942
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
Conversation
2cc84be
to
ab7280d
Compare
Minor nits. Ack ab7280d |
We used to queue the preimages to be sent to onchaind only after receiving the onchaind_init_reply. Once we start replaying we might end up in a situation in which we queue the tx that onchaind should react to before providing it with the preimages. This commit just moves the preimages being sent, making it atomic with the init, and without changing the order. Signed-off-by: Christian Decker <[email protected]>
These transactions being seen on the blockchain triggered some action in onchaind so we need to replay them when we restore the onchaind. Signed-off-by: Christian Decker <[email protected]>
This will be used to replay transactions that were witnessed in the blockchain during startup, so that onchaind can be recreate its state. Signed-off-by: Christian Decker <[email protected]>
ab7280d
to
bf93d17
Compare
wallet/wallet.c
Outdated
" type," | ||
" transaction_id," | ||
" input_num" | ||
", blockheight" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent comma usage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. I started using your comma placement, which is much nicer, half way through and forgot to fixup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another wrinkle of comma placement, is that one notices that SQL consistently, uses longer command keywords, than the keywords for clauses: SELECT
vs WHERE
vs ORDER
vs FROM
; INSERT
vs VALUES
; UPDATE
vs WHERE
vs SET
, etc.
SELECT col1
, col2
FROM table
WHERE col3 = 'whatever'
AND col4 = 42
;
This has the advantage of never requiring spaces before the end of strings being concatenated, since everything after the initial command keyword gets an extra space before it:
"SELECT col1"
" , col2"
" FROM table"
" WHERE col3 = 'whatever'"
" AND col4 = 42"
" ;"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a bit much imho, with the spacing that I added we actually get the same spacing as if we'd written it in the shell (.schema
will show excessive spaces in the CREATE
statement for example, which is hard to read).
wallet/wallet.h
Outdated
@@ -128,7 +128,7 @@ struct channel_stats { | |||
u64 out_msatoshi_offered, out_msatoshi_fulfilled; | |||
}; | |||
|
|||
struct onchaindtx { | |||
struct channeltx { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this particular change can be put in the previous commit instead?
tests/test_lightningd.py
Outdated
@@ -312,10 +312,10 @@ def fund_channel(self, l1, l2, amount): | |||
decoded2 = bitcoind.rpc.decoderawtransaction(tx) | |||
raise ValueError("Can't find {} payment in {} (1={} 2={})".format(amount, tx, decoded, decoded2)) | |||
|
|||
def line_graph(self, n=2): | |||
def line_graph(self, n=2, options=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An improvement certainly, but not used in the same commit that contains it. In principle, better to separate in a different commit. Fine by me either way though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed changes, I initially used line_graph
in the onchaind test but then needed to instruct one to fail, so I used the primitives instead.
lightningd/options.c
Outdated
@@ -481,6 +488,9 @@ static const struct config mainnet_config = { | |||
|
|||
/* Mainnet should have more stable fees */ | |||
.ignore_fee_limits = false, | |||
|
|||
/* Do not rescan */ | |||
.rescan = 15, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
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"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand.
There was a problem hiding this comment.
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...
Signed-off-by: Christian Decker <[email protected]>
This will allow us in the next commit to store the transactions that triggered this event in the DB and thus allowing us to replay them later on. Signed-off-by: Christian Decker <[email protected]>
Since we reference the channel ID to allow cascades in the database we also need the ability to look up a channel by its database ID. Signed-off-by: Christian Decker <[email protected]>
bf93d17
to
5377f46
Compare
Signed-off-by: Christian Decker <[email protected]>
Signed-off-by: Christian Decker <[email protected]>
This is a big simplification, we just report the DBs current blockchain height as the point to continue scanning, or the passed in default. No more guessing where to continue from or whether the wallet was used and when it first saw the light of day. Signed-off-by: Christian Decker <[email protected]>
5377f46
to
e4ed39a
Compare
Since this may break a thing or too, I'd like to wait for @rustyrussell to sign off as well, just as a sanity check :-) |
i want this nooow! |
This is intended to recover from an inconsistent state, involving `onchaind`. Should we for some reason not restore the `onchaind` process correctly we can instruct `lightningd` to go back in time and just replay everything. Signed-off-by: Christian Decker <[email protected]>
Simplification of the offset calculation to use the rescan parameter, and rename of `wallet_first_blocknum`. We now use either relative rescan from our last known location, or absolute if a negative rescan was given. It's all handled in a single location (except the case in which the blockcount is below our precomputed offset), so this should reduce surprises. Signed-off-by: Christian Decker <[email protected]>
Signed-off-by: Christian Decker <[email protected]>
This shaves off about 15% of our integration testing suite on my machine. It assumes we never reorg below the first block the node starts with, which is true for all tests, so it's safe. Signed-off-by: Christian Decker <[email protected]>
3679fef
to
ddd53f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only concern is over existing deployments monitoring channels which upgrade and don't get replay.
wallet/wallet.h
Outdated
@@ -128,6 +128,16 @@ struct channel_stats { | |||
u64 out_msatoshi_offered, out_msatoshi_fulfilled; | |||
}; | |||
|
|||
struct onchaindtx { | |||
u32 channel_id; | |||
int type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int type
always makes me wince. I prefer an enum, if only for readability.
struct bitcoin_txid txid; | ||
struct bitcoin_tx *tx; | ||
u32 input_num; | ||
u32 depth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depth and blockheight seem implicitly related; it seems odd to have this here. Perhaps I'll revise that comment when reviewing a followon patch?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are different: the blockheight
is the height at which the TX was confirmed, the depth
is the difference between current blockheight and confirmation blockheight at the time the original notification was delivered (notice that this is not the current blockheight when we are replaying the events).
", input_num" | ||
", blockheight" | ||
") VALUES (?, ?, ?, ?, ?);"); | ||
sqlite3_bind_int(stmt, 1, chan->dbid); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the 'id' database field used at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an autoincrement field that is used in the SELECT
below to make sure we replay the events in the same order they were created, i.e., seen originally. This is just for added consistency should we have some dependency on the order (multiple transactions being confirmed in the same block).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is declared INTEGER PRIMARY KEY
, but note, this implies it is equivalent to ROWID
(an internal detail) under SQLITE. In particular, if a row is deleted, it is possible for SQLITE to reuse that row when another row is inserted. We need an explicit AUTOINCREMENT
in the declaration of id
in CREATE TABLE channeltxs
.
https://www.sqlite.org/autoinc.html
If the largest ROWID is equal to the largest possible integer (9223372036854775807) then the database engine starts picking positive candidate ROWIDs at random until it finds one that is not previously used. If no unused ROWID can be found after a reasonable number of attempts, the insert operation fails with an SQLITE_FULL error.
...skip...
The normal ROWID selection algorithm described above will generate monotonically increasing unique ROWIDs as long as you never use the maximum ROWID value and you never delete the entry in the table with the largest ROWID. If you ever delete rows or if you ever create a row with the maximum possible ROWID, then ROWIDs from previously deleted rows might be reused when creating new rows and newly created ROWIDs might not be in strictly ascending order.
Admittedly the condition of reaching 2^63 is extremely unlikely, and if we neglect that, we can generally think of INTEGER PRIMARY KEY
as implicitly AUTOINCREMENT
, but I think explicitness is a win here.
Indeed, later on it pretty much admits that the only difference between INTEGER PRIMARY KEY
and INTEGER PRIMARY KEY AUTOINCREMENT
is that the latter immediately fails hard when 2^63 is reached, whereas the former will try to recover at the cost of violating monotonically-increasing numbers.
wallet/wallet.c
Outdated
sqlite3_finalize(stmt); | ||
return blockheight; | ||
} else | ||
return first_possible; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If they are currently onchain, and upgrade, this will break them? I think this logic needs to stay for a while :(
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"); |
There was a problem hiding this comment.
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...
ACK ddd53f1 |
Thanks for the review @rustyrussell. Regarding backward compatibility we could insert a dummy block at a given height as a migration and start the scan from there (when lightning first became interesting seems like a good place to start). |
The no-rescan change requires us to rescan one last time from the first_blocknum of our channels (if we have any). The migrations just drop blocks that are higher, then insert a dummy with the first_blocknum, and then clean up after us. If we don't have any channels we don't go back at all. Signed-off-by: Christian Decker <[email protected]>
@rustyrussell I added 3 migrations that'll insert a dummy block with I tested it with a testnet node with and without channels, and it does what I expected. |
ACK fc2b6c0 |
I have one c-lightning node that's normally connected to a pruned node, though I temporarily connected it to an unpruned node. I updated to the latest master and it's currently catching up again using the full node. Once that's done, I'll switch it back to the pruned node and see what happens. |
That'd be great @Sjors, though this is not really a permanent solution for pruned nodes: if the c-lightning node is not running for a long time and it's sync horizon drops below the pruning limit for |
With these changes
onchaind
no longer relies on rescanning the blockchain from the wallet's birth height on each startup. Instead we remember the transactions that triggered some behavior inonchaind
and replay them before syncing with the blockchain.This is the continuation of #1364 which took care of storing transactions we are interested in and tracking their confirmations when syncing. The
onchaind
persistence is a small layer on top of those transactions remembering which transaction triggered what notification toonchaind
.In addition the sync offset calculation was simplified, and we can now use the
--rescan
command line option to specify either a relative or absolute height to continue syncing from. The default is 30 blocks for testnet and 15 for mainnet, to counter potential forks.This may also partially address pruning nodes, as long as the sync horizon doesn't get pruned out from under our feet.
Fixes #1168
Fixes #1179
Fixes #1283
Fixes #1331
Fixes #1358
Fixes #1246
Fixes #1243
And maybe some more?