zebra_rpc/methods/types/
get_block_template.rs

1//! Types and functions for the `getblocktemplate` RPC.
2
3pub 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/// An alias to indicate that a usize value represents the depth of in-block dependencies of a
64/// transaction.
65///
66/// See the `dependencies_depth()` function in [`zip317`] for more details.
67#[cfg(test)]
68type InBlockTxDependenciesDepth = usize;
69
70/// A serialized `getblocktemplate` RPC response in template mode.
71///
72/// This is the output of the `getblocktemplate` RPC in the default 'template' mode. See
73/// [`BlockProposalResponse`] for the output in 'proposal' mode.
74#[allow(clippy::too_many_arguments)]
75#[derive(Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, Getters, new)]
76pub struct BlockTemplateResponse {
77    /// The getblocktemplate RPC capabilities supported by Zebra.
78    ///
79    /// At the moment, Zebra does not support any of the extra capabilities from the specification:
80    /// - `proposal`: <https://en.bitcoin.it/wiki/BIP_0023#Block_Proposal>
81    /// - `longpoll`: <https://en.bitcoin.it/wiki/BIP_0022#Optional:_Long_Polling>
82    /// - `serverlist`: <https://en.bitcoin.it/wiki/BIP_0023#Logical_Services>
83    ///
84    /// By the above, Zebra will always return an empty vector here.
85    pub(crate) capabilities: Vec<String>,
86
87    /// The version of the block format.
88    /// Always 4 for new Zcash blocks.
89    pub(crate) version: u32,
90
91    /// The hash of the previous block.
92    #[serde(rename = "previousblockhash")]
93    #[serde(with = "hex")]
94    #[getter(copy)]
95    pub(crate) previous_block_hash: block::Hash,
96
97    /// The block commitment for the new block's header.
98    ///
99    /// Same as [`DefaultRoots.block_commitments_hash`], see that field for details.
100    #[serde(rename = "blockcommitmentshash")]
101    #[serde(with = "hex")]
102    #[getter(copy)]
103    pub(crate) block_commitments_hash: ChainHistoryBlockTxAuthCommitmentHash,
104
105    /// Legacy backwards-compatibility header root field.
106    ///
107    /// Same as [`DefaultRoots.block_commitments_hash`], see that field for details.
108    #[serde(rename = "lightclientroothash")]
109    #[serde(with = "hex")]
110    #[getter(copy)]
111    pub(crate) light_client_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
112
113    /// Legacy backwards-compatibility header root field.
114    ///
115    /// Same as [`DefaultRoots.block_commitments_hash`], see that field for details.
116    #[serde(rename = "finalsaplingroothash")]
117    #[serde(with = "hex")]
118    #[getter(copy)]
119    pub(crate) final_sapling_root_hash: ChainHistoryBlockTxAuthCommitmentHash,
120
121    /// The block header roots for [`GetBlockTemplate.transactions`].
122    ///
123    /// If the transactions in the block template are modified, these roots must be recalculated
124    /// [according to the specification](https://zcash.github.io/rpc/getblocktemplate.html).
125    #[serde(rename = "defaultroots")]
126    pub(crate) default_roots: DefaultRoots,
127
128    /// The non-coinbase transactions selected for this block template.
129    pub(crate) transactions: Vec<TransactionTemplate<amount::NonNegative>>,
130
131    /// The coinbase transaction generated from `transactions` and `height`.
132    #[serde(rename = "coinbasetxn")]
133    pub(crate) coinbase_txn: TransactionTemplate<amount::NegativeOrZero>,
134
135    /// An ID that represents the chain tip and mempool contents for this template.
136    #[serde(rename = "longpollid")]
137    #[getter(copy)]
138    pub(crate) long_poll_id: LongPollId,
139
140    /// The expected difficulty for the new block displayed in expanded form.
141    #[serde(with = "hex")]
142    #[getter(copy)]
143    pub(crate) target: ExpandedDifficulty,
144
145    /// > For each block other than the genesis block, nTime MUST be strictly greater than
146    /// > the median-time-past of that block.
147    ///
148    /// <https://zips.z.cash/protocol/protocol.pdf#blockheader>
149    #[serde(rename = "mintime")]
150    #[getter(copy)]
151    pub(crate) min_time: DateTime32,
152
153    /// Hardcoded list of block fields the miner is allowed to change.
154    pub(crate) mutable: Vec<String>,
155
156    /// A range of valid nonces that goes from `u32::MIN` to `u32::MAX`.
157    #[serde(rename = "noncerange")]
158    pub(crate) nonce_range: String,
159
160    /// Max legacy signature operations in the block.
161    #[serde(rename = "sigoplimit")]
162    pub(crate) sigop_limit: u64,
163
164    /// Max block size in bytes
165    #[serde(rename = "sizelimit")]
166    pub(crate) size_limit: u64,
167
168    /// > the current time as seen by the server (recommended for block time).
169    /// > note this is not necessarily the system clock, and must fall within the mintime/maxtime rules
170    ///
171    /// <https://en.bitcoin.it/wiki/BIP_0022#Block_Template_Request>
172    #[serde(rename = "curtime")]
173    #[getter(copy)]
174    pub(crate) cur_time: DateTime32,
175
176    /// The expected difficulty for the new block displayed in compact form.
177    #[serde(with = "hex")]
178    #[getter(copy)]
179    pub(crate) bits: CompactDifficulty,
180
181    /// The height of the next block in the best chain.
182    // Optional TODO: use Height type, but check that deserialized heights are within Height::MAX
183    pub(crate) height: u32,
184
185    /// > the maximum time allowed
186    ///
187    /// <https://en.bitcoin.it/wiki/BIP_0023#Mutations>
188    ///
189    /// Zebra adjusts the minimum and current times for testnet minimum difficulty blocks,
190    /// so we need to tell miners what the maximum valid time is.
191    ///
192    /// This field is not in `zcashd` or the Zcash RPC reference yet.
193    ///
194    /// Currently, some miners just use `min_time` or `cur_time`. Others calculate `max_time` from the
195    /// fixed 90 minute consensus rule, or a smaller fixed interval (like 1000s).
196    /// Some miners don't check the maximum time. This can cause invalid blocks after network downtime,
197    /// a significant drop in the hash rate, or after the testnet minimum difficulty interval.
198    #[serde(rename = "maxtime")]
199    #[getter(copy)]
200    pub(crate) max_time: DateTime32,
201
202    /// > only relevant for long poll responses:
203    /// > indicates if work received prior to this response remains potentially valid (default)
204    /// > and should have its shares submitted;
205    /// > if false, the miner may wish to discard its share queue
206    ///
207    /// <https://en.bitcoin.it/wiki/BIP_0022#Optional:_Long_Polling>
208    ///
209    /// This field is not in `zcashd` or the Zcash RPC reference yet.
210    ///
211    /// In Zebra, `submit_old` is `false` when the tip block changed or max time is reached,
212    /// and `true` if only the mempool transactions have changed.
213    #[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        // A block with a lot of transactions can be extremely long in logs.
223        let mut transactions_truncated = self.transactions.clone();
224        if self.transactions.len() > 4 {
225            // Remove transaction 3 onwards, but leave the last transaction
226            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    /// Returns a `Vec` of capabilities supported by the `getblocktemplate` RPC
259    pub fn all_capabilities() -> Vec<String> {
260        CAPABILITIES_FIELD.iter().map(ToString::to_string).collect()
261    }
262
263    /// Returns a new [`BlockTemplateResponse`] struct, based on the supplied arguments and defaults.
264    ///
265    /// The result of this method only depends on the supplied arguments and constants.
266    #[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        // Calculate the next block height.
278        let next_block_height =
279            (chain_tip_and_local_time.tip_height + 1).expect("tip is far below Height::MAX");
280
281        // Convert transactions into TransactionTemplates
282        #[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        // Transaction selection returns transactions in an arbitrary order,
287        // but Zebra's snapshot tests expect the same order every time.
288        //
289        // # Correctness
290        //
291        // Transactions that spend outputs created in the same block must appear
292        // after the transactions that create those outputs.
293        #[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            // `zcashd` sorts in serialized data order, excluding the length byte.
305            // It sometimes seems to do this, but other times the order is arbitrary.
306            // Sort by hash, this is faster.
307            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        // Generate the coinbase transaction and default roots
318        //
319        // TODO: move expensive root, hash, and tree cryptography to a rayon thread?
320        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        // Convert difficulty
331        let target = chain_tip_and_local_time
332            .expected_difficulty
333            .to_expanded()
334            .expect("state always returns a valid difficulty value");
335
336        // Convert default values
337        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)]
392/// A `getblocktemplate` RPC response.
393pub enum GetBlockTemplateResponse {
394    /// `getblocktemplate` RPC request in template mode.
395    TemplateMode(Box<BlockTemplateResponse>),
396
397    /// `getblocktemplate` RPC request in proposal mode.
398    ProposalMode(BlockProposalResponse),
399}
400
401impl GetBlockTemplateResponse {
402    /// Returns the inner template, if the response is in template mode.
403    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    /// Returns the inner proposal, if the response is in proposal mode.
411    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///  Handler for the `getblocktemplate` RPC.
420#[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    /// Address for receiving miner subsidy and tx fees.
432    miner_address: Option<Address>,
433
434    /// Extra data to include in coinbase transaction inputs.
435    /// Limited to around 95 bytes by the consensus rules.
436    extra_coinbase_data: Vec<u8>,
437
438    /// The chain verifier, used for submitting blocks.
439    block_verifier_router: BlockVerifierRouter,
440
441    /// The chain sync status, used for checking if Zebra is likely close to the network chain tip.
442    sync_status: SyncStatus,
443
444    /// A channel to send successful block submissions to the block gossip task,
445    /// so they can be advertised to peers.
446    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    /// Creates a new [`GetBlockTemplateHandler`].
460    ///
461    /// # Panics
462    ///
463    /// - If the `miner_address` in `conf` is not valid.
464    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        // Check that the configured miner address is valid.
472        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                // TODO: Remove this panic once we support mining to shielded addresses.
478                panic!("miner_address can't receive transparent funds")
479            }
480        });
481
482        // A limit on the configured extra coinbase data, regardless of the current block height.
483        // This is different from the consensus rule, which limits the total height + data.
484        const EXTRA_COINBASE_DATA_LIMIT: usize =
485            MAX_COINBASE_DATA_LEN - MAX_COINBASE_HEIGHT_DATA_LEN;
486
487        // Hex-decode to bytes if possible, otherwise UTF-8 encode to bytes.
488        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    /// Returns a valid miner address, if any.
513    pub fn miner_address(&self) -> Option<Address> {
514        self.miner_address.clone()
515    }
516
517    /// Returns the extra coinbase data.
518    pub fn extra_coinbase_data(&self) -> Vec<u8> {
519        self.extra_coinbase_data.clone()
520    }
521
522    /// Returns the sync status.
523    pub fn sync_status(&self) -> SyncStatus {
524        self.sync_status.clone()
525    }
526
527    /// Returns the block verifier router.
528    pub fn block_verifier_router(&self) -> BlockVerifierRouter {
529        self.block_verifier_router.clone()
530    }
531
532    /// Advertises the mined block.
533    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        // Skip fields without debug impls
555        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
562// - Parameter checks
563
564/// Checks that `data` is omitted in `Template` mode or provided in `Proposal` mode,
565///
566/// Returns an error if there's a mismatch between the mode and whether `data` is provided.
567pub 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
608/// Attempts to validate block proposal against all of the server's
609/// usual acceptance rules (except proof-of-work).
610///
611/// Returns a [`GetBlockTemplateResponse`].
612pub 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
666// - State and syncer checks
667
668/// Returns an error if Zebra is not synced to the consensus chain tip.
669/// Returns early with `Ok(())` if Proof-of-Work is disabled on the provided `network`.
670/// This error might be incorrect if the local clock is skewed.
671pub 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    // The tip estimate may not be the same as the one coming from the state
685    // but this is ok for an estimate
686    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
715// - State and mempool data fetches
716
717/// Returns the state data for the block template.
718///
719/// You should call `check_synced_to_tip()` before calling this function.
720/// If the state does not have enough blocks, returns an error.
721pub 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
748/// Returns the transactions that are currently in `mempool`, or None if the
749/// `last_seen_tip_hash` from the mempool response doesn't match the tip hash from the state.
750///
751/// You should call `check_synced_to_tip()` before calling this function.
752/// If the mempool is inactive because Zebra is not synced to the tip, returns no transactions.
753pub 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    // TODO: Order transactions in block templates based on their dependencies
771
772    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    // Check that the mempool and state were in sync when we made the requests
782    Ok((last_seen_tip_hash == chain_tip_hash).then_some((transactions, transaction_dependencies)))
783}
784
785// - Response processing
786
787/// Generates and returns the coinbase transaction and default roots.
788pub 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    // Calculate block default roots
809    //
810    // TODO: move expensive root, hash, and tree cryptography to a rayon thread?
811    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
824/// Returns the total miner fee for `mempool_txs`.
825pub 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
835/// Returns the standard funding stream and miner reward transparent output scripts
836/// for `network`, `height` and `miner_fee`.
837///
838/// Only works for post-Canopy heights.
839pub 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    // Optional TODO: move this into a zebra_consensus function?
850    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    // Collect all the funding streams and convert them to outputs.
870    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    // Combine the miner reward and funding streams into a list of coinbase amounts and addresses.
877    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    // The HashMap returns funding streams in an arbitrary order,
890    // but Zebra's snapshot tests expect the same order every time.
891
892    // zcashd sorts outputs in serialized data order, excluding the length field
893    coinbase_outputs.sort_by_key(|(_amount, script)| script.clone());
894
895    // The miner reward is always the first output independent of the sort order
896    coinbase_outputs.insert(0, (miner_reward, script));
897
898    coinbase_outputs
899}
900
901// - Transaction roots processing
902
903/// Returns the default block roots for the supplied coinbase and mempool transactions,
904/// and the supplied history tree.
905///
906/// This function runs expensive cryptographic operations.
907pub 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}