-
Notifications
You must be signed in to change notification settings - Fork 22
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
feature(consensus): Recovery #1043
base: master
Are you sure you want to change the base?
Conversation
af9aa0b
to
3be949e
Compare
30cb871
to
e879e44
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.
Pretty clean approach. Added some comments. Probably we want to add a basic test for each recovery strategy.
}); | ||
|
||
// To avoid confusion, the order is reversed so it fits the natural `from..to` order | ||
blocks.collect::<Vec<_>>().await.into_iter().rev().collect() |
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 it would be better to return the stream here instead of the collected vec. Then collect it externally when used. Is more generic case imo.
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.
The reason for this is to force it to natural order, because afaik, you can't reverse an stream.
Due to unfold
's nature, items are in to
to from
order, which could be confusing to callers.
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.
There is an iter.rev()
which might be what you are looking for? This works on iterators, not sure if streams are something else?
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 probably what I'm already using in the into_iter().rev()
section. It makes sense to me that you can't chain a Stream
with a rev
without explicitly storing it first: You need to store the whole stream in memory (e.g.: vector) before reversing it, otherwise how do you know what stream item is the first one?
relays: &CryptarchiaConsensusRelays< | ||
BlendAdapter, | ||
BS, | ||
ClPool, | ||
ClPoolAdapter, | ||
DaPool, | ||
DaPoolAdapter, | ||
NetAdapter, | ||
SamplingBackend, | ||
SamplingRng, | ||
Storage, | ||
TxS, | ||
>, |
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.
Now that I see this. CryptarchiaConsensusRelays should be a type alias to hide generics except in a single point.
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 there a way to do it without an associated type?
match initial_state.recovery_strategy() { | ||
CryptarchiaInitialisationStrategy::Genesis => { | ||
info!("Building Cryptarchia from genesis..."); | ||
let leader = Leader::from_genesis(genesis_id, leader_config, ledger_config.clone()); | ||
let cryptarchia = Cryptarchia::new(genesis_id, genesis_state, ledger_config); | ||
|
||
(cryptarchia, leader) | ||
} | ||
CryptarchiaInitialisationStrategy::RecoveryFromGenesis(GenesisRecoveryStrategy { | ||
tip, | ||
}) => { | ||
info!("Recovering Cryptarchia with Genesis strategy."); | ||
let mut leader = | ||
Leader::from_genesis(genesis_id, leader_config, ledger_config.clone()); | ||
let cryptarchia = Self::recover_cryptarchia( | ||
genesis_id, | ||
genesis_state, | ||
tip, | ||
&mut leader, | ||
ledger_config, | ||
relays, | ||
block_subscription_sender, | ||
) | ||
.await; | ||
|
||
(cryptarchia, leader) | ||
} | ||
CryptarchiaInitialisationStrategy::RecoveryFromSecurity(strategy) => { | ||
let SecurityRecoveryStrategy { | ||
tip, | ||
security_block_id, | ||
security_ledger_state, | ||
security_leader_notes, | ||
} = *strategy; | ||
|
||
info!("Recovering Cryptarchia with Security strategy."); | ||
let mut leader = Leader::new( | ||
security_block_id, | ||
security_leader_notes, | ||
leader_config.nf_sk, | ||
ledger_config.clone(), | ||
); | ||
|
||
let cryptarchia = Self::recover_cryptarchia( | ||
security_block_id, | ||
security_ledger_state, | ||
tip, | ||
&mut leader, | ||
ledger_config, | ||
relays, | ||
block_subscription_sender, | ||
) | ||
.await; | ||
|
||
(cryptarchia, leader) | ||
} | ||
} | ||
} |
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.
Nitpick: This could be refactore as an implementation of CryptarchiaInitialisationStrategy
(?), with independent methods for each case.
impl CryptarchiaInitializationStrategy {
pub fn recover(&self, *args) -> (Cryptarchia, Leader) {
match self {
CryptarchiaInitialisationStrategy::Genesis => recover_from_genesis(self, *args),
...
}
}
}
Or at least separated methods for each branch.
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.
The thought did cross my mind but I kinda discarded because I assumed it'd get a bit messy with parameters and such. I'll give it a try. Thanks!
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.
As good as it sounded moving that code to the strategy structs, it propagates generics to them as well which seemed a bit messy.
For now I ended up just separating them into methods for each branch, and they can be easily moved into strategy if we want.
Do you guys consider worth it worth? That is, moving them to the strategy section despite that.
@@ -143,7 +143,7 @@ impl<Id> Cryptarchia<Id> | |||
where | |||
Id: Eq + std::hash::Hash + Copy, | |||
{ | |||
pub fn from_genesis(id: Id, config: Config) -> Self { | |||
pub fn new(id: Id, config: Config) -> Self { |
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 there be a clearer name for this 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.
I'd say it's quite accurate. It's really not doing anything fancy, nor anything attached to a special state (genesis), just getting an instance with a given id
.
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.
Naming is fine I think.
LeaderConfig { notes, nf_sk }: LeaderConfig, | ||
config: Config, | ||
) -> Self { | ||
Leader::new(genesis, notes, nf_sk, config) |
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 pedantic clippy suggests me this as a better alternative
Leader::new(genesis, notes, nf_sk, config) | |
Self::new(genesis, notes, nf_sk, config) |
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 slipped by, thanks.
@@ -126,6 +135,10 @@ impl Leader { | |||
|
|||
None | |||
} | |||
|
|||
pub(crate) fn notes(&self, header_id: &HeaderId) -> Option<&Vec<NoteWitness>> { |
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.
Would it make sense to rater return an impl Iterator
here? Then the consumer can collect it however they want to.
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.
self.notes
is already a Vec
, do you think it's actually worth casting it to return it 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.
Probaly Option<&[NoteWitness]>
is fine. From that can be transformer into iter if needed, or be indexed (which probably is not needed but anyway...).
type Settings = CryptarchiaSettings<TxS, BxS, NetworkAdapterSettings, BlendAdapterSettings>; | ||
type Error = Error; | ||
|
||
fn from_settings(_settings: &Self::Settings) -> Result<Self, Self::Error> { |
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.
Nothing to do with this PR, but this is the bit I was talking about elsewhere. All states must do this, and it looks like every time the implementation will always be the same, which to me indicates it's not optimal design.
e879e44
to
462ab57
Compare
generics' constraints.
…ist in StorageAdapter.
…ead. Simplify parameters on restore_from_recovery.
…sed on recovery strategy, now it doesn't need to update from genesis to k.
462ab57
to
c151fbc
Compare
DaPool::BlockId: Debug, | ||
DaPool::Item: Debug + DeserializeOwned + Eq + Hash + Clone + Send + Sync + 'static, | ||
DaPool::Key: Debug + 'static, | ||
// |
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.
leftover?
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.
Yup, from the rebase, still cleaning up stuff.
1. What does this PR implement?
Rebuild Node state from a previously saved state:
StorageAdapter
wrapper over the storage relay.tip
, andBlock
andLedgerState
andNoteWitness
in security position.Cryptarchia
andLeader
, defaulting to genesis strategy if no data is available.Test plans to be implemented: #1139
2. Does the code have enough context to be clearly understood?
Yes
3. Who are the specification authors and who is accountable for this PR?
@AlejandroCabeza
4. Is the specification accurate and complete?
N/A
5. Does the implementation introduce changes in the specification?
N/A
Checklist
Warning
Do not merge the PR if any of the following is missing: