Skip to main content

ckb_tx_pool/block_assembler/
mod.rs

1//! Generate a new block
2
3mod candidate_uncles;
4mod process;
5
6#[cfg(test)]
7mod tests;
8
9use crate::component::entry::TxEntry;
10use crate::error::BlockAssemblerError;
11pub use candidate_uncles::CandidateUncles;
12use ckb_app_config::BlockAssemblerConfig;
13use ckb_dao::DaoCalculator;
14use ckb_error::{AnyError, InternalErrorKind};
15use ckb_jsonrpc_types::{
16    BlockTemplate as JsonBlockTemplate, CellbaseTemplate, TransactionTemplate, UncleTemplate,
17};
18use ckb_logger::{debug, error, trace};
19use ckb_reward_calculator::RewardCalculator;
20use ckb_snapshot::Snapshot;
21use ckb_store::ChainStore;
22use ckb_systemtime::unix_time_as_millis;
23use ckb_types::{
24    bytes,
25    core::{
26        BlockNumber, Capacity, Cycle, EpochExt, EpochNumberWithFraction, ScriptHashType,
27        TransactionBuilder, TransactionView, UncleBlockView, Version,
28        cell::{OverlayCellChecker, TransactionsChecker},
29    },
30    packed::{
31        self, Byte32, Bytes, CellInput, CellOutput, CellbaseWitness, ProposalShortId, Script,
32        Transaction,
33    },
34    prelude::*,
35};
36use http_body_util::Full;
37use hyper::{Method, Request};
38use hyper_util::client::legacy::{Client, connect::HttpConnector};
39use std::collections::HashSet;
40use std::sync::{
41    Arc,
42    atomic::{AtomicU64, Ordering},
43};
44use std::time::Duration;
45use std::{cmp, iter};
46use tokio::process::Command;
47use tokio::sync::{Mutex, RwLock};
48use tokio::task::block_in_place;
49use tokio::time::timeout;
50
51use crate::TxPool;
52pub(crate) use process::process;
53
54#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
55pub(crate) struct TemplateSize {
56    pub(crate) txs: usize,
57    pub(crate) proposals: usize,
58    pub(crate) uncles: usize,
59    pub(crate) total: usize,
60}
61
62impl TemplateSize {
63    pub(crate) fn calc_total_by_proposals(&self, new_proposals_size: usize) -> usize {
64        if new_proposals_size > self.proposals {
65            self.total
66                .saturating_add(new_proposals_size - self.proposals)
67        } else {
68            self.total
69                .saturating_sub(self.proposals - new_proposals_size)
70        }
71    }
72
73    pub(crate) fn calc_total_by_uncles(&self, new_uncles_size: usize) -> usize {
74        if new_uncles_size > self.uncles {
75            self.total.saturating_add(new_uncles_size - self.uncles)
76        } else {
77            self.total.saturating_sub(self.uncles - new_uncles_size)
78        }
79    }
80
81    pub(crate) fn calc_total_by_txs(&self, new_txs_size: usize) -> usize {
82        if new_txs_size > self.txs {
83            self.total.saturating_add(new_txs_size - self.txs)
84        } else {
85            self.total.saturating_sub(self.txs - new_txs_size)
86        }
87    }
88}
89
90#[derive(Clone)]
91pub(crate) struct CurrentTemplate {
92    pub(crate) template: BlockTemplate,
93    pub(crate) size: TemplateSize,
94    pub(crate) snapshot: Arc<Snapshot>,
95    pub(crate) epoch: EpochExt,
96}
97
98/// Block generator
99#[derive(Clone)]
100pub struct BlockAssembler {
101    pub(crate) config: Arc<BlockAssemblerConfig>,
102    pub(crate) work_id: Arc<AtomicU64>,
103    pub(crate) candidate_uncles: Arc<Mutex<CandidateUncles>>,
104    pub(crate) current: Arc<Mutex<CurrentTemplate>>,
105    pub(crate) poster: Arc<Client<HttpConnector, Full<bytes::Bytes>>>,
106}
107
108impl BlockAssembler {
109    /// Construct new block generator
110    pub fn new(config: BlockAssemblerConfig, snapshot: Arc<Snapshot>) -> Self {
111        let consensus = snapshot.consensus();
112        let tip_header = snapshot.tip_header();
113        let current_epoch = consensus
114            .next_epoch_ext(tip_header, &snapshot.borrow_as_data_loader())
115            .expect("tip header's epoch should be stored")
116            .epoch();
117        let mut builder = BlockTemplateBuilder::new(&snapshot, &current_epoch);
118
119        let cellbase = Self::build_cellbase(&config, &snapshot)
120            .expect("build cellbase for BlockAssembler initial");
121
122        let extension =
123            Self::build_extension(&snapshot).expect("build extension for BlockAssembler initial");
124        let basic_block_size =
125            Self::basic_block_size(cellbase.data(), &[], iter::empty(), extension.clone());
126
127        let (dao, _checked_txs, _failed_txs) =
128            Self::calc_dao(&snapshot, &current_epoch, cellbase.clone(), vec![])
129                .expect("calc_dao for BlockAssembler initial");
130
131        let work_id = AtomicU64::new(0);
132
133        builder
134            .transactions(vec![])
135            .proposals(vec![])
136            .cellbase(cellbase)
137            .work_id(work_id.fetch_add(1, Ordering::SeqCst))
138            .current_time(cmp::max(unix_time_as_millis(), tip_header.timestamp() + 1))
139            .dao(dao);
140        if let Some(data) = extension {
141            builder.extension(data);
142        }
143        let template = builder.build();
144
145        let size = TemplateSize {
146            txs: 0,
147            proposals: 0,
148            uncles: 0,
149            total: basic_block_size,
150        };
151
152        let current = CurrentTemplate {
153            template,
154            size,
155            snapshot,
156            epoch: current_epoch,
157        };
158
159        Self {
160            config: Arc::new(config),
161            work_id: Arc::new(work_id),
162            candidate_uncles: Arc::new(Mutex::new(CandidateUncles::new())),
163            current: Arc::new(Mutex::new(current)),
164            poster: Arc::new(
165                Client::builder(hyper_util::rt::TokioExecutor::new())
166                    .build::<_, Full<bytes::Bytes>>(HttpConnector::new()),
167            ),
168        }
169    }
170
171    pub(crate) async fn update_full(&self, tx_pool: &RwLock<TxPool>) -> Result<(), AnyError> {
172        let mut current = self.current.lock().await;
173        let consensus = current.snapshot.consensus();
174        let max_block_bytes = consensus.max_block_bytes() as usize;
175
176        let current_template = &current.template;
177        let uncles = &current_template.uncles;
178
179        let (proposals, txs, basic_size) = {
180            let tx_pool_reader = tx_pool.read().await;
181            if current.snapshot.tip_hash() != tx_pool_reader.snapshot().tip_hash() {
182                return Ok(());
183            }
184            let proposals =
185                tx_pool_reader.package_proposals(consensus.max_block_proposals_limit(), uncles);
186
187            let basic_size = Self::basic_block_size(
188                current_template.cellbase.data(),
189                uncles,
190                proposals.iter(),
191                current_template.extension.clone(),
192            );
193
194            let txs_size_limit = max_block_bytes
195                .checked_sub(basic_size)
196                .ok_or(BlockAssemblerError::Overflow)?;
197
198            let max_block_cycles = consensus.max_block_cycles();
199            let (txs, _txs_size, _cycles) =
200                tx_pool_reader.package_txs(max_block_cycles, txs_size_limit);
201            (proposals, txs, basic_size)
202        };
203
204        let proposals_size = proposals.len() * ProposalShortId::serialized_size();
205        let (dao, checked_txs, failed_txs) = Self::calc_dao(
206            &current.snapshot,
207            &current.epoch,
208            current_template.cellbase.clone(),
209            txs,
210        )?;
211        if !failed_txs.is_empty() {
212            let mut tx_pool_writer = tx_pool.write().await;
213            for id in failed_txs {
214                tx_pool_writer.remove_tx(&id);
215            }
216        }
217
218        let txs_size = checked_txs.iter().map(|tx| tx.size).sum();
219        let total_size = basic_size + txs_size;
220
221        let mut builder = BlockTemplateBuilder::from_template(&current.template);
222        builder
223            .set_proposals(Vec::from_iter(proposals))
224            .set_transactions(checked_txs)
225            .work_id(self.work_id.fetch_add(1, Ordering::SeqCst))
226            .current_time(cmp::max(
227                unix_time_as_millis(),
228                current.template.current_time,
229            ))
230            .dao(dao);
231
232        current.template = builder.build();
233        current.size.txs = txs_size;
234        current.size.total = total_size;
235        current.size.proposals = proposals_size;
236
237        trace!(
238            "[BlockAssembler] update_full {} uncles-{} proposals-{} txs-{}",
239            current.template.number,
240            current.template.uncles.len(),
241            current.template.proposals.len(),
242            current.template.transactions.len(),
243        );
244
245        Ok(())
246    }
247
248    pub(crate) async fn update_blank(&self, snapshot: Arc<Snapshot>) -> Result<(), AnyError> {
249        let consensus = snapshot.consensus();
250        let tip_header = snapshot.tip_header();
251        let current_epoch = consensus
252            .next_epoch_ext(tip_header, &snapshot.borrow_as_data_loader())
253            .expect("tip header's epoch should be stored")
254            .epoch();
255        let mut builder = BlockTemplateBuilder::new(&snapshot, &current_epoch);
256
257        let cellbase = Self::build_cellbase(&self.config, &snapshot)?;
258        let uncles = self.prepare_uncles(&snapshot, &current_epoch).await;
259        let uncles_size = uncles.len() * UncleBlockView::serialized_size_in_block();
260
261        let extension = Self::build_extension(&snapshot)?;
262        let basic_block_size =
263            Self::basic_block_size(cellbase.data(), &uncles, iter::empty(), extension.clone());
264
265        let (dao, _checked_txs, _failed_txs) =
266            Self::calc_dao(&snapshot, &current_epoch, cellbase.clone(), vec![])?;
267
268        builder
269            .transactions(vec![])
270            .proposals(vec![])
271            .cellbase(cellbase)
272            .uncles(uncles)
273            .work_id(self.work_id.fetch_add(1, Ordering::SeqCst))
274            .current_time(cmp::max(unix_time_as_millis(), tip_header.timestamp() + 1))
275            .dao(dao);
276        if let Some(data) = extension {
277            builder.extension(data);
278        }
279        let template = builder.build();
280
281        trace!(
282            "[BlockAssembler] update_blank {} uncles-{} proposals-{} txs-{}",
283            template.number,
284            template.uncles.len(),
285            template.proposals.len(),
286            template.transactions.len(),
287        );
288
289        let size = TemplateSize {
290            txs: 0,
291            proposals: 0,
292            uncles: uncles_size,
293            total: basic_block_size,
294        };
295
296        let new_blank = CurrentTemplate {
297            template,
298            size,
299            snapshot,
300            epoch: current_epoch,
301        };
302
303        *self.current.lock().await = new_blank;
304        Ok(())
305    }
306
307    pub(crate) async fn update_uncles(&self) {
308        let mut current = self.current.lock().await;
309        let consensus = current.snapshot.consensus();
310        let max_block_bytes = consensus.max_block_bytes() as usize;
311        let max_uncles_num = consensus.max_uncles_num();
312        let current_uncles_num = current.template.uncles.len();
313        if current_uncles_num < max_uncles_num {
314            let remain_size = max_block_bytes.saturating_sub(current.size.total);
315
316            if remain_size > UncleBlockView::serialized_size_in_block() {
317                let uncles = self.prepare_uncles(&current.snapshot, &current.epoch).await;
318
319                let new_uncle_size = uncles.len() * UncleBlockView::serialized_size_in_block();
320                let new_total_size = current.size.calc_total_by_uncles(new_uncle_size);
321
322                if new_total_size < max_block_bytes {
323                    let mut builder = BlockTemplateBuilder::from_template(&current.template);
324                    builder
325                        .set_uncles(uncles)
326                        .work_id(self.work_id.fetch_add(1, Ordering::SeqCst))
327                        .current_time(cmp::max(
328                            unix_time_as_millis(),
329                            current.template.current_time,
330                        ));
331                    current.template = builder.build();
332                    current.size.uncles = new_uncle_size;
333                    current.size.total = new_total_size;
334
335                    trace!(
336                        "[BlockAssembler] update_uncles-{} epoch-{} uncles-{} proposals-{} txs-{}",
337                        current.template.number,
338                        current.template.epoch.number(),
339                        current.template.uncles.len(),
340                        current.template.proposals.len(),
341                        current.template.transactions.len(),
342                    );
343                }
344            }
345        }
346    }
347
348    pub(crate) async fn update_proposals(&self, tx_pool: &RwLock<TxPool>) {
349        let mut current = self.current.lock().await;
350        let consensus = current.snapshot.consensus();
351        let uncles = &current.template.uncles;
352        let proposals = {
353            let tx_pool_reader = tx_pool.read().await;
354            if current.snapshot.tip_hash() != tx_pool_reader.snapshot().tip_hash() {
355                return;
356            }
357            tx_pool_reader.package_proposals(consensus.max_block_proposals_limit(), uncles)
358        };
359
360        let new_proposals_size = proposals.len() * ProposalShortId::serialized_size();
361        let new_total_size = current.size.calc_total_by_proposals(new_proposals_size);
362        let max_block_bytes = consensus.max_block_bytes() as usize;
363        if new_total_size < max_block_bytes {
364            let mut builder = BlockTemplateBuilder::from_template(&current.template);
365            builder
366                .set_proposals(Vec::from_iter(proposals))
367                .work_id(self.work_id.fetch_add(1, Ordering::SeqCst))
368                .current_time(cmp::max(
369                    unix_time_as_millis(),
370                    current.template.current_time,
371                ));
372            current.template = builder.build();
373            current.size.proposals = new_proposals_size;
374            current.size.total = new_total_size;
375
376            trace!(
377                "[BlockAssembler] update_proposals-{} epoch-{} uncles-{} proposals-{} txs-{}",
378                current.template.number,
379                current.template.epoch.number(),
380                current.template.uncles.len(),
381                current.template.proposals.len(),
382                current.template.transactions.len(),
383            );
384        }
385    }
386
387    pub(crate) async fn update_transactions(
388        &self,
389        tx_pool: &RwLock<TxPool>,
390    ) -> Result<(), AnyError> {
391        let mut current = self.current.lock().await;
392        let consensus = current.snapshot.consensus();
393        let current_template = &current.template;
394        let max_block_bytes = consensus.max_block_bytes() as usize;
395        let extension = Self::build_extension(&current.snapshot)?;
396        let txs = {
397            let tx_pool_reader = tx_pool.read().await;
398            if current.snapshot.tip_hash() != tx_pool_reader.snapshot().tip_hash() {
399                return Ok(());
400            }
401
402            let basic_block_size = Self::basic_block_size(
403                current_template.cellbase.data(),
404                &current_template.uncles,
405                current_template.proposals.iter(),
406                extension.clone(),
407            );
408
409            let txs_size_limit = max_block_bytes.checked_sub(basic_block_size);
410
411            if txs_size_limit.is_none() {
412                return Ok(());
413            }
414
415            let max_block_cycles = consensus.max_block_cycles();
416            let (txs, _txs_size, _cycles) = tx_pool_reader
417                .package_txs(max_block_cycles, txs_size_limit.expect("overflow checked"));
418            txs
419        };
420
421        if let Ok((dao, checked_txs, _failed_txs)) = Self::calc_dao(
422            &current.snapshot,
423            &current.epoch,
424            current_template.cellbase.clone(),
425            txs,
426        ) {
427            let new_txs_size = checked_txs.iter().map(|tx| tx.size).sum();
428            let new_total_size = current.size.calc_total_by_txs(new_txs_size);
429            let mut builder = BlockTemplateBuilder::from_template(&current.template);
430            builder
431                .set_transactions(checked_txs)
432                .work_id(self.work_id.fetch_add(1, Ordering::SeqCst))
433                .current_time(cmp::max(
434                    unix_time_as_millis(),
435                    current.template.current_time,
436                ))
437                .dao(dao);
438            if let Some(data) = extension {
439                builder.extension(data);
440            }
441            current.template = builder.build();
442            current.size.txs = new_txs_size;
443            current.size.total = new_total_size;
444
445            trace!(
446                "[BlockAssembler] update_transactions-{} epoch-{} uncles-{} proposals-{} txs-{}",
447                current.template.number,
448                current.template.epoch.number(),
449                current.template.uncles.len(),
450                current.template.proposals.len(),
451                current.template.transactions.len(),
452            );
453        }
454        Ok(())
455    }
456
457    pub(crate) async fn get_current(&self) -> JsonBlockTemplate {
458        let current = self.current.lock().await;
459        (&current.template).into()
460    }
461
462    pub(crate) fn build_cellbase_witness(
463        config: &BlockAssemblerConfig,
464        snapshot: &Snapshot,
465    ) -> CellbaseWitness {
466        let hash_type: ScriptHashType = config.hash_type.into();
467        let cellbase_lock = Script::new_builder()
468            .args(config.args.as_bytes())
469            .code_hash(&config.code_hash)
470            .hash_type(hash_type)
471            .build();
472        let tip = snapshot.tip_header();
473
474        let mut message = vec![];
475        if let Some(version) = snapshot.compute_versionbits(tip) {
476            message.extend_from_slice(&version.to_le_bytes());
477            message.extend_from_slice(b" ");
478        }
479        if config.use_binary_version_as_message_prefix {
480            message.extend_from_slice(config.binary_version.as_bytes());
481        }
482        if !config.message.is_empty() {
483            message.extend_from_slice(b" ");
484            message.extend_from_slice(config.message.as_bytes());
485        }
486
487        CellbaseWitness::new_builder()
488            .lock(cellbase_lock)
489            .message(message)
490            .build()
491    }
492
493    /// Miner mined block H(c), the block reward will be finalized at H(c + w_far + 1).
494    /// Miner specify own lock in cellbase witness.
495    /// The cellbase have only one output,
496    /// miner should collect the block reward for finalize target H(max(0, c - w_far - 1))
497    pub(crate) fn build_cellbase(
498        config: &BlockAssemblerConfig,
499        snapshot: &Snapshot,
500    ) -> Result<TransactionView, AnyError> {
501        let tip = snapshot.tip_header();
502        let candidate_number = tip.number() + 1;
503        let cellbase_witness = Self::build_cellbase_witness(config, snapshot);
504
505        let tx = {
506            let (target_lock, block_reward) = block_in_place(|| {
507                RewardCalculator::new(snapshot.consensus(), snapshot).block_reward_to_finalize(tip)
508            })?;
509            let input = CellInput::new_cellbase_input(candidate_number);
510            let output = CellOutput::new_builder()
511                .capacity(block_reward.total)
512                .lock(target_lock)
513                .build();
514
515            let witness = cellbase_witness.as_bytes();
516            let no_finalization_target =
517                candidate_number <= snapshot.consensus().finalization_delay_length();
518            let tx_builder = TransactionBuilder::default().input(input).witness(witness);
519            let insufficient_reward_to_create_cell = output.is_lack_of_capacity(Capacity::zero())?;
520            if no_finalization_target || insufficient_reward_to_create_cell {
521                tx_builder.build()
522            } else {
523                tx_builder
524                    .output(output)
525                    .output_data(Bytes::default())
526                    .build()
527            }
528        };
529
530        Ok(tx)
531    }
532
533    pub(crate) fn build_extension(snapshot: &Snapshot) -> Result<Option<packed::Bytes>, AnyError> {
534        let tip_header = snapshot.tip_header();
535        // The use of the epoch number of the tip here leads to an off-by-one bug,
536        // so be careful, it needs to be preserved for consistency reasons and not fixed directly.
537        let mmr_activate = snapshot
538            .consensus()
539            .rfc0044_active(tip_header.epoch().number());
540        if mmr_activate {
541            let chain_root = snapshot
542                .chain_root_mmr(tip_header.number())
543                .get_root()
544                .map_err(|e| InternalErrorKind::MMR.other(e))?;
545            let bytes = chain_root.calc_mmr_hash().as_bytes().into();
546            Ok(Some(bytes))
547        } else {
548            Ok(None)
549        }
550    }
551
552    pub(crate) async fn prepare_uncles(
553        &self,
554        snapshot: &Snapshot,
555        current_epoch: &EpochExt,
556    ) -> Vec<UncleBlockView> {
557        let mut guard = self.candidate_uncles.lock().await;
558        guard.prepare_uncles(snapshot, current_epoch)
559    }
560
561    pub(crate) fn basic_block_size<'a>(
562        cellbase: Transaction,
563        uncles: &[UncleBlockView],
564        proposals: impl Iterator<Item = &'a ProposalShortId>,
565        extension_opt: Option<packed::Bytes>,
566    ) -> usize {
567        let empty_dao = packed::Byte32::default();
568        let raw_header = packed::RawHeader::new_builder().dao(empty_dao).build();
569        let header = packed::Header::new_builder().raw(raw_header).build();
570        let block = if let Some(extension) = extension_opt {
571            packed::BlockV1::new_builder()
572                .header(header)
573                .transactions(vec![cellbase])
574                .uncles(uncles.iter().map(|u| u.data()).collect::<Vec<_>>())
575                .proposals(proposals.cloned().collect::<Vec<_>>())
576                .extension(extension)
577                .build()
578                .as_v0()
579        } else {
580            packed::Block::new_builder()
581                .header(header)
582                .transactions(vec![cellbase])
583                .uncles(uncles.iter().map(|u| u.data()).collect::<Vec<_>>())
584                .proposals(proposals.cloned().collect::<Vec<_>>())
585                .build()
586        };
587        block.serialized_size_without_uncle_proposals()
588    }
589
590    fn calc_dao(
591        snapshot: &Snapshot,
592        current_epoch: &EpochExt,
593        cellbase: TransactionView,
594        entries: Vec<TxEntry>,
595    ) -> Result<(Byte32, Vec<TxEntry>, Vec<ProposalShortId>), AnyError> {
596        let tip_header = snapshot.tip_header();
597        let consensus = snapshot.consensus();
598        let mut seen_inputs = HashSet::new();
599        let mut transactions_checker = TransactionsChecker::new(iter::once(&cellbase));
600
601        let mut checked_failed_txs = vec![];
602        let checked_entries: Vec<_> = block_in_place(|| {
603            entries
604                .into_iter()
605                .filter_map(|entry| {
606                    let overlay_cell_checker =
607                        OverlayCellChecker::new(&transactions_checker, snapshot);
608                    if let Err(err) =
609                        entry
610                            .rtx
611                            .check(&mut seen_inputs, &overlay_cell_checker, snapshot)
612                    {
613                        error!(
614                            "Resolving transactions while building block template, \
615                             tip_number: {}, tip_hash: {}, tx_hash: {}, error: {:?}",
616                            tip_header.number(),
617                            tip_header.hash(),
618                            entry.transaction().hash(),
619                            err
620                        );
621                        checked_failed_txs.push(entry.proposal_short_id());
622                        None
623                    } else {
624                        transactions_checker.insert(entry.transaction());
625                        Some(entry)
626                    }
627                })
628                .collect()
629        });
630
631        let dummy_cellbase_entry = TxEntry::dummy_resolve(cellbase, 0, Capacity::zero(), 0);
632        let entries_iter = iter::once(&dummy_cellbase_entry)
633            .chain(checked_entries.iter())
634            .map(|entry| entry.rtx.as_ref());
635
636        // Generate DAO fields here
637        let dao = DaoCalculator::new(consensus, &snapshot.borrow_as_data_loader())
638            .dao_field_with_current_epoch(entries_iter, tip_header, current_epoch)?;
639
640        Ok((dao, checked_entries, checked_failed_txs))
641    }
642
643    pub(crate) async fn notify(&self) {
644        if !self.need_to_notify() {
645            return;
646        }
647        let template = self.get_current().await;
648        if let Ok(template_json) = serde_json::to_string(&template) {
649            let notify_timeout = Duration::from_millis(self.config.notify_timeout_millis);
650            for url in &self.config.notify {
651                if let Ok(req) = Request::builder()
652                    .method(Method::POST)
653                    .uri(url.as_ref())
654                    .header("content-type", "application/json")
655                    .body(Full::new(template_json.to_owned().into()))
656                {
657                    let client = Arc::clone(&self.poster);
658                    let url = url.to_owned();
659                    tokio::spawn(async move {
660                        let _resp =
661                            timeout(notify_timeout, client.request(req))
662                                .await
663                                .map_err(|_| {
664                                    ckb_logger::warn!(
665                                        "block assembler notifying {} timed out",
666                                        url
667                                    );
668                                });
669                    });
670                }
671            }
672
673            for script in &self.config.notify_scripts {
674                let script = script.to_owned();
675                let template_json = template_json.to_owned();
676                tokio::spawn(async move {
677                    // Errors
678                    // This future will return an error if the child process cannot be spawned
679                    // or if there is an error while awaiting its status.
680
681                    // On Unix platforms this method will fail with std::io::ErrorKind::WouldBlock
682                    // if the system process limit is reached
683                    // (which includes other applications running on the system).
684                    match timeout(
685                        notify_timeout,
686                        Command::new(&script).arg(template_json).status(),
687                    )
688                    .await
689                    {
690                        Ok(ret) => match ret {
691                            Ok(status) => debug!("the command exited with: {}", status),
692                            Err(e) => error!("the script {} failed to spawn {}", script, e),
693                        },
694                        Err(_) => {
695                            ckb_logger::warn!("block assembler notifying {} timed out", script)
696                        }
697                    }
698                });
699            }
700        }
701    }
702
703    fn need_to_notify(&self) -> bool {
704        !self.config.notify.is_empty() || !self.config.notify_scripts.is_empty()
705    }
706}
707
708#[derive(Clone)]
709pub(crate) struct BlockTemplate {
710    pub(crate) version: Version,
711    pub(crate) compact_target: u32,
712    pub(crate) number: BlockNumber,
713    pub(crate) epoch: EpochNumberWithFraction,
714    pub(crate) parent_hash: Byte32,
715    pub(crate) cycles_limit: Cycle,
716    pub(crate) bytes_limit: u64,
717    pub(crate) uncles_count_limit: u8,
718
719    // option
720    pub(crate) uncles: Vec<UncleBlockView>,
721    pub(crate) transactions: Vec<TxEntry>,
722    pub(crate) proposals: Vec<ProposalShortId>,
723    pub(crate) cellbase: TransactionView,
724    pub(crate) work_id: u64,
725    pub(crate) dao: Byte32,
726    pub(crate) current_time: u64,
727    pub(crate) extension: Option<Bytes>,
728}
729
730impl<'a> From<&'a BlockTemplate> for JsonBlockTemplate {
731    fn from(template: &'a BlockTemplate) -> JsonBlockTemplate {
732        JsonBlockTemplate {
733            version: template.version.into(),
734            compact_target: template.compact_target.into(),
735            number: template.number.into(),
736            epoch: template.epoch.into(),
737            parent_hash: (&template.parent_hash).into(),
738            cycles_limit: template.cycles_limit.into(),
739            bytes_limit: template.bytes_limit.into(),
740            uncles_count_limit: u64::from(template.uncles_count_limit).into(),
741            uncles: template.uncles.iter().map(uncle_to_template).collect(),
742            transactions: template
743                .transactions
744                .iter()
745                .map(tx_entry_to_template)
746                .collect(),
747            proposals: template.proposals.iter().map(Into::into).collect(),
748            cellbase: cellbase_to_template(&template.cellbase),
749            work_id: template.work_id.into(),
750            dao: template.dao.clone().into(),
751            current_time: template.current_time.into(),
752            extension: template.extension.as_ref().map(Into::into),
753        }
754    }
755}
756
757#[derive(Clone)]
758pub(crate) struct BlockTemplateBuilder {
759    pub(crate) version: Version,
760    pub(crate) compact_target: u32,
761    pub(crate) number: BlockNumber,
762    pub(crate) epoch: EpochNumberWithFraction,
763    pub(crate) parent_hash: Byte32,
764    pub(crate) cycles_limit: Cycle,
765    pub(crate) bytes_limit: u64,
766    pub(crate) uncles_count_limit: u8,
767
768    // option
769    pub(crate) uncles: Vec<UncleBlockView>,
770    pub(crate) transactions: Vec<TxEntry>,
771    pub(crate) proposals: Vec<ProposalShortId>,
772    pub(crate) cellbase: Option<TransactionView>,
773    pub(crate) work_id: Option<u64>,
774    pub(crate) dao: Option<Byte32>,
775    pub(crate) current_time: Option<u64>,
776    pub(crate) extension: Option<Bytes>,
777}
778
779impl BlockTemplateBuilder {
780    pub(crate) fn new(snapshot: &Snapshot, current_epoch: &EpochExt) -> Self {
781        let consensus = snapshot.consensus();
782        let tip_header = snapshot.tip_header();
783        let tip_hash = tip_header.hash();
784        let candidate_number = tip_header.number() + 1;
785
786        let version = consensus.block_version();
787        let max_block_bytes = consensus.max_block_bytes();
788        let cycles_limit = consensus.max_block_cycles();
789        let uncles_count_limit = consensus.max_uncles_num() as u8;
790
791        Self {
792            version,
793            compact_target: current_epoch.compact_target(),
794
795            number: candidate_number,
796            epoch: current_epoch.number_with_fraction(candidate_number),
797            parent_hash: tip_hash,
798            cycles_limit,
799            bytes_limit: max_block_bytes,
800            uncles_count_limit,
801            // option
802            uncles: vec![],
803            transactions: vec![],
804            proposals: vec![],
805            cellbase: None,
806            work_id: None,
807            dao: None,
808            current_time: None,
809            extension: None,
810        }
811    }
812
813    pub(crate) fn from_template(template: &BlockTemplate) -> Self {
814        Self {
815            version: template.version,
816            compact_target: template.compact_target,
817            number: template.number,
818            epoch: template.epoch,
819            parent_hash: template.parent_hash.clone(),
820            cycles_limit: template.cycles_limit,
821            bytes_limit: template.bytes_limit,
822            uncles_count_limit: template.uncles_count_limit,
823            extension: template.extension.clone(),
824            // option
825            uncles: template.uncles.clone(),
826            transactions: template.transactions.clone(),
827            proposals: template.proposals.clone(),
828            cellbase: Some(template.cellbase.clone()),
829            work_id: None,
830            dao: Some(template.dao.clone()),
831            current_time: None,
832        }
833    }
834
835    pub(crate) fn uncles(&mut self, uncles: impl IntoIterator<Item = UncleBlockView>) -> &mut Self {
836        self.uncles.extend(uncles);
837        self
838    }
839
840    pub(crate) fn set_uncles(&mut self, uncles: Vec<UncleBlockView>) -> &mut Self {
841        self.uncles = uncles;
842        self
843    }
844
845    pub(crate) fn transactions(
846        &mut self,
847        transactions: impl IntoIterator<Item = TxEntry>,
848    ) -> &mut Self {
849        self.transactions.extend(transactions);
850        self
851    }
852
853    pub(crate) fn set_transactions(&mut self, transactions: Vec<TxEntry>) -> &mut Self {
854        self.transactions = transactions;
855        self
856    }
857
858    pub(crate) fn proposals(
859        &mut self,
860        proposals: impl IntoIterator<Item = ProposalShortId>,
861    ) -> &mut Self {
862        self.proposals.extend(proposals);
863        self
864    }
865
866    pub(crate) fn set_proposals(&mut self, proposals: Vec<ProposalShortId>) -> &mut Self {
867        self.proposals = proposals;
868        self
869    }
870
871    pub(crate) fn cellbase(&mut self, cellbase: TransactionView) -> &mut Self {
872        self.cellbase = Some(cellbase);
873        self
874    }
875
876    pub(crate) fn work_id(&mut self, work_id: u64) -> &mut Self {
877        self.work_id = Some(work_id);
878        self
879    }
880
881    pub(crate) fn dao(&mut self, dao: Byte32) -> &mut Self {
882        self.dao = Some(dao);
883        self
884    }
885
886    pub(crate) fn current_time(&mut self, current_time: u64) -> &mut Self {
887        self.current_time = Some(current_time);
888        self
889    }
890
891    #[allow(dead_code)]
892    pub(crate) fn extension(&mut self, extension: Bytes) -> &mut Self {
893        self.extension = Some(extension);
894        self
895    }
896
897    pub(crate) fn build(self) -> BlockTemplate {
898        assert!(self.cellbase.is_some(), "cellbase must be set");
899        assert!(self.work_id.is_some(), "work_id must be set");
900        assert!(self.current_time.is_some(), "current_time must be set");
901        assert!(self.dao.is_some(), "dao must be set");
902
903        BlockTemplate {
904            version: self.version,
905            compact_target: self.compact_target,
906
907            number: self.number,
908            epoch: self.epoch,
909            parent_hash: self.parent_hash,
910            cycles_limit: self.cycles_limit,
911            bytes_limit: self.bytes_limit,
912            uncles_count_limit: self.uncles_count_limit,
913            uncles: self.uncles,
914            transactions: self.transactions,
915            proposals: self.proposals,
916            cellbase: self.cellbase.expect("cellbase assert checked"),
917            work_id: self.work_id.expect("work_id assert checked"),
918            dao: self.dao.expect("dao assert checked"),
919            current_time: self.current_time.expect("current_time assert checked"),
920            extension: self.extension,
921        }
922    }
923}
924
925pub(crate) fn uncle_to_template(uncle: &UncleBlockView) -> UncleTemplate {
926    UncleTemplate {
927        hash: uncle.hash().into(),
928        required: false,
929        proposals: uncle
930            .data()
931            .proposals()
932            .into_iter()
933            .map(Into::into)
934            .collect(),
935        header: uncle.data().header().into(),
936    }
937}
938
939pub(crate) fn tx_entry_to_template(entry: &TxEntry) -> TransactionTemplate {
940    TransactionTemplate {
941        hash: entry.transaction().hash().into(),
942        required: false, // unimplemented
943        cycles: Some(entry.cycles.into()),
944        depends: None, // unimplemented
945        data: entry.transaction().data().into(),
946    }
947}
948
949pub(crate) fn cellbase_to_template(tx: &TransactionView) -> CellbaseTemplate {
950    CellbaseTemplate {
951        hash: tx.hash().into(),
952        cycles: None,
953        data: tx.data().into(),
954    }
955}