1pub mod constants;
4pub mod parameters;
5pub mod proposal;
6pub mod zip317;
7
8#[cfg(test)]
9mod tests;
10
11use std::{collections::HashMap, fmt, iter, sync::Arc};
12
13use derive_getters::Getters;
14use derive_new::new;
15use jsonrpsee::core::RpcResult;
16use jsonrpsee_types::{ErrorCode, ErrorObject};
17use tokio::sync::watch::{self, error::SendError};
18use tower::{Service, ServiceExt};
19use zcash_keys::address::Address;
20use zcash_protocol::PoolType;
21
22use zebra_chain::{
23 amount::{self, Amount, NegativeOrZero, NonNegative},
24 block::{
25 self, Block, ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Height,
26 MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION,
27 },
28 chain_sync_status::ChainSyncStatus,
29 chain_tip::ChainTip,
30 parameters::{
31 subsidy::{block_subsidy, funding_stream_values, miner_subsidy, FundingStreamReceiver},
32 Network, NetworkUpgrade,
33 },
34 serialization::{DateTime32, ZcashDeserializeInto},
35 transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
36 transparent::{
37 self, EXTRA_ZEBRA_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN,
38 },
39 work::difficulty::{CompactDifficulty, ExpandedDifficulty},
40};
41use zebra_consensus::{funding_stream_address, MAX_BLOCK_SIGOPS};
42use zebra_node_services::mempool::{self, TransactionDependencies};
43use zebra_state::GetBlockTemplateChainInfo;
44
45use crate::{
46 config,
47 methods::types::{
48 default_roots::DefaultRoots, long_poll::LongPollId, submit_block,
49 transaction::TransactionTemplate,
50 },
51 server::error::OkOrError,
52};
53
54use constants::{
55 CAPABILITIES_FIELD, MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, MUTABLE_FIELD,
56 NONCE_RANGE_FIELD, NOT_SYNCED_ERROR_CODE,
57};
58pub use parameters::{
59 GetBlockTemplateCapability, GetBlockTemplateParameters, GetBlockTemplateRequestMode,
60};
61pub use proposal::{BlockProposalResponse, BlockTemplateTimeSource};
62
63#[cfg(test)]
68type InBlockTxDependenciesDepth = usize;
69
70#[allow(clippy::too_many_arguments)]
75#[derive(Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
76pub struct BlockTemplateResponse {
77 pub(crate) capabilities: Vec<String>,
86
87 pub(crate) version: u32,
90
91 #[serde(rename = "previousblockhash")]
93 #[serde(with = "hex")]
94 #[getter(copy)]
95 pub(crate) previous_block_hash: block::Hash,
96
97 #[serde(rename = "blockcommitmentshash")]
101 #[serde(with = "hex")]
102 #[getter(copy)]
103 pub(crate) block_commitments_hash: ChainHistoryBlockTxAuthCommitmentHash,
104
105 #[serde(rename = "lightclientroothash")]
109 #[serde(with = "hex")]
110 #[getter(copy)]
111 pub(crate) light_client_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
112
113 #[serde(rename = "finalsaplingroothash")]
117 #[serde(with = "hex")]
118 #[getter(copy)]
119 pub(crate) final_sapling_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
120
121 #[serde(rename = "defaultroots")]
126 pub(crate) default_roots: DefaultRoots,
127
128 pub(crate) transactions: Vec<TransactionTemplate<amount::NonNegative>>,
130
131 #[serde(rename = "coinbasetxn")]
133 pub(crate) coinbase_txn: TransactionTemplate<amount::NegativeOrZero>,
134
135 #[serde(rename = "longpollid")]
137 #[getter(copy)]
138 pub(crate) long_poll_id: LongPollId,
139
140 #[serde(with = "hex")]
142 #[getter(copy)]
143 pub(crate) target: ExpandedDifficulty,
144
145 #[serde(rename = "mintime")]
150 #[getter(copy)]
151 pub(crate) min_time: DateTime32,
152
153 pub(crate) mutable: Vec<String>,
155
156 #[serde(rename = "noncerange")]
158 pub(crate) nonce_range: String,
159
160 #[serde(rename = "sigoplimit")]
162 pub(crate) sigop_limit: u64,
163
164 #[serde(rename = "sizelimit")]
166 pub(crate) size_limit: u64,
167
168 #[serde(rename = "curtime")]
173 #[getter(copy)]
174 pub(crate) cur_time: DateTime32,
175
176 #[serde(with = "hex")]
178 #[getter(copy)]
179 pub(crate) bits: CompactDifficulty,
180
181 pub(crate) height: u32,
184
185 #[serde(rename = "maxtime")]
199 #[getter(copy)]
200 pub(crate) max_time: DateTime32,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
214 #[serde(default)]
215 #[serde(rename = "submitold")]
216 #[getter(copy)]
217 pub(crate) submit_old: Option<bool>,
218}
219
220impl fmt::Debug for BlockTemplateResponse {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 let mut transactions_truncated = self.transactions.clone();
224 if self.transactions.len() > 4 {
225 let end = self.transactions.len() - 2;
227 transactions_truncated.splice(3..=end, Vec::new());
228 }
229
230 f.debug_struct("GetBlockTemplate")
231 .field("capabilities", &self.capabilities)
232 .field("version", &self.version)
233 .field("previous_block_hash", &self.previous_block_hash)
234 .field("block_commitments_hash", &self.block_commitments_hash)
235 .field("light_client_root_hash", &self.light_client_root_hash)
236 .field("final_sapling_root_hash", &self.final_sapling_root_hash)
237 .field("default_roots", &self.default_roots)
238 .field("transaction_count", &self.transactions.len())
239 .field("transactions", &transactions_truncated)
240 .field("coinbase_txn", &self.coinbase_txn)
241 .field("long_poll_id", &self.long_poll_id)
242 .field("target", &self.target)
243 .field("min_time", &self.min_time)
244 .field("mutable", &self.mutable)
245 .field("nonce_range", &self.nonce_range)
246 .field("sigop_limit", &self.sigop_limit)
247 .field("size_limit", &self.size_limit)
248 .field("cur_time", &self.cur_time)
249 .field("bits", &self.bits)
250 .field("height", &self.height)
251 .field("max_time", &self.max_time)
252 .field("submit_old", &self.submit_old)
253 .finish()
254 }
255}
256
257impl BlockTemplateResponse {
258 pub fn all_capabilities() -> Vec<String> {
260 CAPABILITIES_FIELD.iter().map(ToString::to_string).collect()
261 }
262
263 #[allow(clippy::too_many_arguments)]
267 pub(crate) fn new_internal(
268 network: &Network,
269 miner_address: &Address,
270 chain_tip_and_local_time: &GetBlockTemplateChainInfo,
271 long_poll_id: LongPollId,
272 #[cfg(not(test))] mempool_txs: Vec<VerifiedUnminedTx>,
273 #[cfg(test)] mempool_txs: Vec<(InBlockTxDependenciesDepth, VerifiedUnminedTx)>,
274 submit_old: Option<bool>,
275 extra_coinbase_data: Vec<u8>,
276 ) -> Self {
277 let next_block_height =
279 (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX");
280
281 #[cfg(not(test))]
283 let (mempool_tx_templates, mempool_txs): (Vec<_>, Vec<_>) =
284 mempool_txs.into_iter().map(|tx| ((&tx).into(), tx)).unzip();
285
286 #[cfg(test)]
294 let (mempool_tx_templates, mempool_txs): (Vec<_>, Vec<_>) = {
295 let mut mempool_txs_with_templates: Vec<(
296 InBlockTxDependenciesDepth,
297 TransactionTemplate<amount::NonNegative>,
298 VerifiedUnminedTx,
299 )> = mempool_txs
300 .into_iter()
301 .map(|(min_tx_index, tx)| (min_tx_index, (&tx).into(), tx))
302 .collect();
303
304 mempool_txs_with_templates.sort_by_key(|(min_tx_index, tx_template, _tx)| {
308 (*min_tx_index, tx_template.hash.bytes_in_display_order())
309 });
310
311 mempool_txs_with_templates
312 .into_iter()
313 .map(|(_, template, tx)| (template, tx))
314 .unzip()
315 };
316
317 let (coinbase_txn, default_roots) = generate_coinbase_and_roots(
321 network,
322 next_block_height,
323 miner_address,
324 &mempool_txs,
325 chain_tip_and_local_time.chain_history_root,
326 extra_coinbase_data,
327 )
328 .expect("coinbase should be valid under the given parameters");
329
330 let target = chain_tip_and_local_time
332 .expected_difficulty
333 .to_expanded()
334 .expect("state always returns a valid difficulty value");
335
336 let capabilities: Vec<String> = Self::all_capabilities();
338 let mutable: Vec<String> = MUTABLE_FIELD.iter().map(ToString::to_string).collect();
339
340 tracing::debug!(
341 selected_txs = ?mempool_txs
342 .iter()
343 .map(|tx| (tx.transaction.id.mined_id(), tx.unpaid_actions))
344 .collect::<Vec<_>>(),
345 "creating template ... "
346 );
347
348 BlockTemplateResponse {
349 capabilities,
350
351 version: ZCASH_BLOCK_VERSION,
352
353 previous_block_hash: chain_tip_and_local_time.tip_hash,
354 block_commitments_hash: default_roots.block_commitments_hash,
355 light_client_root_hash: default_roots.block_commitments_hash,
356 final_sapling_root_hash: default_roots.block_commitments_hash,
357 default_roots,
358
359 transactions: mempool_tx_templates,
360
361 coinbase_txn,
362
363 long_poll_id,
364
365 target,
366
367 min_time: chain_tip_and_local_time.min_time,
368
369 mutable,
370
371 nonce_range: NONCE_RANGE_FIELD.to_string(),
372
373 sigop_limit: MAX_BLOCK_SIGOPS,
374
375 size_limit: MAX_BLOCK_BYTES,
376
377 cur_time: chain_tip_and_local_time.cur_time,
378
379 bits: chain_tip_and_local_time.expected_difficulty,
380
381 height: next_block_height.0,
382
383 max_time: chain_tip_and_local_time.max_time,
384
385 submit_old,
386 }
387 }
388}
389
390#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
391#[serde(untagged)]
392pub enum GetBlockTemplateResponse {
394 TemplateMode(Box<BlockTemplateResponse>),
396
397 ProposalMode(BlockProposalResponse),
399}
400
401impl GetBlockTemplateResponse {
402 pub fn try_into_template(self) -> Option<BlockTemplateResponse> {
404 match self {
405 Self::TemplateMode(template) => Some(*template),
406 Self::ProposalMode(_) => None,
407 }
408 }
409
410 pub fn try_into_proposal(self) -> Option<BlockProposalResponse> {
412 match self {
413 Self::TemplateMode(_) => None,
414 Self::ProposalMode(proposal) => Some(proposal),
415 }
416 }
417}
418
419#[derive(Clone)]
421pub struct GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
422where
423 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
424 + Clone
425 + Send
426 + Sync
427 + 'static,
428 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
429 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
430{
431 miner_address: Option<Address>,
433
434 extra_coinbase_data: Vec<u8>,
437
438 block_verifier_router: BlockVerifierRouter,
440
441 sync_status: SyncStatus,
443
444 mined_block_sender: watch::Sender<(block::Hash, block::Height)>,
447}
448
449impl<BlockVerifierRouter, SyncStatus> GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
450where
451 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
452 + Clone
453 + Send
454 + Sync
455 + 'static,
456 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
457 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
458{
459 pub fn new(
465 net: &Network,
466 conf: config::mining::Config,
467 block_verifier_router: BlockVerifierRouter,
468 sync_status: SyncStatus,
469 mined_block_sender: Option<watch::Sender<(block::Hash, block::Height)>>,
470 ) -> Self {
471 let miner_address = conf.miner_address.map(|zaddr| {
473 if zaddr.can_receive_as(PoolType::Transparent) {
474 Address::try_from_zcash_address(net, zaddr)
475 .expect("miner_address must be a valid Zcash address")
476 } else {
477 panic!("miner_address can't receive transparent funds")
479 }
480 });
481
482 const EXTRA_COINBASE_DATA_LIMIT: usize =
485 MAX_COINBASE_DATA_LEN - MAX_COINBASE_HEIGHT_DATA_LEN;
486
487 let extra_coinbase_data = conf
489 .extra_coinbase_data
490 .unwrap_or_else(|| EXTRA_ZEBRA_COINBASE_DATA.to_string());
491 let extra_coinbase_data = hex::decode(&extra_coinbase_data)
492 .unwrap_or_else(|_error| extra_coinbase_data.as_bytes().to_vec());
493
494 assert!(
495 extra_coinbase_data.len() <= EXTRA_COINBASE_DATA_LIMIT,
496 "extra coinbase data is {} bytes, but Zebra's limit is {}.\n\
497 Configure mining.extra_coinbase_data with a shorter string",
498 extra_coinbase_data.len(),
499 EXTRA_COINBASE_DATA_LIMIT,
500 );
501
502 Self {
503 miner_address,
504 extra_coinbase_data,
505 block_verifier_router,
506 sync_status,
507 mined_block_sender: mined_block_sender
508 .unwrap_or(submit_block::SubmitBlockChannel::default().sender()),
509 }
510 }
511
512 pub fn miner_address(&self) -> Option<Address> {
514 self.miner_address.clone()
515 }
516
517 pub fn extra_coinbase_data(&self) -> Vec<u8> {
519 self.extra_coinbase_data.clone()
520 }
521
522 pub fn sync_status(&self) -> SyncStatus {
524 self.sync_status.clone()
525 }
526
527 pub fn block_verifier_router(&self) -> BlockVerifierRouter {
529 self.block_verifier_router.clone()
530 }
531
532 pub fn advertise_mined_block(
534 &self,
535 block: block::Hash,
536 height: block::Height,
537 ) -> Result<(), SendError<(block::Hash, block::Height)>> {
538 self.mined_block_sender.send((block, height))
539 }
540}
541
542impl<BlockVerifierRouter, SyncStatus> fmt::Debug
543 for GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
544where
545 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
546 + Clone
547 + Send
548 + Sync
549 + 'static,
550 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
551 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
552{
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554 f.debug_struct("GetBlockTemplateRpcImpl")
556 .field("miner_address", &self.miner_address)
557 .field("extra_coinbase_data", &self.extra_coinbase_data)
558 .finish()
559 }
560}
561
562pub fn check_parameters(parameters: &Option<GetBlockTemplateParameters>) -> RpcResult<()> {
568 let Some(parameters) = parameters else {
569 return Ok(());
570 };
571
572 match parameters {
573 GetBlockTemplateParameters {
574 mode: GetBlockTemplateRequestMode::Template,
575 data: None,
576 ..
577 }
578 | GetBlockTemplateParameters {
579 mode: GetBlockTemplateRequestMode::Proposal,
580 data: Some(_),
581 ..
582 } => Ok(()),
583
584 GetBlockTemplateParameters {
585 mode: GetBlockTemplateRequestMode::Proposal,
586 data: None,
587 ..
588 } => Err(ErrorObject::borrowed(
589 ErrorCode::InvalidParams.code(),
590 "\"data\" parameter must be \
591 provided in \"proposal\" mode",
592 None,
593 )),
594
595 GetBlockTemplateParameters {
596 mode: GetBlockTemplateRequestMode::Template,
597 data: Some(_),
598 ..
599 } => Err(ErrorObject::borrowed(
600 ErrorCode::InvalidParams.code(),
601 "\"data\" parameter must be \
602 omitted in \"template\" mode",
603 None,
604 )),
605 }
606}
607
608pub async fn validate_block_proposal<BlockVerifierRouter, Tip, SyncStatus>(
613 mut block_verifier_router: BlockVerifierRouter,
614 block_proposal_bytes: Vec<u8>,
615 network: Network,
616 latest_chain_tip: Tip,
617 sync_status: SyncStatus,
618) -> RpcResult<GetBlockTemplateResponse>
619where
620 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
621 + Clone
622 + Send
623 + Sync
624 + 'static,
625 Tip: ChainTip + Clone + Send + Sync + 'static,
626 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
627{
628 check_synced_to_tip(&network, latest_chain_tip, sync_status)?;
629
630 let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
631 Ok(block) => block,
632 Err(parse_error) => {
633 tracing::info!(
634 ?parse_error,
635 "error response from block parser in CheckProposal request"
636 );
637
638 return Ok(BlockProposalResponse::rejected(
639 "invalid proposal format",
640 parse_error.into(),
641 )
642 .into());
643 }
644 };
645
646 let block_verifier_router_response = block_verifier_router
647 .ready()
648 .await
649 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
650 .call(zebra_consensus::Request::CheckProposal(Arc::new(block)))
651 .await;
652
653 Ok(block_verifier_router_response
654 .map(|_hash| BlockProposalResponse::Valid)
655 .unwrap_or_else(|verify_chain_error| {
656 tracing::info!(
657 ?verify_chain_error,
658 "error response from block_verifier_router in CheckProposal request"
659 );
660
661 BlockProposalResponse::rejected("invalid proposal", verify_chain_error)
662 })
663 .into())
664}
665
666pub fn check_synced_to_tip<Tip, SyncStatus>(
672 network: &Network,
673 latest_chain_tip: Tip,
674 sync_status: SyncStatus,
675) -> RpcResult<()>
676where
677 Tip: ChainTip + Clone + Send + Sync + 'static,
678 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
679{
680 if network.is_a_test_network() {
681 return Ok(());
682 }
683
684 let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip
687 .estimate_distance_to_network_chain_tip(network)
688 .ok_or_misc_error("no chain tip available yet")?;
689
690 if !sync_status.is_close_to_tip()
691 || estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
692 {
693 tracing::info!(
694 ?estimated_distance_to_chain_tip,
695 ?local_tip_height,
696 "Zebra has not synced to the chain tip. \
697 Hint: check your network connection, clock, and time zone settings."
698 );
699
700 return Err(ErrorObject::owned(
701 NOT_SYNCED_ERROR_CODE.code(),
702 format!(
703 "Zebra has not synced to the chain tip, \
704 estimated distance: {estimated_distance_to_chain_tip:?}, \
705 local tip: {local_tip_height:?}. \
706 Hint: check your network connection, clock, and time zone settings."
707 ),
708 None::<()>,
709 ));
710 }
711
712 Ok(())
713}
714
715pub async fn fetch_state_tip_and_local_time<State>(
722 state: State,
723) -> RpcResult<GetBlockTemplateChainInfo>
724where
725 State: Service<
726 zebra_state::ReadRequest,
727 Response = zebra_state::ReadResponse,
728 Error = zebra_state::BoxError,
729 > + Clone
730 + Send
731 + Sync
732 + 'static,
733{
734 let request = zebra_state::ReadRequest::ChainInfo;
735 let response = state
736 .oneshot(request.clone())
737 .await
738 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
739
740 let chain_info = match response {
741 zebra_state::ReadResponse::ChainInfo(chain_info) => chain_info,
742 _ => unreachable!("incorrect response to {request:?}"),
743 };
744
745 Ok(chain_info)
746}
747
748pub async fn fetch_mempool_transactions<Mempool>(
754 mempool: Mempool,
755 chain_tip_hash: block::Hash,
756) -> RpcResult<Option<(Vec<VerifiedUnminedTx>, TransactionDependencies)>>
757where
758 Mempool: Service<
759 mempool::Request,
760 Response = mempool::Response,
761 Error = zebra_node_services::BoxError,
762 > + 'static,
763 Mempool::Future: Send,
764{
765 let response = mempool
766 .oneshot(mempool::Request::FullTransactions)
767 .await
768 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
769
770 let mempool::Response::FullTransactions {
773 transactions,
774 transaction_dependencies,
775 last_seen_tip_hash,
776 } = response
777 else {
778 unreachable!("unmatched response to a mempool::FullTransactions request")
779 };
780
781 Ok((last_seen_tip_hash == chain_tip_hash).then_some((transactions, transaction_dependencies)))
783}
784
785pub fn generate_coinbase_and_roots(
789 network: &Network,
790 height: Height,
791 miner_address: &Address,
792 mempool_txs: &[VerifiedUnminedTx],
793 chain_history_root: Option<ChainHistoryMmrRootHash>,
794 miner_data: Vec<u8>,
795) -> Result<(TransactionTemplate<NegativeOrZero>, DefaultRoots), &'static str> {
796 let miner_fee = calculate_miner_fee(mempool_txs);
797 let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee);
798
799 let tx = match NetworkUpgrade::current(network, height) {
800 NetworkUpgrade::Canopy => Transaction::new_v4_coinbase(height, outputs, miner_data),
801 NetworkUpgrade::Nu5 | NetworkUpgrade::Nu6 | NetworkUpgrade::Nu6_1 | NetworkUpgrade::Nu7 => {
802 Transaction::new_v5_coinbase(network, height, outputs, miner_data)
803 }
804 _ => Err("Zebra does not support generating pre-Canopy coinbase transactions")?,
805 }
806 .into();
807
808 let chain_history_root = chain_history_root
812 .or_else(|| {
813 (NetworkUpgrade::Heartwood.activation_height(network) == Some(height))
814 .then_some([0; 32].into())
815 })
816 .expect("history tree can't be empty");
817
818 Ok((
819 TransactionTemplate::from_coinbase(&tx, miner_fee),
820 calculate_default_root_hashes(&tx, mempool_txs, chain_history_root),
821 ))
822}
823
824pub fn calculate_miner_fee(mempool_txs: &[VerifiedUnminedTx]) -> Amount<NonNegative> {
826 let miner_fee: amount::Result<Amount<NonNegative>> =
827 mempool_txs.iter().map(|tx| tx.miner_fee).sum();
828
829 miner_fee.expect(
830 "invalid selected transactions: \
831 fees in a valid block can not be more than MAX_MONEY",
832 )
833}
834
835pub fn standard_coinbase_outputs(
840 network: &Network,
841 height: Height,
842 miner_address: &Address,
843 miner_fee: Amount<NonNegative>,
844) -> Vec<(Amount<NonNegative>, transparent::Script)> {
845 let expected_block_subsidy = block_subsidy(height, network).expect("valid block subsidy");
846 let funding_streams = funding_stream_values(height, network, expected_block_subsidy)
847 .expect("funding stream value calculations are valid for reasonable chain heights");
848
849 let funding_streams: HashMap<
851 FundingStreamReceiver,
852 (Amount<NonNegative>, &transparent::Address),
853 > = funding_streams
854 .into_iter()
855 .filter_map(|(receiver, amount)| {
856 Some((
857 receiver,
858 (amount, funding_stream_address(height, network, receiver)?),
859 ))
860 })
861 .collect();
862
863 let miner_reward = miner_subsidy(height, network, expected_block_subsidy)
864 .expect("reward calculations are valid for reasonable chain heights")
865 + miner_fee;
866 let miner_reward =
867 miner_reward.expect("reward calculations are valid for reasonable chain heights");
868
869 let funding_streams_outputs: Vec<(Amount<NonNegative>, &transparent::Address)> =
871 funding_streams
872 .into_iter()
873 .map(|(_receiver, (amount, address))| (amount, address))
874 .collect();
875
876 let mut coinbase_outputs: Vec<(Amount<NonNegative>, transparent::Script)> =
878 funding_streams_outputs
879 .iter()
880 .map(|(amount, address)| (*amount, address.script()))
881 .collect();
882
883 let script = miner_address
884 .to_transparent_address()
885 .expect("address must have a transparent component")
886 .script()
887 .into();
888
889 coinbase_outputs.sort_by_key(|(_amount, script)| script.clone());
894
895 coinbase_outputs.insert(0, (miner_reward, script));
897
898 coinbase_outputs
899}
900
901pub fn calculate_default_root_hashes(
908 coinbase_txn: &UnminedTx,
909 mempool_txs: &[VerifiedUnminedTx],
910 chain_history_root: ChainHistoryMmrRootHash,
911) -> DefaultRoots {
912 let block_txs = || iter::once(coinbase_txn).chain(mempool_txs.iter().map(|tx| &tx.transaction));
913 let merkle_root = block_txs().cloned().collect();
914 let auth_data_root = block_txs().cloned().collect();
915
916 let block_commitments_hash = if chain_history_root == [0; 32].into() {
917 [0; 32].into()
918 } else {
919 ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
920 &chain_history_root,
921 &auth_data_root,
922 )
923 };
924
925 DefaultRoots {
926 merkle_root,
927 chain_history_root,
928 auth_data_root,
929 block_commitments_hash,
930 }
931}