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

fix(light-client): do not skip the genesis block for last state proofs #3697

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::cmp::{max, min, Ordering};
use std::cmp::{min, Ordering};

use ckb_merkle_mountain_range::leaf_index_to_pos;
use ckb_network::{CKBProtocolContext, PeerIndex};
Expand Down Expand Up @@ -134,10 +134,6 @@ impl BlockSampler {
let mut headers = Vec::new();

for number in numbers {
// Genesis block doesn't has chain root.
if *number == 0 {
continue;
}
if let Some(ancestor_header) = active_chain.get_ancestor(last_hash, *number) {
let position = leaf_index_to_pos(*number);
positions.push(position);
Expand All @@ -155,7 +151,9 @@ impl BlockSampler {
let uncles_hash = ancestor_block.calc_uncles_hash();
let extension = ancestor_block.extension();

let parent_chain_root = {
let parent_chain_root = if *number == 0 {
Default::default()
} else {
let mmr = snapshot.chain_root_mmr(*number - 1);
match mmr.get_root() {
Ok(root) => root,
Expand Down Expand Up @@ -244,11 +242,7 @@ impl<'a> GetLastStateProofProcess<'a> {
{
Vec::new()
} else {
// Genesis block doesn't has chain root.
let min_block_number = max(
1,
start_block_number - min(start_block_number, last_n_blocks),
);
let min_block_number = start_block_number - min(start_block_number, last_n_blocks);
(min_block_number..start_block_number).collect()
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use ckb_merkle_mountain_range::{leaf_index_to_mmr_size, leaf_index_to_pos};
use ckb_network::{CKBProtocolHandler, PeerIndex, SupportProtocols};
use ckb_types::{
packed,
prelude::*,
utilities::merkle_mountain_range::{MMRProof, VerifiableHeader},
};

use crate::tests::{
prelude::*,
utils::{MockChain, MockNetworkContext},
};

#[tokio::test(flavor = "multi_thread")]
async fn get_last_state_proof_with_the_genesis_block() {
let chain = MockChain::new();
let nc = MockNetworkContext::new(SupportProtocols::LightClient);

chain.mine_to(1);

let snapshot = chain.shared().snapshot();
let verifiable_tip_header: VerifiableHeader =
snapshot.get_verifiable_header_by_number(1).unwrap().into();
let tip_header = verifiable_tip_header.header();
let genesis_header = snapshot.get_header_by_number(0).unwrap();

let mut protocol = chain.create_light_client_protocol();

let data = {
let content = packed::GetLastStateProof::new_builder()
.last_hash(tip_header.hash())
.start_hash(genesis_header.hash())
.start_number(0u64.pack())
.last_n_blocks(10u64.pack())
.difficulty_boundary(genesis_header.difficulty().pack())
.build();
packed::LightClientMessage::new_builder()
.set(content)
.build()
}
.as_bytes();

assert!(nc.sent_messages().borrow().is_empty());

let peer_index = PeerIndex::new(1);
protocol.received(nc.context(), peer_index, data).await;

assert!(nc.not_banned(peer_index));

assert_eq!(nc.sent_messages().borrow().len(), 1);

let data = &nc.sent_messages().borrow()[0].2;
let message = packed::LightClientMessageReader::new_unchecked(data);
let content = if let packed::LightClientMessageUnionReader::SendLastStateProof(content) =
message.to_enum()
{
content
} else {
panic!("unexpected message");
}
.to_entity();

// Verify MMR Proof
{
let parent_chain_root = verifiable_tip_header.parent_chain_root();
let proof: MMRProof = {
let mmr_size = leaf_index_to_mmr_size(parent_chain_root.end_number().unpack());
let proof = content.proof().into_iter().collect();
MMRProof::new(mmr_size, proof)
};
let digests_with_positions = {
let result = content
.headers()
.into_iter()
.map(|verifiable_header| {
let header = verifiable_header.header().into_view();
let index = header.number();
let position = leaf_index_to_pos(index);
let digest = header.digest();
digest.verify()?;
Ok((position, digest))
})
.collect::<Result<Vec<_>, String>>();
assert!(result.is_ok(), "failed since {}", result.unwrap_err());
result.unwrap()
};
let result = proof.verify(parent_chain_root, digests_with_positions);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Without the fix, this line will throw the error "Corrupted proof".

assert!(result.is_ok(), "failed since {}", result.unwrap_err());
}

assert_eq!(content.headers().len(), 1);

let verifiable_header: VerifiableHeader = content.headers().get(0).unwrap().into();
assert!(verifiable_header.header().is_genesis());
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod get_blocks_proof;
mod get_last_state_proof;
mod get_transactions_proof;
34 changes: 22 additions & 12 deletions util/types/src/utilities/merkle_mountain_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ impl packed::Header {
}

impl packed::HeaderDigest {
fn is_default(&self) -> bool {
let default = Self::default();
self.as_slice() == default.as_slice()
}

/// Verify the MMR header digest
pub fn verify(&self) -> Result<(), String> {
// 1. Check block numbers.
Expand Down Expand Up @@ -216,19 +221,24 @@ impl VerifiableHeader {

/// Checks if the current verifiable header is valid.
pub fn is_valid(&self, mmr_activated_epoch: EpochNumber) -> bool {
let has_chain_root =
!self.header().is_genesis() && self.header().epoch().number() >= mmr_activated_epoch;
let has_chain_root = self.header().epoch().number() >= mmr_activated_epoch;
if has_chain_root {
let is_extension_beginning_with_chain_root_hash = self
.extension()
.map(|extension| {
let actual_extension_data = extension.raw_data();
let parent_chain_root_hash = self.parent_chain_root().calc_mmr_hash();
actual_extension_data.starts_with(parent_chain_root_hash.as_slice())
})
.unwrap_or(false);
if !is_extension_beginning_with_chain_root_hash {
return false;
if self.header().is_genesis() {
if !self.parent_chain_root().is_default() {
return false;
}
} else {
let is_extension_beginning_with_chain_root_hash = self
.extension()
.map(|extension| {
let actual_extension_data = extension.raw_data();
let parent_chain_root_hash = self.parent_chain_root().calc_mmr_hash();
actual_extension_data.starts_with(parent_chain_root_hash.as_slice())
})
.unwrap_or(false);
if !is_extension_beginning_with_chain_root_hash {
return false;
}
}
}

Expand Down