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