diff --git a/src/protocols/light_client/components/send_last_state_proof.rs b/src/protocols/light_client/components/send_last_state_proof.rs index 6359c52..40d1794 100644 --- a/src/protocols/light_client/components/send_last_state_proof.rs +++ b/src/protocols/light_client/components/send_last_state_proof.rs @@ -1077,7 +1077,7 @@ pub(crate) fn verify_mmr_proof<'a, T: Iterator>( raw_proof: packed::HeaderDigestVecReader, headers: T, ) -> Result<(), Status> { - if last_header.is_valid(mmr_activated_epoch) { + if last_header.patched_is_valid(mmr_activated_epoch) { trace!( "passed: verify extra hash for block-{} ({:#x})", last_header.header().number(), diff --git a/src/protocols/light_client/mod.rs b/src/protocols/light_client/mod.rs index d2c5a03..942ec9c 100644 --- a/src/protocols/light_client/mod.rs +++ b/src/protocols/light_client/mod.rs @@ -260,7 +260,7 @@ impl LightClientProtocol { ) -> Result<(), Status> { let mmr_activated_epoch = self.mmr_activated_epoch(); for header in headers { - if !header.is_valid(mmr_activated_epoch) { + if !header.patched_is_valid(mmr_activated_epoch) { let header = header.header(); let errmsg = format!( "failed to verify chain root for block#{}, hash: {:#x}", @@ -285,7 +285,7 @@ impl LightClientProtocol { return Err(StatusCode::InvalidNonce.with_context(errmsg)); } // Check Chain Root - if !verifiable_header.is_valid(self.mmr_activated_epoch()) { + if !verifiable_header.patched_is_valid(self.mmr_activated_epoch()) { let errmsg = format!( "failed to verify chain root for block#{}, hash: {:#x}", header.number(), @@ -414,8 +414,8 @@ impl LightClientProtocol { pub(crate) fn new(storage: Storage, peers: Arc, consensus: Consensus) -> Self { // Ref: https://github.com/nervosnetwork/rfcs/blob/01f3bc64ef8f54c94c7b0dcf9d30c84b6c8418b0/rfcs/0044-ckb-light-client/0044-ckb-light-client.md#deployment let mmr_activated_epoch = match consensus.id.as_str() { - mainnet::CHAIN_SPEC_NAME => 8648, - testnet::CHAIN_SPEC_NAME => 5676, + mainnet::CHAIN_SPEC_NAME => 8651, + testnet::CHAIN_SPEC_NAME => 5711, _ => 0, }; Self { diff --git a/src/protocols/light_client/prelude.rs b/src/protocols/light_client/prelude.rs index f51b4da..a2944b2 100644 --- a/src/protocols/light_client/prelude.rs +++ b/src/protocols/light_client/prelude.rs @@ -1,5 +1,10 @@ use ckb_network::{CKBProtocolContext, PeerIndex}; -use ckb_types::{core::HeaderView, packed::LightClientMessage, prelude::*}; +use ckb_types::{ + core::{EpochNumber, EpochNumberWithFraction, ExtraHashView, HeaderView}, + packed::LightClientMessage, + prelude::*, + utilities::merkle_mountain_range::VerifiableHeader, +}; use super::{Status, StatusCode}; @@ -38,3 +43,44 @@ impl HeaderUtils for HeaderView { && self.hash() == child.parent_hash() } } + +// TODO Remove patch after the upstream fixed. +// +// Ref: https://github.com/nervosnetwork/ckb/blob/v0.112.1/util/types/src/utilities/merkle_mountain_range.rs#L212-L241 +pub(crate) trait VerifiableHeaderPatch { + fn patched_is_valid(&self, mmr_activated_epoch_number: EpochNumber) -> bool; +} + +impl VerifiableHeaderPatch for VerifiableHeader { + fn patched_is_valid(&self, mmr_activated_epoch_number: EpochNumber) -> bool { + let mmr_activated_epoch = EpochNumberWithFraction::new(mmr_activated_epoch_number, 0, 1); + let has_chain_root = self.header().epoch() > mmr_activated_epoch; + if has_chain_root { + 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; + } + } + } + + let expected_extension_hash = self + .extension() + .map(|extension| extension.calc_raw_data_hash()); + let extra_hash_view = ExtraHashView::new(self.uncles_hash(), expected_extension_hash); + let expected_extra_hash = extra_hash_view.extra_hash(); + let actual_extra_hash = self.header().extra_hash(); + expected_extra_hash == actual_extra_hash + } +} diff --git a/src/tests/protocols/light_client/send_last_state.rs b/src/tests/protocols/light_client/send_last_state.rs index a1f3104..981b9ec 100644 --- a/src/tests/protocols/light_client/send_last_state.rs +++ b/src/tests/protocols/light_client/send_last_state.rs @@ -79,7 +79,7 @@ async fn invalid_chain_root() { let data = { let header = HeaderBuilder::default() - .epoch(EpochNumberWithFraction::new(1, 0, 10).pack()) + .epoch(EpochNumberWithFraction::new(1, 1, 10).pack()) .number(11u64.pack()) .build(); let last_header = packed::VerifiableHeader::new_builder() diff --git a/src/tests/protocols/light_client/send_last_state_proof.rs b/src/tests/protocols/light_client/send_last_state_proof.rs index d0f90a8..e7778cb 100644 --- a/src/tests/protocols/light_client/send_last_state_proof.rs +++ b/src/tests/protocols/light_client/send_last_state_proof.rs @@ -920,6 +920,7 @@ async fn invalid_parent_chain_root_for_the_genesis_block() { } async fn test_parent_chain_root_for_the_genesis_block(should_passed: bool) { + setup(); let chain = MockChain::new_with_dummy_pow("test-light-client").start(); let nc = MockNetworkContext::new(SupportProtocols::LightClient); @@ -934,8 +935,8 @@ async fn test_parent_chain_root_for_the_genesis_block(should_passed: bool) { protocol.set_mmr_activated_epoch(0); protocol.set_last_n_blocks(3); - let num = 1; - chain.mine_to(1); + let num = 10; + chain.mine_to(num + 1); let snapshot = chain.shared().snapshot(); @@ -971,9 +972,11 @@ async fn test_parent_chain_root_for_the_genesis_block(should_passed: bool) { let headers = (0..num) .into_iter() .map(|n| { - if !should_passed && n == 0 { + if !should_passed && n == num / 2 { + // Set a wrong parent chain root: + // - Use n's chain root as n's parent chain root let parent_chain_root = snapshot - .chain_root_mmr(1) + .chain_root_mmr(n) .get_root() .expect("has chain root"); snapshot