zebra_chain/parameters/network/
subsidy.rs

1//! Constants and calculations for Block Subsidy and Funding Streams
2//!
3//! This module contains the consensus parameters which are required for
4//! verification.
5//!
6//! Some consensus parameters change based on network upgrades. Each network
7//! upgrade happens at a particular block height. Some parameters have a value
8//! (or function) before the upgrade height, at the upgrade height, and after
9//! the upgrade height. (For example, the value of the reserved field in the
10//! block header during the Heartwood upgrade.)
11//!
12//! Typically, consensus parameters are accessed via a function that takes a
13//! `Network` and `block::Height`.
14
15use std::collections::HashMap;
16
17use lazy_static::lazy_static;
18
19use crate::{
20    amount::{self, Amount, NonNegative, COIN},
21    block::{Height, HeightDiff},
22    parameters::{constants::activation_heights, Network, NetworkUpgrade},
23    transparent,
24};
25
26/// The largest block subsidy, used before the first halving.
27///
28/// We use `25 / 2` instead of `12.5`, so that we can calculate the correct value without using floating-point.
29/// This calculation is exact, because COIN is divisible by 2, and the division is done last.
30pub const MAX_BLOCK_SUBSIDY: u64 = ((25 * COIN) / 2) as u64;
31
32/// Used as a multiplier to get the new halving interval after Blossom.
33///
34/// Calculated as `PRE_BLOSSOM_POW_TARGET_SPACING / POST_BLOSSOM_POW_TARGET_SPACING`
35/// in the Zcash specification.
36pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u32 = 2;
37
38/// Halving is at about every 4 years, before Blossom block time is 150 seconds.
39///
40/// `(60 * 60 * 24 * 365 * 4) / 150 = 840960`
41pub const PRE_BLOSSOM_HALVING_INTERVAL: HeightDiff = 840_000;
42
43/// After Blossom the block time is reduced to 75 seconds but halving period should remain around 4 years.
44pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff =
45    PRE_BLOSSOM_HALVING_INTERVAL * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
46
47/// The first halving height in the testnet is at block height `1_116_000`
48/// as specified in [protocol specification §7.10.1][7.10.1]
49///
50/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
51pub(crate) const FIRST_HALVING_TESTNET: Height = Height(1_116_000);
52
53/// The first halving height in the regtest is at block height `287`.
54const FIRST_HALVING_REGTEST: Height = Height(287);
55
56/// The funding stream receiver categories.
57#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
58pub enum FundingStreamReceiver {
59    /// The Electric Coin Company (Bootstrap Foundation) funding stream.
60    #[serde(rename = "ECC")]
61    Ecc,
62
63    /// The Zcash Foundation funding stream.
64    ZcashFoundation,
65
66    /// The Major Grants (Zcash Community Grants) funding stream.
67    MajorGrants,
68
69    /// The deferred pool contribution, see [ZIP-1015](https://zips.z.cash/zip-1015) for more details.
70    Deferred,
71}
72
73impl FundingStreamReceiver {
74    /// Returns a human-readable name and a specification URL for the receiver, as described in
75    /// [ZIP-1014] and [`zcashd`] before NU6. After NU6, the specification is in the [ZIP-1015].
76    ///
77    /// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract
78    /// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32
79    /// [ZIP-1015]: https://zips.z.cash/zip-1015
80    pub fn info(&self, is_post_nu6: bool) -> (&'static str, &'static str) {
81        if is_post_nu6 {
82            (
83                match self {
84                    FundingStreamReceiver::Ecc => "Electric Coin Company",
85                    FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
86                    FundingStreamReceiver::MajorGrants => "Zcash Community Grants NU6",
87                    FundingStreamReceiver::Deferred => "Lockbox NU6",
88                },
89                LOCKBOX_SPECIFICATION,
90            )
91        } else {
92            (
93                match self {
94                    FundingStreamReceiver::Ecc => "Electric Coin Company",
95                    FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
96                    FundingStreamReceiver::MajorGrants => "Major Grants",
97                    FundingStreamReceiver::Deferred => "Lockbox NU6",
98                },
99                FUNDING_STREAM_SPECIFICATION,
100            )
101        }
102    }
103
104    /// Returns true if this [`FundingStreamReceiver`] is [`FundingStreamReceiver::Deferred`].
105    pub fn is_deferred(&self) -> bool {
106        matches!(self, Self::Deferred)
107    }
108}
109
110/// Denominator as described in [protocol specification §7.10.1][7.10.1].
111///
112/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
113pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
114
115/// The specification for pre-NU6 funding stream receivers, a URL that links to [ZIP-214].
116///
117/// [ZIP-214]: https://zips.z.cash/zip-0214
118pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214";
119
120/// The specification for post-NU6 funding stream and lockbox receivers, a URL that links to [ZIP-1015].
121///
122/// [ZIP-1015]: https://zips.z.cash/zip-1015
123pub const LOCKBOX_SPECIFICATION: &str = "https://zips.z.cash/zip-1015";
124
125/// Funding stream recipients and height ranges.
126#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
127pub struct FundingStreams {
128    /// Start and end Heights for funding streams
129    /// as described in [protocol specification §7.10.1][7.10.1].
130    ///
131    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
132    height_range: std::ops::Range<Height>,
133    /// Funding stream recipients by [`FundingStreamReceiver`].
134    recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
135}
136
137impl FundingStreams {
138    /// Creates a new [`FundingStreams`].
139    pub fn new(
140        height_range: std::ops::Range<Height>,
141        recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
142    ) -> Self {
143        Self {
144            height_range,
145            recipients,
146        }
147    }
148
149    /// Creates a new empty [`FundingStreams`] representing no funding streams.
150    pub fn empty() -> Self {
151        Self::new(Height::MAX..Height::MAX, HashMap::new())
152    }
153
154    /// Returns height range where these [`FundingStreams`] should apply.
155    pub fn height_range(&self) -> &std::ops::Range<Height> {
156        &self.height_range
157    }
158
159    /// Returns recipients of these [`FundingStreams`].
160    pub fn recipients(&self) -> &HashMap<FundingStreamReceiver, FundingStreamRecipient> {
161        &self.recipients
162    }
163
164    /// Returns a recipient with the provided receiver.
165    pub fn recipient(&self, receiver: FundingStreamReceiver) -> Option<&FundingStreamRecipient> {
166        self.recipients.get(&receiver)
167    }
168
169    /// Accepts a target number of addresses that all recipients of this funding stream
170    /// except the [`FundingStreamReceiver::Deferred`] receiver should have.
171    ///
172    /// Extends the addresses for all funding stream recipients by repeating their
173    /// existing addresses until reaching the provided target number of addresses.
174    pub fn extend_recipient_addresses(&mut self, target_len: usize) {
175        for (receiver, recipient) in &mut self.recipients {
176            if receiver.is_deferred() {
177                continue;
178            }
179
180            recipient.extend_addresses(target_len);
181        }
182    }
183}
184
185/// A funding stream recipient as specified in [protocol specification §7.10.1][7.10.1]
186///
187/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
188#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
189pub struct FundingStreamRecipient {
190    /// The numerator for each funding stream receiver category
191    /// as described in [protocol specification §7.10.1][7.10.1].
192    ///
193    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
194    numerator: u64,
195    /// Addresses for the funding stream recipient
196    addresses: Vec<transparent::Address>,
197}
198
199impl FundingStreamRecipient {
200    /// Creates a new [`FundingStreamRecipient`].
201    pub fn new<I, T>(numerator: u64, addresses: I) -> Self
202    where
203        T: ToString,
204        I: IntoIterator<Item = T>,
205    {
206        Self {
207            numerator,
208            addresses: addresses
209                .into_iter()
210                .map(|addr| {
211                    let addr = addr.to_string();
212                    addr.parse()
213                        .expect("funding stream address must deserialize")
214                })
215                .collect(),
216        }
217    }
218
219    /// Returns the numerator for this funding stream.
220    pub fn numerator(&self) -> u64 {
221        self.numerator
222    }
223
224    /// Returns the receiver of this funding stream.
225    pub fn addresses(&self) -> &[transparent::Address] {
226        &self.addresses
227    }
228
229    /// Accepts a target number of addresses that this recipient should have.
230    ///
231    /// Extends the addresses for this funding stream recipient by repeating
232    /// existing addresses until reaching the provided target number of addresses.
233    ///
234    /// # Panics
235    ///
236    /// If there are no recipient addresses.
237    pub fn extend_addresses(&mut self, target_len: usize) {
238        assert!(
239            !self.addresses.is_empty(),
240            "cannot extend addresses for empty recipient"
241        );
242
243        self.addresses = self
244            .addresses
245            .iter()
246            .cycle()
247            .take(target_len)
248            .cloned()
249            .collect();
250    }
251}
252
253lazy_static! {
254    /// The funding streams for Mainnet as described in:
255    /// - [protocol specification §7.10.1][7.10.1]
256    /// - [ZIP-1015](https://zips.z.cash/zip-1015)
257    /// - [ZIP-214#funding-streams](https://zips.z.cash/zip-0214#funding-streams)
258    ///
259    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
260    pub static ref FUNDING_STREAMS_MAINNET: Vec<FundingStreams> = vec![
261        FundingStreams {
262            height_range: Height(1_046_400)..Height(2_726_400),
263            recipients: [
264                (
265                    FundingStreamReceiver::Ecc,
266                    FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET),
267                ),
268                (
269                    FundingStreamReceiver::ZcashFoundation,
270                    FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET),
271                ),
272                (
273                    FundingStreamReceiver::MajorGrants,
274                    FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET),
275                ),
276            ]
277            .into_iter()
278            .collect(),
279        },
280        FundingStreams {
281            height_range: POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET,
282            recipients: [
283                (
284                    FundingStreamReceiver::Deferred,
285                    FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
286                ),
287                (
288                    FundingStreamReceiver::MajorGrants,
289                    FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_MAINNET),
290                ),
291            ]
292            .into_iter()
293            .collect(),
294        },
295
296        FundingStreams {
297            height_range: activation_heights::mainnet::NU6_1..Height(4_406_400),
298            recipients: [
299                (
300                    FundingStreamReceiver::Deferred,
301                    FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
302                ),
303                (
304                    FundingStreamReceiver::MajorGrants,
305                    FundingStreamRecipient::new(8, POST_NU6_1_FUNDING_STREAM_FPF_ADDRESSES_MAINNET),
306                ),
307            ]
308            .into_iter()
309            .collect(),
310        },
311    ];
312
313    /// The funding streams for Testnet as described in:
314    /// - [protocol specification §7.10.1][7.10.1]
315    /// - [ZIP-1015](https://zips.z.cash/zip-1015)
316    /// - [ZIP-214#funding-streams](https://zips.z.cash/zip-0214#funding-streams)
317    ///
318    /// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
319    pub static ref FUNDING_STREAMS_TESTNET: Vec<FundingStreams> = vec![
320        FundingStreams {
321            height_range: Height(1_028_500)..Height(2_796_000),
322            recipients: [
323                (
324                    FundingStreamReceiver::Ecc,
325                    FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET),
326                ),
327                (
328                    FundingStreamReceiver::ZcashFoundation,
329                    FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET),
330                ),
331                (
332                    FundingStreamReceiver::MajorGrants,
333                    FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET),
334                ),
335            ]
336            .into_iter()
337            .collect(),
338        },
339        FundingStreams {
340            height_range: POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET,
341            recipients: [
342                (
343                    FundingStreamReceiver::Deferred,
344                    FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
345                ),
346                (
347                    FundingStreamReceiver::MajorGrants,
348                    FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET),
349                ),
350            ]
351            .into_iter()
352            .collect(),
353        },
354        FundingStreams {
355            height_range: activation_heights::testnet::NU6_1..Height(4_476_000),
356            recipients: [
357                (
358                    FundingStreamReceiver::Deferred,
359                    FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
360                ),
361                (
362                    FundingStreamReceiver::MajorGrants,
363                    FundingStreamRecipient::new(8, POST_NU6_1_FUNDING_STREAM_FPF_ADDRESSES_TESTNET),
364                ),
365            ]
366            .into_iter()
367            .collect(),
368        },
369    ];
370}
371
372/// The start height of post-NU6 funding streams on Mainnet as described in [ZIP-1015](https://zips.z.cash/zip-1015).
373const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;
374
375/// The start height of post-NU6 funding streams on Testnet as described in [ZIP-1015](https://zips.z.cash/zip-1015).
376const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_976_000;
377
378/// The one-time lockbox disbursement output addresses and amounts expected in the NU6.1 activation block's
379/// coinbase transaction on Mainnet.
380/// See:
381/// - <https://zips.z.cash/zip-0271#one-timelockboxdisbursement>
382/// - <https://zips.z.cash/zip-0214#mainnet-recipients-for-revision-2>
383pub const NU6_1_LOCKBOX_DISBURSEMENTS_MAINNET: [(&str, Amount<NonNegative>); 10] = [(
384    "t3ev37Q2uL1sfTsiJQJiWJoFzQpDhmnUwYo",
385    EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_MAINNET.div_exact(10),
386); 10];
387
388/// The one-time lockbox disbursement output addresses and amounts expected in the NU6.1 activation block's
389/// coinbase transaction on Testnet.
390/// See:
391/// - <https://zips.z.cash/zip-0271#one-timelockboxdisbursement>
392/// - <https://zips.z.cash/zip-0214#testnet-recipients-for-revision-2>
393pub const NU6_1_LOCKBOX_DISBURSEMENTS_TESTNET: [(&str, Amount<NonNegative>); 10] = [(
394    "t2RnBRiqrN1nW4ecZs1Fj3WWjNdnSs4kiX8",
395    EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_TESTNET.div_exact(10),
396); 10];
397
398/// The expected total amount of the one-time lockbox disbursement on Mainnet.
399/// See: <https://zips.z.cash/zip-0271#one-timelockboxdisbursement>.
400pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_MAINNET: Amount<NonNegative> =
401    Amount::new_from_zec(78_750);
402
403/// The expected total amount of the one-time lockbox disbursement on Testnet.
404/// See <https://zips.z.cash/zip-0271#one-timelockboxdisbursement>.
405pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_TESTNET: Amount<NonNegative> =
406    Amount::new_from_zec(78_750);
407
408/// The number of blocks contained in the post-NU6 funding streams height ranges on Mainnet or Testnet, as specified
409/// in [ZIP-1015](https://zips.z.cash/zip-1015).
410const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;
411
412/// The post-NU6 funding stream height range on Mainnet
413const POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET: std::ops::Range<Height> =
414    Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET)
415        ..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
416
417/// The post-NU6 funding stream height range on Testnet
418const POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET: std::ops::Range<Height> =
419    Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET)
420        ..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
421
422/// Address change interval function here as a constant
423/// as described in [protocol specification §7.10.1][7.10.1].
424///
425/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
426pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: HeightDiff = POST_BLOSSOM_HALVING_INTERVAL / 48;
427
428/// Number of addresses for each funding stream in the Mainnet.
429/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
430/// however we know this value beforehand so we prefer to make it a constant instead.
431///
432/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
433pub const FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 48;
434
435/// List of addresses for the ECC funding stream in the Mainnet.
436pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = [
437    "t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif",
438    "t3Toxk1vJQ6UjWQ42tUJz2rV2feUWkpbTDs",
439    "t3ZBdBe4iokmsjdhMuwkxEdqMCFN16YxKe6",
440    "t3ZuaJziLM8xZ32rjDUzVjVtyYdDSz8GLWB",
441    "t3bAtYWa4bi8VrtvqySxnbr5uqcG9czQGTZ",
442    "t3dktADfb5Rmxncpe1HS5BRS5Gcj7MZWYBi",
443    "t3hgskquvKKoCtvxw86yN7q8bzwRxNgUZmc",
444    "t3R1VrLzwcxAZzkX4mX3KGbWpNsgtYtMntj",
445    "t3ff6fhemqPMVujD3AQurxRxTdvS1pPSaa2",
446    "t3cEUQFG3KYnFG6qYhPxSNgGi3HDjUPwC3J",
447    "t3WR9F5U4QvUFqqx9zFmwT6xFqduqRRXnaa",
448    "t3PYc1LWngrdUrJJbHkYPCKvJuvJjcm85Ch",
449    "t3bgkjiUeatWNkhxY3cWyLbTxKksAfk561R",
450    "t3Z5rrR8zahxUpZ8itmCKhMSfxiKjUp5Dk5",
451    "t3PU1j7YW3fJ67jUbkGhSRto8qK2qXCUiW3",
452    "t3S3yaT7EwNLaFZCamfsxxKwamQW2aRGEkh",
453    "t3eutXKJ9tEaPSxZpmowhzKhPfJvmtwTEZK",
454    "t3gbTb7brxLdVVghSPSd3ycGxzHbUpukeDm",
455    "t3UCKW2LrHFqPMQFEbZn6FpjqnhAAbfpMYR",
456    "t3NyHsrnYbqaySoQqEQRyTWkjvM2PLkU7Uu",
457    "t3QEFL6acxuZwiXtW3YvV6njDVGjJ1qeaRo",
458    "t3PdBRr2S1XTDzrV8bnZkXF3SJcrzHWe1wj",
459    "t3ZWyRPpWRo23pKxTLtWsnfEKeq9T4XPxKM",
460    "t3he6QytKCTydhpztykFsSsb9PmBT5JBZLi",
461    "t3VWxWDsLb2TURNEP6tA1ZSeQzUmPKFNxRY",
462    "t3NmWLvZkbciNAipauzsFRMxoZGqmtJksbz",
463    "t3cKr4YxVPvPBG1mCvzaoTTdBNokohsRJ8n",
464    "t3T3smGZn6BoSFXWWXa1RaoQdcyaFjMfuYK",
465    "t3gkDUe9Gm4GGpjMk86TiJZqhztBVMiUSSA",
466    "t3eretuBeBXFHe5jAqeSpUS1cpxVh51fAeb",
467    "t3dN8g9zi2UGJdixGe9txeSxeofLS9t3yFQ",
468    "t3S799pq9sYBFwccRecoTJ3SvQXRHPrHqvx",
469    "t3fhYnv1S5dXwau7GED3c1XErzt4n4vDxmf",
470    "t3cmE3vsBc5xfDJKXXZdpydCPSdZqt6AcNi",
471    "t3h5fPdjJVHaH4HwynYDM5BB3J7uQaoUwKi",
472    "t3Ma35c68BgRX8sdLDJ6WR1PCrKiWHG4Da9",
473    "t3LokMKPL1J8rkJZvVpfuH7dLu6oUWqZKQK",
474    "t3WFFGbEbhJWnASZxVLw2iTJBZfJGGX73mM",
475    "t3L8GLEsUn4QHNaRYcX3EGyXmQ8kjpT1zTa",
476    "t3PgfByBhaBSkH8uq4nYJ9ZBX4NhGCJBVYm",
477    "t3WecsqKDhWXD4JAgBVcnaCC2itzyNZhJrv",
478    "t3ZG9cSfopnsMQupKW5v9sTotjcP5P6RTbn",
479    "t3hC1Ywb5zDwUYYV8LwhvF5rZ6m49jxXSG5",
480    "t3VgMqDL15ZcyQDeqBsBW3W6rzfftrWP2yB",
481    "t3LC94Y6BwLoDtBoK2NuewaEbnko1zvR9rm",
482    "t3cWCUZJR3GtALaTcatrrpNJ3MGbMFVLRwQ",
483    "t3YYF4rPLVxDcF9hHFsXyc5Yq1TFfbojCY6",
484    "t3XHAGxRP2FNfhAjxGjxbrQPYtQQjc3RCQD",
485];
486
487/// Functionality specific to block subsidy-related consensus rules
488pub trait ParameterSubsidy {
489    /// Returns the minimum height after the first halving
490    /// as described in [protocol specification §7.10][7.10]
491    ///
492    /// [7.10]: <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
493    fn height_for_first_halving(&self) -> Height;
494
495    /// Returns the halving interval after Blossom
496    fn post_blossom_halving_interval(&self) -> HeightDiff;
497
498    /// Returns the halving interval before Blossom
499    fn pre_blossom_halving_interval(&self) -> HeightDiff;
500
501    /// Returns the address change interval for funding streams
502    /// as described in [protocol specification §7.10][7.10].
503    ///
504    /// > FSRecipientChangeInterval := PostBlossomHalvingInterval / 48
505    ///
506    /// [7.10]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
507    fn funding_stream_address_change_interval(&self) -> HeightDiff;
508}
509
510/// Network methods related to Block Subsidy and Funding Streams
511impl ParameterSubsidy for Network {
512    fn height_for_first_halving(&self) -> Height {
513        // First halving on Mainnet is at Canopy
514        // while in Testnet is at block constant height of `1_116_000`
515        // <https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams>
516        match self {
517            Network::Mainnet => NetworkUpgrade::Canopy
518                .activation_height(self)
519                .expect("canopy activation height should be available"),
520            Network::Testnet(params) => {
521                if params.is_regtest() {
522                    FIRST_HALVING_REGTEST
523                } else if params.is_default_testnet() {
524                    FIRST_HALVING_TESTNET
525                } else {
526                    height_for_halving(1, self).expect("first halving height should be available")
527                }
528            }
529        }
530    }
531
532    fn post_blossom_halving_interval(&self) -> HeightDiff {
533        match self {
534            Network::Mainnet => POST_BLOSSOM_HALVING_INTERVAL,
535            Network::Testnet(params) => params.post_blossom_halving_interval(),
536        }
537    }
538
539    fn pre_blossom_halving_interval(&self) -> HeightDiff {
540        match self {
541            Network::Mainnet => PRE_BLOSSOM_HALVING_INTERVAL,
542            Network::Testnet(params) => params.pre_blossom_halving_interval(),
543        }
544    }
545
546    fn funding_stream_address_change_interval(&self) -> HeightDiff {
547        self.post_blossom_halving_interval() / 48
548    }
549}
550
551/// List of addresses for the Zcash Foundation funding stream in the Mainnet.
552pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
553    ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
554
555/// List of addresses for the Major Grants funding stream in the Mainnet.
556pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
557    ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
558
559/// Number of addresses for each post-NU6 funding stream on Mainnet.
560/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
561/// however we know this value beforehand so we prefer to make it a constant instead.
562///
563/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
564pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 12;
565
566/// List of addresses for the Major Grants post-NU6 funding stream on Mainnet administered by the Financial Privacy Fund (FPF).
567pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_MAINNET: [&str;
568    POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
569    ["t3cFfPt1Bcvgez9ZbMBFWeZsskxTkPzGCow"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
570
571/// Number of addresses for each post-NU6.1 funding stream on Mainnet.
572/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
573/// however we know this value beforehand so we prefer to make it a constant instead.
574///
575/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
576pub const POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 36;
577
578/// List of addresses for the Major Grants post-NU6.1 funding stream on Mainnet administered by the Financial Privacy Fund (FPF).
579pub const POST_NU6_1_FUNDING_STREAM_FPF_ADDRESSES_MAINNET: [&str;
580    POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
581    ["t3cFfPt1Bcvgez9ZbMBFWeZsskxTkPzGCow"; POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
582
583/// Number of addresses for each funding stream in the Testnet.
584/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
585/// however we know this value beforehand so we prefer to make it a constant instead.
586///
587/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
588pub const FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 51;
589
590/// List of addresses for the ECC funding stream in the Testnet.
591pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = [
592    "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
593    "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
594    "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
595    "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
596    "t2NNHrgPpE388atmWSF4DxAb3xAoW5Yp45M",
597    "t2VMN28itPyMeMHBEd9Z1hm6YLkQcGA1Wwe",
598    "t2CHa1TtdfUV8UYhNm7oxbzRyfr8616BYh2",
599    "t2F77xtr28U96Z2bC53ZEdTnQSUAyDuoa67",
600    "t2ARrzhbgcpoVBDPivUuj6PzXzDkTBPqfcT",
601    "t278aQ8XbvFR15mecRguiJDQQVRNnkU8kJw",
602    "t2Dp1BGnZsrTXZoEWLyjHmg3EPvmwBnPDGB",
603    "t2KzeqXgf4ju33hiSqCuKDb8iHjPCjMq9iL",
604    "t2Nyxqv1BiWY1eUSiuxVw36oveawYuo18tr",
605    "t2DKFk5JRsVoiuinK8Ti6eM4Yp7v8BbfTyH",
606    "t2CUaBca4k1x36SC4q8Nc8eBoqkMpF3CaLg",
607    "t296SiKL7L5wvFmEdMxVLz1oYgd6fTfcbZj",
608    "t29fBCFbhgsjL3XYEZ1yk1TUh7eTusB6dPg",
609    "t2FGofLJXa419A76Gpf5ncxQB4gQXiQMXjK",
610    "t2ExfrnRVnRiXDvxerQ8nZbcUQvNvAJA6Qu",
611    "t28JUffLp47eKPRHKvwSPzX27i9ow8LSXHx",
612    "t2JXWPtrtyL861rFWMZVtm3yfgxAf4H7uPA",
613    "t2QdgbJoWfYHgyvEDEZBjHmgkr9yNJff3Hi",
614    "t2QW43nkco8r32ZGRN6iw6eSzyDjkMwCV3n",
615    "t2DgYDXMJTYLwNcxighQ9RCgPxMVATRcUdC",
616    "t2Bop7dg33HGZx3wunnQzi2R2ntfpjuti3M",
617    "t2HVeEwovcLq9RstAbYkqngXNEsCe2vjJh9",
618    "t2HxbP5keQSx7p592zWQ5bJ5GrMmGDsV2Xa",
619    "t2TJzUg2matao3mztBRJoWnJY6ekUau6tPD",
620    "t29pMzxmo6wod25YhswcjKv3AFRNiBZHuhj",
621    "t2QBQMRiJKYjshJpE6RhbF7GLo51yE6d4wZ",
622    "t2F5RqnqguzZeiLtYHFx4yYfy6pDnut7tw5",
623    "t2CHvyZANE7XCtg8AhZnrcHCC7Ys1jJhK13",
624    "t2BRzpMdrGWZJ2upsaNQv6fSbkbTy7EitLo",
625    "t2BFixHGQMAWDY67LyTN514xRAB94iEjXp3",
626    "t2Uvz1iVPzBEWfQBH1p7NZJsFhD74tKaG8V",
627    "t2CmFDj5q6rJSRZeHf1SdrowinyMNcj438n",
628    "t2ErNvWEReTfPDBaNizjMPVssz66aVZh1hZ",
629    "t2GeJQ8wBUiHKDVzVM5ZtKfY5reCg7CnASs",
630    "t2L2eFtkKv1G6j55kLytKXTGuir4raAy3yr",
631    "t2EK2b87dpPazb7VvmEGc8iR6SJ289RywGL",
632    "t2DJ7RKeZJxdA4nZn8hRGXE8NUyTzjujph9",
633    "t2K1pXo4eByuWpKLkssyMLe8QKUbxnfFC3H",
634    "t2TB4mbSpuAcCWkH94Leb27FnRxo16AEHDg",
635    "t2Phx4gVL4YRnNsH3jM1M7jE4Fo329E66Na",
636    "t2VQZGmeNomN8c3USefeLL9nmU6M8x8CVzC",
637    "t2RicCvTVTY5y9JkreSRv3Xs8q2K67YxHLi",
638    "t2JrSLxTGc8wtPDe9hwbaeUjCrCfc4iZnDD",
639    "t2Uh9Au1PDDSw117sAbGivKREkmMxVC5tZo",
640    "t2FDwoJKLeEBMTy3oP7RLQ1Fihhvz49a3Bv",
641    "t2FY18mrgtb7QLeHA8ShnxLXuW8cNQ2n1v8",
642    "t2L15TkDYum7dnQRBqfvWdRe8Yw3jVy9z7g",
643];
644
645/// List of addresses for the Zcash Foundation funding stream in the Testnet.
646pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
647    ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
648
649/// List of addresses for the Major Grants funding stream in the Testnet.
650pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
651    ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
652
653/// Number of addresses for each post-NU6 funding stream in the Testnet.
654/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
655/// however we know this value beforehand so we prefer to make it a constant instead.
656///
657/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
658pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;
659
660/// Number of addresses for each post-NU6 funding stream in the Testnet.
661/// In the spec ([protocol specification §7.10][7.10]) this is defined as: `fs.addressindex(fs.endheight - 1)`
662/// however we know this value beforehand so we prefer to make it a constant instead.
663///
664/// There are 27 funding stream periods across the 939,500 blocks for which the post-NU6.1 funding streams are
665/// active. See Testnet funding streams in revision 2 of <https://zips.z.cash/zip-0214#funding-streams>.
666///
667/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
668pub const POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 27;
669
670/// List of addresses for the Major Grants post-NU6 funding stream on Testnet administered by the Financial Privacy Fund (FPF).
671pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET: [&str;
672    POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
673    ["t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
674
675/// List of addresses for the Major Grants post-NU6.1 funding stream on Testnet administered by the Financial Privacy Fund (FPF).
676pub const POST_NU6_1_FUNDING_STREAM_FPF_ADDRESSES_TESTNET: [&str;
677    POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
678    ["t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"; POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
679
680/// Returns the address change period
681/// as described in [protocol specification §7.10][7.10]
682///
683/// [7.10]: https://zips.z.cash/protocol/protocol.pdf#fundingstreams
684pub fn funding_stream_address_period<N: ParameterSubsidy>(height: Height, network: &N) -> u32 {
685    // Spec equation: `address_period = floor((height - (height_for_halving(1) - post_blossom_halving_interval))/funding_stream_address_change_interval)`,
686    // <https://zips.z.cash/protocol/protocol.pdf#fundingstreams>
687    //
688    // Note that the brackets make it so the post blossom halving interval is added to the total.
689    //
690    // In Rust, "integer division rounds towards zero":
691    // <https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators>
692    // This is the same as `floor()`, because these numbers are all positive.
693
694    let height_after_first_halving = height - network.height_for_first_halving();
695
696    let address_period = (height_after_first_halving + network.post_blossom_halving_interval())
697        / network.funding_stream_address_change_interval();
698
699    address_period
700        .try_into()
701        .expect("all values are positive and smaller than the input height")
702}
703
704/// The first block height of the halving at the provided halving index for a network.
705///
706/// See `Halving(height)`, as described in [protocol specification §7.8][7.8]
707///
708/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
709pub fn height_for_halving(halving: u32, network: &Network) -> Option<Height> {
710    if halving == 0 {
711        return Some(Height(0));
712    }
713
714    let slow_start_shift = i64::from(network.slow_start_shift().0);
715    let blossom_height = i64::from(NetworkUpgrade::Blossom.activation_height(network)?.0);
716    let pre_blossom_halving_interval = network.pre_blossom_halving_interval();
717    let halving_index = i64::from(halving);
718
719    let unscaled_height = halving_index.checked_mul(pre_blossom_halving_interval)?;
720
721    let pre_blossom_height = unscaled_height
722        .min(blossom_height)
723        .checked_add(slow_start_shift)?;
724
725    let post_blossom_height = 0
726        .max(unscaled_height - blossom_height)
727        .checked_mul(i64::from(BLOSSOM_POW_TARGET_SPACING_RATIO))?
728        .checked_add(slow_start_shift)?;
729
730    let height = pre_blossom_height.checked_add(post_blossom_height)?;
731
732    let height = u32::try_from(height).ok()?;
733    height.try_into().ok()
734}
735
736/// Returns the `fs.Value(height)` for each stream receiver
737/// as described in [protocol specification §7.8][7.8]
738///
739/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
740pub fn funding_stream_values(
741    height: Height,
742    network: &Network,
743    expected_block_subsidy: Amount<NonNegative>,
744) -> Result<HashMap<FundingStreamReceiver, Amount<NonNegative>>, crate::amount::Error> {
745    let canopy_height = NetworkUpgrade::Canopy.activation_height(network).unwrap();
746    let mut results = HashMap::new();
747
748    if height >= canopy_height {
749        let funding_streams = network.funding_streams(height);
750        if let Some(funding_streams) = funding_streams {
751            for (&receiver, recipient) in funding_streams.recipients() {
752                // - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`:
753                //   https://zips.z.cash/protocol/protocol.pdf#subsidies
754                // - In Rust, "integer division rounds towards zero":
755                //   https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
756                //   This is the same as `floor()`, because these numbers are all positive.
757                let amount_value = ((expected_block_subsidy * recipient.numerator())?
758                    / FUNDING_STREAM_RECEIVER_DENOMINATOR)?;
759
760                results.insert(receiver, amount_value);
761            }
762        }
763    }
764
765    Ok(results)
766}
767
768/// Block subsidy errors.
769#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)]
770#[allow(missing_docs)]
771pub enum SubsidyError {
772    #[error("no coinbase transaction in block")]
773    NoCoinbase,
774
775    #[error("funding stream expected output not found")]
776    FundingStreamNotFound,
777
778    #[error("one-time lockbox disbursement output not found")]
779    OneTimeLockboxDisbursementNotFound,
780
781    #[error("miner fees are invalid")]
782    InvalidMinerFees,
783
784    #[error("a sum of amounts overflowed")]
785    SumOverflow,
786
787    #[error("unsupported height")]
788    UnsupportedHeight,
789
790    #[error("invalid amount")]
791    InvalidAmount(#[from] amount::Error),
792}
793
794/// The divisor used for halvings.
795///
796/// `1 << Halving(height)`, as described in [protocol specification §7.8][7.8]
797///
798/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
799///
800/// Returns `None` if the divisor would overflow a `u64`.
801pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
802    // Some far-future shifts can be more than 63 bits
803    1u64.checked_shl(num_halvings(height, network))
804}
805
806/// The halving index for a block height and network.
807///
808/// `Halving(height)`, as described in [protocol specification §7.8][7.8]
809///
810/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
811pub fn num_halvings(height: Height, network: &Network) -> u32 {
812    let slow_start_shift = network.slow_start_shift();
813    let blossom_height = NetworkUpgrade::Blossom
814        .activation_height(network)
815        .expect("blossom activation height should be available");
816
817    let halving_index = if height < slow_start_shift {
818        0
819    } else if height < blossom_height {
820        let pre_blossom_height = height - slow_start_shift;
821        pre_blossom_height / network.pre_blossom_halving_interval()
822    } else {
823        let pre_blossom_height = blossom_height - slow_start_shift;
824        let scaled_pre_blossom_height =
825            pre_blossom_height * HeightDiff::from(BLOSSOM_POW_TARGET_SPACING_RATIO);
826
827        let post_blossom_height = height - blossom_height;
828
829        (scaled_pre_blossom_height + post_blossom_height) / network.post_blossom_halving_interval()
830    };
831
832    halving_index
833        .try_into()
834        .expect("already checked for negatives")
835}
836
837/// `BlockSubsidy(height)` as described in [protocol specification §7.8][7.8]
838///
839/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
840pub fn block_subsidy(
841    height: Height,
842    network: &Network,
843) -> Result<Amount<NonNegative>, SubsidyError> {
844    let blossom_height = NetworkUpgrade::Blossom
845        .activation_height(network)
846        .expect("blossom activation height should be available");
847
848    // If the halving divisor is larger than u64::MAX, the block subsidy is zero,
849    // because amounts fit in an i64.
850    //
851    // Note: bitcoind incorrectly wraps here, which restarts large block rewards.
852    let Some(halving_div) = halving_divisor(height, network) else {
853        return Ok(Amount::zero());
854    };
855
856    // Zebra doesn't need to calculate block subsidies for blocks with heights in the slow start
857    // interval because it handles those blocks through checkpointing.
858    if height < network.slow_start_interval() {
859        Err(SubsidyError::UnsupportedHeight)
860    } else if height < blossom_height {
861        // this calculation is exact, because the halving divisor is 1 here
862        Ok(Amount::try_from(MAX_BLOCK_SUBSIDY / halving_div)?)
863    } else {
864        let scaled_max_block_subsidy =
865            MAX_BLOCK_SUBSIDY / u64::from(BLOSSOM_POW_TARGET_SPACING_RATIO);
866        // in future halvings, this calculation might not be exact
867        // Amount division is implemented using integer division,
868        // which truncates (rounds down) the result, as specified
869        Ok(Amount::try_from(scaled_max_block_subsidy / halving_div)?)
870    }
871}
872
873/// `MinerSubsidy(height)` as described in [protocol specification §7.8][7.8]
874///
875/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
876pub fn miner_subsidy(
877    height: Height,
878    network: &Network,
879    expected_block_subsidy: Amount<NonNegative>,
880) -> Result<Amount<NonNegative>, amount::Error> {
881    let total_funding_stream_amount: Result<Amount<NonNegative>, _> =
882        funding_stream_values(height, network, expected_block_subsidy)?
883            .values()
884            .sum();
885
886    expected_block_subsidy - total_funding_stream_amount?
887}