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