1pub mod constants;
4pub mod parameters;
5pub mod proposal;
6pub mod zip317;
7
8use std::{collections::HashMap, fmt, iter, sync::Arc};
9
10use jsonrpsee::core::RpcResult;
11use jsonrpsee_types::{ErrorCode, ErrorObject};
12use tokio::sync::watch::{self, error::SendError};
13use tower::{Service, ServiceExt};
14
15use zebra_chain::{
16 amount::{self, Amount, NegativeOrZero, NonNegative},
17 block::{
18 self,
19 merkle::{self, AuthDataRoot},
20 Block, ChainHistoryBlockTxAuthCommitmentHash, ChainHistoryMmrRootHash, Height,
21 MAX_BLOCK_BYTES, ZCASH_BLOCK_VERSION,
22 },
23 chain_sync_status::ChainSyncStatus,
24 chain_tip::ChainTip,
25 parameters::{
26 subsidy::{block_subsidy, funding_stream_values, miner_subsidy, FundingStreamReceiver},
27 Network, NetworkKind, NetworkUpgrade,
28 },
29 serialization::{DateTime32, ZcashDeserializeInto},
30 transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
31 transparent::{
32 self, EXTRA_ZEBRA_COINBASE_DATA, MAX_COINBASE_DATA_LEN, MAX_COINBASE_HEIGHT_DATA_LEN,
33 },
34 work::difficulty::{CompactDifficulty, ExpandedDifficulty},
35};
36use zebra_consensus::{funding_stream_address, MAX_BLOCK_SIGOPS};
37use zebra_node_services::mempool::{self, TransactionDependencies};
38use zebra_state::GetBlockTemplateChainInfo;
39
40use crate::{
41 config,
42 methods::{
43 types::{
44 default_roots::DefaultRoots, long_poll::LongPollId, submit_block,
45 transaction::TransactionTemplate,
46 },
47 GetBlockHash,
48 },
49 server::error::OkOrError,
50};
51
52pub use constants::{
53 CAPABILITIES_FIELD, DEFAULT_SOLUTION_RATE_WINDOW_SIZE,
54 MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, MEMPOOL_LONG_POLL_INTERVAL, MUTABLE_FIELD,
55 NONCE_RANGE_FIELD, NOT_SYNCED_ERROR_CODE, ZCASHD_FUNDING_STREAM_ORDER,
56};
57pub use parameters::{GetBlockTemplateRequestMode, JsonParameters};
58pub use proposal::{ProposalResponse, TimeSource};
59
60pub type InBlockTxDependenciesDepth = usize;
65
66#[derive(Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
71pub struct GetBlockTemplate {
72 pub capabilities: Vec<String>,
81
82 pub version: u32,
85
86 #[serde(rename = "previousblockhash")]
88 pub previous_block_hash: GetBlockHash,
89
90 #[serde(rename = "blockcommitmentshash")]
94 #[serde(with = "hex")]
95 pub block_commitments_hash: ChainHistoryBlockTxAuthCommitmentHash,
96
97 #[serde(rename = "lightclientroothash")]
101 #[serde(with = "hex")]
102 pub light_client_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
103
104 #[serde(rename = "finalsaplingroothash")]
108 #[serde(with = "hex")]
109 pub final_sapling_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
110
111 #[serde(rename = "defaultroots")]
116 pub default_roots: DefaultRoots,
117
118 pub transactions: Vec<TransactionTemplate<amount::NonNegative>>,
120
121 #[serde(rename = "coinbasetxn")]
123 pub coinbase_txn: TransactionTemplate<amount::NegativeOrZero>,
124
125 #[serde(rename = "longpollid")]
127 pub long_poll_id: LongPollId,
128
129 #[serde(with = "hex")]
131 pub target: ExpandedDifficulty,
132
133 #[serde(rename = "mintime")]
138 pub min_time: DateTime32,
139
140 pub mutable: Vec<String>,
142
143 #[serde(rename = "noncerange")]
145 pub nonce_range: String,
146
147 #[serde(rename = "sigoplimit")]
149 pub sigop_limit: u64,
150
151 #[serde(rename = "sizelimit")]
153 pub size_limit: u64,
154
155 #[serde(rename = "curtime")]
160 pub cur_time: DateTime32,
161
162 #[serde(with = "hex")]
164 pub bits: CompactDifficulty,
165
166 pub height: u32,
169
170 #[serde(rename = "maxtime")]
184 pub max_time: DateTime32,
185
186 #[serde(skip_serializing_if = "Option::is_none")]
198 #[serde(default)]
199 #[serde(rename = "submitold")]
200 pub submit_old: Option<bool>,
201}
202
203impl fmt::Debug for GetBlockTemplate {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 let mut transactions_truncated = self.transactions.clone();
207 if self.transactions.len() > 4 {
208 let end = self.transactions.len() - 2;
210 transactions_truncated.splice(3..=end, Vec::new());
211 }
212
213 f.debug_struct("GetBlockTemplate")
214 .field("capabilities", &self.capabilities)
215 .field("version", &self.version)
216 .field("previous_block_hash", &self.previous_block_hash)
217 .field("block_commitments_hash", &self.block_commitments_hash)
218 .field("light_client_root_hash", &self.light_client_root_hash)
219 .field("final_sapling_root_hash", &self.final_sapling_root_hash)
220 .field("default_roots", &self.default_roots)
221 .field("transaction_count", &self.transactions.len())
222 .field("transactions", &transactions_truncated)
223 .field("coinbase_txn", &self.coinbase_txn)
224 .field("long_poll_id", &self.long_poll_id)
225 .field("target", &self.target)
226 .field("min_time", &self.min_time)
227 .field("mutable", &self.mutable)
228 .field("nonce_range", &self.nonce_range)
229 .field("sigop_limit", &self.sigop_limit)
230 .field("size_limit", &self.size_limit)
231 .field("cur_time", &self.cur_time)
232 .field("bits", &self.bits)
233 .field("height", &self.height)
234 .field("max_time", &self.max_time)
235 .field("submit_old", &self.submit_old)
236 .finish()
237 }
238}
239
240impl GetBlockTemplate {
241 pub fn capabilities() -> Vec<String> {
243 CAPABILITIES_FIELD.iter().map(ToString::to_string).collect()
244 }
245
246 #[allow(clippy::too_many_arguments)]
253 pub fn new(
254 network: &Network,
255 miner_address: &transparent::Address,
256 chain_tip_and_local_time: &GetBlockTemplateChainInfo,
257 long_poll_id: LongPollId,
258 #[cfg(not(test))] mempool_txs: Vec<VerifiedUnminedTx>,
259 #[cfg(test)] mempool_txs: Vec<(InBlockTxDependenciesDepth, VerifiedUnminedTx)>,
260 submit_old: Option<bool>,
261 like_zcashd: bool,
262 extra_coinbase_data: Vec<u8>,
263 ) -> Self {
264 let next_block_height =
266 (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX");
267
268 #[cfg(not(test))]
270 let (mempool_tx_templates, mempool_txs): (Vec<_>, Vec<_>) =
271 mempool_txs.into_iter().map(|tx| ((&tx).into(), tx)).unzip();
272
273 #[cfg(test)]
281 let (mempool_tx_templates, mempool_txs): (Vec<_>, Vec<_>) = {
282 let mut mempool_txs_with_templates: Vec<(
283 InBlockTxDependenciesDepth,
284 TransactionTemplate<amount::NonNegative>,
285 VerifiedUnminedTx,
286 )> = mempool_txs
287 .into_iter()
288 .map(|(min_tx_index, tx)| (min_tx_index, (&tx).into(), tx))
289 .collect();
290
291 if like_zcashd {
292 mempool_txs_with_templates.sort_by_key(|(min_tx_index, tx_template, _tx)| {
295 (*min_tx_index, tx_template.data.clone())
296 });
297 } else {
298 mempool_txs_with_templates.sort_by_key(|(min_tx_index, tx_template, _tx)| {
300 (*min_tx_index, tx_template.hash.bytes_in_display_order())
301 });
302 }
303 mempool_txs_with_templates
304 .into_iter()
305 .map(|(_, template, tx)| (template, tx))
306 .unzip()
307 };
308
309 let (coinbase_txn, default_roots) = generate_coinbase_and_roots(
313 network,
314 next_block_height,
315 miner_address,
316 &mempool_txs,
317 chain_tip_and_local_time.chain_history_root,
318 like_zcashd,
319 extra_coinbase_data,
320 );
321
322 let target = chain_tip_and_local_time
324 .expected_difficulty
325 .to_expanded()
326 .expect("state always returns a valid difficulty value");
327
328 let capabilities: Vec<String> = Self::capabilities();
330 let mutable: Vec<String> = MUTABLE_FIELD.iter().map(ToString::to_string).collect();
331
332 tracing::debug!(
333 selected_txs = ?mempool_txs
334 .iter()
335 .map(|tx| (tx.transaction.id.mined_id(), tx.unpaid_actions))
336 .collect::<Vec<_>>(),
337 "creating template ... "
338 );
339
340 GetBlockTemplate {
341 capabilities,
342
343 version: ZCASH_BLOCK_VERSION,
344
345 previous_block_hash: GetBlockHash(chain_tip_and_local_time.tip_hash),
346 block_commitments_hash: default_roots.block_commitments_hash,
347 light_client_root_hash: default_roots.block_commitments_hash,
348 final_sapling_root_hash: default_roots.block_commitments_hash,
349 default_roots,
350
351 transactions: mempool_tx_templates,
352
353 coinbase_txn,
354
355 long_poll_id,
356
357 target,
358
359 min_time: chain_tip_and_local_time.min_time,
360
361 mutable,
362
363 nonce_range: NONCE_RANGE_FIELD.to_string(),
364
365 sigop_limit: MAX_BLOCK_SIGOPS,
366
367 size_limit: MAX_BLOCK_BYTES,
368
369 cur_time: chain_tip_and_local_time.cur_time,
370
371 bits: chain_tip_and_local_time.expected_difficulty,
372
373 height: next_block_height.0,
374
375 max_time: chain_tip_and_local_time.max_time,
376
377 submit_old,
378 }
379 }
380}
381
382#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
383#[serde(untagged)]
384pub enum Response {
386 TemplateMode(Box<GetBlockTemplate>),
388
389 ProposalMode(ProposalResponse),
391}
392
393impl Response {
394 pub fn try_into_template(self) -> Option<GetBlockTemplate> {
396 match self {
397 Response::TemplateMode(template) => Some(*template),
398 Response::ProposalMode(_) => None,
399 }
400 }
401
402 pub fn try_into_proposal(self) -> Option<ProposalResponse> {
404 match self {
405 Response::TemplateMode(_) => None,
406 Response::ProposalMode(proposal) => Some(proposal),
407 }
408 }
409}
410
411#[derive(Clone)]
413pub struct GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
414where
415 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
416 + Clone
417 + Send
418 + Sync
419 + 'static,
420 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
421 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
422{
423 miner_address: Option<transparent::Address>,
427
428 extra_coinbase_data: Vec<u8>,
431
432 block_verifier_router: BlockVerifierRouter,
434
435 sync_status: SyncStatus,
437
438 mined_block_sender: watch::Sender<(block::Hash, block::Height)>,
441}
442
443impl<BlockVerifierRouter, SyncStatus> GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
444where
445 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
446 + Clone
447 + Send
448 + Sync
449 + 'static,
450 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
451 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
452{
453 pub fn new(
459 net: &Network,
460 conf: config::mining::Config,
461 block_verifier_router: BlockVerifierRouter,
462 sync_status: SyncStatus,
463 mined_block_sender: Option<watch::Sender<(block::Hash, block::Height)>>,
464 ) -> Self {
465 if let Some(miner_address) = conf.miner_address.clone() {
467 match net.kind() {
468 NetworkKind::Mainnet => assert_eq!(
469 miner_address.network_kind(),
470 NetworkKind::Mainnet,
471 "Incorrect config: Zebra is configured to run on a Mainnet network, \
472 which implies the configured mining address needs to be for Mainnet, \
473 but the provided address is for {}.",
474 miner_address.network_kind(),
475 ),
476 network_kind @ (NetworkKind::Testnet | NetworkKind::Regtest) => assert_eq!(
478 miner_address.network_kind(),
479 NetworkKind::Testnet,
480 "Incorrect config: Zebra is configured to run on a {network_kind} network, \
481 which implies the configured mining address needs to be for Testnet, \
482 but the provided address is for {}.",
483 miner_address.network_kind(),
484 ),
485 }
486 }
487
488 const EXTRA_COINBASE_DATA_LIMIT: usize =
491 MAX_COINBASE_DATA_LEN - MAX_COINBASE_HEIGHT_DATA_LEN;
492
493 let debug_like_zcashd = conf.debug_like_zcashd;
494
495 let extra_coinbase_data = conf.extra_coinbase_data.unwrap_or_else(|| {
497 if debug_like_zcashd {
498 ""
499 } else {
500 EXTRA_ZEBRA_COINBASE_DATA
501 }
502 .to_string()
503 });
504 let extra_coinbase_data = hex::decode(&extra_coinbase_data)
505 .unwrap_or_else(|_error| extra_coinbase_data.as_bytes().to_vec());
506
507 assert!(
508 extra_coinbase_data.len() <= EXTRA_COINBASE_DATA_LIMIT,
509 "extra coinbase data is {} bytes, but Zebra's limit is {}.\n\
510 Configure mining.extra_coinbase_data with a shorter string",
511 extra_coinbase_data.len(),
512 EXTRA_COINBASE_DATA_LIMIT,
513 );
514
515 Self {
516 miner_address: conf.miner_address,
517 extra_coinbase_data,
518 block_verifier_router,
519 sync_status,
520 mined_block_sender: mined_block_sender
521 .unwrap_or(submit_block::SubmitBlockChannel::default().sender()),
522 }
523 }
524
525 pub fn miner_address(&self) -> Option<transparent::Address> {
527 self.miner_address.clone()
528 }
529
530 pub fn extra_coinbase_data(&self) -> Vec<u8> {
532 self.extra_coinbase_data.clone()
533 }
534
535 pub fn sync_status(&self) -> SyncStatus {
537 self.sync_status.clone()
538 }
539
540 pub fn block_verifier_router(&self) -> BlockVerifierRouter {
542 self.block_verifier_router.clone()
543 }
544
545 pub fn advertise_mined_block(
547 &self,
548 block: block::Hash,
549 height: block::Height,
550 ) -> Result<(), SendError<(block::Hash, block::Height)>> {
551 self.mined_block_sender.send((block, height))
552 }
553}
554
555impl<BlockVerifierRouter, SyncStatus> fmt::Debug
556 for GetBlockTemplateHandler<BlockVerifierRouter, SyncStatus>
557where
558 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
559 + Clone
560 + Send
561 + Sync
562 + 'static,
563 <BlockVerifierRouter as Service<zebra_consensus::Request>>::Future: Send,
564 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
565{
566 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
567 f.debug_struct("GetBlockTemplateRpcImpl")
569 .field("miner_address", &self.miner_address)
570 .field("extra_coinbase_data", &self.extra_coinbase_data)
571 .finish()
572 }
573}
574
575pub fn check_parameters(parameters: &Option<JsonParameters>) -> RpcResult<()> {
581 let Some(parameters) = parameters else {
582 return Ok(());
583 };
584
585 match parameters {
586 JsonParameters {
587 mode: GetBlockTemplateRequestMode::Template,
588 data: None,
589 ..
590 }
591 | JsonParameters {
592 mode: GetBlockTemplateRequestMode::Proposal,
593 data: Some(_),
594 ..
595 } => Ok(()),
596
597 JsonParameters {
598 mode: GetBlockTemplateRequestMode::Proposal,
599 data: None,
600 ..
601 } => Err(ErrorObject::borrowed(
602 ErrorCode::InvalidParams.code(),
603 "\"data\" parameter must be \
604 provided in \"proposal\" mode",
605 None,
606 )),
607
608 JsonParameters {
609 mode: GetBlockTemplateRequestMode::Template,
610 data: Some(_),
611 ..
612 } => Err(ErrorObject::borrowed(
613 ErrorCode::InvalidParams.code(),
614 "\"data\" parameter must be \
615 omitted in \"template\" mode",
616 None,
617 )),
618 }
619}
620
621pub fn check_miner_address(
623 miner_address: Option<transparent::Address>,
624) -> RpcResult<transparent::Address> {
625 miner_address.ok_or_misc_error(
626 "set `mining.miner_address` in `zebrad.toml` to a transparent address".to_string(),
627 )
628}
629
630pub async fn validate_block_proposal<BlockVerifierRouter, Tip, SyncStatus>(
635 mut block_verifier_router: BlockVerifierRouter,
636 block_proposal_bytes: Vec<u8>,
637 network: Network,
638 latest_chain_tip: Tip,
639 sync_status: SyncStatus,
640) -> RpcResult<Response>
641where
642 BlockVerifierRouter: Service<zebra_consensus::Request, Response = block::Hash, Error = zebra_consensus::BoxError>
643 + Clone
644 + Send
645 + Sync
646 + 'static,
647 Tip: ChainTip + Clone + Send + Sync + 'static,
648 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
649{
650 check_synced_to_tip(&network, latest_chain_tip, sync_status)?;
651
652 let block: Block = match block_proposal_bytes.zcash_deserialize_into() {
653 Ok(block) => block,
654 Err(parse_error) => {
655 tracing::info!(
656 ?parse_error,
657 "error response from block parser in CheckProposal request"
658 );
659
660 return Ok(
661 ProposalResponse::rejected("invalid proposal format", parse_error.into()).into(),
662 );
663 }
664 };
665
666 let block_verifier_router_response = block_verifier_router
667 .ready()
668 .await
669 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
670 .call(zebra_consensus::Request::CheckProposal(Arc::new(block)))
671 .await;
672
673 Ok(block_verifier_router_response
674 .map(|_hash| ProposalResponse::Valid)
675 .unwrap_or_else(|verify_chain_error| {
676 tracing::info!(
677 ?verify_chain_error,
678 "error response from block_verifier_router in CheckProposal request"
679 );
680
681 ProposalResponse::rejected("invalid proposal", verify_chain_error)
682 })
683 .into())
684}
685
686pub fn check_synced_to_tip<Tip, SyncStatus>(
692 network: &Network,
693 latest_chain_tip: Tip,
694 sync_status: SyncStatus,
695) -> RpcResult<()>
696where
697 Tip: ChainTip + Clone + Send + Sync + 'static,
698 SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
699{
700 if network.is_a_test_network() {
701 return Ok(());
702 }
703
704 let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip
707 .estimate_distance_to_network_chain_tip(network)
708 .ok_or_misc_error("no chain tip available yet")?;
709
710 if !sync_status.is_close_to_tip()
711 || estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
712 {
713 tracing::info!(
714 ?estimated_distance_to_chain_tip,
715 ?local_tip_height,
716 "Zebra has not synced to the chain tip. \
717 Hint: check your network connection, clock, and time zone settings."
718 );
719
720 return Err(ErrorObject::owned(
721 NOT_SYNCED_ERROR_CODE.code(),
722 format!(
723 "Zebra has not synced to the chain tip, \
724 estimated distance: {estimated_distance_to_chain_tip:?}, \
725 local tip: {local_tip_height:?}. \
726 Hint: check your network connection, clock, and time zone settings."
727 ),
728 None::<()>,
729 ));
730 }
731
732 Ok(())
733}
734
735pub async fn fetch_state_tip_and_local_time<State>(
742 state: State,
743) -> RpcResult<GetBlockTemplateChainInfo>
744where
745 State: Service<
746 zebra_state::ReadRequest,
747 Response = zebra_state::ReadResponse,
748 Error = zebra_state::BoxError,
749 > + Clone
750 + Send
751 + Sync
752 + 'static,
753{
754 let request = zebra_state::ReadRequest::ChainInfo;
755 let response = state
756 .oneshot(request.clone())
757 .await
758 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
759
760 let chain_info = match response {
761 zebra_state::ReadResponse::ChainInfo(chain_info) => chain_info,
762 _ => unreachable!("incorrect response to {request:?}"),
763 };
764
765 Ok(chain_info)
766}
767
768pub async fn fetch_mempool_transactions<Mempool>(
774 mempool: Mempool,
775 chain_tip_hash: block::Hash,
776) -> RpcResult<Option<(Vec<VerifiedUnminedTx>, TransactionDependencies)>>
777where
778 Mempool: Service<
779 mempool::Request,
780 Response = mempool::Response,
781 Error = zebra_node_services::BoxError,
782 > + 'static,
783 Mempool::Future: Send,
784{
785 let response = mempool
786 .oneshot(mempool::Request::FullTransactions)
787 .await
788 .map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
789
790 let mempool::Response::FullTransactions {
793 transactions,
794 transaction_dependencies,
795 last_seen_tip_hash,
796 } = response
797 else {
798 unreachable!("unmatched response to a mempool::FullTransactions request")
799 };
800
801 Ok((last_seen_tip_hash == chain_tip_hash).then_some((transactions, transaction_dependencies)))
803}
804
805pub fn generate_coinbase_and_roots(
812 network: &Network,
813 block_template_height: Height,
814 miner_address: &transparent::Address,
815 mempool_txs: &[VerifiedUnminedTx],
816 chain_history_root: Option<ChainHistoryMmrRootHash>,
817 like_zcashd: bool,
818 extra_coinbase_data: Vec<u8>,
819) -> (TransactionTemplate<NegativeOrZero>, DefaultRoots) {
820 let miner_fee = calculate_miner_fee(mempool_txs);
822 let coinbase_txn = generate_coinbase_transaction(
823 network,
824 block_template_height,
825 miner_address,
826 miner_fee,
827 like_zcashd,
828 extra_coinbase_data,
829 );
830
831 let chain_history_root = chain_history_root
835 .or_else(|| {
836 (NetworkUpgrade::Heartwood.activation_height(network) == Some(block_template_height))
837 .then_some([0; 32].into())
838 })
839 .expect("history tree can't be empty");
840 let default_roots =
841 calculate_default_root_hashes(&coinbase_txn, mempool_txs, chain_history_root);
842
843 let coinbase_txn = TransactionTemplate::from_coinbase(&coinbase_txn, miner_fee);
844
845 (coinbase_txn, default_roots)
846}
847
848pub fn generate_coinbase_transaction(
855 network: &Network,
856 height: Height,
857 miner_address: &transparent::Address,
858 miner_fee: Amount<NonNegative>,
859 like_zcashd: bool,
860 extra_coinbase_data: Vec<u8>,
861) -> UnminedTx {
862 let outputs = standard_coinbase_outputs(network, height, miner_address, miner_fee, like_zcashd);
863
864 if like_zcashd {
865 Transaction::new_v4_coinbase(network, height, outputs, like_zcashd, extra_coinbase_data)
866 .into()
867 } else {
868 Transaction::new_v5_coinbase(network, height, outputs, extra_coinbase_data).into()
869 }
870}
871
872pub fn calculate_miner_fee(mempool_txs: &[VerifiedUnminedTx]) -> Amount<NonNegative> {
874 let miner_fee: amount::Result<Amount<NonNegative>> =
875 mempool_txs.iter().map(|tx| tx.miner_fee).sum();
876
877 miner_fee.expect(
878 "invalid selected transactions: \
879 fees in a valid block can not be more than MAX_MONEY",
880 )
881}
882
883pub fn standard_coinbase_outputs(
891 network: &Network,
892 height: Height,
893 miner_address: &transparent::Address,
894 miner_fee: Amount<NonNegative>,
895 like_zcashd: bool,
896) -> Vec<(Amount<NonNegative>, transparent::Script)> {
897 let expected_block_subsidy = block_subsidy(height, network).expect("valid block subsidy");
898 let funding_streams = funding_stream_values(height, network, expected_block_subsidy)
899 .expect("funding stream value calculations are valid for reasonable chain heights");
900
901 let funding_streams: HashMap<
903 FundingStreamReceiver,
904 (Amount<NonNegative>, &transparent::Address),
905 > = funding_streams
906 .into_iter()
907 .filter_map(|(receiver, amount)| {
908 Some((
909 receiver,
910 (amount, funding_stream_address(height, network, receiver)?),
911 ))
912 })
913 .collect();
914
915 let miner_reward = miner_subsidy(height, network, expected_block_subsidy)
916 .expect("reward calculations are valid for reasonable chain heights")
917 + miner_fee;
918 let miner_reward =
919 miner_reward.expect("reward calculations are valid for reasonable chain heights");
920
921 combine_coinbase_outputs(funding_streams, miner_address, miner_reward, like_zcashd)
922}
923
924fn combine_coinbase_outputs(
929 funding_streams: HashMap<FundingStreamReceiver, (Amount<NonNegative>, &transparent::Address)>,
930 miner_address: &transparent::Address,
931 miner_reward: Amount<NonNegative>,
932 like_zcashd: bool,
933) -> Vec<(Amount<NonNegative>, transparent::Script)> {
934 let funding_streams_outputs: Vec<(Amount<NonNegative>, &transparent::Address)> =
936 funding_streams
937 .into_iter()
938 .map(|(_receiver, (amount, address))| (amount, address))
939 .collect();
940
941 let mut coinbase_outputs: Vec<(Amount<NonNegative>, transparent::Script)> =
942 funding_streams_outputs
943 .iter()
944 .map(|(amount, address)| (*amount, address.create_script_from_address()))
945 .collect();
946
947 if like_zcashd {
950 coinbase_outputs.sort_by_key(|(_amount, script)| script.clone());
952
953 coinbase_outputs.insert(
955 0,
956 (miner_reward, miner_address.create_script_from_address()),
957 );
958 } else {
959 coinbase_outputs.push((miner_reward, miner_address.create_script_from_address()));
961
962 coinbase_outputs.sort_by_key(|(_amount, script)| script.clone());
966 coinbase_outputs.sort_by_key(|(amount, _script)| *amount);
967 }
968
969 coinbase_outputs
970}
971
972pub fn calculate_default_root_hashes(
979 coinbase_txn: &UnminedTx,
980 mempool_txs: &[VerifiedUnminedTx],
981 chain_history_root: ChainHistoryMmrRootHash,
982) -> DefaultRoots {
983 let (merkle_root, auth_data_root) = calculate_transaction_roots(coinbase_txn, mempool_txs);
984
985 let block_commitments_hash = if chain_history_root == [0; 32].into() {
986 [0; 32].into()
987 } else {
988 ChainHistoryBlockTxAuthCommitmentHash::from_commitments(
989 &chain_history_root,
990 &auth_data_root,
991 )
992 };
993
994 DefaultRoots {
995 merkle_root,
996 chain_history_root,
997 auth_data_root,
998 block_commitments_hash,
999 }
1000}
1001
1002pub fn calculate_transaction_roots(
1008 coinbase_txn: &UnminedTx,
1009 mempool_txs: &[VerifiedUnminedTx],
1010) -> (merkle::Root, AuthDataRoot) {
1011 let block_transactions =
1012 || iter::once(coinbase_txn).chain(mempool_txs.iter().map(|tx| &tx.transaction));
1013
1014 let merkle_root = block_transactions().cloned().collect();
1015 let auth_data_root = block_transactions().cloned().collect();
1016
1017 (merkle_root, auth_data_root)
1018}