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::{Network, NetworkUpgrade, NU6_1_ACTIVATION_HEIGHT_TESTNET},
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 #[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#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
190pub struct FundingStreamRecipient {
191 numerator: u64,
196 addresses: Vec<transparent::Address>,
198}
199
200impl FundingStreamRecipient {
201 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 pub fn numerator(&self) -> u64 {
222 self.numerator
223 }
224
225 pub fn addresses(&self) -> &[transparent::Address] {
227 &self.addresses
228 }
229
230 #[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 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 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
358const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;
360
361const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_976_000;
363
364pub const NU6_1_LOCKBOX_DISBURSEMENTS_MAINNET: [(&str, Amount<NonNegative>); 0] = [];
370
371pub 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
381pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_MAINNET: Amount<NonNegative> =
384 Amount::new_from_zec(78_750);
385
386pub(crate) const EXPECTED_NU6_1_LOCKBOX_DISBURSEMENTS_TOTAL_TESTNET: Amount<NonNegative> =
389 Amount::new_from_zec(78_750);
390
391const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;
394
395const 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
400const 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
405pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: HeightDiff = POST_BLOSSOM_HALVING_INTERVAL / 48;
410
411pub const FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 48;
417
418pub 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
470pub trait ParameterSubsidy {
472 fn height_for_first_halving(&self) -> Height;
477
478 fn post_blossom_halving_interval(&self) -> HeightDiff;
480
481 fn pre_blossom_halving_interval(&self) -> HeightDiff;
483
484 fn funding_stream_address_change_interval(&self) -> HeightDiff;
491}
492
493impl ParameterSubsidy for Network {
495 fn height_for_first_halving(&self) -> Height {
496 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
534pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
536 ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
537
538pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
540 ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
541
542pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 12;
548
549pub 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
554pub const FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 51;
560
561pub 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
616pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
618 ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
619
620pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
622 ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
623
624pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;
630
631pub const POST_NU6_1_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 27;
637
638pub 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
643pub 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
648pub fn funding_stream_address_period<N: ParameterSubsidy>(height: Height, network: &N) -> u32 {
653 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
672pub 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
716pub 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 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#[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
774pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
782 1u64.checked_shl(num_halvings(height, network))
784}
785
786pub 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
817pub 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 let Some(halving_div) = halving_divisor(height, network) else {
833 return Ok(Amount::zero());
834 };
835
836 if height < network.slow_start_interval() {
839 Err(SubsidyError::UnsupportedHeight)
840 } else if height < blossom_height {
841 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 Ok(Amount::try_from(scaled_max_block_subsidy / halving_div)?)
850 }
851}
852
853pub 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}