-
Notifications
You must be signed in to change notification settings - Fork 403
Disallow dual-sync-async persistence without restarting #3737
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
Disallow dual-sync-async persistence without restarting #3737
Conversation
TheBlueMatt
commented
Apr 15, 2025
👋 Thanks for assigning @valentinewallace as a reviewer! |
We may be able to remove the test |
4e41cb7
to
70f9409
Compare
Would it be a good take-a-Friday issue to fix the legacy tests that use both sync + async so we don't need to gate the panics anymore? |
/// We only support using one of [`ChannelMonitorUpdateStatus::InProgress`] and | ||
/// [`ChannelMonitorUpdateStatus::Completed`] without restarting. Because the API does not |
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.
Some thoughts: is there a way to enforce this with the compiler? And is it a goal of the project to continue supporting both sync + async persistence forever?
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 suppose we could have some kind of flag you set on startup which indicates which persistence method you're using, but I'm sure really sure that it would be an issue in practice - we expect users to use either Persist
or some future AsyncPersist
which will handle this stuff for them, and I don't really see why someone would ever persist sometimes-sync-sometimes-async - either your disk is slow and you need async or you don't.
We definitely want to support both at the project level, though, for the same reason we want to support async and sync everywhere in the project - Rust-async-native projects are becoming more common, but Rust-non-async projects also exist (as well as platforms where that's required) as well as our current bindings are not async.
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.
We definitely want to support both at the project level, though, for the same reason we want to support async and sync everywhere in the project - Rust-async-native projects are becoming more common, but Rust-non-async projects also exist (as well as platforms where that's required) as well as our current bindings are not async.
To clarify, by sync + async persistence I mean supporting either returning ChannelMonitorUpdateStatus::Completed
or ::InProgress
. It feels like we expect most production users to persist data asynchronously and then later call channel_monitor_updated
, so it could be reasonable to drop support for the ::Completed
option down the line. Seems like users that are the exception to that can just call channel_monitor_updated
immediately, as well.
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? I imagine many small nodes will still want the simplicity of sync.
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, seems worth exploring though. Bookmarking to discuss further.
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.
But if sync users would need to use async with an immediate callback now, I think it is no longer so clear that async is not production ready?
Hmm? This PR is not requiring that.
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.
PR is not requiring that, but just following up on @valentinewallace comment. That if we would switch to async-interface-only now and just instruct sync users to call channel_monitor_updated
immediately, it wouldn't be so clear on the API level what the experimental part still is.
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.
But that would push all updates through the async pipeline, which is explicitly still experimental? I don't think it makes sense to force all our users to use something experimental right now to get a very marginal simplification?
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.
In general I think it's a good goal to have a minimal API surface, not saying this has to happen right now. Although async persist is experimental right now, you did state above that the same code paths are used in both places so hopefully that means it's getting close :p
Maybe? I imagine many small nodes will still want the simplicity of sync.
Re: this, ISTM those nodes will use LDK Node so they won't even know what the underlying LDK core API is here?
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.
In general I think it's a good goal to have a minimal API surface, not saying this has to happen right now. Although async persist is experimental right now, you did state above that the same code paths are used in both places so hopefully that means it's getting close :p
Ha, same code paths yes, but the number of things that can go wrong when you suddenly release a lock and let things change out from under you instead of holding it the whole time is quite big :). But, yes, I think this is a reasonable long-term goal.
Re: this, ISTM those nodes will use LDK Node so they won't even know what the underlying LDK core API is here?
Yea, fair, I think I agree its a fine long-term goal. Also because ChainMonitor
can map the sync -> async if we really want to.
fuzz/src/chanmon_consistency.rs
Outdated
let empty_node_a_ser = node_a.encode(); | ||
let empty_node_b_ser = node_b.encode(); | ||
let empty_node_c_ser = node_c.encode(); |
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.
These don't seem to be used anywhere?
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, part of a later patch :)
70f9409
to
ddccbda
Compare
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #3737 +/- ##
==========================================
+ Coverage 89.12% 90.26% +1.13%
==========================================
Files 156 158 +2
Lines 123514 134266 +10752
Branches 123514 134266 +10752
==========================================
+ Hits 110086 121195 +11109
+ Misses 10749 10536 -213
+ Partials 2679 2535 -144 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Yea, I imagine its a good bit of work, though. |
Needs a squash |
ddccbda
to
d009b39
Compare
Squashed with a minor comment fix: $ git diff-tree -U1 ddccbdabd d009b3918
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index c86eed77b..54b607d88 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -2573,4 +2573,4 @@ where
/// [`ChannelMonitorUpdateStatus::Completed`] without restarting. Because the API does not
- /// otherwise directly enforce this, we enforce it in debug builds here by storing which one is
- /// in use.
+ /// otherwise directly enforce this, we enforce it in non-test builds here by storing which one
+ /// is in use.
#[cfg(not(test))] |
These externalized tests seem to be failing:
|
In general, we don't expect users to persist `ChannelMonitor[Update]`s both synchronously and asynchronously for a single `ChannelManager` instance. If a user has implemented asynchronous persistence, they should generally always use that, as there is then no advantage to them to occasionally persist synchronously. Even still, in 920d96e we fixed some bugs related to such operation, and noted that "there isn't much cost to supporting it". Sadly, this is not true. Specifically, the dual-sync-async persistence flow is ill-defined and difficult to define in away that a user can realistically implement. Consider the case of a `ChannelMonitorUpdate` which is persisted asynchronously and while it is still being persisted a new `ChannelMonitorUpdate` is created. If the second `ChannelMonitorUpdate` is persisted synchronously, the `ChannelManager` will be left with a single pending `ChannelMonitorUpdate` which is not the latest. If we were to then restart, the latest copy of the `ChannelMonitor` would be that without any updates, but the `ChannelManager` has a pending `ChannelMonitorUpdate` for the next update, but not the one after that. The user would then have to handle the replayed `ChannelMonitorUpdate` and then find the second `ChannelMonitorUpdate` on disk and somehow know to replay that one as well. Further, we currently have a bug in handling this scenario as we'll complete all pending post-update actions when the second `ChannelMonitorUpdate` gets persisted synchronously, even though the first `ChannelMonitorUpdate` is still pending. While we could rather trivially fix these issues, addressing the larger API question above is difficult and as we don't anticipate this use-case being important, we just disable it here. Note that we continue to support it internally as some 39 tests rely on it. We do, however, remove test_sync_async_persist_doesnt_hang which was added specifically to test for this use-case, and we now do not support it. Issue highlighted by (changes to the) chanmon_consistency fuzz target (in the next commit).
cb6219d
d009b39
to
cb6219d
Compare
Oof, ugh, right. Squashed-pushed a fix: $ git diff-tree -U1 d009b3918 cb6219df6
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 54b607d88..34751bfc9 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -2575,3 +2575,3 @@ where
/// is in use.
- #[cfg(not(test))]
+ #[cfg(not(any(test, feature = "_externalize_tests")))]
monitor_update_type: AtomicUsize,
@@ -3321,3 +3321,3 @@ macro_rules! handle_new_monitor_update {
ChannelMonitorUpdateStatus::InProgress => {
- #[cfg(not(test))]
+ #[cfg(not(any(test, feature = "_externalize_tests")))]
if $self.monitor_update_type.swap(1, Ordering::Relaxed) == 2 {
@@ -3330,3 +3330,3 @@ macro_rules! handle_new_monitor_update {
ChannelMonitorUpdateStatus::Completed => {
- #[cfg(not(test))]
+ #[cfg(not(any(test, feature = "_externalize_tests")))]
if $self.monitor_update_type.swap(2, Ordering::Relaxed) == 1 {
@@ -3594,3 +3594,3 @@ where
- #[cfg(not(test))]
+ #[cfg(not(any(test, feature = "_externalize_tests")))]
monitor_update_type: AtomicUsize::new(0),
@@ -14767,3 +14767,3 @@ where
- #[cfg(not(test))]
+ #[cfg(not(any(test, feature = "_externalize_tests")))]
monitor_update_type: AtomicUsize::new(0), |
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.
Wondering whether the problem where chan mgr will be left with a single pending update which is not the latest can also occur when purely async is used, but channel_monitor_updated
are called in the reverse order?
|
cb6219d
to
3805ca1
Compare
LGTM after squash |
Re-dropped the constant after discussion. |
3805ca1
to
90aec07
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.
I just posted that drive-by comment, but was happy to review the full PR as requested as it is context that I probably need for my own future work too.
Just see what you want to do with the comments, as this PR was already in the later stages with two reviewers on it already.
for (channel_id, monitor) in monitors.iter() { | ||
monitor_refs.insert(*channel_id, monitor); | ||
} | ||
let reload_node = |ser: &Vec<u8>, |
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.
Do this as the first commit to reduce total delta? I think it's better for review when refactors come before functional changes in a 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.
Is it really worth changing? Indeed, its generally nicer, but its three extra lines of diff and I wrote it the other way originally, not sure its worth bothering to change.
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.
No, not blocking.
fuzz/src/chanmon_consistency.rs
Outdated
@@ -680,8 +679,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) { | |||
let mon_style = [default_mon_style.clone(), default_mon_style.clone(), default_mon_style]; | |||
|
|||
macro_rules! reload_node { | |||
($ser: expr, $node_id: expr, $old_monitors: expr, $keys_manager: expr, $fee_estimator: expr) => {{ | |||
let keys_manager = Arc::clone(&$keys_manager); | |||
($ser: expr, $node_id: expr, $old_monitors: expr, $use_old_mons: expr, $keys: expr, $fee_estimator: expr) => {{ |
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.
use_old_mons could be an enum for readability and then match 0x2a | 0x2b | 0x2c etc directly to enum values. Makes it all a bit more explicit with fewer explanation about +1 +2 required.
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 went ahead and broke backwards compat so I don't think this is worth it anymore (its always just passing v
).
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.
Indeed, it's now a fuzz value also with more complex meaning.
fuzz/src/chanmon_consistency.rs
Outdated
($ser: expr, $node_id: expr, $old_monitors: expr, $keys_manager: expr, $fee_estimator: expr) => {{ | ||
let keys_manager = Arc::clone(&$keys_manager); | ||
($ser: expr, $node_id: expr, $old_monitors: expr, $use_old_mons: expr, $keys: expr, $fee_estimator: expr) => {{ | ||
let keys_manager = Arc::clone(&$keys); |
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 think the arc/clone/keys thing is an unrelated refactor?
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.
Hmm? Those already existed (the only change is that the fee_estimator
clone was changed to Arc::clone
rather than the implicit clone
for consistency, but its calling the same method).
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.
And, avoiding cloning a clone by renaming the arg to keys
?
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'm confused what you're referring to. The only clone avoided in this patch is the signer_provider: keys_manager
line in ChannelManagerReadArgs
(where we provide keys three times, and previously we cloned all three now we just clone twice). It seemed like a trivial enough change it wasn't worth separating out.
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.
Original code was:
$keys_manager: expr
let keys_manager = Arc::clone(&$keys_manager);
Arc::clone(&$keys_manager)
Now it is:
$keys: expr,
let keys_manager = Arc::clone(&$keys);
Arc::clone(&$keys),
That's what I meant by 'cloning the clone' that was done previously. I am sure this is just me asking beginner questions.
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 okay I missed that in your comment. I don't believe its a change - I changed $keys_manager
cases to keys
, but left the let keys_manager...
in place. Rust macros differentiate between $keys_manager
and keys_manager
so using a different variable name now that its a variable just avoids changing the logic.
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 got it. This is not review, but learning rust. It makes sense that those aren't the same thing. Thanks.
@@ -674,6 +676,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) { | |||
}}; | |||
} | |||
|
|||
let default_mon_style = RefCell::new(ChannelMonitorUpdateStatus::Completed); |
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.
Are the changes to this file a preparation for the next commit somehow and/or are there also independent refactors in here? I struggle a bit to see the link with the chanmgr change.
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.
Its because we're no longer allowed to switch monitor connection styles mid-stream. So instead we're changing to storing the monitor style we'll start applying on the next node reload here, and applying it only on reload.
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 that is the case, isn't it better to pick the monitor style from the fuzz bytes only when restarting the node? Also for clarity.
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.
You mean like have a new set of bytes for restart that also set the monitor style? After the latest changes here we're already using 9(+3+3) bytes for reload types, and we only have 256 bytes, multiplying that by 2 starts to feel wasteful, IMO, and I don't see a need to break compat even more.
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.
Not a new set of bytes for the main match. Just if you restart, take another byte from the fuzz slice to decide mon style based on that.
In the PR as is, it can happen that monitor styles are switched more than once in between restarts, which has no effect and is unnecessarily expanding the search space?
Is compat breaking not a binary thing?
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.
Not a new set of bytes for the main match. Just if you restart, take another byte from the fuzz slice to decide mon style based on that.
Ah, I definitely want to avoid that if we can. Currently the chanmon_consistency
fuzzer only ever operates on single bytes and never reads multi-byte input, which I imagine makes it easier for the fuzzer to move things around. If it has to start moving around pairs there's more wasted work as it "discovers" that.
In the PR as is, it can happen that monitor styles are switched more than once in between restarts, which has no effect and is unnecessarily expanding the search space?
Indeed, though the fuzzer should see that as uninteresting/no new coverage so I'm not sure its a huge deal.
Is compat breaking not a binary thing?
In the current change I can pretty easily go binary-sed my existing corpus and keep relatively the same behavior. This wouldn't allow for that (as easily).
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 info, thanks for explaining.
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.
Also finding these fuzzer explanations interesting! A writeup could be good at some point, seems like these are good guidelines for fuzzer design/improvement.
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 mean to be fair I'm mostly speculating, informed only somewhat by experience. So I wouldn't take this as gospel by any means :)
/// We only support using one of [`ChannelMonitorUpdateStatus::InProgress`] and | ||
/// [`ChannelMonitorUpdateStatus::Completed`] without restarting. Because the API does not |
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.
A luxury API for a minority group of sync users may not be the best choice long term?
But if sync users would need to use async with an immediate callback now, I think it is no longer so clear that async is not production ready?
Hopefully we can get to super 100% soon.
90aec07
to
d27c161
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.
Good with a squash
} else if use_old_mons % 3 == 1 { | ||
// Reload with the second-oldest `ChannelMonitor` | ||
let old_mon = prev_state.persisted_monitor; | ||
prev_state.pending_monitors.drain(..).next().map(|(_, v)| v).unwrap_or(old_mon) |
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.
Could you comment why we're emptying pending_monitors
rather than just removing the first one? We don't clear the vec below so it reads a bit inconsistent to me
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.
Just seemed shorter to write. remove(0)
panics if there is no zero element, and get(0)
would return a reference but we need an owned vec.
@@ -674,6 +676,9 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) { | |||
}}; | |||
} | |||
|
|||
let default_mon_style = RefCell::new(ChannelMonitorUpdateStatus::Completed); |
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 that is the case, isn't it better to pick the monitor style from the fuzz bytes only when restarting the node? Also for clarity.
/// We only support using one of [`ChannelMonitorUpdateStatus::InProgress`] and | ||
/// [`ChannelMonitorUpdateStatus::Completed`] without restarting. Because the API does not |
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.
PR is not requiring that, but just following up on @valentinewallace comment. That if we would switch to async-interface-only now and just instruct sync users to call channel_monitor_updated
immediately, it wouldn't be so clear on the API level what the experimental part still is.
fuzz/src/chanmon_consistency.rs
Outdated
@@ -680,8 +679,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) { | |||
let mon_style = [default_mon_style.clone(), default_mon_style.clone(), default_mon_style]; | |||
|
|||
macro_rules! reload_node { | |||
($ser: expr, $node_id: expr, $old_monitors: expr, $keys_manager: expr, $fee_estimator: expr) => {{ | |||
let keys_manager = Arc::clone(&$keys_manager); | |||
($ser: expr, $node_id: expr, $old_monitors: expr, $use_old_mons: expr, $keys: expr, $fee_estimator: expr) => {{ |
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.
Indeed, it's now a fuzz value also with more complex meaning.
fuzz/src/chanmon_consistency.rs
Outdated
($ser: expr, $node_id: expr, $old_monitors: expr, $keys_manager: expr, $fee_estimator: expr) => {{ | ||
let keys_manager = Arc::clone(&$keys_manager); | ||
($ser: expr, $node_id: expr, $old_monitors: expr, $use_old_mons: expr, $keys: expr, $fee_estimator: expr) => {{ | ||
let keys_manager = Arc::clone(&$keys); |
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.
And, avoiding cloning a clone by renaming the arg to keys
?
8fe1ec0
to
0b524ae
Compare
Okay, I believe all feedback has been addressed. |
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 after squash
fuzz/src/chanmon_consistency.rs
Outdated
@@ -718,7 +718,8 @@ pub fn do_test<Out: Output>(data: &[u8], underlying_out: Out, anchors: bool) { | |||
let old_mon = prev_state.persisted_monitor; | |||
prev_state.pending_monitors.pop().map(|(_, v)| v).unwrap_or(old_mon) | |||
}; | |||
// Use a different value of `use_old_mons` if we have another monitor (only node B) | |||
// Use a different value of `use_old_mons` if we have another monitor (only for | |||
// node B) by shifting `use_old_mons` one in base-3. |
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.
Shifting one in base-3, that has to do 😅
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 after squash
When we reload a node in the `chanmon_consistency` fuzzer, we always reload with the latest `ChannelMonitor` state which was confirmed as persisted to the running `ChannelManager`. This is nice in that it tests losing the latest `ChannelMonitor`, but there may also be bugs in the on-startup `ChannelMonitor` replay. Thus, here, we optionally reload with a newer `ChannelMonitor` than the last-persisted one. Note that this breaks backwards compat for existing `chanmon_consistency` tests, requiring that 0x2c bytes be replaced with 0xb1, 0x2d with 0xb4 and 0x2e with 0xbd.
Since we changed the values of bytes for reloading nodes in the `chanmon_consistency` fuzzer, here we move them down so that the match arms are in byte order again.
`chanmon_consistency` was originally written with lots of macros due to some misguided concept of code being unrolled at compile-time. This is, of course, a terrible idea not just for compile times but also for performance. Here, we make `reload_node` a function in anticipation of it being used in additional places in future work.
Squashed without further changes. |
0b524ae
to
7ebaefb
Compare
fuzz/src/chanmon_consistency.rs
Outdated
// Use a different value of `use_old_mons` if we have another monitor (only for | ||
// node B) by shifting `use_old_mons` one in base-3. |
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.
nit: this belongs in the previous commit
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.
Its also in that commit, its just that which word reached 100 chars changed so git doesn't highlight it as a move.