1mod 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#[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 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, ¤t_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, ¤t_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 = ¤t.template;
177 let uncles = ¤t_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 ¤t.snapshot,
207 ¤t.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(¤t.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, ¤t_epoch);
256
257 let cellbase = Self::build_cellbase(&self.config, &snapshot)?;
258 let uncles = self.prepare_uncles(&snapshot, ¤t_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, ¤t_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(¤t.snapshot, ¤t.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(¤t.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 = ¤t.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(¤t.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 = ¤t.template;
394 let max_block_bytes = consensus.max_block_bytes() as usize;
395 let extension = Self::build_extension(¤t.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 ¤t_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 ¤t.snapshot,
423 ¤t.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(¤t.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 (¤t.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.clone().into();
467 let cellbase_lock = Script::new_builder()
468 .args(config.args.as_bytes().pack())
469 .code_hash(config.code_hash.pack())
470 .hash_type(hash_type.into())
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.pack())
490 .build()
491 }
492
493 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.pack())
512 .lock(target_lock)
513 .build();
514
515 let witness = cellbase_witness.as_bytes().pack();
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 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().pack();
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].pack())
574 .uncles(uncles.iter().map(|u| u.data()).pack())
575 .proposals(proposals.cloned().collect::<Vec<_>>().pack())
576 .extension(extension)
577 .build()
578 .as_v0()
579 } else {
580 packed::Block::new_builder()
581 .header(header)
582 .transactions(vec![cellbase].pack())
583 .uncles(uncles.iter().map(|u| u.data()).pack())
584 .proposals(proposals.cloned().collect::<Vec<_>>().pack())
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 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 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 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.unpack(),
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 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 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 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().unpack(),
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().unpack(),
942 required: false, cycles: Some(entry.cycles.into()),
944 depends: None, data: entry.transaction().data().into(),
946 }
947}
948
949pub(crate) fn cellbase_to_template(tx: &TransactionView) -> CellbaseTemplate {
950 CellbaseTemplate {
951 hash: tx.hash().unpack(),
952 cycles: None,
953 data: tx.data().into(),
954 }
955}