Skip to content

Partial integration of the Era Supervisor with the Reactor#42

Merged
afck merged 4 commits intomasterfrom
highway_synchronizer
Jul 1, 2020
Merged

Partial integration of the Era Supervisor with the Reactor#42
afck merged 4 commits intomasterfrom
highway_synchronizer

Conversation

@fizyk20
Copy link
Contributor

@fizyk20 fizyk20 commented Jun 30, 2020

This PR wires up some events and effects of the Era Supervisor with the Reactor. There are also some changes to the implementation of the synchronizer for the Highway protocol.

https://casperlabs.atlassian.net/browse/HWY-74
https://casperlabs.atlassian.net/browse/HWY-87

let storage = Storage::new(cfg.storage)?;
let (api_server, api_server_effects) = ApiServer::new(cfg.http_server, effect_builder);
let (consensus, consensus_effects) = Consensus::new(effect_builder);
let consensus = EraSupervisor::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

Nitpick: this is a bit leaking – reactor shouldn't have to know or care that there's something like eras or EraSupervisor.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we "merged" Consensus and EraSupervisor, but maybe it should be called Consensus.

/// The piece of information that will become the content of a future block (isn't finalized or
/// executed yet)
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProtoBlock {
Copy link
Contributor

@goral09 goral09 Jul 1, 2020

Choose a reason for hiding this comment

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

It's missing more block information like seq number, panorama, signature etc. I guess that will come later? If so, can we create a ticket for it already?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it will contain the panorama or any signatures; that would be redundant, and an implementation detail of the consensus algorithm.

Copy link
Contributor

Choose a reason for hiding this comment

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

I must be missing something – is this data transferred over the wire to other nodes?

Copy link
Contributor

Choose a reason for hiding this comment

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

BTW, Isn't this ProtoBlock specific to Highway only? I thought that messages that will be transmitted over the wire will be generic in that.

Copy link
Contributor

Choose a reason for hiding this comment

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

The ProtoBlock is precisely meant to be the Highway/Casper-independent part of the block: the "consensus value".

So it is transferred over the wire, as part of a Highway block.

Copy link
Contributor

Choose a reason for hiding this comment

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

The ProtoBlock is precisely meant to be the Highway/Casper-independent part of the block: the "consensus value".

Ah, that isn't obvious. There's no comment that explains the intent and the name is also misleading. It even says proto but we won't be using protobuf most probably.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's proto as in prototype, not as in protocol ;)

#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ExecutedBlock {
/// The executed proto-block
pub proto_block: ProtoBlock,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just refer to the proto_block by its hash?

Copy link
Contributor

Choose a reason for hiding this comment

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

We could, but it might complicate things a bit: Then we'd need something like a proto block store as well…
Also: Once it's executed we don't really need to proto block alone anymore, do we?

Copy link
Contributor

Choose a reason for hiding this comment

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

We will need that anyway – in case of crashes, restarts, catching up?

Copy link
Contributor

Choose a reason for hiding this comment

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

But if it has already been added to the Highway protocol state, it would be part of it. (If we don't just store it as a hash.) So if we persist the protocol state, we automatically also persist the consensus values, i.e. the proto blocks.

If we don't want it in the protocol state, and use hashes instead, then we'd need a separate storage for them, yes.

Copy link
Contributor

Choose a reason for hiding this comment

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

ProtocolState is ephemeral and we need the finalized linear chain to be persistent.

}

/// Checks whether the deploys included in the proto-block exist on the network
pub(crate) async fn validate_proto_block<I>(
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this be done in the synchronizer of the consensus protocol?

Copy link
Contributor

Choose a reason for hiding this comment

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

We were thinking it makes more sense for another component (deploy fetcher?) to do this.
But it does happen after what the synchronizer does (downloading dependencies) and before adding the vertex to the DAG.

CreateNewBlock,
ScheduleTimer(u64, TimerId),
/// Request deploys for a new block, whose timestamp will be the given `u64`.
CreateNewBlock(u64),
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not entirely clear to me how this message is going to be handled. Is it returned from the ConsensusProtocol to reactor? Or just EraSupervisor that will have access to a DeployBuffer (or some other deploy storage)? Shouldn't it also (besides the block timestamp) also carry an information about consensus value proposed between last finalized block and the current tip? I understand that it's not part of this task but we should leave a comment that is complete and clear.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the intention here is that another component (TBD) will select deploys.
I agree, a TODO makes sense. Although it's hard to "forget": We'll simply need that information once we try to handle deploy dependencies.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why we would need yet another component for that. Only an active validator can ever produce new blocks so it would make sense to hide this logic there.

Copy link
Contributor

Choose a reason for hiding this comment

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

But it means that the consensus component — in addition to all the stuff it already deals with — needs to care about block gas limits, size limits, deploy dependencies, etc.

And it seems like it would be a relatively small interface between consensus and deploy buffer, if we make that separate.

}

#[derive(Clone, Debug)]
struct EraInstance<Id> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's not used anywhere? At least I can't find it.

}

/// Passes the timestamp of a future block for which deploys are to be proposed
pub(crate) async fn request_proto_block(self, instant: u64) -> ProtoBlock {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is request a good verb here? Is it going to actually make any requests or just accept all the necessary information in the argument list?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, it's a "request" from the consensus component to some other component (deploy buffer?) to select a bunch of deploys. Not sure if it's a good word.

Copy link
Contributor

Choose a reason for hiding this comment

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

So it's related to another comment https://github.com/CasperLabs/casperlabs-node/pull/42/files#r448237398 . In any case, it should probably also carry an information about all the deploys that are already known to the current consensus instance.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, let's add a TODO for that, too: It'll probably be a nontrivial issue in itself to add that.

.ignore(),
ConsensusProtocolResult::ScheduleTimer(_timestamp, _timer_id) => {
// TODO: we need to get the current system time here somehow, in order to schedule
// a timer for the correct moment - and we don't want to use std::Instant
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't timestamp already a "real" moment in time in the future?

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we don't want to use std::Instant?

Copy link
Contributor

Choose a reason for hiding this comment

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

Instants are purely local (i.e. subjective), monotonically increasing timestamps: You can't communicate and agree on an Instant among different nodes. Highway assumes synchronized clocks and the timer needs to be scheduled at a particular "absolute" point in time (i.e. not relative to any particular local event).
See the discussion on Slack: https://casperlabs-team.slack.com/archives/C015VHEF532/p1593530821205400

));
}
Ok(SynchronizerEffect::InvalidVertex(v, sender, err)) => {
warn!("Invalid vertex from {:?}: {:?}, {:?}", v, sender, err);
Copy link
Contributor

Choose a reason for hiding this comment

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

There's an InvalidIncomingMessage variant in the ConsensusProtocolResult enum.

/// Request deploys for a new block, whose timestamp will be the given `u64`.
CreateNewBlock(u64),
FinalizedBlock(C),
ValidateConsensusValue(NodeId, C),
Copy link
Contributor

Choose a reason for hiding this comment

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

What does it mean to "validate a consensus value"? Can you leave a comment with a TODO what needs to be done? It's not clear to me whether "validating" means checking signatures (this should be done at the moment of receiving the deploy)? Downloading it from the peers (then it shouldn't be propagated to the reactor)? Or something different?

Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe:

/// A consensus message we received from the specified node contains a consensus value.
// TODO: Add a `ConsensusProtocol::consensus_value_validated` method to call back after validation.

Since C is generic here I wouldn't refer to ProtoBlocks, deploys and downloading then from the sender. That part should be explained in the era supervisor, where the consensus value is now ProtoBlock and not a generic argument anymore.

Anyway, concretely it means: "Make sure you have and validated all deploys mentioned in this ProtoBlock."

Copy link
Contributor

Choose a reason for hiding this comment

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

The only validations I can imagine performed on a deploy should be either done at the moment of receiving it or are consensus-instance specific. I can't think of anything that would require contact with an external component.

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 idea here is that consensus values might have some intrinsic validity conditions (not related to signatures or the consensus protocol itself), so when you receive a proposal for a value from another node, you might want to check whether the value can actually be legally proposed. In our case, this intrinsic condition is the existence of all the deploys mentioned by hash within the value (proto-block), but the general idea could also be applied to other use cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, I wasn't aware that you changed the plan about where the syncing of consensus value will happen.

Copy link
Contributor

Choose a reason for hiding this comment

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

But the component that will usually be receiving and validating deploys (also gossiped pending ones) — do you think that should also be the consensus component?

Copy link
Contributor

@afck afck left a comment

Choose a reason for hiding this comment

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

I'll remove myself from the reviewers, since I'm one of the authors.
(Edit: Looks like GitHub doesn't count me as a reviewer anyway. 👍 )

CreateNewBlock,
ScheduleTimer(u64, TimerId),
/// Request deploys for a new block, whose timestamp will be the given `u64`.
CreateNewBlock(u64),
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, the intention here is that another component (TBD) will select deploys.
I agree, a TODO makes sense. Although it's hard to "forget": We'll simply need that information once we try to handle deploy dependencies.

/// Request deploys for a new block, whose timestamp will be the given `u64`.
CreateNewBlock(u64),
FinalizedBlock(C),
ValidateConsensusValue(NodeId, C),
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe:

/// A consensus message we received from the specified node contains a consensus value.
// TODO: Add a `ConsensusProtocol::consensus_value_validated` method to call back after validation.

Since C is generic here I wouldn't refer to ProtoBlocks, deploys and downloading then from the sender. That part should be explained in the era supervisor, where the consensus value is now ProtoBlock and not a generic argument anymore.

Anyway, concretely it means: "Make sure you have and validated all deploys mentioned in this ProtoBlock."

.ignore(),
ConsensusProtocolResult::ScheduleTimer(_timestamp, _timer_id) => {
// TODO: we need to get the current system time here somehow, in order to schedule
// a timer for the correct moment - and we don't want to use std::Instant
Copy link
Contributor

Choose a reason for hiding this comment

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

Instants are purely local (i.e. subjective), monotonically increasing timestamps: You can't communicate and agree on an Instant among different nodes. Highway assumes synchronized clocks and the timer needs to be scheduled at a particular "absolute" point in time (i.e. not relative to any particular local event).
See the discussion on Slack: https://casperlabs-team.slack.com/archives/C015VHEF532/p1593530821205400

}

/// Passes the timestamp of a future block for which deploys are to be proposed
pub(crate) async fn request_proto_block(self, instant: u64) -> ProtoBlock {
Copy link
Contributor

Choose a reason for hiding this comment

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

Well, it's a "request" from the consensus component to some other component (deploy buffer?) to select a bunch of deploys. Not sure if it's a good word.

}

/// Checks whether the deploys included in the proto-block exist on the network
pub(crate) async fn validate_proto_block<I>(
Copy link
Contributor

Choose a reason for hiding this comment

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

We were thinking it makes more sense for another component (deploy fetcher?) to do this.
But it does happen after what the synchronizer does (downloading dependencies) and before adding the vertex to the DAG.

let storage = Storage::new(cfg.storage)?;
let (api_server, api_server_effects) = ApiServer::new(cfg.http_server, effect_builder);
let (consensus, consensus_effects) = Consensus::new(effect_builder);
let consensus = EraSupervisor::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we "merged" Consensus and EraSupervisor, but maybe it should be called Consensus.

/// The piece of information that will become the content of a future block (isn't finalized or
/// executed yet)
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ProtoBlock {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it will contain the panorama or any signatures; that would be redundant, and an implementation detail of the consensus algorithm.

#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ExecutedBlock {
/// The executed proto-block
pub proto_block: ProtoBlock,
Copy link
Contributor

Choose a reason for hiding this comment

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

We could, but it might complicate things a bit: Then we'd need something like a proto block store as well…
Also: Once it's executed we don't really need to proto block alone anymore, do we?

Copy link
Contributor

@goral09 goral09 left a comment

Choose a reason for hiding this comment

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

No blockers, few requests for a TODO.

@afck afck merged commit f503cc6 into master Jul 1, 2020
@afck afck deleted the highway_synchronizer branch July 1, 2020 12:45
rafal-ch pushed a commit that referenced this pull request Sep 11, 2024
Current `Extend` trait of Rust doesn't support builder-like
implementations.

Hence, we need an explicit method for the `Hasher`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants