From e1799c2034f56915f6d2f93236525030e91a7793 Mon Sep 17 00:00:00 2001 From: Boyu Yang Date: Wed, 25 Sep 2019 13:38:13 +0800 Subject: [PATCH] feat: use epoch as the basic maturity unit --- benches/benches/benchmarks/overall.rs | 6 +- benches/benches/benchmarks/util.rs | 8 +- chain/src/tests/reward.rs | 6 +- chain/src/tests/util.rs | 5 +- rpc/src/test.rs | 6 +- spec/src/consensus.rs | 19 +-- spec/src/lib.rs | 12 +- sync/src/relayer/tests/helper.rs | 6 +- sync/src/tests/synchronizer.rs | 4 +- .../tx_pool/reference_header_maturity.rs | 70 +++++++--- test/src/specs/tx_pool/valid_since.rs | 42 +----- test/src/utils.rs | 5 + tx-pool/src/process/submit_txs.rs | 6 +- util/snapshot/src/lib.rs | 9 +- util/test-chain-utils/src/chain.rs | 5 +- util/types/src/core/extras.rs | 26 ++-- verification/src/contextual_block_verifier.rs | 14 +- .../src/tests/transaction_verifier.rs | 123 +++++++++++------- verification/src/transaction_verifier.rs | 31 +++-- 19 files changed, 238 insertions(+), 165 deletions(-) diff --git a/benches/benches/benchmarks/overall.rs b/benches/benches/benchmarks/overall.rs index 3e98fc3b7b..d69ef7715a 100644 --- a/benches/benches/benchmarks/overall.rs +++ b/benches/benches/benchmarks/overall.rs @@ -12,8 +12,8 @@ use ckb_tx_pool::BlockAssemblerConfig; use ckb_types::{ bytes::Bytes, core::{ - capacity_bytes, BlockBuilder, BlockView, Capacity, ScriptHashType, TransactionBuilder, - TransactionView, + capacity_bytes, BlockBuilder, BlockView, Capacity, EpochNumberWithFraction, ScriptHashType, + TransactionBuilder, TransactionView, }, packed::{Block, CellDep, CellInput, CellOutput, Header, OutPoint}, prelude::*, @@ -73,7 +73,7 @@ pub fn setup_chain(txs_size: usize) -> (Shared, ChainController) { .build(); let mut consensus = ConsensusBuilder::default() - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .genesis_block(genesis_block) .build(); consensus.tx_proposal_window = ProposalWindow(1, 10); diff --git a/benches/benches/benchmarks/util.rs b/benches/benches/benchmarks/util.rs index b7d46d048f..58cd4866d4 100644 --- a/benches/benches/benchmarks/util.rs +++ b/benches/benches/benchmarks/util.rs @@ -15,8 +15,8 @@ use ckb_types::{ core::{ capacity_bytes, cell::{resolve_transaction, OverlayCellProvider, TransactionsProvider}, - BlockBuilder, BlockView, Capacity, HeaderView, ScriptHashType, TransactionBuilder, - TransactionView, + BlockBuilder, BlockView, Capacity, EpochNumberWithFraction, HeaderView, ScriptHashType, + TransactionBuilder, TransactionView, }, h160, h256, packed::{Byte32, CellDep, CellInput, CellOutput, OutPoint, ProposalShortId, Script}, @@ -66,7 +66,7 @@ pub fn new_always_success_chain(txs_size: usize, chains_num: usize) -> Chains { .build(); let mut consensus = ConsensusBuilder::default() - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .genesis_block(genesis_block) .build(); consensus.tx_proposal_window = ProposalWindow(1, 10); @@ -271,7 +271,7 @@ pub fn new_secp_chain(txs_size: usize, chains_num: usize) -> Chains { .build(); let mut consensus = ConsensusBuilder::default() - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .genesis_block(genesis_block) .build(); consensus.tx_proposal_window = ProposalWindow(1, 10); diff --git a/chain/src/tests/reward.rs b/chain/src/tests/reward.rs index 84e5256129..e6965a99dd 100644 --- a/chain/src/tests/reward.rs +++ b/chain/src/tests/reward.rs @@ -10,8 +10,8 @@ use ckb_types::prelude::*; use ckb_types::{ bytes::Bytes, core::{ - capacity_bytes, BlockBuilder, BlockView, Capacity, HeaderView, ScriptHashType, - TransactionBuilder, TransactionView, UncleBlockView, + capacity_bytes, BlockBuilder, BlockView, Capacity, EpochNumberWithFraction, HeaderView, + ScriptHashType, TransactionBuilder, TransactionView, UncleBlockView, }, packed::{ self, CellDep, CellInput, CellOutputBuilder, OutPoint, ProposalShortId, Script, @@ -142,7 +142,7 @@ fn finalize_reward() { .build(); let consensus = ConsensusBuilder::default() - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .genesis_block(genesis_block) .build(); diff --git a/chain/src/tests/util.rs b/chain/src/tests/util.rs index 2ae57f3709..6ada8830f9 100644 --- a/chain/src/tests/util.rs +++ b/chain/src/tests/util.rs @@ -13,7 +13,8 @@ use ckb_types::{ core::{ capacity_bytes, cell::{resolve_transaction, OverlayCellProvider, TransactionsProvider}, - BlockBuilder, BlockView, Capacity, HeaderView, TransactionBuilder, TransactionView, + BlockBuilder, BlockView, Capacity, EpochNumberWithFraction, HeaderView, TransactionBuilder, + TransactionView, }, packed::{self, Byte32, CellDep, CellInput, CellOutputBuilder, OutPoint}, U256, @@ -49,7 +50,7 @@ pub(crate) fn start_chain(consensus: Option) -> (ChainController, Sha .transaction(tx) .build(); ConsensusBuilder::default() - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .genesis_block(genesis_block) .build() }); diff --git a/rpc/src/test.rs b/rpc/src/test.rs index b1a6970a30..0450a93306 100644 --- a/rpc/src/test.rs +++ b/rpc/src/test.rs @@ -22,8 +22,8 @@ use ckb_sync::{SyncSharedState, Synchronizer}; use ckb_test_chain_utils::{always_success_cell, always_success_cellbase}; use ckb_types::{ core::{ - capacity_bytes, cell::resolve_transaction, BlockBuilder, BlockView, Capacity, HeaderView, - TransactionBuilder, TransactionView, + capacity_bytes, cell::resolve_transaction, BlockBuilder, BlockView, Capacity, + EpochNumberWithFraction, HeaderView, TransactionBuilder, TransactionView, }, h256, packed::{AlertBuilder, CellDep, CellInput, CellOutputBuilder, OutPoint, RawAlertBuilder}, @@ -81,7 +81,7 @@ fn always_success_consensus() -> Consensus { ConsensusBuilder::default() .genesis_block(genesis) .epoch_reward(Capacity::shannons(EPOCH_REWARD)) - .cellbase_maturity(CELLBASE_MATURITY) + .cellbase_maturity(EpochNumberWithFraction::from_full_value(CELLBASE_MATURITY)) .build() } diff --git a/spec/src/consensus.rs b/spec/src/consensus.rs index e272831770..f35f0a2eb7 100644 --- a/spec/src/consensus.rs +++ b/spec/src/consensus.rs @@ -11,8 +11,8 @@ use ckb_resource::Resource; use ckb_types::{ constants::BLOCK_VERSION, core::{ - BlockBuilder, BlockNumber, BlockView, Capacity, Cycle, EpochExt, HeaderView, Ratio, - TransactionBuilder, TransactionView, Version, + BlockBuilder, BlockNumber, BlockView, Capacity, Cycle, EpochExt, EpochNumberWithFraction, + HeaderView, Ratio, TransactionBuilder, TransactionView, Version, }, h160, packed::{Byte32, CellInput, Script}, @@ -28,10 +28,11 @@ pub(crate) const DEFAULT_SECONDARY_EPOCH_REWARD: Capacity = Capacity::shannons(6 pub(crate) const DEFAULT_EPOCH_REWARD: Capacity = Capacity::shannons(1_917_808_21917808); const MAX_UNCLE_NUM: usize = 2; pub(crate) const TX_PROPOSAL_WINDOW: ProposalWindow = ProposalWindow(2, 10); -// Cellbase outputs are "locked" and require 4 * MAX_EPOCH_LENGTH(1800) confirmations(approximately 16 hours) -// before they mature sufficiently to be spendable, +// Cellbase outputs are "locked" and require 4 epoch confirmations (approximately 16 hours) before +// they mature sufficiently to be spendable, // This is to reduce the risk of later txs being reversed if a chain reorganization occurs. -pub(crate) const CELLBASE_MATURITY: BlockNumber = 4 * MAX_EPOCH_LENGTH; +pub(crate) const CELLBASE_MATURITY: EpochNumberWithFraction = + EpochNumberWithFraction::new_unchecked(4, 0, 1); // TODO: should adjust this value based on CKB average block time const MEDIAN_TIME_BLOCK_COUNT: usize = 37; @@ -117,7 +118,7 @@ pub struct ConsensusBuilder { tx_proposal_window: ProposalWindow, proposer_reward_ratio: Ratio, pow: Pow, - cellbase_maturity: BlockNumber, + cellbase_maturity: EpochNumberWithFraction, median_time_block_count: usize, max_block_cycles: Cycle, max_block_bytes: u64, @@ -366,7 +367,7 @@ impl ConsensusBuilder { } #[must_use] - pub fn cellbase_maturity(mut self, cellbase_maturity: BlockNumber) -> Self { + pub fn cellbase_maturity(mut self, cellbase_maturity: EpochNumberWithFraction) -> Self { self.cellbase_maturity = cellbase_maturity; self } @@ -411,7 +412,7 @@ pub struct Consensus { // For each input, if the referenced output transaction is cellbase, // it must have at least `cellbase_maturity` confirmations; // else reject this transaction. - pub cellbase_maturity: BlockNumber, + pub cellbase_maturity: EpochNumberWithFraction, // This parameter indicates the count of past blocks used in the median time calculation pub median_time_block_count: usize, // Maximum cycles that all the scripts in all the commit transactions can take @@ -514,7 +515,7 @@ impl Consensus { self.pow.engine() } - pub fn cellbase_maturity(&self) -> BlockNumber { + pub fn cellbase_maturity(&self) -> EpochNumberWithFraction { self.cellbase_maturity } diff --git a/spec/src/lib.rs b/spec/src/lib.rs index 9dc7cef848..18fecef397 100644 --- a/spec/src/lib.rs +++ b/spec/src/lib.rs @@ -25,8 +25,8 @@ use ckb_types::{ bytes::Bytes, constants::TYPE_ID_CODE_HASH, core::{ - capacity_bytes, BlockBuilder, BlockNumber, BlockView, Capacity, Cycle, EpochExt, Ratio, - ScriptHashType, TransactionBuilder, TransactionView, + capacity_bytes, BlockBuilder, BlockView, Capacity, Cycle, EpochExt, + EpochNumberWithFraction, Ratio, ScriptHashType, TransactionBuilder, TransactionView, }, h256, packed, prelude::*, @@ -66,7 +66,7 @@ pub struct Params { pub epoch_reward: Capacity, pub secondary_epoch_reward: Capacity, pub max_block_cycles: Cycle, - pub cellbase_maturity: BlockNumber, + pub cellbase_maturity: u64, } impl Default for Params { @@ -79,7 +79,7 @@ impl Default for Params { epoch_reward: DEFAULT_EPOCH_REWARD, secondary_epoch_reward: DEFAULT_SECONDARY_EPOCH_REWARD, max_block_cycles: MAX_BLOCK_CYCLES, - cellbase_maturity: CELLBASE_MATURITY, + cellbase_maturity: CELLBASE_MATURITY.full_value(), } } } @@ -222,7 +222,9 @@ impl ChainSpec { let consensus = ConsensusBuilder::new(genesis_block, self.params.epoch_reward, genesis_epoch_ext) .id(self.name.clone()) - .cellbase_maturity(self.params.cellbase_maturity) + .cellbase_maturity(EpochNumberWithFraction::from_full_value( + self.params.cellbase_maturity, + )) .secondary_epoch_reward(self.params.secondary_epoch_reward) .max_block_cycles(self.params.max_block_cycles) .pow(self.pow.clone()) diff --git a/sync/src/relayer/tests/helper.rs b/sync/src/relayer/tests/helper.rs index 0c98dc0a67..21e79b7af2 100644 --- a/sync/src/relayer/tests/helper.rs +++ b/sync/src/relayer/tests/helper.rs @@ -11,8 +11,8 @@ use ckb_types::prelude::*; use ckb_types::{ bytes::Bytes, core::{ - capacity_bytes, BlockBuilder, BlockNumber, Capacity, HeaderBuilder, HeaderView, - TransactionBuilder, TransactionView, + capacity_bytes, BlockBuilder, BlockNumber, Capacity, EpochNumberWithFraction, + HeaderBuilder, HeaderView, TransactionBuilder, TransactionView, }, packed::{ CellDep, CellInput, CellOutputBuilder, IndexTransaction, IndexTransactionBuilder, OutPoint, @@ -110,7 +110,7 @@ pub(crate) fn build_chain(tip: BlockNumber) -> (Relayer, OutPoint) { .build(); let consensus = ConsensusBuilder::default() .genesis_block(genesis) - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .build(); SharedBuilder::default() .consensus(consensus) diff --git a/sync/src/tests/synchronizer.rs b/sync/src/tests/synchronizer.rs index d7dc5c036a..2cf4a0493e 100644 --- a/sync/src/tests/synchronizer.rs +++ b/sync/src/tests/synchronizer.rs @@ -14,7 +14,7 @@ use ckb_test_chain_utils::always_success_cell; use ckb_types::prelude::*; use ckb_types::{ bytes::Bytes, - core::{cell::resolve_transaction, BlockBuilder, TransactionBuilder}, + core::{cell::resolve_transaction, BlockBuilder, EpochNumberWithFraction, TransactionBuilder}, packed::{self, CellInput, CellOutputBuilder, OutPoint}, U256, }; @@ -96,7 +96,7 @@ fn setup_node(height: u64) -> (TestNode, Shared) { let consensus = ConsensusBuilder::default() .genesis_block(block.clone()) - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .build(); let (shared, table) = SharedBuilder::default() .consensus(consensus) diff --git a/test/src/specs/tx_pool/reference_header_maturity.rs b/test/src/specs/tx_pool/reference_header_maturity.rs index 09f4e4baff..652e8b09c2 100644 --- a/test/src/specs/tx_pool/reference_header_maturity.rs +++ b/test/src/specs/tx_pool/reference_header_maturity.rs @@ -1,10 +1,10 @@ use crate::utils::assert_send_transaction_fail; use crate::{utils::is_committed, Net, Spec, DEFAULT_TX_PROPOSAL_WINDOW}; use ckb_chain_spec::ChainSpec; -use ckb_types::core::BlockNumber; +use ckb_types::core::EpochNumberWithFraction; use log::info; -const MATURITY: BlockNumber = 5; +const CELLBASE_MATURITY_VALUE: u64 = 3; pub struct ReferenceHeaderMaturity; @@ -18,8 +18,29 @@ impl Spec for ReferenceHeaderMaturity { node.generate_block(); info!("Use generated block's cellbase as tx input"); let base_block = node.get_tip_block(); - info!("Ensure cellbase is matured"); - node.generate_blocks(5); + + let cellbase_maturity = EpochNumberWithFraction::from_full_value(CELLBASE_MATURITY_VALUE); + + { + info!("Ensure cellbase is matured"); + let base_epoch = base_block.epoch(); + let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational(); + loop { + let tip_block = node.get_tip_block(); + let tip_epoch = tip_block.epoch(); + let current = tip_epoch.to_rational(); + if current < threshold { + if tip_epoch.number() < base_epoch.number() + cellbase_maturity.number() { + let remained_blocks_in_epoch = tip_epoch.length() - tip_epoch.index(); + node.generate_blocks(remained_blocks_in_epoch as usize); + } else { + node.generate_block(); + } + } else { + break; + } + } + } info!("Reference tip block's header to test for maturity"); let tip_block = node.get_tip_block(); @@ -31,21 +52,38 @@ impl Spec for ReferenceHeaderMaturity { .header_dep(tip_block.hash()) .build(); - (0..MATURITY).for_each(|i| { - info!("Tx is not matured in N + {} block", i); - assert_send_transaction_fail(node, &tx, "ImmatureHeader"); - node.generate_block(); - }); + { + let base_epoch = tip_block.epoch(); + let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational(); + loop { + let tip_block = node.get_tip_block(); + let tip_epoch = tip_block.epoch(); + let current = tip_epoch.to_rational(); + if current < threshold { + info!( + "Tx is not matured in {} block (epoch = {})", + tip_block.number(), + tip_block.epoch() + ); + assert_send_transaction_fail(node, &tx, "ImmatureHeader"); + } else { + break; + } + if tip_epoch.number() < base_epoch.number() + cellbase_maturity.number() { + let remained_blocks_in_epoch = tip_epoch.length() - tip_epoch.index(); + node.generate_blocks(remained_blocks_in_epoch as usize); + } else { + node.generate_block(); + } + } + } - info!("Tx will be added to pending pool in N + {} block", MATURITY,); + info!("Tx will be added to pending pool"); let tx_hash = node.rpc_client().send_transaction(tx.clone().data().into()); assert_eq!(tx_hash, tx.hash()); node.assert_tx_pool_size(1, 0); - info!( - "Tx will be added to proposed pool in N + {} block", - MATURITY - ); + info!("Tx will be added to proposed pool"); (0..DEFAULT_TX_PROPOSAL_WINDOW.0).for_each(|_| { node.generate_block(); }); @@ -54,7 +92,7 @@ impl Spec for ReferenceHeaderMaturity { node.generate_block(); node.assert_tx_pool_size(0, 0); - info!("Tx will be eventually accepted on chain",); + info!("Tx will be eventually accepted on chain"); node.generate_blocks(5); let tx_status = node .rpc_client() @@ -69,7 +107,7 @@ impl Spec for ReferenceHeaderMaturity { fn modify_chain_spec(&self) -> Box ()> { Box::new(|spec_config| { - spec_config.params.cellbase_maturity = MATURITY; + spec_config.params.cellbase_maturity = CELLBASE_MATURITY_VALUE; }) } } diff --git a/test/src/specs/tx_pool/valid_since.rs b/test/src/specs/tx_pool/valid_since.rs index 2414ce5938..157e809856 100644 --- a/test/src/specs/tx_pool/valid_since.rs +++ b/test/src/specs/tx_pool/valid_since.rs @@ -6,7 +6,6 @@ use crate::{Net, Node, Spec, DEFAULT_TX_PROPOSAL_WINDOW}; use ckb_chain_spec::ChainSpec; use ckb_types::core::BlockNumber; use log::info; -use std::cmp::max; use std::thread::sleep; use std::time::Duration; @@ -27,37 +26,24 @@ impl Spec for ValidSince { } fn modify_chain_spec(&self) -> Box ()> { - let cellbase_maturity = self.cellbase_maturity(); Box::new(move |spec_config: &mut ChainSpec| { - spec_config.params.cellbase_maturity = cellbase_maturity; + spec_config.params.cellbase_maturity = 0; }) } } impl ValidSince { - pub fn cellbase_maturity(&self) -> u64 { - DEFAULT_TX_PROPOSAL_WINDOW.0 + 2 - } - - // (current, current+cellbase_maturity): Err(Transaction(CellbaseImmaturity)) - // [current+cellbase_maturity, current+relative_number): Err(Transaction(Immature)) pub fn test_since_relative_block_number(&self, node: &Node) { node.generate_block(); - let relative: BlockNumber = self.cellbase_maturity() + 5; + let relative: BlockNumber = 5; let since = since_from_relative_block_number(relative); let transaction = { let cellbase = node.get_tip_block().transactions()[0].clone(); node.new_transaction_with_since(cellbase.hash(), since) }; - // Failed to send transaction since CellbaseImmaturity - for _ in 1..self.cellbase_maturity() { - assert_send_transaction_fail(node, &transaction, "Transaction(CellbaseImmaturity)"); - node.generate_block(); - } - // Failed to send transaction since SinceImmaturity - for _ in self.cellbase_maturity()..relative { + for _ in 1..relative { assert_send_transaction_fail(node, &transaction, "Transaction(Immature)"); node.generate_block(); } @@ -74,24 +60,15 @@ impl ValidSince { ); } - // (current, current+cellbase_maturity): Err(Transaction(CellbaseImmaturity)) - // [current+cellbase_maturity, absolute_number): Err(Transaction(Immature)) pub fn test_since_absolute_block_number(&self, node: &Node) { node.generate_block(); - let absolute: BlockNumber = - node.rpc_client().get_tip_block_number() + self.cellbase_maturity() + 5; + let absolute: BlockNumber = node.rpc_client().get_tip_block_number() + 5; let since = since_from_absolute_block_number(absolute); let transaction = { let cellbase = node.get_tip_block().transactions()[0].clone(); node.new_transaction_with_since(cellbase.hash(), since) }; - // Failed to send transaction since CellbaseImmaturity - for _ in 1..self.cellbase_maturity() { - assert_send_transaction_fail(node, &transaction, "Transaction(CellbaseImmaturity)"); - node.generate_block(); - } - // Failed to send transaction since SinceImmaturity let tip_number = node.rpc_client().get_tip_block_number(); for _ in tip_number + 1..absolute { @@ -118,10 +95,7 @@ impl ValidSince { let old_median_time: u64 = node.rpc_client().get_blockchain_info().median_time.into(); sleep(Duration::from_secs(2)); - let n = max(self.cellbase_maturity(), median_time_block_count); - (0..n).for_each(|_| { - node.generate_block(); - }); + node.generate_blocks(median_time_block_count as usize); // Calculate the current block median time let tip_number = node.rpc_client().get_tip_block_number(); @@ -165,10 +139,8 @@ impl ValidSince { let median_time_block_count = node.consensus().median_time_block_count() as u64; node.generate_block(); let cellbase = node.get_tip_block().transactions()[0].clone(); - let n = max(self.cellbase_maturity(), median_time_block_count); - (0..n).for_each(|_| { - node.generate_block(); - }); + + node.generate_blocks(median_time_block_count as usize); // Calculate current block median time let tip_number = node.rpc_client().get_tip_block_number(); diff --git a/test/src/utils.rs b/test/src/utils.rs index 988971481c..726d0ebe96 100644 --- a/test/src/utils.rs +++ b/test/src/utils.rs @@ -155,6 +155,11 @@ pub fn assert_send_transaction_fail(node: &Node, transaction: &TransactionView, .lock() .send_transaction(transaction.data().into()) .call(); + assert!( + result.is_err(), + "expect error \"{}\" but got \"Ok(())\"", + message, + ); let error = result.expect_err(&format!("transaction is invalid since {}", message)); let error_string = error.to_string(); assert!( diff --git a/tx-pool/src/process/submit_txs.rs b/tx-pool/src/process/submit_txs.rs index 47d9312740..ebfb15257a 100644 --- a/tx-pool/src/process/submit_txs.rs +++ b/tx-pool/src/process/submit_txs.rs @@ -282,7 +282,7 @@ fn verify_rtxs( ) -> Result, Error> { let tip_header = snapshot.tip_header(); let tip_number = tip_header.number(); - let epoch_number = tip_header.epoch(); + let epoch = tip_header.epoch(); let consensus = snapshot.consensus(); txs.into_iter() @@ -293,7 +293,7 @@ fn verify_rtxs( &tx, snapshot, tip_number + 1, - epoch_number.clone(), + epoch, tip_header.hash(), consensus, ) @@ -304,7 +304,7 @@ fn verify_rtxs( &tx, snapshot, tip_number + 1, - epoch_number.clone(), + epoch, tip_header.hash(), consensus, snapshot, diff --git a/util/snapshot/src/lib.rs b/util/snapshot/src/lib.rs index ba2bb7082a..1a02f7d80a 100644 --- a/util/snapshot/src/lib.rs +++ b/util/snapshot/src/lib.rs @@ -168,9 +168,12 @@ impl CellProvider for Snapshot { impl HeaderChecker for Snapshot { fn check_valid(&self, block_hash: &Byte32) -> Result<(), Error> { - match self.get_block_number(block_hash) { - Some(block_number) => { - if self.tip_number() < block_number + self.consensus.cellbase_maturity() { + match self.get_block_header(block_hash) { + Some(header) => { + let threshold = + self.consensus.cellbase_maturity().to_rational() + header.epoch().to_rational(); + let current = self.tip_header().epoch().to_rational(); + if current < threshold { Err(OutPointError::ImmatureHeader(block_hash.clone()).into()) } else { Ok(()) diff --git a/util/test-chain-utils/src/chain.rs b/util/test-chain-utils/src/chain.rs index eafeae7b86..ddefb96c33 100644 --- a/util/test-chain-utils/src/chain.rs +++ b/util/test-chain-utils/src/chain.rs @@ -3,7 +3,8 @@ use ckb_dao_utils::genesis_dao_data; use ckb_types::{ bytes::Bytes, core::{ - BlockBuilder, BlockNumber, Capacity, ScriptHashType, TransactionBuilder, TransactionView, + BlockBuilder, BlockNumber, Capacity, EpochNumberWithFraction, ScriptHashType, + TransactionBuilder, TransactionView, }, packed::{CellInput, CellOutput, OutPoint, Script}, prelude::*, @@ -60,7 +61,7 @@ pub fn always_success_consensus() -> Consensus { .build(); ConsensusBuilder::default() .genesis_block(genesis) - .cellbase_maturity(0) + .cellbase_maturity(EpochNumberWithFraction::new(0, 0, 1)) .build() } diff --git a/util/types/src/core/extras.rs b/util/types/src/core/extras.rs index 1ed03cc76a..cea1e3f5f3 100644 --- a/util/types/src/core/extras.rs +++ b/util/types/src/core/extras.rs @@ -81,7 +81,7 @@ impl EpochExt { // Simple Getters // - pub fn number(&self) -> BlockNumber { + pub fn number(&self) -> EpochNumber { self.number } @@ -266,12 +266,18 @@ impl EpochExtBuilder { /// Represents an epoch number with a fraction unit, it can be /// used to accurately represent the position for a block within /// an epoch. -#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)] pub struct EpochNumberWithFraction(u64); impl fmt::Display for EpochNumberWithFraction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) + write!( + f, + "Epoch {{ number: {}, index: {}, length: {} }}", + self.number(), + self.index(), + self.length() + ) } } @@ -323,6 +329,10 @@ impl EpochNumberWithFraction { debug_assert!(index < Self::INDEX_MAXIMUM_VALUE); debug_assert!(length < Self::LENGTH_MAXIMUM_VALUE); debug_assert!(length > 0); + Self::new_unchecked(number, index, length) + } + + pub const fn new_unchecked(number: u64, index: u64, length: u64) -> Self { EpochNumberWithFraction( (length << Self::LENGTH_OFFSET) | (index << Self::INDEX_OFFSET) @@ -330,19 +340,19 @@ impl EpochNumberWithFraction { ) } - pub fn number(&self) -> EpochNumber { + pub fn number(self) -> EpochNumber { (self.0 >> Self::NUMBER_OFFSET) & Self::NUMBER_MASK } - pub fn index(&self) -> u64 { + pub fn index(self) -> u64 { (self.0 >> Self::INDEX_OFFSET) & Self::INDEX_MASK } - pub fn length(&self) -> u64 { + pub fn length(self) -> u64 { (self.0 >> Self::LENGTH_OFFSET) & Self::LENGTH_MASK } - pub fn full_value(&self) -> u64 { + pub fn full_value(self) -> u64 { self.0 } @@ -360,7 +370,7 @@ impl EpochNumberWithFraction { } } - pub fn to_rational(&self) -> RationalU256 { + pub fn to_rational(self) -> RationalU256 { RationalU256::new(self.index().into(), self.length().into()) + U256::from(self.number()) } } diff --git a/verification/src/contextual_block_verifier.rs b/verification/src/contextual_block_verifier.rs index e84f16a2ad..09fb666af8 100644 --- a/verification/src/contextual_block_verifier.rs +++ b/verification/src/contextual_block_verifier.rs @@ -76,11 +76,13 @@ impl<'a, CS: ChainStore<'a>> BlockMedianTimeContext for VerifyContext<'a, CS> { impl<'a, CS: ChainStore<'a>> HeaderChecker for VerifyContext<'a, CS> { fn check_valid(&self, block_hash: &Byte32) -> Result<(), Error> { - match self.store.get_block_number(block_hash) { - Some(block_number) => { + match self.store.get_block_header(block_hash) { + Some(header) => { let tip_header = self.store.get_tip_header().expect("tip should exist"); - let tip_block_number = tip_header.number(); - if tip_block_number < block_number + self.consensus.cellbase_maturity() { + let threshold = + self.consensus.cellbase_maturity().to_rational() + header.epoch().to_rational(); + let current = tip_header.epoch().to_rational(); + if current < threshold { Err(OutPointError::ImmatureHeader(block_hash.clone()).into()) } else { Ok(()) @@ -375,7 +377,7 @@ impl<'a, CS: ChainStore<'a>> BlockTxsVerifier<'a, CS> { &tx, self.context, self.block_number, - self.epoch_number_with_fraction.clone(), + self.epoch_number_with_fraction, self.parent_hash.clone(), self.context.consensus, ) @@ -393,7 +395,7 @@ impl<'a, CS: ChainStore<'a>> BlockTxsVerifier<'a, CS> { &tx, self.context, self.block_number, - self.epoch_number_with_fraction.clone(), + self.epoch_number_with_fraction, self.parent_hash.clone(), self.context.consensus, self.context.store, diff --git a/verification/src/tests/transaction_verifier.rs b/verification/src/tests/transaction_verifier.rs index 1b98754f3e..018826b7ed 100644 --- a/verification/src/tests/transaction_verifier.rs +++ b/verification/src/tests/transaction_verifier.rs @@ -128,6 +128,8 @@ pub fn test_inputs_cellbase_maturity() { let output = CellOutput::new_builder() .capacity(capacity_bytes!(50).pack()) .build(); + let base_epoch = EpochNumberWithFraction::new(10, 0, 10); + let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1); let rtx = ResolvedTransaction { transaction, @@ -135,27 +137,35 @@ pub fn test_inputs_cellbase_maturity() { resolved_dep_groups: Vec::new(), resolved_inputs: vec![ CellMetaBuilder::from_cell_output(output.clone(), Bytes::new()) - .transaction_info(MockMedianTime::get_transaction_info( - 30, - EpochNumberWithFraction::new(0, 0, 10), - 0, - )) + .transaction_info(MockMedianTime::get_transaction_info(30, base_epoch, 0)) .build(), ], }; - let tip_number = 70; - let cellbase_maturity = 100; - let verifier1 = MaturityVerifier::new(&rtx, tip_number, cellbase_maturity); - - assert_error_eq( - verifier1.verify().unwrap_err(), - TransactionError::CellbaseImmaturity, - ); - - let tip_number = 130; - let verifier2 = MaturityVerifier::new(&rtx, tip_number, cellbase_maturity); - assert!(verifier2.verify().is_ok()); + let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10); + let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational(); + while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 { + let verifier = MaturityVerifier::new(&rtx, current_epoch, cellbase_maturity); + let current = current_epoch.to_rational(); + if current < threshold { + assert_error_eq( + verifier.verify().unwrap_err(), + TransactionError::CellbaseImmaturity, + ); + } else { + assert!(verifier.verify().is_ok()); + } + { + let number = current_epoch.number(); + let length = current_epoch.length(); + let index = current_epoch.index(); + current_epoch = if index == length { + EpochNumberWithFraction::new(number + 1, 0, length) + } else { + EpochNumberWithFraction::new(number, index + 1, length) + }; + } + } } #[test] @@ -164,6 +174,8 @@ fn test_ignore_genesis_cellbase_maturity() { let output = CellOutput::new_builder() .capacity(capacity_bytes!(50).pack()) .build(); + let base_epoch = EpochNumberWithFraction::new(0, 0, 10); + let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1); // Transaction use genesis cellbase let rtx = ResolvedTransaction { transaction, @@ -171,18 +183,26 @@ fn test_ignore_genesis_cellbase_maturity() { resolved_dep_groups: Vec::new(), resolved_inputs: vec![ CellMetaBuilder::from_cell_output(output.clone(), Bytes::new()) - .transaction_info(MockMedianTime::get_transaction_info( - 0, - EpochNumberWithFraction::new(0, 0, 10), - 0, - )) + .transaction_info(MockMedianTime::get_transaction_info(0, base_epoch, 0)) .build(), ], }; - let tip_number = 70; - let cellbase_maturity = 100; - let verifier = MaturityVerifier::new(&rtx, tip_number, cellbase_maturity); - assert!(verifier.verify().is_ok()); + + let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10); + while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 { + let verifier = MaturityVerifier::new(&rtx, current_epoch, cellbase_maturity); + assert!(verifier.verify().is_ok()); + { + let number = current_epoch.number(); + let length = current_epoch.length(); + let index = current_epoch.index(); + current_epoch = if index == length { + EpochNumberWithFraction::new(number + 1, 0, length) + } else { + EpochNumberWithFraction::new(number, index + 1, length) + }; + } + } } // deps immature verify @@ -193,41 +213,48 @@ pub fn test_deps_cellbase_maturity() { .capacity(capacity_bytes!(50).pack()) .build(); + let base_epoch = EpochNumberWithFraction::new(0, 0, 10); + let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1); + // The 1st dep is cellbase, the 2nd one is not. let rtx = ResolvedTransaction { transaction, resolved_cell_deps: vec![ CellMetaBuilder::from_cell_output(output.clone(), Bytes::new()) - .transaction_info(MockMedianTime::get_transaction_info( - 30, - EpochNumberWithFraction::new(0, 0, 10), - 0, - )) + .transaction_info(MockMedianTime::get_transaction_info(30, base_epoch, 0)) .build(), CellMetaBuilder::from_cell_output(output.clone(), Bytes::new()) - .transaction_info(MockMedianTime::get_transaction_info( - 40, - EpochNumberWithFraction::new(0, 0, 10), - 1, - )) + .transaction_info(MockMedianTime::get_transaction_info(40, base_epoch, 1)) .build(), ], resolved_inputs: Vec::new(), resolved_dep_groups: vec![], }; - let tip_number = 70; - let cellbase_maturity = 100; - let verifier = MaturityVerifier::new(&rtx, tip_number, cellbase_maturity); - - assert_error_eq( - verifier.verify().unwrap_err(), - TransactionError::CellbaseImmaturity, - ); - - let tip_number = 130; - let verifier = MaturityVerifier::new(&rtx, tip_number, cellbase_maturity); - assert!(verifier.verify().is_ok()); + let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10); + let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational(); + while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 { + let verifier = MaturityVerifier::new(&rtx, current_epoch, cellbase_maturity); + let current = current_epoch.to_rational(); + if current < threshold { + assert_error_eq( + verifier.verify().unwrap_err(), + TransactionError::CellbaseImmaturity, + ); + } else { + assert!(verifier.verify().is_ok()); + } + { + let number = current_epoch.number(); + let length = current_epoch.length(); + let index = current_epoch.index(); + current_epoch = if index == length { + EpochNumberWithFraction::new(number + 1, 0, length) + } else { + EpochNumberWithFraction::new(number, index + 1, length) + }; + } + } } #[test] diff --git a/verification/src/transaction_verifier.rs b/verification/src/transaction_verifier.rs index 330b4ae893..20efdcf63f 100644 --- a/verification/src/transaction_verifier.rs +++ b/verification/src/transaction_verifier.rs @@ -35,7 +35,11 @@ where consensus: &'a Consensus, ) -> Self { ContextualTransactionVerifier { - maturity: MaturityVerifier::new(&rtx, block_number, consensus.cellbase_maturity()), + maturity: MaturityVerifier::new( + &rtx, + epoch_number_with_fraction, + consensus.cellbase_maturity(), + ), since: SinceVerifier::new( rtx, median_time_context, @@ -84,7 +88,11 @@ where version: VersionVerifier::new(&rtx.transaction), size: SizeVerifier::new(&rtx.transaction, consensus.max_block_bytes()), empty: EmptyVerifier::new(&rtx.transaction), - maturity: MaturityVerifier::new(&rtx, block_number, consensus.cellbase_maturity()), + maturity: MaturityVerifier::new( + &rtx, + epoch_number_with_fraction, + consensus.cellbase_maturity(), + ), duplicate_deps: DuplicateDepsVerifier::new(&rtx.transaction), outputs_data_verifier: OutputsDataVerifier::new(&rtx.transaction), script: ScriptVerifier::new(rtx, chain_store), @@ -192,19 +200,19 @@ impl<'a> EmptyVerifier<'a> { pub struct MaturityVerifier<'a> { transaction: &'a ResolvedTransaction, - block_number: BlockNumber, - cellbase_maturity: BlockNumber, + epoch: EpochNumberWithFraction, + cellbase_maturity: EpochNumberWithFraction, } impl<'a> MaturityVerifier<'a> { pub fn new( transaction: &'a ResolvedTransaction, - block_number: BlockNumber, - cellbase_maturity: BlockNumber, + epoch: EpochNumberWithFraction, + cellbase_maturity: EpochNumberWithFraction, ) -> Self { MaturityVerifier { transaction, - block_number, + epoch, cellbase_maturity, } } @@ -214,9 +222,12 @@ impl<'a> MaturityVerifier<'a> { meta.transaction_info .as_ref() .map(|info| { - info.block_number > 0 - && info.is_cellbase() - && self.block_number < info.block_number + self.cellbase_maturity + info.block_number > 0 && info.is_cellbase() && { + let threshold = + self.cellbase_maturity.to_rational() + info.block_epoch.to_rational(); + let current = self.epoch.to_rational(); + current < threshold + } }) .unwrap_or(false) };