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

New Block structure #3101

Merged
merged 30 commits into from
Jan 22, 2025
Merged

New Block structure #3101

merged 30 commits into from
Jan 22, 2025

Conversation

deuszx
Copy link
Contributor

@deuszx deuszx commented Jan 8, 2025

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:

  • there is no distinction between block's "metadata" and the "body" - which would allow for light verification of the blocks.
  • the events/messages/operations are all layed out, flat, in the 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:

  • (block) header
  • (block) body.

BlockHeader is a succint representation of the chain's new block. It contains metadata that suffices to track the chain's progress:

pub struct BlockHeader {
    /// The block version.
    pub version: u8, // TODO: More granular versioning. #3078
    /// The chain to which this block belongs.
    pub chain_id: ChainId,
    /// The number identifying the current configuration.
    pub epoch: Epoch,
    /// The block height.
    pub height: BlockHeight,
    /// The timestamp when this block was created.
    pub timestamp: Timestamp,
    /// The hash of the chain's execution state after this block.
    pub state_hash: CryptoHash,
    /// Certified hash of the previous block in the
    /// chain, if any.
    pub previous_block_hash: Option<CryptoHash>,
    /// The user signing for the operations in the block and paying for their execution
    /// fees. If set, this must be the `owner` in the block proposal. `None` means that
    /// the default account of the chain is used. This value is also used as recipient of
    /// potential refunds for the message grants created by the operations.
    pub authenticated_signer: Option<Owner>,

    // Inputs to the block, chosen by the block proposer.
    /// Cryptographic hash of all the incoming bundles in the block.
    pub bundles_hash: CryptoHash,
    /// Cryptographic hash of all the operations in the block.
    pub operations_hash: CryptoHash,

    // Outcome of the block execution.
    /// Cryptographic hash of all the messages in the block.
    pub messages_hash: CryptoHash,
    /// Cryptographic hash of all the oracle responses in the block.
    pub oracle_responses_hash: CryptoHash,
    /// Cryptographic hash of all the events in the block.
    pub events_hash: CryptoHash,
}

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 (into CryptoHash) and the result of that hashing is put into the BlockHeader. This way it's possible to verify inclusion of any specific element w/o having to sent all of the body.

/// The body of a block containing all the data included in the block.
#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, SimpleObject)]
pub struct BlockBody {
    /// A selection of incoming messages to be executed first. Successive messages of same
    /// sender and height are grouped together for conciseness.
    pub incoming_bundles: Vec<IncomingBundle>,
    /// The operations to execute.
    pub operations: Vec<Operation>,
    /// The list of outgoing messages for each transaction.
    pub messages: Vec<Vec<OutgoingMessage>>,
    /// The record of oracle responses for each transaction.
    pub oracle_responses: Vec<Vec<OracleResponse>>,
    /// The list of events produced by each transaction.
    pub events: Vec<Vec<EventRecord>>,
}

Test Plan

All of the tests have been updated to work with the new types.

Release Plan

  • These changes follow the usual release cycle. Although we may want to defer this update since this change is not backwards compatible.

Links

@deuszx deuszx force-pushed the new-block-structure branch from 96cbde5 to 6ab368e Compare January 8, 2025 14:30
@deuszx deuszx force-pushed the new-block-structure branch from 6ab368e to facb07d Compare January 9, 2025 12:01
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.
Copy link
Contributor Author

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.

linera-base/src/crypto.rs Outdated Show resolved Hide resolved
@deuszx deuszx force-pushed the new-block-structure branch from 6a9cccf to bb50374 Compare January 9, 2025 18:33
@deuszx deuszx changed the title New block structure New Block structure Jan 10, 2025
linera-base/src/crypto.rs Outdated Show resolved Hide resolved
}

/// 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()
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 the handle the renaming executed_block -> block first in a separate PR?

Copy link
Contributor Author

@deuszx deuszx Jan 10, 2025

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.

@deuszx deuszx force-pushed the new-block-structure branch from dca5da3 to d13da68 Compare January 10, 2025 18:21
@deuszx deuszx marked this pull request as ready for review January 10, 2025 18:45
@deuszx deuszx requested review from afck and bart-linera January 10, 2025 18:47
@afck afck self-requested a review January 13, 2025 13:24
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.
@deuszx deuszx force-pushed the new-block-structure branch from f3c8e49 to 652604e Compare January 13, 2025 13:42
@@ -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 {
Copy link
Contributor

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

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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? 😕

@deuszx deuszx force-pushed the new-block-structure branch from 51161cf to 543802f Compare January 13, 2025 16:35
@deuszx deuszx requested review from ma2bd and afck January 13, 2025 16:38
@deuszx deuszx force-pushed the new-block-structure branch from 543802f to 5cbe42e Compare January 13, 2025 16:42
linera-base/src/crypto.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Show resolved Hide resolved
linera-chain/src/chain.rs Outdated Show resolved Hide resolved
linera-chain/src/chain.rs Outdated Show resolved Hide resolved
linera-chain/src/chain.rs Show resolved Hide resolved
@deuszx deuszx requested a review from afck January 14, 2025 10:44
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/block.rs Outdated Show resolved Hide resolved
linera-chain/src/manager.rs Show resolved Hide resolved
authenticated_signer: parent_block.authenticated_signer,
timestamp: parent_block.timestamp,
height: parent_header.height.try_add_one().unwrap(),
authenticated_signer: parent_header.authenticated_signer,
Copy link
Contributor

Choose a reason for hiding this comment

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

(Weird! What if this is proposed by someone else? Not new in this PR, of course.)

linera-chain/src/unit_tests/chain_tests.rs Outdated Show resolved Hide resolved
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.

No blockers from my side, but:

  • Can we remove ExecutedBlock now? (Maybe in another PR.)
  • One test is still failing.
  • Since this is a very central type, it would be great if we could get a second review.

@deuszx
Copy link
Contributor Author

deuszx commented Jan 17, 2025

  • Can we remove ExecutedBlock now? (Maybe in another PR.)

Discussed offline - and decided not to do that. ExecutedBlock is still being used as a return type from execution. it's also more useful as the internal, intermediate type b/c it retains the original proposal <> outcome distinction (Block is the end type) and that is used in client (due to pre-execution before sending the proposal) and on the server to differentiate between initial proposal and a retry.

  • One test is still failing.

I think I fixed that now.

  • Since this is a very central type, it would be great if we could get a second review.

I agree, let's wait.

@afck afck merged commit 6a8789e into main Jan 22, 2025
23 of 24 checks passed
@afck afck deleted the new-block-structure branch January 22, 2025 17:08
afck added a commit that referenced this pull request Jan 23, 2025
## Motivation

The discussion
#3101 (comment)
was never resolved; a proposal has a content, which has a proposal.

## Proposal

Rename `Proposal` to `ProposedBlock`, to distinguish it from the outer
type.

(I also added some destructuring in `Block::new`, because I ran into a
naming conflict and this way the compiler will warn us if we add any
fields and forget them there.)

## Test Plan

Only renaming.

## Release Plan

- Nothing to do / These changes follow the usual release cycle.

## Links

- Related to #3101
- [reviewer
checklist](https://github.com/linera-io/linera-protocol/blob/main/CONTRIBUTING.md#reviewer-checklist)
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.

Add new Block and BlockHeader structs
3 participants