Skip to content
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

Open
wants to merge 39 commits into
base: master
Choose a base branch
from

Conversation

AlejandroCabeza
Copy link
Contributor

@AlejandroCabeza AlejandroCabeza commented Feb 11, 2025

1. What does this PR implement?

Rebuild Node state from a previously saved state:

  • Implement a StorageAdapter wrapper over the storage relay.
  • Save tip, and Block and LedgerState and NoteWitness in security position.
  • Use saved data (if there is) to initialise Cryptarchia and Leader, defaulting to genesis strategy if no data is available.
  • Cleanup for clarity: Renaming, reordering, logs, etc.

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:

  • 1. Description added.
  • 2. Context and links to Specification document(s) added.
  • 3. Main contact(s) (developers and specification authors) added
  • 4. Implementation and Specification are 100% in sync including changes. This is critical.
  • 5. Link PR to a specific milestone.

@AlejandroCabeza AlejandroCabeza added this to the Iteration 10 milestone Feb 11, 2025
@AlejandroCabeza AlejandroCabeza self-assigned this Feb 11, 2025
Base automatically changed from chore/group-consensus-relays to master February 17, 2025 12:47
@AlejandroCabeza AlejandroCabeza force-pushed the feature/recovery/consensus branch from af9aa0b to 3be949e Compare February 19, 2025 21:33
@AlejandroCabeza AlejandroCabeza changed the title [DRAFT] feature(consensus): Recovery feature(consensus): Recovery Feb 20, 2025
@AlejandroCabeza AlejandroCabeza force-pushed the feature/recovery/consensus branch 2 times, most recently from 30cb871 to e879e44 Compare February 21, 2025 13:35
@AlejandroCabeza AlejandroCabeza marked this pull request as ready for review February 21, 2025 13:35
Copy link
Collaborator

@danielSanchezQ danielSanchezQ left a 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()
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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?

Comment on lines 930 to 1070
relays: &CryptarchiaConsensusRelays<
BlendAdapter,
BS,
ClPool,
ClPoolAdapter,
DaPool,
DaPoolAdapter,
NetAdapter,
SamplingBackend,
SamplingRng,
Storage,
TxS,
>,
Copy link
Collaborator

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.

Copy link
Contributor Author

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?

Comment on lines 945 to 1103
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)
}
}
}
Copy link
Collaborator

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.

Copy link
Contributor Author

@AlejandroCabeza AlejandroCabeza Feb 28, 2025

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!

Copy link
Contributor Author

@AlejandroCabeza AlejandroCabeza Mar 14, 2025

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

@AlejandroCabeza AlejandroCabeza Feb 28, 2025

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.

Copy link
Collaborator

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)
Copy link
Contributor

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

Suggested change
Leader::new(genesis, notes, nf_sk, config)
Self::new(genesis, notes, nf_sk, config)

Copy link
Contributor Author

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>> {
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Collaborator

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> {
Copy link
Contributor

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.

@AlejandroCabeza AlejandroCabeza force-pushed the feature/recovery/consensus branch from e879e44 to 462ab57 Compare February 28, 2025 21:45
@AlejandroCabeza AlejandroCabeza force-pushed the feature/recovery/consensus branch from 462ab57 to c151fbc Compare March 14, 2025 14:23
DaPool::BlockId: Debug,
DaPool::Item: Debug + DeserializeOwned + Eq + Hash + Clone + Send + Sync + 'static,
DaPool::Key: Debug + 'static,
//
Copy link
Collaborator

Choose a reason for hiding this comment

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

leftover?

Copy link
Contributor Author

@AlejandroCabeza AlejandroCabeza Mar 14, 2025

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants