-
Notifications
You must be signed in to change notification settings - Fork 178
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
New Block structure #3101
base: main
Are you sure you want to change the base?
New Block structure #3101
Conversation
96cbde5
to
6ab368e
Compare
6ab368e
to
facb07d
Compare
let new_round = certificate.round; | ||
if let Some(Vote { value, round, .. }) = &self.confirmed_vote { | ||
if value.inner().executed_block().block == *new_block && *round == new_round { | ||
if value.inner().block() == new_block && *round == new_round { |
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.
TODO: Double-check. Previously we were comparing the Proposal
and now we compare the new Block
type. It means we also compare the execution results now.
linera-core/src/client/mod.rs
Outdated
if certificate.executed_block().block | ||
== confirmed_value.inner().executed_block().block => | ||
if certificate.block() == confirmed_value.inner().block() => | ||
// TODO: double check this. Previously we were comparing the Proposal only. |
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.
TODO: Double-check. Same here as above.
@@ -1235,5 +1241,5 @@ fn empty_executed_block_size() { | |||
outcome: crate::data_types::BlockExecutionOutcome::default(), | |||
}; | |||
let size = bcs::serialized_size(&executed_block).unwrap(); | |||
assert_eq!(size, EMPTY_EXECUTED_BLOCK_SIZE); | |||
assert_eq!(size, EMPTY_BLOCK_SIZE); |
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.
TODO: Test for empty block (new) size.
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.
Done in Testy empty Block size.
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.
What's the point of testing a test-only variable? (But as I said I think we should just use this one in production.)
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're right - it doesn't make sense to keep this test for the ExecutedBlock
type.
6a9cccf
to
bb50374
Compare
where | ||
S: serde::ser::Serializer, | ||
{ | ||
serializer.serialize_newtype_struct("CryptoHashVec", &self.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.
Another #[transparent]
?
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, good 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.
Done in dca5da3.
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.
Actually, this won't work, b/c we will start getting the errors that we can call type_name()
only on struct
or newtype
(b/c we'll call Deserialize
on the Vec<_>
and that will fail).
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 why do we need this in the first place?
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.
When constructing the new Block
, we are hashing the contents of the BlockBody
- we iterate over elements of incoming_bundles
, operations
, etc. turning them into CryptoHash
-es and then turning resulting Vec<CryptoHash>
into a CryptoHash
- here. When calling CryptoHash::new(Vec<CryptoHash>)
the code would fail b/c calling serde_name::trace_name
on a Vec
throws. To work around that we have this newtype struct that wraps the Vec<CryptoHash>
first.
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.
Wouldn't deriving Serialize
and Deserialize
(without #[transparent]
) do exactly the same as this implementation?
} | ||
|
||
/// Returns whether this value contains the message with the specified ID. | ||
pub fn has_message(&self, message_id: &MessageId) -> bool { | ||
self.executed_block().message_by_id(message_id).is_some() | ||
self.block().message_by_id(message_id).is_some() |
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 the handle the renaming executed_block
-> block
first 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.
Actually, I didn't get rid of ExecutedBlock
. I tried but the Proposal
/BlockExecutionOutcome
is used in a couple of places (we execute before proposing, we execute when we receive a proposal, we re-execute when we receive a confirmed certificate, etc.) so I kept the ExecutedBlock
as an internal data structure that caries the result of execution.
This particular change that you commented on is a follow-up to changing the ConfirmedBlock(ExecutedBlock)
to ConfirmedBlock(Block)
so it's not a rename only, it's actually a new Block
type inside.
dca5da3
to
d13da68
Compare
200252e
to
f3c8e49
Compare
When (de)serializing a Block, don't (de)serialize the hashes from the BlockHeader but rather compute them from the elements of the BlockBody. This makes sure that we never simply _trust_ the values that are being sent and rather compute them ourselves.
f3c8e49
to
652604e
Compare
@@ -395,16 +399,16 @@ impl OutgoingMessage { | |||
} | |||
} | |||
|
|||
/// A [`Block`], together with the outcome from its execution. | |||
/// A [`Proposal`], together with the outcome from its execution. | |||
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)] | |||
pub struct ExecutedBlock { |
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 ProposalWithOutcome
. Or even inline it, if it's not used in many places anymore?
@@ -76,7 +76,7 @@ pub struct Block { | |||
pub previous_block_hash: Option<CryptoHash>, | |||
} | |||
|
|||
impl Block { | |||
impl Proposal { |
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 a BlockProposal
contains a ProposalContent
which contains a Proposal
.
Maybe call this type ProposedBlock
?
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 didn't call it ProposedBlock
on purpose b/c we have the BlockProposal
which sounds basically equivalent that is the encompassing type. It seemed weird that BlockProposal
contains ProposedBlock
.
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 isn't a BlockProposal
containing a Proposal
even worse?
Maybe BlockInput
? BlockTemplate
? BlockPlan
? 😕
51161cf
to
543802f
Compare
543802f
to
5cbe42e
Compare
where | ||
S: serde::ser::Serializer, | ||
{ | ||
serializer.serialize_newtype_struct("CryptoHashVec", &self.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.
Wouldn't deriving Serialize
and Deserialize
(without #[transparent]
) do exactly the same as this implementation?
|
||
/// Block defines the atomic unit of growth of the Linera chain. | ||
/// As part of the block body, contains all the incoming messages | ||
/// and operations to executed which define state transition of the chain. |
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 operations to executed which define state transition of the chain. | |
/// and operations to execute which define a state transition of the chain. |
@@ -192,3 +206,362 @@ pub enum ConversionError { | |||
#[error("Expected a `TimeoutCertificate` value")] | |||
Timeout, | |||
} | |||
|
|||
/// Block defines the atomic unit of growth of the Linera chain. | |||
/// As part of the block body, contains all the incoming messages |
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's common to add a blank line (i.e. new paragraph in markdown; also below).
/// As part of the block body, contains all the incoming messages | |
/// | |
/// As part of the block body, contains all the incoming messages |
linera-chain/src/chain.rs
Outdated
@@ -147,9 +148,13 @@ static STATE_HASH_COMPUTATION_LATENCY: LazyLock<HistogramVec> = LazyLock::new(|| | |||
) | |||
}); | |||
|
|||
/// The BCS-serialized size of an empty `ExecutedBlock`. | |||
/// The BCS-serialized size of an empty [`crate::data_types::ExecutedBlock`]. |
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 we still need to serialize ExtecutedBlock
s at all? Can't we always use Block
instead?
And this constant is about the block size limit: I'd say that should apply to Block
, not to ExecutedBlock
?
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're right. We don't need them anymore.
linera-chain/src/chain.rs
Outdated
@@ -698,7 +707,7 @@ where | |||
account: block.authenticated_signer, | |||
}; | |||
resource_controller | |||
.track_executed_block_size(EMPTY_EXECUTED_BLOCK_SIZE) | |||
.track_block_size(EMPTY_EXECUTED_BLOCK_SIZE) |
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 rather use EMPTY_BLOCK_SIZE
here.
.track_block_size(EMPTY_EXECUTED_BLOCK_SIZE) | |
.track_block_size(EMPTY_EXECUTED_BLOCK_SIZE) |
@@ -1235,5 +1241,5 @@ fn empty_executed_block_size() { | |||
outcome: crate::data_types::BlockExecutionOutcome::default(), | |||
}; | |||
let size = bcs::serialized_size(&executed_block).unwrap(); | |||
assert_eq!(size, EMPTY_EXECUTED_BLOCK_SIZE); | |||
assert_eq!(size, EMPTY_BLOCK_SIZE); |
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.
What's the point of testing a test-only variable? (But as I said I think we should just use this one in production.)
Motivation
Verifying Linera (micro)chains with a light client requires following the chain's blocks and subsequently confirming whether an event, a message, state change, were part of that block. Our current block definition -
ExecutedBlock
type - was not made for this purpose:BlockExecutionOutcome
w/o a way to easily verify whether a particular event is part of that set other than sending the whole vector.Proposal
In this PR we introduce the new block structures. The new
Block
type consists of two elements:BlockHeader
is a succint representation of the chain's new block. It contains metadata that suffices to track the chain's progress:BlockBody
holds the data that is not necessary for (light) verification but was either an input to the block's execution (incoming bundles, operations) or its output (oracle responses, messages sent, events emitted). Each vector is deterministically hashed (intoCryptoHash
) and the result of that hashing is put into theBlockHeader
. This way it's possible to verify inclusion of any specific element w/o having to sent all of the body.Test Plan
All of the tests have been updated to work with the new types.
Release Plan
Links