1use 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
26pub const MAX_BLOCK_SUBSIDY: u64 = ((25 * COIN) / 2) as u64;
31
32pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u32 = 2;
37
38pub const PRE_BLOSSOM_HALVING_INTERVAL: HeightDiff = 840_000;
42
43pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff =
45 PRE_BLOSSOM_HALVING_INTERVAL * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
46
47pub(crate) const FIRST_HALVING_TESTNET: Height = Height(1_116_000);
52
53const FIRST_HALVING_REGTEST: Height = Height(287);
55
56#[derive(Serialize, Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
58pub enum FundingStreamReceiver {
59 #[serde(rename = "ECC")]
61 Ecc,
62
63 ZcashFoundation,
65
66 MajorGrants,
68
69 Deferred,
71}
72
73impl FundingStreamReceiver {
74 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 pub fn is_deferred(&self) -> bool {
106 matches!(self, Self::Deferred)
107 }
108}
109
110pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
114
115pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214";
119
120pub const LOCKBOX_SPECIFICATION: &str = "https://zips.z.cash/zip-1015";
124
125#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
127pub struct FundingStreams {
128 height_range: std::ops::Range<Height>,
133 recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
135}
136
137impl FundingStreams {
138 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 pub fn empty() -> Self {
151 Self::new(Height::MAX..Height::MAX, HashMap::new())
152 }
153
154 pub fn height_range(&self) -> &std::ops::Range<Height> {
156 &self.height_range
157 }
158
159 pub fn recipients(&self) -> &HashMap<FundingStreamReceiver, FundingStreamRecipient> {
161 &self.recipients
162 }
163
164 pub fn recipient(&self, receiver: FundingStreamReceiver) -> Option<&FundingStreamRecipient> {
166 self.recipients.get(&receiver)
167 }
168
169 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#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
189pub struct FundingStreamRecipient {
190 numerator: u64,
195 addresses: Vec<transparent::Address>,
197}
198
199impl FundingStreamRecipient {
200 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 pub fn numerator(&self) -> u64 {
221 self.numerator
222 }
223
224 pub fn addresses(&self) -> &[transparent::Address] {
226 &self.addresses
227 }
228
229 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 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 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
372const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;
374
375const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_976_000;
377
378pub 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
388pub 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
398pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_MAINNET: Amount<NonNegative> =
401 Amount::new_from_zec(78_750);
402
403pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_TESTNET: Amount<NonNegative> =
406 Amount::new_from_zec(78_750);
407
408const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;
411
412const 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
417const 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
422pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: HeightDiff = POST_BLOSSOM_HALVING_INTERVAL / 48;
427
428pub const FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 48;
434
435pub 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
487pub trait ParameterSubsidy {
489 fn height_for_first_halving(&self) -> Height;
494
495 fn post_blossom_halving_interval(&self) -> HeightDiff;
497
498 fn pre_blossom_halving_interval(&self) -> HeightDiff;
500
501 fn funding_stream_address_change_interval(&self) -> HeightDiff;
508}
509
510impl ParameterSubsidy for Network {
512 fn height_for_first_halving(&self) -> Height {
513 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
551pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
553 ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
554
555pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
557 ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
558
559pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 12;
565
566pub 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
571pub const POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 36;
577
578pub 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
583pub const FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 51;
589
590pub 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
645pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
647 ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
648
649pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
651 ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
652
653pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;
659
660pub const POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 27;
669
670pub 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
675pub 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
680pub fn funding_stream_address_period<N: ParameterSubsidy>(height: Height, network: &N) -> u32 {
685 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
704pub 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
736pub 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 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#[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
794pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
802 1u64.checked_shl(num_halvings(height, network))
804}
805
806pub 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
837pub 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 let Some(halving_div) = halving_divisor(height, network) else {
853 return Ok(Amount::zero());
854 };
855
856 if height < network.slow_start_interval() {
859 Err(SubsidyError::UnsupportedHeight)
860 } else if height < blossom_height {
861 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 Ok(Amount::try_from(scaled_max_block_subsidy / halving_div)?)
870 }
871}
872
873pub 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}