-
Notifications
You must be signed in to change notification settings - Fork 407
Replace default hash with birthday hash #823
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
Replace default hash with birthday hash #823
Conversation
I kept the field names the same across |
Codecov Report
@@ Coverage Diff @@
## main #823 +/- ##
==========================================
+ Coverage 90.97% 91.02% +0.05%
==========================================
Files 48 48
Lines 26547 26538 -9
==========================================
+ Hits 24151 24157 +6
+ Misses 2396 2381 -15
Continue to review full report at Codecov.
|
79bb425
to
677fd37
Compare
lightning/src/ln/channelmanager.rs
Outdated
let channel = Channel::new_outbound(&self.fee_estimator, &self.keys_manager, their_network_key, channel_value_satoshis, push_msat, user_id, config)?; | ||
let block_hash = *self.last_block_hash.lock().unwrap(); | ||
let channel = Channel::new_outbound(&self.fee_estimator, &self.keys_manager, their_network_key, channel_value_satoshis, push_msat, user_id, config, block_hash)?; |
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.
@TheBlueMatt It occurred to me while working on a follow-up that we may want to hold the last_block_hash
lock for the duration of creating the channel (i.e., also covering the duration channel_state
lock is held here). And that the same should be done in block_connected
/ block_disconnected
.
Otherwise, we may create a channel with a block hash, process a new block, then add the new channel after the block has been processed but with the previous block hash. But maybe this is not a problem?
However, holding locks in this manner means create_channel
/ internal_open_channel
would block block_connected
/ block_disconnected
. And this includes calling out to the fee estimator.
(Note that both already involve holding these locks only in a non-overlapping manner.)
Does arranging locking like this sound reasonable? If not clear, I'm proposing:
- lock
last_block_hash
- lock
channel_state
- unlock
channel_state
- unlock
last_block_hash
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.
Ah! That is a problem as we'll refuse to deserialize a ChannelManager
that has Channels with different current block hashes set (as long as they aren't 0). I think an easier fix is to just move the last_block_hash update to the top of block_connected/disconnected, which avoids taking locks for longer.
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.
Won't channels still get different block hashes in the following scenario?
create_channel
lockslast_block_hash
and uses this for the new channelcreate_channel
unlockslast_block_hash
block_connected
lockslast_block_hash
and updates its valueblock_connected
unlockslast_block_hash
block_connected
lockschannel_state
and callsblock_connected
on each channelblock_connected
unlockschannel_state
create_channel
lockschannel_state
and inserts the new channel but with the previouslast_block_hash
create_channel
unlockschannel_state
Another question that came to mine: does it matter that a new Channel
is given last_block_hash
even though block_connected
was not called on it for that hash?
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.
Won't channels still get different block hashes in the following scenario?
Ugh, indeed. Seems fine enough to just hold the lock until the channel is inserted.
Another question that came to mine: does it matter that a new Channel is given last_block_hash even though block_connected was not called on it for that hash?
I don't believe so. The channel-current-hash is basically unused, its now just used to initialize the ChannelMonitor. Its checked at deserialization time, but that's about it. Maybe an easier way to solve the locking issue is just to remove it.
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 don't believe so. The channel-current-hash is basically unused, its now just used to initialize the ChannelMonitor. Its checked at deserialization time, but that's about it. Maybe an easier way to solve the locking issue is just to remove it.
Ah, good idea! So just pass it to Channel
when needed to create a ChannelMonitor
.
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.
Sounds good to me, no races there.
Were you also going to fix the bug you found where we incorrectly track the current block hash in |
Yeah, I have it ready but wanted to resolve the locking question first. I can include in this PR without any problem. |
677fd37
to
390ff52
Compare
All should be good for review now. I included some lock acquisition ordering in 1c948a5, which may not be strictly necessary but gives consistency. Additionally, I removed some
Included in 390ff52. |
This actually scares me - Rust has no lock-order-consistency checking, as far as I know, so any time we take two locks at once we have to manually track and ensure correctness of lock ordering. Its definitely simpler to just avoid it where possible, but if we can't we at least need to document the lock order requirements carefully (in this case I think its fine, lock order must always be channel_state followed by block hash). All the other changes look good. Do you think it would make sense to take this to add runtime assertions to ensure users connect in-order (would likely need documentation to the effect that block events may panic if you screw up ordering)? https://git.bitcoin.ninja/?p=rust-lightning;a=commitdiff;h=c60560291c1f426ade3c42863dd2a008525a3f73 |
Fair point. I consider moving
I started down this route but a significant number of tests broke as it seems you've noticed by configuring against it. Looks like we use the default hash for Eventually, we may want shore up our testing code, but for now conditionally checking and documenting is definitely worthwhile. I'll add it. |
Hmm, I'm ok with whatever. I kinda prefer just leaving things be out-of-sync (that's what total_consistency_lock is for!) but we could move into the |
Going to merge #808 which is going to (rather trivially) conflict with this one, sorry. |
Co-authored-by: Matt Corallo <[email protected]> Co-authored-by: Jeffrey Czyz <[email protected]>
390ff52
to
3ec662b
Compare
Ended up holding the locks independently of one another.
Rebased. |
/// Chain-related parameters used to construct a new `ChannelManager`. | ||
/// | ||
/// Typically, the block-specific parameters are derived from the best block hash for the network, | ||
/// as a newly constructed `ChannelManager` will not have created any channels yet. These parameters |
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.
Does it make sense to note that once you instantiate the ChannelManager, you have to start connecting blocks starting from the ones provided?
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, though added to ChannelManager::new
docs since that is where block_connected
is discussed and ChainParameters
is used.
lightning/src/ln/channelmanager.rs
Outdated
@@ -3353,6 +3384,12 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana | |||
// during initialization prior to the chain_monitor being fully configured in some cases. | |||
// See the docs for `ChannelManagerReadArgs` for more. | |||
let _persistence_guard = PersistenceNotifierGuard::new(&self.total_consistency_lock, &self.persistence_notifier); | |||
|
|||
assert_eq!(*self.last_block_hash.read().unwrap(), header.block_hash(), |
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.
Oops, probably best to drop the test commit, looks like its still failing the fuzzer, we can take it in a separate PR.
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.
Odd, was passing locally. Will drop the commit for now.
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.
Two comments, but otherwise good.
When ChannelMonitors are persisted, they need to store the most recent block hash seen. However, for newly created channels the default block hash is used. If persisted before a block is connected, the funding output may be missed when syncing after a restart. Instead, initialize ChannelManager with a "birthday" hash so it can be used later when creating channels.
Calling block_connected for the same block was necessary when clients were expected to re-fetch filtered blocks with relevant transactions. At the time, all listeners were notified of the connected block again for re-scanning. Thus, de-duplication logic was put in place. However, this requirement is no longer applicable for ChannelManager as of commit bd39b20, so remove this logic.
Tracking the last block was only used to de-duplicate block_connected calls, but this is no longer required as of the previous commit. Further, the ChannelManager can pass the latest block hash when needing to create a ChannelMonitor rather than have each Channel maintain an up-to-date copy. This is implemented in the next commit.
ChannelMonitor keeps track of the last block connected. However, it is initialized with the default block hash, which is a problem if the ChannelMonitor is serialized before a block is connected. Instead, pass ChannelManager's last_block_hash, which is initialized with a "birthday" hash, when creating a new ChannelMonitor.
ChannelManager reads channel_state and last_block_hash while processing funding_created and funding_signed messages. It writes these while processing block_connected and block_disconnected events. To avoid any potential deadlocks, have each site hold these locks independent of one another and in a consistent order. Additionally, use a RwLock instead of Mutex for last_block_hash since exclusive access is not needed in funding_created / funding_signed and cannot be guaranteed in block_connected / block_disconnected because of the reads in the former.
When a block is disconnected, the hash of the disconnected block was used to update the last connected block. However, this amounts to a no-op because these hashes should be equal. Successive disconnections would update the hash but leave it one block off. Normally, this not a problem because the last block_disconnected should be followed by block_connected since the former is triggered by a chain re-org. However, this assumes the user calls the API correctly and that no failure occurs that would prevent block_connected from being called (e.g., if fetching the connected block fails). Instead, update the last block hash with the disconnected block's previous block hash.
3ec662b
to
8730148
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.
LGTM
When
ChannelMonitor
s are persisted, they need to store the most recent block hash seen. However, for newly created channels the default block hash is used. If persisted before a block is connected, the funding output may be missed when syncing after a restart. Instead, initializeChannelManager
with a "birthday" hash so it can be used later when creating channels.Fixes #820.