mod candidate_uncles;
#[cfg(test)]
mod tests;
use crate::component::entry::TxEntry;
use crate::error::BlockAssemblerError;
pub use candidate_uncles::CandidateUncles;
use ckb_app_config::BlockAssemblerConfig;
use ckb_chain_spec::consensus::Consensus;
use ckb_error::AnyError;
use ckb_jsonrpc_types::{BlockTemplate, CellbaseTemplate, TransactionTemplate, UncleTemplate};
use ckb_reward_calculator::RewardCalculator;
use ckb_snapshot::Snapshot;
use ckb_store::ChainStore;
use ckb_types::{
bytes::Bytes,
core::{
BlockNumber, Capacity, Cycle, EpochExt, HeaderView, TransactionBuilder, TransactionView,
UncleBlockView, Version,
},
packed::{self, Byte32, CellInput, CellOutput, CellbaseWitness, ProposalShortId, Transaction},
prelude::*,
};
use lru::LruCache;
use std::collections::HashSet;
use std::sync::{atomic::AtomicU64, Arc};
use tokio::sync::Mutex;
use tokio::task::block_in_place;
const BLOCK_TEMPLATE_TIMEOUT: u64 = 3000;
const TEMPLATE_CACHE_SIZE: usize = 10;
pub(crate) struct TemplateCache {
pub(crate) time: u64,
pub(crate) uncles_updated_at: u64,
pub(crate) txs_updated_at: u64,
pub(crate) template: BlockTemplate,
}
impl TemplateCache {
pub(crate) fn is_outdate(&self, current_time: u64) -> bool {
current_time.saturating_sub(self.time) > BLOCK_TEMPLATE_TIMEOUT
}
pub(crate) fn is_modified(
&self,
last_uncles_updated_at: u64,
last_txs_updated_at: u64,
) -> bool {
last_uncles_updated_at != self.uncles_updated_at
|| last_txs_updated_at != self.txs_updated_at
}
}
pub(crate) type BlockTemplateCacheKey = (Byte32, Cycle, u64, Version);
#[derive(Clone)]
pub struct BlockAssembler {
pub(crate) config: Arc<BlockAssemblerConfig>,
pub(crate) work_id: Arc<AtomicU64>,
pub(crate) last_uncles_updated_at: Arc<AtomicU64>,
pub(crate) template_caches: Arc<Mutex<LruCache<BlockTemplateCacheKey, TemplateCache>>>,
pub(crate) candidate_uncles: Arc<Mutex<CandidateUncles>>,
}
impl BlockAssembler {
pub fn new(config: BlockAssemblerConfig) -> Self {
Self {
config: Arc::new(config),
work_id: Arc::new(AtomicU64::new(0)),
last_uncles_updated_at: Arc::new(AtomicU64::new(0)),
template_caches: Arc::new(Mutex::new(LruCache::new(TEMPLATE_CACHE_SIZE))),
candidate_uncles: Arc::new(Mutex::new(CandidateUncles::new())),
}
}
pub(crate) fn transform_params(
consensus: &Consensus,
bytes_limit: Option<u64>,
proposals_limit: Option<u64>,
max_version: Option<Version>,
) -> (u64, u64, Version) {
let bytes_limit = bytes_limit
.min(Some(consensus.max_block_bytes()))
.unwrap_or_else(|| consensus.max_block_bytes());
let proposals_limit = proposals_limit
.min(Some(consensus.max_block_proposals_limit()))
.unwrap_or_else(|| consensus.max_block_proposals_limit());
let version = max_version
.min(Some(consensus.block_version()))
.unwrap_or_else(|| consensus.block_version());
(bytes_limit, proposals_limit, version)
}
pub(crate) fn transform_uncle(uncle: &UncleBlockView) -> UncleTemplate {
UncleTemplate {
hash: uncle.hash().unpack(),
required: false,
proposals: uncle
.data()
.proposals()
.into_iter()
.map(Into::into)
.collect(),
header: uncle.data().header().into(),
}
}
pub(crate) fn transform_cellbase(
tx: &TransactionView,
cycles: Option<Cycle>,
) -> CellbaseTemplate {
CellbaseTemplate {
hash: tx.hash().unpack(),
cycles: cycles.map(Into::into),
data: tx.data().into(),
}
}
pub(crate) fn transform_tx(
entry: &TxEntry,
required: bool,
depends: Option<Vec<u32>>,
) -> TransactionTemplate {
TransactionTemplate {
hash: entry.transaction().hash().unpack(),
required,
cycles: Some(entry.cycles.into()),
depends: depends.map(|deps| deps.into_iter().map(|x| u64::from(x).into()).collect()),
data: entry.transaction().data().into(),
}
}
pub(crate) fn calculate_txs_size_limit(
bytes_limit: u64,
cellbase: Transaction,
uncles: &[UncleBlockView],
proposals: &HashSet<ProposalShortId>,
extension_opt: Option<packed::Bytes>,
) -> Result<usize, AnyError> {
let empty_dao = packed::Byte32::default();
let raw_header = packed::RawHeader::new_builder().dao(empty_dao).build();
let header = packed::Header::new_builder().raw(raw_header).build();
let block = if let Some(extension) = extension_opt {
packed::BlockV1::new_builder()
.header(header)
.transactions(vec![cellbase].pack())
.uncles(uncles.iter().map(|u| u.data()).pack())
.proposals(
proposals
.iter()
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.pack(),
)
.extension(extension)
.build()
.as_v0()
} else {
packed::Block::new_builder()
.header(header)
.transactions(vec![cellbase].pack())
.uncles(uncles.iter().map(|u| u.data()).pack())
.proposals(
proposals
.iter()
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.pack(),
)
.build()
};
let serialized_size = block.serialized_size_without_uncle_proposals();
let bytes_limit = bytes_limit as usize;
bytes_limit.checked_sub(serialized_size).ok_or_else(|| {
BlockAssemblerError::InvalidParams(format!("bytes_limit {}", bytes_limit)).into()
})
}
pub(crate) fn build_cellbase(
snapshot: &Snapshot,
tip: &HeaderView,
cellbase_witness: CellbaseWitness,
) -> Result<TransactionView, AnyError> {
let candidate_number = tip.number() + 1;
let tx = {
let (target_lock, block_reward) = block_in_place(|| {
RewardCalculator::new(snapshot.consensus(), snapshot).block_reward_to_finalize(tip)
})?;
let input = CellInput::new_cellbase_input(candidate_number);
let output = CellOutput::new_builder()
.capacity(block_reward.total.pack())
.lock(target_lock)
.build();
let witness = cellbase_witness.as_bytes().pack();
let no_finalization_target =
candidate_number <= snapshot.consensus().finalization_delay_length();
let tx_builder = TransactionBuilder::default().input(input).witness(witness);
let insufficient_reward_to_create_cell = output.is_lack_of_capacity(Capacity::zero())?;
if no_finalization_target || insufficient_reward_to_create_cell {
tx_builder.build()
} else {
tx_builder
.output(output)
.output_data(Bytes::default().pack())
.build()
}
};
Ok(tx)
}
pub fn prepare_uncles(
snapshot: &Snapshot,
candidate_number: BlockNumber,
current_epoch_ext: &EpochExt,
candidate_uncles: &mut CandidateUncles,
) -> Vec<UncleBlockView> {
let epoch_number = current_epoch_ext.number();
let max_uncles_num = snapshot.consensus().max_uncles_num();
let mut uncles: Vec<UncleBlockView> = Vec::with_capacity(max_uncles_num);
let mut removed = Vec::new();
for uncle in candidate_uncles.values() {
if uncles.len() == max_uncles_num {
break;
}
let parent_hash = uncle.header().parent_hash();
if uncle.compact_target() != current_epoch_ext.compact_target()
|| uncle.epoch().number() != epoch_number
{
removed.push(uncle.clone());
} else if !snapshot.is_main_chain(&uncle.hash())
&& !snapshot.is_uncle(&uncle.hash())
&& uncle.number() < candidate_number
&& (uncles.iter().any(|u| u.hash() == parent_hash)
|| snapshot.is_main_chain(&parent_hash)
|| snapshot.is_uncle(&parent_hash))
{
uncles.push(uncle.clone());
}
}
for r in removed {
candidate_uncles.remove_by_number(&r);
}
uncles
}
}