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

adds chained merkle shreds variant #34787

Merged

Conversation

behzadnouri
Copy link
Contributor

@behzadnouri behzadnouri commented Jan 15, 2024

Problem

Harden shreds spec.

Summary of Changes

With the new variants, each Merkle shred will also include the Merkle
root of the previous erasure batch.

- For easier review, I have removed all the plumbing from this commit.
- The commit only includes the changes to shred struct and the code to generate
- and recover shreds.
- The rest would be done in separate commits.

@behzadnouri behzadnouri force-pushed the chained-merkle-shreds-lite branch 2 times, most recently from 9c3587f to 836ede2 Compare January 16, 2024 13:35
Copy link

codecov bot commented Jan 16, 2024

Codecov Report

Attention: 31 lines in your changes are missing coverage. Please review.

Comparison is base (acef7ba) 0.0% compared to head (66d69c9) 81.7%.
Report is 2 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master   #34787       +/-   ##
===========================================
+ Coverage        0    81.7%    +81.7%     
===========================================
  Files           0      826      +826     
  Lines           0   223324   +223324     
===========================================
+ Hits            0   182461   +182461     
- Misses          0    40863    +40863     

@behzadnouri behzadnouri force-pushed the chained-merkle-shreds-lite branch 3 times, most recently from b5d567f to 5f05e9f Compare January 17, 2024 23:21
@behzadnouri behzadnouri marked this pull request as ready for review January 17, 2024 23:21
@behzadnouri behzadnouri force-pushed the chained-merkle-shreds-lite branch 3 times, most recently from e15abe6 to 3b31d6c Compare January 17, 2024 23:25
@@ -93,6 +93,7 @@ impl Shredder {
self.version,
self.reference_tick,
is_last_in_slot,
None, // chained_merkle_root
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Following commits will do the plumbing here.

Copy link
Contributor

Choose a reason for hiding this comment

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

for the first FEC set will we chain the merkle root from the last FEC set of the parent block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@AshwinSekar no real objection to do so (aside from slightly more code to look that up from blockstore),
but can you clarify the motivation to do so?

Copy link
Contributor

@AshwinSekar AshwinSekar Jan 29, 2024

Choose a reason for hiding this comment

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

essentially it gives us a unique identifier for a fork. this is useful for comparison if we add the merkle root to votes.

imagine we have some unplayed slot S in our blockstore and S gets confirmed through votes. we can easily compare S to the merkle root in the votes to verify that we have the correct version of S.

if we also chain the merkle root to the parent block, the same comparison will tell us if we have the correct version of S - 1, S -2, and such ancestors.

in the event that we have parent block P and child block C where the chaining of the first FEC set of C does not match the last FEC set of P, we can conclude that we have incompatible versions of P and C. Depending on which merkle root the votes for C or its descendants contain, we can reconcile the situation by dumping and repairing either P or C appropriately.

ledger/src/shred.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@bw-solana bw-solana left a comment

Choose a reason for hiding this comment

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

Looks pretty good to me. Lots of eyeballing that size/offset calculations are functioning as intended, so we'll have to lean on testing

ci/do-audit.sh Outdated
@@ -30,6 +30,7 @@ cargo_audit_ignores=(
--ignore RUSTSEC-2023-0001

--ignore RUSTSEC-2022-0093
--ignore RUSTSEC-2024-0003
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: This has been addressed upstream so you can rebase and remove this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done.

bw-solana
bw-solana previously approved these changes Jan 18, 2024
Copy link
Contributor

@bw-solana bw-solana left a comment

Choose a reason for hiding this comment

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

LGTM. Should probably wait for others to weigh in as well

carllin
carllin previously approved these changes Jan 19, 2024
Copy link
Contributor

@carllin carllin 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!

ledger/src/shred.rs Show resolved Hide resolved
Comment on lines +310 to +345
let ShredVariant::MerkleCode(proof_size, chained) = self.common_header.shred_variant else {
return Err(Error::InvalidShredVariant);
};
Self::get_proof_offset(proof_size, chained)
}

fn get_proof_offset(proof_size: u8, chained: bool) -> Result<usize, Error> {
Ok(Self::SIZE_OF_HEADERS
+ Self::capacity(proof_size, chained)?
+ if chained { SIZE_OF_MERKLE_ROOT } else { 0 })
}

fn chained_merkle_root_offset(&self) -> Result<usize, Error> {
let ShredVariant::MerkleCode(proof_size, /*chained:*/ true) =
self.common_header.shred_variant
else {
return Err(Error::InvalidShredVariant);
};
Self::get_proof_offset(proof_size)
Ok(Self::SIZE_OF_HEADERS + Self::capacity(proof_size, /*chained:*/ true)?)
}

fn chained_merkle_root(&self) -> Result<Hash, Error> {
let offset = self.chained_merkle_root_offset()?;
self.payload
.get(offset..offset + SIZE_OF_MERKLE_ROOT)
.map(Hash::new)
.ok_or(Error::InvalidPayloadSize(self.payload.len()))
}

fn get_proof_offset(proof_size: u8) -> Result<usize, Error> {
Ok(Self::SIZE_OF_HEADERS + Self::capacity(proof_size)?)
fn set_chained_merkle_root(&mut self, chained_merkle_root: &Hash) -> Result<(), Error> {
let offset = self.chained_merkle_root_offset()?;
let Some(buffer) = self.payload.get_mut(offset..offset + SIZE_OF_MERKLE_ROOT) else {
return Err(Error::InvalidPayloadSize(self.payload.len()));
};
buffer.copy_from_slice(chained_merkle_root.as_ref());
Ok(())
Copy link
Contributor

Choose a reason for hiding this comment

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

The worst type of code to write + review, but seems right 😃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In what sense?

Copy link
Contributor

Choose a reason for hiding this comment

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

the tedium of having to remember where all the offsets are haha

let ShredVariant::MerkleData(proof_size, chained) = self.common_header.shred_variant else {
return Err(Error::InvalidShredVariant);
};
let offset = Self::SIZE_OF_HEADERS + Self::capacity(proof_size, chained)?;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this calculation still equivalent to calling self.proof_offset()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no. the chained merkle root is not part of the erasure coding.
so if the shred is chained, then proof_offset would be 32 bytes past the offset calculated here.

Copy link
Contributor

@carllin carllin Jan 20, 2024

Choose a reason for hiding this comment

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

no. the chained merkle root is not part of the erasure coding

Ah yeah, thats the critical part here, thanks.

ledger/src/shred/merkle.rs Show resolved Hide resolved
ledger/src/shred/merkle.rs Show resolved Hide resolved
With the new variants, each Merkle shred will also include the Merkle
root of the previous erasure batch.
@behzadnouri behzadnouri merged commit 9a520fd into solana-labs:master Jan 20, 2024
35 checks passed
@behzadnouri behzadnouri deleted the chained-merkle-shreds-lite branch January 20, 2024 16:08
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.

5 participants