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

feat(sequencer): implement proving of action tree root inside Header::data_hash #286

Merged
merged 66 commits into from
Aug 24, 2023

Conversation

noot
Copy link
Collaborator

@noot noot commented Aug 18, 2023

Summary

  • implement generation of merkle inclusion proof of the action tree commitment (first "tx" in a sequencer block) inside the block header's data_hash (aka transactions root)

Background

  • required for validation of data inside conductor

Changes

  • update SequencerBlockData and SequencerNamespaceData to contain action_tree_root and action_tree_root_inclusion_proof
  • SequencerBlockData::from_tendermint_block now generates the inclusion proof
  • SequencerBlockData::new() checks the inclusion proof

Testing

  • unit tests

Related Issues

#153 (does not close yet, follow up will close it)

noot added 30 commits August 2, 2023 17:46
// we don't need to verify the action tree root here,
// as [`SequencerBlockData`] would not be constructable
// if it was invalid

Copy link
Member

Choose a reason for hiding this comment

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

nit: remove empty line between code + comment?

action_tree_root_inclusion_proof
.verify(action_tree_root.as_bytes(), data_hash)
.wrap_err("failed to verify action tree root inclusion proof")?;
// TODO: also verify last_commit_hash (#270)
Copy link
Member

Choose a reason for hiding this comment

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

nit: full link preferable

Copy link
Member

Choose a reason for hiding this comment

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

Please provide a full link to the issue or PR, like so:

Suggested change
// TODO: also verify last_commit_hash (#270)
// TODO(https://github.com/astriaorg/astria/issues/270): also verify last_commit_hash

@@ -97,12 +122,23 @@ impl SequencerBlockData {

#[allow(clippy::type_complexity)]
#[must_use]
pub fn into_values(self) -> (Hash, Header, Option<Commit>, HashMap<Namespace, RollupData>) {
pub fn into_values(
self,
Copy link
Member

Choose a reason for hiding this comment

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

Is this formatting correct? Feels weird to push the self to new line because the return is

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is what rustfmt wants :|

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ct-merkle = "0.1"
ct-merkle = { version = "0.1", features = ["serde"] }
Copy link
Member

Choose a reason for hiding this comment

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

Should this be using the workspace version?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the workspace version is under a patch, not an actual import, so it overrides this w/ the patch

Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

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

Saw a few things that should be fixed prior to accepting.

Orthogonal to this PR I had a chance to read the wrapper of CtMerkle. I was wondering why you chose to implement this as a newtype wrapper instead of using an extension trait (or simply utility functions)?

The difficulty with wrappers is often that one ends up reimplementing more and more of the methods of the wrapped type, without providing proper documentation. That could be avoided by simply using the type directly.

EDIT: After reading #256 and thinking it through I agree with not directly exposing ctmerkle and its types.

@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ct-merkle = "0.1"
ct-merkle = { version = "0.1", features = ["serde"] }
Copy link
Member

Choose a reason for hiding this comment

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

ct-merkle is imported here, but there is a ct-merkle in the workspace cargo toml that refers to a git repo under the astria org.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah that's under the patch section (i had to make some changes to the lib, see here rozbb/ct-merkle#2)

pub struct SequencerBlockData {
pub(crate) block_hash: Hash,
pub(crate) header: Header,
/// This field should be set for every block with height > 1.
pub(crate) last_commit: Option<Commit>,
/// namespace -> rollup data (chain ID and transactions)
pub(crate) rollup_data: HashMap<Namespace, RollupData>,
/// The root of the action tree for this block.
pub(crate) action_tree_root: Hash,
/// The inclusion proof that the action tree root in included
Copy link
Member

Choose a reason for hiding this comment

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

typo:

Suggested change
/// The inclusion proof that the action tree root in included
/// The inclusion proof that the action tree root is included

action_tree_root_inclusion_proof
.verify(action_tree_root.as_bytes(), data_hash)
.wrap_err("failed to verify action tree root inclusion proof")?;
// TODO: also verify last_commit_hash (#270)
Copy link
Member

Choose a reason for hiding this comment

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

Please provide a full link to the issue or PR, like so:

Suggested change
// TODO: also verify last_commit_hash (#270)
// TODO(https://github.com/astriaorg/astria/issues/270): also verify last_commit_hash

@@ -97,12 +122,23 @@ impl SequencerBlockData {

#[allow(clippy::type_complexity)]
#[must_use]
pub fn into_values(self) -> (Hash, Header, Option<Commit>, HashMap<Namespace, RollupData>) {
pub fn into_values(
Copy link
Member

Choose a reason for hiding this comment

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

This is a code smell. We should differentiate between a "raw" SequencerBlockData as a (de)serialization format (where it's ok to make all fields pub), and "verified" SequencerBlockData that upholds invariants (where you want to prevent anyone pulling the rug from under you). Permitting clippy::type_complexity proves that point.

My suggestion: keep the SequencerBlockData as is, but introduce a RawSequencerBlockData that has exactly the same fields, but comes with a RawSequencerBlockData::verify(self) -> eyre::Result<SequencerBlockData>. This way you can keep using SequencerBlockData everywhere as is.

Next, scrap SequencerBlockData::into_values and introduce SequencerBlockData::into_raw. This way users can do the following instead of destructuring tuples:

fn do_something(data: SequencerBlockData) {
    let RawSequencerBlockData {
        block_hash,
        ...
    } = data.into_raw();
    further_processing(block_has);
}

We then have an easier path forward to move this into astria-proto.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yeah I didn't really like this either but wasn't sure if I should just make another type with all pub fields. will update!

@@ -136,19 +172,36 @@ impl SequencerBlockData {
/// # Errors
///
/// - if the block has no data hash
/// - if the block has no transactions
Copy link
Member

Choose a reason for hiding this comment

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

This list is great when eventually moving to typed errors, which we should really do...

);
let action_tree_root_inclusion_proof = tx_tree
.prove_inclusion(0) // action tree root is always the first tx in a block
.expect("failed to generate inclusion proof for action tree root");
Copy link
Member

Choose a reason for hiding this comment

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

Looks like this whould have been a wrap_err

@noot noot temporarily deployed to BUF August 23, 2023 16:56 — with GitHub Actions Inactive
@noot noot temporarily deployed to BUF August 23, 2023 17:05 — with GitHub Actions Inactive
@noot noot temporarily deployed to BUF August 23, 2023 17:06 — with GitHub Actions Inactive
@noot noot requested review from SuperFluffy and joroshiba August 23, 2023 17:06
Base automatically changed from noot/sequencer-proofs to main August 23, 2023 17:16
@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Aug 23, 2023
@noot noot temporarily deployed to BUF August 23, 2023 17:58 — with GitHub Actions Inactive
@noot noot temporarily deployed to BUF August 23, 2023 19:33 — with GitHub Actions Inactive
Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

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

Looks good to me. Please address the comment re deserialization so that there is no backdoor into creating types where invariants are violated (in this case inclusion proofs).

@@ -10,6 +10,7 @@ astria-sequencer-validation = { path = "../astria-sequencer-validation" }
telemetry = { package = "astria-telemetry", path = "../astria-telemetry" }

anyhow = "1"
digest = "0.10"
Copy link
Member

Choose a reason for hiding this comment

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

rm?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

woops haha

@@ -63,15 +72,29 @@ impl SequencerBlockData {
header: Header,
last_commit: Option<Commit>,
rollup_data: HashMap<Namespace, RollupData>,
action_tree_root: Hash,
action_tree_root_inclusion_proof: InclusionProof,
Copy link
Member

Choose a reason for hiding this comment

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

Comment on this entire new constructor: now that you have RawSequencerBlockData, I would replace this ever-growing function with SequencerBlockData::try_from_raw or smth like that.

self.last_commit,
self.rollup_data,
)
pub fn into_raw(self) -> RawSequencerBlockData {
Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment on top of this function.

Nit: destructuring self looks slightly nicer (less self.)

    let Self {
        block_hash,
        header,
        // and the rest
    } = self;
    RawSequencerBlockData {
        block_hash,
        header,
        // and the rest
    }

@@ -42,14 +46,19 @@ pub struct RollupData {

/// `SequencerBlockData` represents a sequencer block's data
/// to be submitted to the DA layer.
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct SequencerBlockData {
pub(crate) block_hash: Hash,
Copy link
Member

Choose a reason for hiding this comment

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

Do you still need all of this to be pub(crate)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

no :D

@@ -42,14 +46,19 @@ pub struct RollupData {

/// `SequencerBlockData` represents a sequencer block's data
/// to be submitted to the DA layer.
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
Copy link
Member

Choose a reason for hiding this comment

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

serde provides this attribute: https://serde.rs/container-attrs.html#try_from

Can you implement a TryFrom<RawSequencerBlockData> (I'd prefer to not impl a single-use TryFrom and instead use a custom deserializer, but in this case it's probably fine).

This way you can still deserialize, but in addition you get verification. Right now you still serialize directly into a SequencerBlockData and have no assurance that it's legitimate (invariants upheld, etc).

Also see https://serde.rs/container-attrs.html#into for the inverse

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nice, added!

@github-actions github-actions bot removed the sequencer pertaining to the astria-sequencer crate label Aug 23, 2023
@noot noot temporarily deployed to BUF August 23, 2023 22:07 — with GitHub Actions Inactive
@noot noot temporarily deployed to BUF August 23, 2023 22:54 — with GitHub Actions Inactive
@noot noot merged commit 9ea7269 into main Aug 24, 2023
@noot noot deleted the noot/data-hash-proof branch August 24, 2023 16:21
steezeburger added a commit that referenced this pull request Aug 28, 2023
* main:
  fix(sequencer): fix `deliver_tx` to accept the action tree root (#291)
  feat(composer): adding more docs (#295)
  feat(sequencer): implement proving of action tree root inside `Header::data_hash` (#286)
  ci: Update to reusable success, nested names (#306)
  feat(sequencer, relayer): generate and write merkle inclusion proofs as part of `RollupNamespaceData` (#256)
  ci: broad restructuring of worklows (#280)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
conductor pertaining to the astria-conductor crate sequencer-relayer pertaining to the astria-sequencer-relayer crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants