diff --git a/Cargo.lock b/Cargo.lock index c4b7e4d3b9..3c223b6d1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1430,6 +1430,7 @@ dependencies = [ "ckb-error", "ckb-launcher", "ckb-logger", + "ckb-merkle-mountain-range", "ckb-reward-calculator", "ckb-shared", "ckb-store", diff --git a/Cargo.toml b/Cargo.toml index cfac2ec80b..d7f60c6c87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,7 +52,6 @@ members = [ "util/types", "util/jsonrpc-types", "freezer", - "block-filter", "resource", "pow", "util/dao/utils", @@ -69,6 +68,7 @@ members = [ "db-migration", "util/network-alert", "store", + "block-filter", "util/chain-iter", "util/test-chain-utils", "util/dao", @@ -84,6 +84,7 @@ members = [ "util/instrument", "rpc", "util/launcher/migration-template", + "util/light-client-protocol-server", "util/launcher", "ckb-bin" ] diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 95c636a37f..b5a74f2593 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -17,18 +17,19 @@ use ckb_stop_handler::{SignalSender, StopHandler}; use ckb_store::{attach_block_cell, detach_block_cell, ChainStore, StoreTransaction}; use ckb_types::{ core::{ - cell::{resolve_transaction, BlockCellProvider, OverlayCellProvider, ResolvedTransaction}, + cell::{ + resolve_transaction, BlockCellProvider, HeaderChecker, OverlayCellProvider, + ResolvedTransaction, + }, service::{Request, DEFAULT_CHANNEL_SIZE, SIGNAL_CHANNEL_SIZE}, - BlockExt, BlockNumber, BlockView, HeaderView, + BlockExt, BlockNumber, BlockView, Capacity, Cycle, HeaderView, }, packed::{Byte32, ProposalShortId}, - prelude::*, utilities::merkle_mountain_range::ChainRootMMR, U256, }; -use ckb_verification::{ - BlockErrorKind, BlockVerifier, InvalidParentError, NonContextualBlockTxsVerifier, -}; +use ckb_verification::cache::Completed; +use ckb_verification::{BlockVerifier, InvalidParentError, NonContextualBlockTxsVerifier}; use ckb_verification_contextual::{ContextualBlockVerifier, VerifyContext}; use ckb_verification_traits::{Switch, Verifier}; use faketime::unix_time_as_millis; @@ -444,8 +445,6 @@ impl ChainService { self.find_fork(&mut fork, current_tip_header.number(), &block, ext); self.rollback(&fork, &db_txn)?; - self.update_chain_root_mmr_after_check(&fork, &db_txn)?; - // update and verify chain root // MUST update index before reconcile_main_chain self.reconcile_main_chain(&db_txn, &mut fork, switch)?; @@ -703,55 +702,6 @@ impl ChainService { is_sorted_assert(fork); } - pub(crate) fn update_chain_root_mmr_after_check<'a>( - &'a self, - fork: &ForkChanges, - txn: &StoreTransaction, - ) -> Result<(), Error> { - let attached_blocks = fork.attached_blocks(); - if attached_blocks.is_empty() { - return Ok(()); - } - - let consensus = self.shared.consensus(); - let mmr_activated_epoch = consensus.hardfork_switch().mmr_activated_epoch(); - - let start_block_header = attached_blocks[0].header(); - - let mmr_size = leaf_index_to_mmr_size(start_block_header.number() - 1); - trace!("light-client: new chain root MMR with size = {}", mmr_size); - let mut mmr = ChainRootMMR::new(mmr_size, txn); - - for block in attached_blocks.iter() { - let has_chain_root = block.epoch().number() >= mmr_activated_epoch; - trace!( - "light-client: attach block#{} (chain root: {})", - block.number(), - has_chain_root - ); - if has_chain_root { - let chain_root = mmr - .get_root() - .map_err(|e| InternalErrorKind::MMR.other(e))?; - let actual_root_hash = chain_root.calc_mmr_hash(); - let extension = block - .extension() - .expect("should be checked in BlockVerifier/BlockExtensionVerifier"); - let expected_root_hash = Byte32::new_unchecked(extension.raw_data().slice(..32)); - if actual_root_hash != expected_root_hash { - return Err(BlockErrorKind::InvalidChainRoot.into()); - } - } - mmr.push(block.digest()) - .map_err(|e| InternalErrorKind::MMR.other(e))?; - } - - trace!("light-client: commit"); - // Before commit, all new MMR nodes are in memory only. - mmr.commit().map_err(|e| InternalErrorKind::MMR.other(e))?; - Ok(()) - } - // we found new best_block pub(crate) fn reconcile_main_chain( &self, @@ -759,14 +709,25 @@ impl ChainService { fork: &mut ForkChanges, switch: Switch, ) -> Result<(), Error> { + if fork.attached_blocks().is_empty() { + return Ok(()); + } + let txs_verify_cache = self.shared.txs_verify_cache(); let consensus = self.shared.consensus(); let async_handle = self.shared.tx_pool_controller().handle(); + let start_block_header = fork.attached_blocks()[0].header(); + let mmr_size = leaf_index_to_mmr_size(start_block_header.number() - 1); + trace!("light-client: new chain root MMR with size = {}", mmr_size); + let mut mmr = ChainRootMMR::new(mmr_size, txn); + let verified_len = fork.verified_len(); for b in fork.attached_blocks().iter().take(verified_len) { txn.attach_block(b)?; attach_block_cell(txn, b)?; + mmr.push(b.digest()) + .map_err(|e| InternalErrorKind::MMR.other(e))?; } let verify_context = VerifyContext::new(txn, consensus); @@ -779,120 +740,164 @@ impl ChainService { { if !switch.disable_all() { if found_error.is_none() { - let contextual_block_verifier = ContextualBlockVerifier::new(&verify_context); - let mut seen_inputs = HashSet::new(); - let block_cp = match BlockCellProvider::new(b) { - Ok(block_cp) => block_cp, - Err(err) => { - found_error = Some(err); - continue; - } - }; - - let transactions = b.transactions(); - let resolved = { - let cell_provider = OverlayCellProvider::new(&block_cp, txn); - transactions - .iter() - .cloned() - .map(|x| { - resolve_transaction( - x, - &mut seen_inputs, - &cell_provider, - &verify_context, - ) - }) - .collect::, _>>() - }; - + let resolved = self.resolve_block_transactions(txn, b, &verify_context); match resolved { Ok(resolved) => { - match contextual_block_verifier.verify( - &resolved, - b, - Arc::clone(&txs_verify_cache), - async_handle, - switch, - ) { + let verified = { + let contextual_block_verifier = ContextualBlockVerifier::new( + &verify_context, + async_handle, + switch, + Arc::clone(&txs_verify_cache), + &mmr, + ); + contextual_block_verifier.verify(&resolved, b) + }; + match verified { Ok((cycles, cache_entries)) => { let txs_fees = cache_entries.iter().map(|entry| entry.fee).collect(); txn.attach_block(b)?; attach_block_cell(txn, b)?; - let mut mut_ext = ext.clone(); - mut_ext.verified = Some(true); - mut_ext.txs_fees = txs_fees; - txn.insert_block_ext(&b.header().hash(), &mut_ext)?; + mmr.push(b.digest()) + .map_err(|e| InternalErrorKind::MMR.other(e))?; + + self.insert_ok_ext( + txn, + &b.header().hash(), + ext.clone(), + Some(txs_fees), + )?; + if !switch.disable_script() && b.transactions().len() > 1 { - info!( - "[block_verifier] block number: {}, hash: {}, size:{}/{}, cycles: {}/{}", - b.number(), - b.hash(), - b.data().serialized_size_without_uncle_proposals(), - self.shared.consensus().max_block_bytes(), + self.monitor_block_txs_verified( + b, + &resolved, + &cache_entries, cycles, - self.shared.consensus().max_block_cycles() ); - - // log tx verification result for monitor node - if log_enabled_target!("ckb_tx_monitor", Trace) { - // `cache_entries` already excludes cellbase tx, but `resolved` includes cellbase tx, skip it - // to make them aligned - for (rtx, cycles) in - resolved.iter().skip(1).zip(cache_entries.iter()) - { - trace_target!( - "ckb_tx_monitor", - r#"{{"tx_hash":"{:#x}","cycles":{}}}"#, - rtx.transaction.hash(), - cycles.cycles - ); - } - } } } Err(err) => { - error!("block verify error, block number: {}, hash: {}, error: {:?}", b.header().number(), - b.header().hash(), err); - if log_enabled!(ckb_logger::Level::Trace) { - trace!("block {}", b.data()); - } + self.print_error(b, &err); found_error = Some(err); - let mut mut_ext = ext.clone(); - mut_ext.verified = Some(false); - txn.insert_block_ext(&b.header().hash(), &mut_ext)?; + self.insert_failure_ext(txn, &b.header().hash(), ext.clone())?; } } } Err(err) => { - found_error = Some(err.into()); - let mut mut_ext = ext.clone(); - mut_ext.verified = Some(false); - txn.insert_block_ext(&b.header().hash(), &mut_ext)?; + found_error = Some(err); + self.insert_failure_ext(txn, &b.header().hash(), ext.clone())?; } } } else { - let mut mut_ext = ext.clone(); - mut_ext.verified = Some(false); - txn.insert_block_ext(&b.header().hash(), &mut_ext)?; + self.insert_failure_ext(txn, &b.header().hash(), ext.clone())?; } } else { txn.attach_block(b)?; attach_block_cell(txn, b)?; - let mut mut_ext = ext.clone(); - mut_ext.verified = Some(true); - txn.insert_block_ext(&b.header().hash(), &mut_ext)?; + mmr.push(b.digest()) + .map_err(|e| InternalErrorKind::MMR.other(e))?; + self.insert_ok_ext(txn, &b.header().hash(), ext.clone(), None)?; } } if let Some(err) = found_error { Err(err) } else { + trace!("light-client: commit"); + // Before commit, all new MMR nodes are in memory only. + mmr.commit().map_err(|e| InternalErrorKind::MMR.other(e))?; Ok(()) } } + fn resolve_block_transactions( + &self, + txn: &StoreTransaction, + block: &BlockView, + verify_context: &HC, + ) -> Result, Error> { + let mut seen_inputs = HashSet::new(); + let block_cp = BlockCellProvider::new(block)?; + let transactions = block.transactions(); + let cell_provider = OverlayCellProvider::new(&block_cp, txn); + let resolved = transactions + .iter() + .cloned() + .map(|tx| resolve_transaction(tx, &mut seen_inputs, &cell_provider, verify_context)) + .collect::, _>>()?; + Ok(resolved) + } + + fn insert_ok_ext( + &self, + txn: &StoreTransaction, + hash: &Byte32, + mut ext: BlockExt, + txs_fees: Option>, + ) -> Result<(), Error> { + ext.verified = Some(true); + if let Some(txs_fees) = txs_fees { + ext.txs_fees = txs_fees; + } + txn.insert_block_ext(hash, &ext) + } + + fn insert_failure_ext( + &self, + txn: &StoreTransaction, + hash: &Byte32, + mut ext: BlockExt, + ) -> Result<(), Error> { + ext.verified = Some(false); + txn.insert_block_ext(hash, &ext) + } + + fn monitor_block_txs_verified( + &self, + b: &BlockView, + resolved: &[ResolvedTransaction], + cache_entries: &[Completed], + cycles: Cycle, + ) { + info!( + "[block_verifier] block number: {}, hash: {}, size:{}/{}, cycles: {}/{}", + b.number(), + b.hash(), + b.data().serialized_size_without_uncle_proposals(), + self.shared.consensus().max_block_bytes(), + cycles, + self.shared.consensus().max_block_cycles() + ); + + // log tx verification result for monitor node + if log_enabled_target!("ckb_tx_monitor", Trace) { + // `cache_entries` already excludes cellbase tx, but `resolved` includes cellbase tx, skip it + // to make them aligned + for (rtx, cycles) in resolved.iter().skip(1).zip(cache_entries.iter()) { + trace_target!( + "ckb_tx_monitor", + r#"{{"tx_hash":"{:#x}","cycles":{}}}"#, + rtx.transaction.hash(), + cycles.cycles + ); + } + } + } + + fn print_error(&self, b: &BlockView, err: &Error) { + error!( + "block verify error, block number: {}, hash: {}, error: {:?}", + b.header().number(), + b.header().hash(), + err + ); + if log_enabled!(ckb_logger::Level::Trace) { + trace!("block {}", b.data()); + } + } + // TODO: beatify fn print_chain(&self, len: u64) { debug!("Chain {{"); diff --git a/chain/src/tests/block_assembler.rs b/chain/src/tests/block_assembler.rs index 3880f741f5..8edd22cd5f 100644 --- a/chain/src/tests/block_assembler.rs +++ b/chain/src/tests/block_assembler.rs @@ -173,7 +173,7 @@ fn test_block_template_message() { .unwrap() .unwrap(); - let cellbase_witness = CellbaseWitness::from_slice( + let _cellbase_witness = CellbaseWitness::from_slice( block_template .cellbase .data @@ -184,7 +184,7 @@ fn test_block_template_message() { ) .expect("should be valid CellbaseWitness slice"); - assert_eq!("TEST".as_bytes(), cellbase_witness.message().raw_data()); + // assert_eq!("TEST".as_bytes(), cellbase_witness.message().raw_data()); } #[test] diff --git a/docs/hashes.toml b/docs/hashes.toml index 6994bbf8bf..e20d56544a 100644 --- a/docs/hashes.toml +++ b/docs/hashes.toml @@ -90,7 +90,7 @@ index = 1 # Spec: ckb_staging [ckb_staging] -spec_hash = "0x754a6a35c71e267e638697a7cfabd1338735dac0e164306d562aa813bc19833c" +spec_hash = "0x1e501d0048dbfee37707432fb5ec4c5e2b0404d887ebc882564c8761ef54e68a" genesis = "0xbc081e6b2e31149c1dc39007f161ed0a0b63d5d30b3b771acc6a3b622133fcc0" cellbase = "0x7295631c414e50a8d9bb73d9845231ac212d10404045c96de7f149ec874ac6b7" @@ -134,7 +134,7 @@ index = 1 # Spec: ckb_dev [ckb_dev] -spec_hash = "0x9b457fe9ba2600eed42bff4e15cbdde2d9a1175eb49fb46e4d793bb4dc4259f0" +spec_hash = "0x307bfa518f3dd6ffe6d82317c5ce7eb738db4623402edcc06b5adbee2e3a6b29" genesis = "0x823b2ff5785b12da8b1363cac9a5cbe566d8b715a4311441b119c39a0367488c" cellbase = "0xa563884b3686078ec7e7677a5f86449b15cf2693f3c1241766c6996f206cc541" diff --git a/resource/specs/dev.toml b/resource/specs/dev.toml index 2e469859fc..6e5609bb62 100644 --- a/resource/specs/dev.toml +++ b/resource/specs/dev.toml @@ -103,7 +103,6 @@ rfc_0031 = 0 rfc_0032 = 0 rfc_0036 = 0 rfc_0038 = 0 -rfc_tmp1 = 1 [pow] func = "Dummy" diff --git a/resource/specs/staging.toml b/resource/specs/staging.toml index 85c248389c..a4de298172 100644 --- a/resource/specs/staging.toml +++ b/resource/specs/staging.toml @@ -101,7 +101,6 @@ rfc_0031 = 0 rfc_0032 = 0 rfc_0036 = 0 rfc_0038 = 0 -rfc_tmp1 = 1 [pow] func = "Eaglesong" diff --git a/rpc/README.md b/rpc/README.md index 3079d2a07d..2f394089c0 100644 --- a/rpc/README.md +++ b/rpc/README.md @@ -1398,8 +1398,7 @@ Response { "rfc": "0031", "epoch_number": "0x0" }, { "rfc": "0032", "epoch_number": "0x0" }, { "rfc": "0036", "epoch_number": "0x0" }, - { "rfc": "0038", "epoch_number": "0x0" }, - { "rfc": "tmp1", "epoch_number": null } + { "rfc": "0038", "epoch_number": "0x0" } ], "id": "main", "initial_primary_epoch_reward": "0x71afd498d000", @@ -2198,7 +2197,7 @@ Response ], "version": "0x0", "witnesses": [ - "0x650000000c00000055000000490000001000000030000000310000001892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df20114000000b2e61ff569acf041b3c2c17724e2379c581eeac30c00000054455354206d657373616765" + "0x650000000c00000055000000490000001000000030000000310000001892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df20114000000b2e61ff569acf041b3c2c17724e2379c581eeac30c00000000000020206d657373616765" ] }, "hash": "0xbaf7e4db2fd002f19a597ca1a31dfe8cfe26ed8cebc91f52b75b16a7a5ec8bab" diff --git a/rpc/src/module/chain.rs b/rpc/src/module/chain.rs index 1950c7db85..db87d3e6da 100644 --- a/rpc/src/module/chain.rs +++ b/rpc/src/module/chain.rs @@ -1133,8 +1133,7 @@ pub trait ChainRpc { /// { "rfc": "0031", "epoch_number": "0x0" }, /// { "rfc": "0032", "epoch_number": "0x0" }, /// { "rfc": "0036", "epoch_number": "0x0" }, - /// { "rfc": "0038", "epoch_number": "0x0" }, - /// { "rfc": "tmp1", "epoch_number": null } + /// { "rfc": "0038", "epoch_number": "0x0" } /// ], /// "id": "main", /// "initial_primary_epoch_reward": "0x71afd498d000", diff --git a/rpc/src/module/miner.rs b/rpc/src/module/miner.rs index b5d3c1fd97..a3ac4811ce 100644 --- a/rpc/src/module/miner.rs +++ b/rpc/src/module/miner.rs @@ -90,7 +90,7 @@ pub trait MinerRpc { /// ], /// "version": "0x0", /// "witnesses": [ - /// "0x650000000c00000055000000490000001000000030000000310000001892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df20114000000b2e61ff569acf041b3c2c17724e2379c581eeac30c00000054455354206d657373616765" + /// "0x650000000c00000055000000490000001000000030000000310000001892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df20114000000b2e61ff569acf041b3c2c17724e2379c581eeac30c00000000000020206d657373616765" /// ] /// }, /// "hash": "0xbaf7e4db2fd002f19a597ca1a31dfe8cfe26ed8cebc91f52b75b16a7a5ec8bab" diff --git a/spec/src/consensus.rs b/spec/src/consensus.rs index 2340f3feb6..6cf3f790f4 100644 --- a/spec/src/consensus.rs +++ b/spec/src/consensus.rs @@ -6,8 +6,8 @@ use crate::{ calculate_block_reward, versionbits::{ - self, Deployment, DeploymentPos, VersionBits, VersionBitsCache, - VersionBitsConditionChecker, VersionBitsIndexer, + self, Deployment, DeploymentPos, ThresholdState, Versionbits, VersionbitsCache, + VersionbitsConditionChecker, VersionbitsIndexer, }, OUTPUT_INDEX_DAO, OUTPUT_INDEX_SECP256K1_BLAKE160_MULTISIG_ALL, OUTPUT_INDEX_SECP256K1_BLAKE160_SIGHASH_ALL, @@ -92,7 +92,7 @@ pub(crate) const SATOSHI_PUBKEY_HASH: H160 = h160!("0x62e907b15cbf27d5425399ebf6 // only affects genesis cellbase's satoshi lock cells. pub(crate) const SATOSHI_CELL_OCCUPIED_RATIO: Ratio = Ratio::new(6, 10); -pub(crate) const MAINNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(9, 10); +// pub(crate) const MAINNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(9, 10); pub(crate) const TESTNET_ACTIVATION_THRESHOLD: Ratio = Ratio::new(3, 4); /// The struct represent CKB two-step-transaction-confirmation params @@ -284,7 +284,7 @@ impl ConsensusBuilder { permanent_difficulty_in_dummy: false, hardfork_switch: HardForkSwitch::new_mirana(), deployments: HashMap::new(), - versionbits_caches: VersionBitsCache::default(), + versionbits_caches: VersionbitsCache::default(), }, } } @@ -475,7 +475,7 @@ impl ConsensusBuilder { /// Sets a soft fork deployments for the new Consensus. pub fn softfork_deployments(mut self, deployments: HashMap) -> Self { - self.inner.versionbits_caches = VersionBitsCache::new(deployments.keys()); + self.inner.versionbits_caches = VersionbitsCache::new(deployments.keys()); self.inner.deployments = deployments; self } @@ -558,7 +558,7 @@ pub struct Consensus { /// Soft fork deployments pub deployments: HashMap, /// Soft fork state cache - pub versionbits_caches: VersionBitsCache, + pub versionbits_caches: VersionbitsCache, } // genesis difficulty should not be zero @@ -955,15 +955,16 @@ impl Consensus { &self.hardfork_switch } - pub fn compute_versionbits( + /// Returns what version a new block should use. + pub fn compute_versionbits( &self, parent: &HeaderView, indexer: &I, ) -> Option { let mut version = versionbits::VERSIONBITS_TOP_BITS; for pos in self.deployments.keys() { - let versionbits = VersionBits::new(*pos, self); - let cache = self.versionbits_caches.cache(pos); + let versionbits = Versionbits::new(*pos, self); + let cache = self.versionbits_caches.cache(pos)?; let state = versionbits.get_state(parent, cache, indexer)?; if state == versionbits::ThresholdState::LockedIn || state == versionbits::ThresholdState::Started @@ -974,6 +975,18 @@ impl Consensus { Some(version) } + /// Returns specified softfork deployment state + pub fn versionbits_state( + &self, + pos: DeploymentPos, + parent: &HeaderView, + indexer: &I, + ) -> Option { + let cache = self.versionbits_caches.cache(&pos)?; + let versionbits = Versionbits::new(pos, self); + versionbits.get_state(parent, cache, indexer) + } + /// If the CKB block chain specification is for an public chain. pub fn is_public_chain(&self) -> bool { matches!( diff --git a/spec/src/hardfork.rs b/spec/src/hardfork.rs index 68f1a2f682..d887e3a19a 100644 --- a/spec/src/hardfork.rs +++ b/spec/src/hardfork.rs @@ -57,8 +57,6 @@ pub struct HardForkConfig { /// Ref: CKB RFC 0038 #[serde(skip_serializing_if = "Option::is_none")] pub rfc_0038: Option, - /// TODO(light-client) update the description - pub rfc_tmp1: Option, } macro_rules! check_default { @@ -85,7 +83,6 @@ impl HardForkConfig { b, mainnet::CKB2021_START_EPOCH, mainnet::RFC0028_START_EPOCH, - mainnet::RFCTMP1_START_EPOCH, )?; b.build() } @@ -98,7 +95,6 @@ impl HardForkConfig { b, testnet::CKB2021_START_EPOCH, testnet::RFC0028_START_EPOCH, - testnet::RFCTMP1_START_EPOCH, )?; b.build() } @@ -108,7 +104,6 @@ impl HardForkConfig { builder: HardForkSwitchBuilder, ckb2021: EpochNumber, rfc_0028_start: EpochNumber, - rfc_tmp1_start: EpochNumber, ) -> Result { let builder = builder .rfc_0028(check_default!(self, rfc_0028, rfc_0028_start)) @@ -117,8 +112,7 @@ impl HardForkConfig { .rfc_0031(check_default!(self, rfc_0031, ckb2021)) .rfc_0032(check_default!(self, rfc_0032, ckb2021)) .rfc_0036(check_default!(self, rfc_0036, ckb2021)) - .rfc_0038(check_default!(self, rfc_0038, ckb2021)) - .rfc_tmp1(check_default!(self, rfc_tmp1, rfc_tmp1_start)); + .rfc_0038(check_default!(self, rfc_0038, ckb2021)); Ok(builder) } @@ -126,12 +120,6 @@ impl HardForkConfig { /// /// Enable features which are set to `None` at the user provided epoch. pub fn complete_with_default(&self, default: EpochNumber) -> Result { - if self.rfc_tmp1.map(|v| v == 0).unwrap_or(false) { - let errmsg = "Found the hard fork feature parameter \"rfc_tmp1\" is \ - in the chain specification file, and its value is 0. - But it should NOT be 0 since genesis block doesn't has chain root."; - return Err(errmsg.to_string()); - } HardForkSwitch::new_builder() .rfc_0028(self.rfc_0028.unwrap_or(default)) .rfc_0029(self.rfc_0029.unwrap_or(default)) @@ -140,7 +128,6 @@ impl HardForkConfig { .rfc_0032(self.rfc_0032.unwrap_or(default)) .rfc_0036(self.rfc_0036.unwrap_or(default)) .rfc_0038(self.rfc_0038.unwrap_or(default)) - .rfc_tmp1(self.rfc_tmp1.unwrap_or(default)) .build() } } diff --git a/spec/src/lib.rs b/spec/src/lib.rs index 48730759e2..43182d35ae 100644 --- a/spec/src/lib.rs +++ b/spec/src/lib.rs @@ -522,7 +522,20 @@ impl ChainSpec { deployments.insert(DeploymentPos::LightClient, light_client); Some(deployments) } - _ => None, + _ => { + let mut deployments = HashMap::new(); + let light_client = Deployment { + bit: 1, + start: 0, + timeout: 0, + min_activation_epoch: 0, + period: 10, + active_mode: ActiveMode::Always, + threshold: TESTNET_ACTIVATION_THRESHOLD, + }; + deployments.insert(DeploymentPos::LightClient, light_client); + Some(deployments) + } } } diff --git a/spec/src/tests/mod.rs b/spec/src/tests/mod.rs index 83c69dcd24..f77f888bbf 100644 --- a/spec/src/tests/mod.rs +++ b/spec/src/tests/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::{build_genesis_epoch_ext, ChainSpec, Params}; mod consensus; +mod versionbits; #[derive(Clone, Debug, Serialize, Deserialize)] struct SystemCell { diff --git a/spec/src/tests/versionbits.rs b/spec/src/tests/versionbits.rs new file mode 100644 index 0000000000..f0009bdaf5 --- /dev/null +++ b/spec/src/tests/versionbits.rs @@ -0,0 +1,325 @@ +use crate::consensus::Consensus; +use crate::consensus::{ + build_genesis_epoch_ext, ConsensusBuilder, DEFAULT_EPOCH_DURATION_TARGET, + DEFAULT_ORPHAN_RATE_TARGET, +}; +use crate::versionbits::{ + ActiveMode, Deployment, DeploymentPos, ThresholdState, VersionbitsIndexer, +}; +use crate::TESTNET_ACTIVATION_THRESHOLD; +use ckb_types::{ + core::{ + capacity_bytes, BlockBuilder, BlockView, Capacity, EpochExt, HeaderView, + TransactionBuilder, TransactionView, Version, + }, + packed::{Byte32, Bytes, CellbaseWitness}, + prelude::*, + utilities::DIFF_TWO, +}; +use std::collections::HashMap; + +type Index = Byte32; +type BlockHash = Byte32; + +#[derive(Clone, Debug)] +struct MockChain { + consensus: Consensus, + current_epoch_ext: EpochExt, + tip: HeaderView, + cellbases: HashMap, + headers: HashMap, + epoch_index: HashMap, + epoch_exts: HashMap, +} + +impl VersionbitsIndexer for MockChain { + fn block_epoch_index(&self, block_hash: &Byte32) -> Option { + self.epoch_index.get(block_hash).cloned() + } + + fn epoch_ext(&self, index: &Byte32) -> Option { + self.epoch_exts.get(index).cloned() + } + + fn block_header(&self, block_hash: &Byte32) -> Option { + self.headers.get(block_hash).cloned() + } + + fn cellbase(&self, block_hash: &Byte32) -> Option { + self.cellbases.get(block_hash).cloned() + } +} + +impl MockChain { + fn new(consensus: Consensus) -> Self { + let genesis = consensus.genesis_block(); + let genesis_epoch_ext = consensus.genesis_epoch_ext.clone(); + let index = genesis_epoch_ext.last_block_hash_in_previous_epoch(); + + let mut cellbases = HashMap::new(); + let mut headers = HashMap::new(); + let mut epoch_index = HashMap::new(); + let mut epoch_exts = HashMap::new(); + + let tip = genesis.header(); + + cellbases.insert(genesis.hash(), genesis.transactions()[0].clone()); + headers.insert(genesis.hash(), genesis.header()); + epoch_index.insert(genesis.hash(), index.clone()); + epoch_exts.insert(index, genesis_epoch_ext.clone()); + + MockChain { + consensus, + cellbases, + headers, + epoch_index, + epoch_exts, + tip, + current_epoch_ext: genesis_epoch_ext, + } + } + + fn compute_versionbits(&self, parent: &HeaderView) -> Option { + self.consensus.compute_versionbits(parent, self) + } + + fn get_state(&self, pos: DeploymentPos) -> Option { + self.consensus.versionbits_state(pos, &self.tip, self) + } + + fn advanced_next_epoch(&mut self) { + let index = self.tip.epoch().index(); + let length = self.tip.epoch().length(); + + let remain = length - index - 1; + + for _ in 0..remain { + let block = self.next_signal_block(); + self.insert_block(block); + } + + let next_epoch = self.next_epoch(&self.tip); + self.insert_epoch_ext(next_epoch); + + let block = self.next_signal_block(); + self.insert_block(block); + } + + fn advanced_next_epoch_without_signal(&mut self) { + let index = self.tip.epoch().index(); + let length = self.tip.epoch().length(); + + let remain = length - index - 1; + for _ in 0..remain { + let block = self.next_block(); + self.insert_block(block); + } + + let next_epoch = self.next_epoch(&self.tip); + self.insert_epoch_ext(next_epoch); + + let block = self.next_signal_block(); + self.insert_block(block); + } + + fn insert_epoch_ext(&mut self, epoch: EpochExt) { + let index = epoch.last_block_hash_in_previous_epoch(); + self.epoch_exts.insert(index, epoch.clone()); + self.current_epoch_ext = epoch; + } + + fn insert_block(&mut self, block: BlockView) { + let index = self.current_epoch_ext.last_block_hash_in_previous_epoch(); + let new_tip = block.header(); + self.cellbases + .insert(block.hash(), block.transactions()[0].clone()); + self.headers.insert(block.hash(), block.header()); + self.epoch_index.insert(block.hash(), index); + self.tip = new_tip; + } + + fn next_epoch(&self, last_header: &HeaderView) -> EpochExt { + let current_epoch_number = self.current_epoch_ext.number(); + self.current_epoch_ext + .clone() + .into_builder() + .number(current_epoch_number + 1) + .last_block_hash_in_previous_epoch(last_header.hash()) + .start_number(last_header.number() + 1) + .build() + } + + fn next_block(&self) -> BlockView { + let parent = &self.tip; + let epoch = &self.current_epoch_ext; + + let cellbase = TransactionBuilder::default().build(); + BlockBuilder::default() + .parent_hash(parent.hash()) + .number((parent.number() + 1).pack()) + .epoch(epoch.number_with_fraction(parent.number() + 1).pack()) + .transaction(cellbase) + .build() + } + + fn next_signal_block(&self) -> BlockView { + let parent = &self.tip; + let epoch = &self.current_epoch_ext; + + let version = self.compute_versionbits(parent).unwrap(); + + let cellbase_witness = CellbaseWitness::new_builder() + .message(version.to_le_bytes().as_slice().pack()) + .build(); + + let witness = cellbase_witness.as_bytes().pack(); + let cellbase = TransactionBuilder::default().witness(witness).build(); + + BlockBuilder::default() + .parent_hash(parent.hash()) + .number((parent.number() + 1).pack()) + .epoch(epoch.number_with_fraction(parent.number() + 1).pack()) + .transaction(cellbase) + .build() + } +} + +#[test] +fn test_versionbits_active() { + let cellbase = TransactionBuilder::default() + .witness(Bytes::default()) + .build(); + let epoch_ext = build_genesis_epoch_ext( + capacity_bytes!(100), + DIFF_TWO, + 4, + DEFAULT_EPOCH_DURATION_TARGET, + DEFAULT_ORPHAN_RATE_TARGET, + ); + let genesis = BlockBuilder::default() + .epoch(epoch_ext.number_with_fraction(0).pack()) + .transaction(cellbase) + .build(); + + let mut deployments = HashMap::new(); + let test_dummy = Deployment { + bit: 1, + start: 1, + timeout: 11, + min_activation_epoch: 11, + period: 2, + active_mode: ActiveMode::Normal, + threshold: TESTNET_ACTIVATION_THRESHOLD, + }; + deployments.insert(DeploymentPos::Testdummy, test_dummy); + + let consensus = ConsensusBuilder::new(genesis, epoch_ext) + .softfork_deployments(deployments) + .build(); + let mut chain = MockChain::new(consensus); + + assert_eq!(chain.current_epoch_ext.number(), 0); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Defined) + ); + + chain.advanced_next_epoch(); + assert_eq!(chain.current_epoch_ext.number(), 1); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Started) + ); + + chain.advanced_next_epoch(); + assert_eq!(chain.current_epoch_ext.number(), 2); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Started) + ); + + for _ in 0..8 { + chain.advanced_next_epoch(); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::LockedIn) + ); + } + + chain.advanced_next_epoch(); + assert_eq!(chain.current_epoch_ext.number(), 11); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Active) + ); + + chain.advanced_next_epoch(); + assert_eq!(chain.current_epoch_ext.number(), 12); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Active) + ); +} + +#[test] +fn test_versionbits_failed() { + let cellbase = TransactionBuilder::default() + .witness(Bytes::default()) + .build(); + let epoch_ext = build_genesis_epoch_ext( + capacity_bytes!(100), + DIFF_TWO, + 4, + DEFAULT_EPOCH_DURATION_TARGET, + DEFAULT_ORPHAN_RATE_TARGET, + ); + let genesis = BlockBuilder::default() + .epoch(epoch_ext.number_with_fraction(0).pack()) + .transaction(cellbase) + .build(); + + let mut deployments = HashMap::new(); + let test_dummy = Deployment { + bit: 1, + start: 1, + timeout: 11, + min_activation_epoch: 11, + period: 2, + active_mode: ActiveMode::Normal, + threshold: TESTNET_ACTIVATION_THRESHOLD, + }; + deployments.insert(DeploymentPos::Testdummy, test_dummy); + + let consensus = ConsensusBuilder::new(genesis, epoch_ext) + .softfork_deployments(deployments) + .build(); + let mut chain = MockChain::new(consensus); + + assert_eq!(chain.current_epoch_ext.number(), 0); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Defined) + ); + + for _ in 0..10 { + chain.advanced_next_epoch_without_signal(); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Started) + ); + } + + chain.advanced_next_epoch_without_signal(); + assert_eq!(chain.current_epoch_ext.number(), 11); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Failed) + ); + + chain.advanced_next_epoch_without_signal(); + assert_eq!(chain.current_epoch_ext.number(), 12); + assert_eq!( + chain.get_state(DeploymentPos::Testdummy), + Some(ThresholdState::Failed) + ); +} diff --git a/spec/src/versionbits/mod.rs b/spec/src/versionbits/mod.rs index c130bd150d..f2535acd39 100644 --- a/spec/src/versionbits/mod.rs +++ b/spec/src/versionbits/mod.rs @@ -1,3 +1,6 @@ +//! Versionbits 9 defines a finite-state-machine to deploy a softfork in multiple stages. +//! + use crate::consensus::Consensus; use ckb_types::{ core::{EpochExt, EpochNumber, HeaderView, Ratio, TransactionView, Version}, @@ -5,7 +8,7 @@ use ckb_types::{ prelude::*, }; use ckb_util::Mutex; -use std::collections::HashMap; +use std::collections::{hash_map, HashMap}; use std::sync::Arc; /// What bits to set in version for versionbits blocks @@ -21,10 +24,17 @@ pub const VERSIONBITS_NUM_BITS: u32 = 29; /// inherited between epochs. All blocks of a epoch share the same state. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ThresholdState { + /// First state that each softfork starts. + /// The 0 epoch is by definition in this state for each deployment. Defined, + /// For epochs past the `start` epoch. Started, + /// For one epoch after the first epoch period with STARTED epochs of + /// which at least `threshold` has the associated bit set in `version`. LockedIn, + /// For all epochs after the LOCKED_IN epoch. Active, + /// For one epoch period past the `timeout_epoch`, if LOCKED_IN was not reached. Failed, } @@ -32,25 +42,51 @@ pub enum ThresholdState { /// process. Only tests that specifically test the behaviour during activation cannot use this. #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum ActiveMode { + /// Indicating that the deployment is normal active. Normal, + /// Indicating that the deployment is always active. + /// This is useful for testing, as it means tests don't need to deal with the activation Always, + /// Indicating that the deployment is never active. + /// This is useful for testing. Never, } -// Soft fork deployment +/// Soft fork deployment #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub enum DeploymentPos { + /// Dummy Testdummy, + /// light client protocol LightClient, } -/// VersionBitsIndexer -pub trait VersionBitsIndexer { - fn get_block_epoch_index(&self, block_hash: &Byte32) -> Option; - fn get_epoch_ext(&self, index: &Byte32) -> Option; - fn get_block_header(&self, block_hash: &Byte32) -> Option; - fn get_cellbase(&self, block_hash: &Byte32) -> Option; - fn get_ancestor_epoch(&self, index: &Byte32, period: EpochNumber) -> Option; +/// VersionbitsIndexer +pub trait VersionbitsIndexer { + /// Gets epoch index by block hash + fn block_epoch_index(&self, block_hash: &Byte32) -> Option; + /// Gets epoch ext by index + fn epoch_ext(&self, index: &Byte32) -> Option; + /// Gets block header by block hash + fn block_header(&self, block_hash: &Byte32) -> Option; + /// Gets cellbase by block hash + fn cellbase(&self, block_hash: &Byte32) -> Option; + /// Gets ancestor of specified epoch. + fn ancestor_epoch(&self, index: &Byte32, target: EpochNumber) -> Option { + let mut epoch_ext = self.epoch_ext(index)?; + + if epoch_ext.number() < target { + return None; + } + while epoch_ext.number() > target { + let last_block_header_in_previous_epoch = + self.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?; + let previous_epoch_index = + self.block_epoch_index(&last_block_header_in_previous_epoch.hash())?; + epoch_ext = self.epoch_ext(&previous_epoch_index)?; + } + Some(epoch_ext) + } } ///Struct for each individual consensus rule change using soft fork. @@ -70,42 +106,57 @@ type Cache = Mutex>; /// RFC0000 allows multiple soft forks to be deployed in parallel. We cache /// per-epoch state for every one of them. */ #[derive(Clone, Debug, Default)] -pub struct VersionBitsCache { +pub struct VersionbitsCache { caches: Arc>, } -impl VersionBitsCache { +impl VersionbitsCache { + /// Construct new VersionbitsCache instance from deployments pub fn new<'a>(deployments: impl Iterator) -> Self { let caches: HashMap<_, _> = deployments .map(|pos| (*pos, Mutex::new(HashMap::new()))) .collect(); - VersionBitsCache { + VersionbitsCache { caches: Arc::new(caches), } } - pub fn cache(&self, pos: &DeploymentPos) -> &Cache { - &self.caches[pos] + /// Returns a reference to the cache corresponding to the deployment. + pub fn cache(&self, pos: &DeploymentPos) -> Option<&Cache> { + self.caches.get(pos) } } -/// implements RFC0000 threshold logic, and caches results. -pub struct VersionBits<'a> { +/// Struct Implements versionbits threshold logic, and caches results. +pub struct Versionbits<'a> { id: DeploymentPos, consensus: &'a Consensus, } -pub trait VersionBitsConditionChecker { +/// Trait that implements versionbits threshold logic, and caches results. +pub trait VersionbitsConditionChecker { + /// Specifies the first epoch in which the bit gains meaning. fn start(&self) -> EpochNumber; + /// Specifies an epoch at which the miner signaling ends. + /// Once this epoch has been reached, + /// if the softfork has not yet locked_in (excluding this epoch block's bit state), + /// the deployment is considered failed on all descendants of the block. fn timeout(&self) -> EpochNumber; + /// Active mode for testing. fn active_mode(&self) -> ActiveMode; // fn condition(&self, header: &HeaderView) -> bool; - fn condition(&self, header: &HeaderView, indexer: &I) -> bool; + /// Determines whether bit in the `version` field of the block is to be used to signal + fn condition(&self, header: &HeaderView, indexer: &I) -> bool; + /// Specifies the epoch at which the softfork is allowed to become active. fn min_activation_epoch(&self) -> EpochNumber; + /// The period for signal statistics are counted fn period(&self) -> EpochNumber; + /// Specifies the minimum ratio of block per epoch, + /// which indicate the locked_in of the softfork during the epoch. fn threshold(&self) -> Ratio; - - fn get_state( + /// Returns the state for a header. Applies any state transition if conditions are present. + /// Caches state from first block of period. + fn get_state( &self, header: &HeaderView, cache: &Cache, @@ -125,36 +176,33 @@ pub trait VersionBitsConditionChecker { return Some(ThresholdState::Failed); } - let start_index = indexer.get_block_epoch_index(&header.hash())?; + let start_index = indexer.block_epoch_index(&header.hash())?; let epoch_number = header.epoch().number(); let target = epoch_number.saturating_sub((epoch_number + 1) % period); - let mut epoch_ext = indexer.get_ancestor_epoch(&start_index, target)?; - let mut epoch_index = epoch_ext.last_block_hash_in_previous_epoch(); + let mut epoch_ext = indexer.ancestor_epoch(&start_index, target)?; let mut g_cache = cache.lock(); let mut to_compute = Vec::new(); - while g_cache.get(&epoch_index).is_none() { - if epoch_ext.is_genesis() { - // The genesis is by definition defined. - g_cache.insert(epoch_index.clone(), ThresholdState::Defined); - break; - } - if epoch_ext.number() < start { - // The genesis is by definition defined. - g_cache.insert(epoch_index.clone(), ThresholdState::Defined); - break; + let mut state = loop { + let epoch_index = epoch_ext.last_block_hash_in_previous_epoch(); + match g_cache.entry(epoch_index.clone()) { + hash_map::Entry::Occupied(entry) => { + break *entry.get(); + } + hash_map::Entry::Vacant(entry) => { + // The genesis is by definition defined. + if epoch_ext.is_genesis() || epoch_ext.number() < start { + entry.insert(ThresholdState::Defined); + break ThresholdState::Defined; + } + let next_epoch_ext = indexer + .ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))?; + to_compute.push(epoch_ext); + epoch_ext = next_epoch_ext; + } } - to_compute.push(epoch_ext.clone()); - - let next_epoch_ext = indexer - .get_ancestor_epoch(&epoch_index, epoch_ext.number().saturating_sub(period))?; - epoch_ext = next_epoch_ext; - epoch_index = epoch_ext.last_block_hash_in_previous_epoch(); - } + }; - let mut state = *g_cache - .get(&epoch_index) - .expect("cache[epoch_index] is known"); while let Some(epoch_ext) = to_compute.pop() { let mut next_state = state; @@ -171,7 +219,7 @@ pub trait VersionBitsConditionChecker { let mut count = 0; let mut total = 0; let mut header = - indexer.get_block_header(&epoch_ext.last_block_hash_in_previous_epoch())?; + indexer.block_header(&epoch_ext.last_block_hash_in_previous_epoch())?; let mut current_epoch_ext = epoch_ext.clone(); for _ in 0..period { @@ -181,14 +229,13 @@ pub trait VersionBitsConditionChecker { if self.condition(&header, indexer) { count += 1; } - header = indexer.get_block_header(&header.parent_hash())?; + header = indexer.block_header(&header.parent_hash())?; } - let last_block_header_in_previous_epoch = indexer.get_block_header( - ¤t_epoch_ext.last_block_hash_in_previous_epoch(), - )?; + let last_block_header_in_previous_epoch = indexer + .block_header(¤t_epoch_ext.last_block_hash_in_previous_epoch())?; let previous_epoch_index = indexer - .get_block_epoch_index(&last_block_header_in_previous_epoch.hash())?; - current_epoch_ext = indexer.get_epoch_ext(&previous_epoch_index)?; + .block_epoch_index(&last_block_header_in_previous_epoch.hash())?; + current_epoch_ext = indexer.epoch_ext(&previous_epoch_index)?; } let threshold_number = threshold_number(total, self.threshold())?; @@ -215,21 +262,23 @@ pub trait VersionBitsConditionChecker { } } -impl<'a> VersionBits<'a> { +impl<'a> Versionbits<'a> { + /// construct new Versionbits wrapper pub fn new(id: DeploymentPos, consensus: &'a Consensus) -> Self { - VersionBits { id, consensus } + Versionbits { id, consensus } } fn deployment(&self) -> &Deployment { &self.consensus.deployments[&self.id] } + /// return bit mask corresponding deployment pub fn mask(&self) -> u32 { 1u32 << self.deployment().bit as u32 } } -impl<'a> VersionBitsConditionChecker for VersionBits<'a> { +impl<'a> VersionbitsConditionChecker for Versionbits<'a> { fn start(&self) -> EpochNumber { self.deployment().start } @@ -242,13 +291,13 @@ impl<'a> VersionBitsConditionChecker for VersionBits<'a> { self.deployment().period } - fn condition(&self, header: &HeaderView, indexer: &I) -> bool { - if let Some(cellbase) = indexer.get_cellbase(&header.hash()) { + fn condition(&self, header: &HeaderView, indexer: &I) -> bool { + if let Some(cellbase) = indexer.cellbase(&header.hash()) { if let Some(witness) = cellbase.witnesses().get(0) { - if let Some(reader) = CellbaseWitnessReader::from_slice(&witness.raw_data()).ok() { + if let Ok(reader) = CellbaseWitnessReader::from_slice(&witness.raw_data()) { let message = reader.message().to_entity(); if message.len() >= 4 { - if let Ok(raw) = message.as_slice()[..4].try_into() { + if let Ok(raw) = message.raw_data()[..4].try_into() { let version = u32::from_le_bytes(raw); return ((version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (version & self.mask()) != 0; diff --git a/store/src/db.rs b/store/src/db.rs index 554af027b7..c7622382de 100644 --- a/store/src/db.rs +++ b/store/src/db.rs @@ -5,7 +5,7 @@ use crate::transaction::StoreTransaction; use crate::write_batch::StoreWriteBatch; use crate::StoreSnapshot; use ckb_app_config::StoreConfig; -use ckb_chain_spec::consensus::Consensus; +use ckb_chain_spec::{consensus::Consensus, versionbits::VersionbitsIndexer}; use ckb_db::{ iter::{DBIter, DBIterator, IteratorMode}, DBPinnableSlice, RocksDB, @@ -14,7 +14,10 @@ use ckb_db_schema::{Col, CHAIN_SPEC_HASH_KEY, MIGRATION_VERSION_KEY}; use ckb_error::{Error, InternalErrorKind}; use ckb_freezer::Freezer; use ckb_types::{ - core::BlockExt, packed, prelude::*, utilities::merkle_mountain_range::ChainRootMMR, + core::{BlockExt, EpochExt, HeaderView, TransactionView}, + packed, + prelude::*, + utilities::merkle_mountain_range::ChainRootMMR, }; use std::sync::Arc; @@ -48,6 +51,24 @@ impl<'a> ChainStore<'a> for ChainDB { } } +impl VersionbitsIndexer for ChainDB { + fn block_epoch_index(&self, block_hash: &packed::Byte32) -> Option { + ChainStore::get_block_epoch_index(self, block_hash) + } + + fn epoch_ext(&self, index: &packed::Byte32) -> Option { + ChainStore::get_epoch_ext(self, index) + } + + fn block_header(&self, block_hash: &packed::Byte32) -> Option { + ChainStore::get_block_header(self, block_hash) + } + + fn cellbase(&self, block_hash: &packed::Byte32) -> Option { + ChainStore::get_cellbase(self, block_hash) + } +} + impl ChainDB { /// TODO(doc): @quake pub fn new(db: RocksDB, config: StoreConfig) -> Self { diff --git a/store/src/transaction.rs b/store/src/transaction.rs index a78c94bb7e..995b9de537 100644 --- a/store/src/transaction.rs +++ b/store/src/transaction.rs @@ -1,5 +1,6 @@ use crate::cache::StoreCache; use crate::store::ChainStore; +use ckb_chain_spec::versionbits::VersionbitsIndexer; use ckb_db::{ iter::{DBIter, DBIterator, IteratorMode}, DBVector, RocksDBTransaction, RocksDBTransactionSnapshot, @@ -17,9 +18,9 @@ use ckb_merkle_mountain_range::{Error as MMRError, MMRStore, Result as MMRResult use ckb_types::{ core::{ cell::{CellChecker, CellProvider, CellStatus}, - BlockExt, BlockView, EpochExt, HeaderView, + BlockExt, BlockView, EpochExt, HeaderView, TransactionView, }, - packed::{self, OutPoint}, + packed::{self, Byte32, OutPoint}, prelude::*, }; use std::sync::Arc; @@ -53,6 +54,24 @@ impl<'a> ChainStore<'a> for StoreTransaction { } } +impl VersionbitsIndexer for StoreTransaction { + fn block_epoch_index(&self, block_hash: &Byte32) -> Option { + ChainStore::get_block_epoch_index(self, block_hash) + } + + fn epoch_ext(&self, index: &Byte32) -> Option { + ChainStore::get_epoch_ext(self, index) + } + + fn block_header(&self, block_hash: &Byte32) -> Option { + ChainStore::get_block_header(self, block_hash) + } + + fn cellbase(&self, block_hash: &Byte32) -> Option { + ChainStore::get_cellbase(self, block_hash) + } +} + impl CellProvider for StoreTransaction { fn cell(&self, out_point: &OutPoint, eager_load: bool) -> CellStatus { match self.get_cell(out_point) { diff --git a/tx-pool/src/block_assembler/mod.rs b/tx-pool/src/block_assembler/mod.rs index 2c045f4412..d481d85c63 100644 --- a/tx-pool/src/block_assembler/mod.rs +++ b/tx-pool/src/block_assembler/mod.rs @@ -10,6 +10,7 @@ use crate::component::entry::TxEntry; use crate::error::BlockAssemblerError; pub use candidate_uncles::CandidateUncles; use ckb_app_config::BlockAssemblerConfig; +use ckb_chain_spec::versionbits::DeploymentPos; use ckb_dao::DaoCalculator; use ckb_error::{AnyError, InternalErrorKind}; use ckb_jsonrpc_types::{ @@ -511,13 +512,14 @@ impl BlockAssembler { } pub(crate) fn build_extension(snapshot: &Snapshot) -> Result, AnyError> { - let consensus = snapshot.consensus(); let tip_header = snapshot.tip_header(); - - let candidate_number = tip_header.number() + 1; - let mmr_activated_epoch = consensus.hardfork_switch().mmr_activated_epoch(); - if tip_header.epoch().minimum_epoch_number_after_n_blocks(1) >= mmr_activated_epoch { - let mmr_size = leaf_index_to_mmr_size(candidate_number - 1); + let mmr_activate = snapshot.versionbits_active(DeploymentPos::LightClient); + if mmr_activate { + // Actually, should use candidate_number here, but +1 - 1 == tip_number, + // the intermediate process omitted + // let candidate_number = tip_header.number() + 1; + // mmr_size = leaf_index_to_mmr_size(candidate_number - 1); + let mmr_size = leaf_index_to_mmr_size(tip_header.number()); let mmr = ChainRootMMR::new(mmr_size, snapshot); let chain_root = mmr .get_root() diff --git a/util/constant/src/hardfork/mainnet.rs b/util/constant/src/hardfork/mainnet.rs index 9f2f55f633..c5ae457896 100644 --- a/util/constant/src/hardfork/mainnet.rs +++ b/util/constant/src/hardfork/mainnet.rs @@ -6,7 +6,3 @@ pub const RFC0028_START_EPOCH: u64 = 5414; /// First epoch number for CKB v2021, at about 2022/05/10 1:00 UTC // pub const CKB2021_START_EPOCH: u64 = 5414; pub const CKB2021_START_EPOCH: u64 = 0; - -// TODO(light-client) update the block number. -/// First epoch which saves the MMR root hash into its header. -pub const RFCTMP1_START_EPOCH: u64 = u64::MAX; diff --git a/util/constant/src/hardfork/testnet.rs b/util/constant/src/hardfork/testnet.rs index 507deec5f5..9855e57a9b 100644 --- a/util/constant/src/hardfork/testnet.rs +++ b/util/constant/src/hardfork/testnet.rs @@ -6,7 +6,3 @@ pub const RFC0028_START_EPOCH: u64 = 3113; /// First epoch number for CKB v2021, at about 2021/10/24 3:15 UTC. // pub const CKB2021_START_EPOCH: u64 = 3113; pub const CKB2021_START_EPOCH: u64 = 0; - -// TODO(light-client) update the block number. -/// First epoch which saves the MMR root hash into its header. -pub const RFCTMP1_START_EPOCH: u64 = u64::MAX; diff --git a/util/jsonrpc-types/src/blockchain.rs b/util/jsonrpc-types/src/blockchain.rs index ee2872c055..52bc317158 100644 --- a/util/jsonrpc-types/src/blockchain.rs +++ b/util/jsonrpc-types/src/blockchain.rs @@ -1348,7 +1348,6 @@ impl HardForkFeature { Self::new("0032", convert(switch.rfc_0032())), Self::new("0036", convert(switch.rfc_0036())), Self::new("0038", convert(switch.rfc_0038())), - Self::new("tmp1", convert(switch.rfc_tmp1())), ] } } diff --git a/util/snapshot/src/lib.rs b/util/snapshot/src/lib.rs index 16c3740991..e95e19a97c 100644 --- a/util/snapshot/src/lib.rs +++ b/util/snapshot/src/lib.rs @@ -3,7 +3,7 @@ use arc_swap::{ArcSwap, Guard}; use ckb_chain_spec::{ consensus::{Consensus, ConsensusProvider}, - versionbits::VersionBitsIndexer, + versionbits::{DeploymentPos, ThresholdState, VersionbitsIndexer}, }; use ckb_db::{ iter::{DBIter, IteratorMode}, @@ -19,7 +19,7 @@ use ckb_types::core::error::OutPointError; use ckb_types::{ core::{ cell::{CellChecker, CellProvider, CellStatus, HeaderChecker}, - BlockNumber, EpochExt, EpochNumber, HeaderView, TransactionView, Version, + BlockNumber, EpochExt, HeaderView, TransactionView, Version, }, packed::{Byte32, HeaderDigest, OutPoint}, U256, @@ -161,9 +161,18 @@ impl Snapshot { &self.total_difficulty } + /// Returns what version a new block should use. pub fn compute_versionbits(&self, parent: &HeaderView) -> Option { self.consensus.compute_versionbits(parent, self) } + + /// Returns specified softfork active or not + pub fn versionbits_active(&self, pos: DeploymentPos) -> bool { + self.consensus + .versionbits_state(pos, &self.tip_header, self) + .map(|state| state == ThresholdState::Active) + .unwrap_or(false) + } } impl<'a> ChainStore<'a> for Snapshot { @@ -194,42 +203,22 @@ impl<'a> ChainStore<'a> for Snapshot { } } -impl VersionBitsIndexer for Snapshot { - fn get_block_epoch_index(&self, block_hash: &Byte32) -> Option { +impl VersionbitsIndexer for Snapshot { + fn block_epoch_index(&self, block_hash: &Byte32) -> Option { ChainStore::get_block_epoch_index(self, block_hash) } - fn get_epoch_ext(&self, index: &Byte32) -> Option { + fn epoch_ext(&self, index: &Byte32) -> Option { ChainStore::get_epoch_ext(self, index) } - fn get_block_header(&self, block_hash: &Byte32) -> Option { + fn block_header(&self, block_hash: &Byte32) -> Option { ChainStore::get_block_header(self, block_hash) } - fn get_cellbase(&self, block_hash: &Byte32) -> Option { + fn cellbase(&self, block_hash: &Byte32) -> Option { ChainStore::get_cellbase(self, block_hash) } - - fn get_ancestor_epoch(&self, index: &Byte32, target: EpochNumber) -> Option { - let mut epoch_ext = ChainStore::get_epoch_ext(self, &index)?; - - if epoch_ext.number() < target { - return None; - } - - while epoch_ext.number() > target { - let last_block_header_in_previous_epoch = - ChainStore::get_block_header(self, &epoch_ext.last_block_hash_in_previous_epoch())?; - let previous_epoch_index = ChainStore::get_block_epoch_index( - self, - &last_block_header_in_previous_epoch.hash(), - )?; - epoch_ext = ChainStore::get_epoch_ext(self, &previous_epoch_index)?; - } - - Some(epoch_ext) - } } impl CellProvider for Snapshot { diff --git a/util/types/src/core/hardfork.rs b/util/types/src/core/hardfork.rs index 0c169666de..d8dc14da68 100644 --- a/util/types/src/core/hardfork.rs +++ b/util/types/src/core/hardfork.rs @@ -101,8 +101,6 @@ pub struct HardForkSwitch { rfc_0032: EpochNumber, rfc_0036: EpochNumber, rfc_0038: EpochNumber, - // TODO(light-client) update the description - rfc_tmp1: EpochNumber, } /// Builder for [`HardForkSwitch`]. @@ -149,8 +147,6 @@ pub struct HardForkSwitchBuilder { /// /// Ref: CKB RFC 0038 pub rfc_0038: Option, - /// TODO(light-client) update the description and the rfc link - pub rfc_tmp1: Option, } impl HardForkSwitch { @@ -169,7 +165,6 @@ impl HardForkSwitch { .rfc_0032(self.rfc_0032()) .rfc_0036(self.rfc_0036()) .rfc_0038(self.rfc_0038()) - .rfc_tmp1(self.rfc_tmp1()) } /// Creates a new mirana instance. @@ -183,7 +178,6 @@ impl HardForkSwitch { .rfc_0032(0) .rfc_0036(0) .rfc_0038(0) - .disable_rfc_tmp1() .build() .unwrap() } @@ -250,13 +244,6 @@ define_methods!( disable_rfc_0038, "RFC PR 0038" ); -define_methods!( - rfc_tmp1, - mmr_activated_epoch, - is_mmr_activated, - disable_rfc_tmp1, - "RFC TMP1" -); impl HardForkSwitchBuilder { /// Build a new [`HardForkSwitch`]. @@ -280,7 +267,6 @@ impl HardForkSwitchBuilder { let rfc_0032 = try_find!(rfc_0032); let rfc_0036 = try_find!(rfc_0036); let rfc_0038 = try_find!(rfc_0038); - let rfc_tmp1 = try_find!(rfc_tmp1); Ok(HardForkSwitch { rfc_0028, @@ -290,7 +276,6 @@ impl HardForkSwitchBuilder { rfc_0032, rfc_0036, rfc_0038, - rfc_tmp1, }) } } diff --git a/verification/contextual/Cargo.toml b/verification/contextual/Cargo.toml index c5abf30116..c52198f28d 100644 --- a/verification/contextual/Cargo.toml +++ b/verification/contextual/Cargo.toml @@ -24,6 +24,7 @@ tokio = { version = "1", features = ["sync", "rt-multi-thread"] } ckb-async-runtime = { path = "../../util/runtime", version = "= 0.105.0-pre" } ckb-verification-traits = { path = "../traits", version = "= 0.105.0-pre" } ckb-verification = { path = "..", version = "= 0.105.0-pre" } +ckb-merkle-mountain-range = "0.4.0" [dev-dependencies] ckb-chain = { path = "../../chain", version = "= 0.105.0-pre" } diff --git a/verification/contextual/src/contextual_block_verifier.rs b/verification/contextual/src/contextual_block_verifier.rs index 2a929dbcbb..bc1e7db5f2 100644 --- a/verification/contextual/src/contextual_block_verifier.rs +++ b/verification/contextual/src/contextual_block_verifier.rs @@ -1,10 +1,14 @@ use crate::uncles_verifier::{UncleProvider, UnclesVerifier}; use ckb_async_runtime::Handle; -use ckb_chain_spec::consensus::{Consensus, ConsensusProvider}; +use ckb_chain_spec::{ + consensus::{Consensus, ConsensusProvider}, + versionbits::{DeploymentPos, ThresholdState, VersionbitsIndexer}, +}; use ckb_dao::DaoCalculator; use ckb_dao_utils::DaoError; -use ckb_error::Error; +use ckb_error::{Error, InternalErrorKind}; use ckb_logger::error_target; +use ckb_merkle_mountain_range::MMRStore; use ckb_reward_calculator::RewardCalculator; use ckb_store::ChainStore; use ckb_traits::HeaderProvider; @@ -14,8 +18,9 @@ use ckb_types::{ cell::{HeaderChecker, ResolvedTransaction}, BlockReward, BlockView, Capacity, Cycle, EpochExt, HeaderView, TransactionView, }, - packed::{Byte32, CellOutput, Script}, + packed::{Byte32, CellOutput, HeaderDigest, Script}, prelude::*, + utilities::merkle_mountain_range::ChainRootMMR, }; use ckb_verification::cache::{ TxVerificationCache, {CacheEntry, Completed}, @@ -37,7 +42,7 @@ pub struct VerifyContext<'a, CS> { pub(crate) consensus: &'a Consensus, } -impl<'a, CS: ChainStore<'a>> VerifyContext<'a, CS> { +impl<'a, CS: ChainStore<'a> + VersionbitsIndexer> VerifyContext<'a, CS> { /// Create new VerifyContext from `Store` and `Consensus` pub fn new(store: &'a CS, consensus: &'a Consensus) -> Self { VerifyContext { store, consensus } @@ -49,6 +54,13 @@ impl<'a, CS: ChainStore<'a>> VerifyContext<'a, CS> { ) -> Result<(Script, BlockReward), DaoError> { RewardCalculator::new(self.consensus, self.store).block_reward_to_finalize(parent) } + + fn versionbits_active(&self, pos: DeploymentPos, header: &HeaderView) -> bool { + self.consensus + .versionbits_state(pos, header, self.store) + .map(|state| state == ThresholdState::Active) + .unwrap_or(false) + } } impl<'a, CS: ChainStore<'a>> HeaderProvider for VerifyContext<'a, CS> { @@ -124,7 +136,7 @@ pub struct TwoPhaseCommitVerifier<'a, CS> { block: &'a BlockView, } -impl<'a, CS: ChainStore<'a>> TwoPhaseCommitVerifier<'a, CS> { +impl<'a, CS: ChainStore<'a> + VersionbitsIndexer> TwoPhaseCommitVerifier<'a, CS> { pub fn new(context: &'a VerifyContext<'a, CS>, block: &'a BlockView) -> Self { TwoPhaseCommitVerifier { context, block } } @@ -206,7 +218,7 @@ pub struct RewardVerifier<'a, 'b, CS> { context: &'a VerifyContext<'a, CS>, } -impl<'a, 'b, CS: ChainStore<'a>> RewardVerifier<'a, 'b, CS> { +impl<'a, 'b, CS: ChainStore<'a> + VersionbitsIndexer> RewardVerifier<'a, 'b, CS> { pub fn new( context: &'a VerifyContext<'a, CS>, resolved: &'a [ResolvedTransaction], @@ -268,7 +280,7 @@ struct DaoHeaderVerifier<'a, 'b, 'c, CS> { header: &'c HeaderView, } -impl<'a, 'b, 'c, CS: ChainStore<'a>> DaoHeaderVerifier<'a, 'b, 'c, CS> { +impl<'a, 'b, 'c, CS: ChainStore<'a> + VersionbitsIndexer> DaoHeaderVerifier<'a, 'b, 'c, CS> { pub fn new( context: &'a VerifyContext<'a, CS>, resolved: &'a [ResolvedTransaction], @@ -309,30 +321,32 @@ impl<'a, 'b, 'c, CS: ChainStore<'a>> DaoHeaderVerifier<'a, 'b, 'c, CS> { struct BlockTxsVerifier<'a, CS> { context: &'a VerifyContext<'a, CS>, header: HeaderView, - resolved: &'a [ResolvedTransaction], + handle: &'a Handle, + txs_verify_cache: &'a Arc>, } -impl<'a, CS: ChainStore<'a>> BlockTxsVerifier<'a, CS> { +impl<'a, CS: ChainStore<'a> + VersionbitsIndexer> BlockTxsVerifier<'a, CS> { pub fn new( context: &'a VerifyContext<'a, CS>, header: HeaderView, - resolved: &'a [ResolvedTransaction], + handle: &'a Handle, + txs_verify_cache: &'a Arc>, ) -> Self { BlockTxsVerifier { context, header, - resolved, + handle, + txs_verify_cache, } } fn fetched_cache + Send + 'static>( &self, - txs_verify_cache: Arc>, keys: K, - handle: &Handle, ) -> HashMap { let (sender, receiver) = oneshot::channel(); - handle.spawn(async move { + let txs_verify_cache = Arc::clone(self.txs_verify_cache); + self.handle.spawn(async move { let guard = txs_verify_cache.read().await; let ret = keys .into_iter() @@ -343,35 +357,42 @@ impl<'a, CS: ChainStore<'a>> BlockTxsVerifier<'a, CS> { error_target!(crate::LOG_TARGET, "TxsVerifier fetched_cache error {:?}", e); }; }); - handle + self.handle .block_on(receiver) .expect("fetched cache no exception") } + fn update_cache(&self, ret: Vec<(Byte32, Completed)>) { + let txs_verify_cache = Arc::clone(self.txs_verify_cache); + self.handle.spawn(async move { + let mut guard = txs_verify_cache.write().await; + for (k, v) in ret { + guard.put(k, CacheEntry::Completed(v)); + } + }); + } + pub fn verify( &self, - txs_verify_cache: Arc>, - handle: &Handle, + resolved: &'a [ResolvedTransaction], skip_script_verify: bool, ) -> Result<(Cycle, Vec), Error> { // We should skip updating tx_verify_cache about the cellbase tx, // putting it in cache that will never be used until lru cache expires. - let fetched_cache = if self.resolved.len() > 1 { - let keys: Vec = self - .resolved + let fetched_cache = if resolved.len() > 1 { + let keys: Vec = resolved .iter() .skip(1) .map(|rtx| rtx.transaction.hash()) .collect(); - self.fetched_cache(Arc::clone(&txs_verify_cache), keys, handle) + self.fetched_cache(keys) } else { HashMap::new() }; // make verifiers orthogonal - let ret = self - .resolved + let ret = resolved .par_iter() .enumerate() .map(|(index, tx)| { @@ -445,12 +466,7 @@ impl<'a, CS: ChainStore<'a>> BlockTxsVerifier<'a, CS> { .cloned() .collect(); if !ret.is_empty() { - handle.spawn(async move { - let mut guard = txs_verify_cache.write().await; - for (k, v) in ret { - guard.put(k, CacheEntry::Completed(v)); - } - }); + self.update_cache(ret); } if sum > self.context.consensus.max_block_cycles() { @@ -497,6 +513,86 @@ impl<'a> EpochVerifier<'a> { } } +/// BlockExtensionVerifier. +/// +/// Check block extension. +#[derive(Clone)] +pub struct BlockExtensionVerifier<'a, 'b, CS, MS: MMRStore> { + context: &'a VerifyContext<'a, CS>, + chain_root_mmr: &'a ChainRootMMR, + parent: &'b HeaderView, +} + +impl<'a, 'b, CS: ChainStore<'a> + VersionbitsIndexer, MS: MMRStore> + BlockExtensionVerifier<'a, 'b, CS, MS> +{ + pub fn new( + context: &'a VerifyContext<'a, CS>, + chain_root_mmr: &'a ChainRootMMR, + parent: &'b HeaderView, + ) -> Self { + BlockExtensionVerifier { + context, + chain_root_mmr, + parent, + } + } + + pub fn verify(&self, block: &BlockView) -> Result<(), Error> { + let extra_fields_count = block.data().count_extra_fields(); + + let mmr_active = self + .context + .versionbits_active(DeploymentPos::LightClient, self.parent); + + match extra_fields_count { + 0 => { + if mmr_active { + return Err(BlockErrorKind::NoBlockExtension.into()); + } + } + 1 => { + let extension = if let Some(data) = block.extension() { + data + } else { + return Err(BlockErrorKind::UnknownFields.into()); + }; + if extension.is_empty() { + return Err(BlockErrorKind::EmptyBlockExtension.into()); + } + if extension.len() > 96 { + return Err(BlockErrorKind::ExceededMaximumBlockExtensionBytes.into()); + } + if mmr_active { + if extension.len() < 32 { + return Err(BlockErrorKind::InvalidBlockExtension.into()); + } + + let chain_root = self + .chain_root_mmr + .get_root() + .map_err(|e| InternalErrorKind::MMR.other(e))?; + let actual_root_hash = chain_root.calc_mmr_hash(); + let expected_root_hash = + Byte32::new_unchecked(extension.raw_data().slice(..32)); + if actual_root_hash != expected_root_hash { + return Err(BlockErrorKind::InvalidChainRoot.into()); + } + } + } + _ => { + return Err(BlockErrorKind::UnknownFields.into()); + } + } + + let actual_extra_hash = block.calc_extra_hash().extra_hash(); + if actual_extra_hash != block.extra_hash() { + return Err(BlockErrorKind::InvalidExtraHash.into()); + } + Ok(()) + } +} + /// Context-dependent verification checks for block /// /// Contains: @@ -506,14 +602,32 @@ impl<'a> EpochVerifier<'a> { /// - [`DaoHeaderVerifier`](./struct.DaoHeaderVerifier.html) /// - [`RewardVerifier`](./struct.RewardVerifier.html) /// - [`BlockTxsVerifier`](./struct.BlockTxsVerifier.html) -pub struct ContextualBlockVerifier<'a, CS> { +pub struct ContextualBlockVerifier<'a, CS, MS: MMRStore> { context: &'a VerifyContext<'a, CS>, + switch: Switch, + handle: &'a Handle, + txs_verify_cache: Arc>, + chain_root_mmr: &'a ChainRootMMR, } -impl<'a, CS: ChainStore<'a>> ContextualBlockVerifier<'a, CS> { +impl<'a, CS: ChainStore<'a> + VersionbitsIndexer, MS: MMRStore> + ContextualBlockVerifier<'a, CS, MS> +{ /// Create new ContextualBlockVerifier - pub fn new(context: &'a VerifyContext<'a, CS>) -> Self { - ContextualBlockVerifier { context } + pub fn new( + context: &'a VerifyContext<'a, CS>, + handle: &'a Handle, + switch: Switch, + txs_verify_cache: Arc>, + chain_root_mmr: &'a ChainRootMMR, + ) -> Self { + ContextualBlockVerifier { + context, + handle, + switch, + txs_verify_cache, + chain_root_mmr, + } } /// Perform context-dependent verification checks for block @@ -521,9 +635,6 @@ impl<'a, CS: ChainStore<'a>> ContextualBlockVerifier<'a, CS> { &'a self, resolved: &'a [ResolvedTransaction], block: &'a BlockView, - txs_verify_cache: Arc>, - handle: &Handle, - switch: Switch, ) -> Result<(Cycle, Vec), Error> { let parent_hash = block.data().header().raw().parent_hash(); let header = block.header(); @@ -547,32 +658,31 @@ impl<'a, CS: ChainStore<'a>> ContextualBlockVerifier<'a, CS> { .epoch() }; - if !switch.disable_epoch() { + if !self.switch.disable_epoch() { EpochVerifier::new(&epoch_ext, block).verify()?; } - if !switch.disable_uncles() { + if !self.switch.disable_uncles() { let uncle_verifier_context = UncleVerifierContext::new(self.context, &epoch_ext); UnclesVerifier::new(uncle_verifier_context, block).verify()?; } - if !switch.disable_two_phase_commit() { + if !self.switch.disable_two_phase_commit() { TwoPhaseCommitVerifier::new(self.context, block).verify()?; } - if !switch.disable_daoheader() { + if !self.switch.disable_daoheader() { DaoHeaderVerifier::new(self.context, resolved, &parent, &block.header()).verify()?; } - if !switch.disable_reward() { + if !self.switch.disable_reward() { RewardVerifier::new(self.context, resolved, &parent).verify()?; } - let ret = BlockTxsVerifier::new(self.context, header, resolved).verify( - txs_verify_cache, - handle, - switch.disable_script(), - )?; + BlockExtensionVerifier::new(self.context, self.chain_root_mmr, &parent).verify(block)?; + + let ret = BlockTxsVerifier::new(self.context, header, self.handle, &self.txs_verify_cache) + .verify(resolved, self.switch.disable_script())?; Ok(ret) } } diff --git a/verification/src/block_verifier.rs b/verification/src/block_verifier.rs index 8860154134..e45fb305fd 100644 --- a/verification/src/block_verifier.rs +++ b/verification/src/block_verifier.rs @@ -40,7 +40,6 @@ impl<'a> Verifier for BlockVerifier<'a> { let max_block_bytes = self.consensus.max_block_bytes(); BlockProposalsLimitVerifier::new(max_block_proposals_limit).verify(target)?; BlockBytesVerifier::new(max_block_bytes).verify(target)?; - BlockExtensionVerifier::new(self.consensus).verify(target)?; CellbaseVerifier::new().verify(target)?; DuplicateVerifier::new().verify(target)?; MerkleRootVerifier::new().verify(target) @@ -238,61 +237,6 @@ impl BlockBytesVerifier { } } -/// BlockExtensionVerifier. -/// -/// Check block extension. -#[derive(Clone)] -pub struct BlockExtensionVerifier<'a> { - consensus: &'a Consensus, -} - -impl<'a> BlockExtensionVerifier<'a> { - pub fn new(consensus: &'a Consensus) -> Self { - BlockExtensionVerifier { consensus } - } - - pub fn verify(&self, block: &BlockView) -> Result<(), Error> { - let extra_fields_count = block.data().count_extra_fields(); - - let mmr_activated_epoch = self.consensus.hardfork_switch().mmr_activated_epoch(); - let has_chain_root = block.epoch().number() >= mmr_activated_epoch; - - match extra_fields_count { - 0 => { - if has_chain_root { - return Err(BlockErrorKind::NoBlockExtension.into()); - } - } - 1 => { - let extension = if let Some(data) = block.extension() { - data - } else { - return Err(BlockErrorKind::UnknownFields.into()); - }; - if extension.is_empty() { - return Err(BlockErrorKind::EmptyBlockExtension.into()); - } - if extension.len() > 96 { - return Err(BlockErrorKind::ExceededMaximumBlockExtensionBytes.into()); - } - if has_chain_root && extension.len() < 32 { - return Err(BlockErrorKind::InvalidBlockExtension.into()); - } - } - _ => { - return Err(BlockErrorKind::UnknownFields.into()); - } - } - - let actual_extra_hash = block.calc_extra_hash().extra_hash(); - if actual_extra_hash != block.extra_hash() { - return Err(BlockErrorKind::InvalidExtraHash.into()); - } - - Ok(()) - } -} - /// Context-independent verification checks for block transactions /// /// Basic checks that don't depend on any context diff --git a/verification/src/tests/block_verifier.rs b/verification/src/tests/block_verifier.rs index 76309101da..2925cc2112 100644 --- a/verification/src/tests/block_verifier.rs +++ b/verification/src/tests/block_verifier.rs @@ -1,15 +1,14 @@ use super::super::block_verifier::{ - BlockBytesVerifier, BlockExtensionVerifier, BlockProposalsLimitVerifier, CellbaseVerifier, - DuplicateVerifier, MerkleRootVerifier, + BlockBytesVerifier, BlockProposalsLimitVerifier, CellbaseVerifier, DuplicateVerifier, + MerkleRootVerifier, }; use crate::{BlockErrorKind, CellbaseError}; -use ckb_chain_spec::consensus::ConsensusBuilder; use ckb_error::assert_error_eq; use ckb_types::{ bytes::Bytes, core::{ - capacity_bytes, BlockBuilder, BlockNumber, Capacity, EpochNumberWithFraction, - HeaderBuilder, TransactionBuilder, TransactionView, + capacity_bytes, BlockBuilder, BlockNumber, Capacity, HeaderBuilder, TransactionBuilder, + TransactionView, }, h256, packed::{Byte32, CellInput, CellOutputBuilder, OutPoint, ProposalShortId, Script}, @@ -409,84 +408,3 @@ pub fn test_max_proposals_limit_verifier() { ); } } - -#[test] -fn test_block_extension_verifier() { - let fork_at = 0; - let epoch = EpochNumberWithFraction::new(fork_at, 0, 1); - - // normal block (no uncles) - let header = HeaderBuilder::default().epoch(epoch.pack()).build(); - let block = BlockBuilder::default().header(header).build(); - - // invalid extra hash (no extension) - let header1 = block - .header() - .as_advanced_builder() - .extra_hash(h256!("0x1").pack()) - .build(); - let block1 = BlockBuilder::default().header(header1).build_unchecked(); - - // empty extension - let block2 = block - .as_advanced_builder() - .extension(Some(Default::default())) - .build(); - // extension has only 1 byte - let block3 = block - .as_advanced_builder() - .extension(Some(vec![0u8].pack())) - .build(); - // extension has 96 bytes - let block4 = block - .as_advanced_builder() - .extension(Some(vec![0u8; 96].pack())) - .build(); - // extension has 97 bytes - let block5 = block - .as_advanced_builder() - .extension(Some(vec![0u8; 97].pack())) - .build(); - - // normal block (with uncles) - let block6 = block - .as_advanced_builder() - .uncle(BlockBuilder::default().build().as_uncle()) - .build(); - - // invalid extra hash (has extension but use uncles hash) - let block7 = block6 - .as_advanced_builder() - .extension(Some(vec![0u8; 32].pack())) - .build_unchecked(); - { - let consensus = ConsensusBuilder::default().build(); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block); - assert!(result.is_ok(), "result = {:?}", result); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block1); - assert_error_eq!(result.unwrap_err(), BlockErrorKind::InvalidExtraHash); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block2); - assert_error_eq!(result.unwrap_err(), BlockErrorKind::EmptyBlockExtension); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block3); - assert!(result.is_ok(), "result = {:?}", result); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block4); - assert!(result.is_ok(), "result = {:?}", result); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block5); - assert_error_eq!( - result.unwrap_err(), - BlockErrorKind::ExceededMaximumBlockExtensionBytes - ); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block6); - assert!(result.is_ok(), "result = {:?}", result); - - let result = BlockExtensionVerifier::new(&consensus).verify(&block7); - assert_error_eq!(result.unwrap_err(), BlockErrorKind::InvalidExtraHash); - } -}