• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacks-network / stacks-core / 26250451051-1

21 May 2026 08:11PM UTC coverage: 85.585% (-0.1%) from 85.712%
26250451051-1

Pull #7215

github

ec9d4c
web-flow
Merge 9487bf852 into af1280aac
Pull Request #7215: Chore: fix flake in non_blocking_minority_configured_to_favour_...

188844 of 220651 relevant lines covered (85.58%)

18975267.44 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.91
/stacks-common/src/types/mod.rs
1
// Copyright (C) 2013-2020 Blockstack PBC, a public benefit corporation
2
// Copyright (C) 2020-2026 Stacks Open Internet Foundation
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16

17
use std::cmp::Ordering;
18
use std::fmt;
19
use std::io::{Read, Write};
20
use std::ops::{Deref, DerefMut, Index, IndexMut};
21
use std::str::FromStr;
22
use std::sync::LazyLock;
23

24
#[cfg(feature = "rusqlite")]
25
pub mod sqlite;
26

27
use crate::address::c32::{c32_address, c32_address_decode};
28
use crate::address::{
29
    public_keys_to_address_hash, to_bits_p2pkh, AddressHashMode,
30
    C32_ADDRESS_VERSION_MAINNET_MULTISIG, C32_ADDRESS_VERSION_MAINNET_SINGLESIG,
31
    C32_ADDRESS_VERSION_TESTNET_MULTISIG, C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
32
};
33
use crate::codec::{read_next, write_next, Error as CodecError, StacksMessageCodec};
34
use crate::consts::{
35
    MICROSTACKS_PER_STACKS, PEER_VERSION_EPOCH_1_0, PEER_VERSION_EPOCH_2_0,
36
    PEER_VERSION_EPOCH_2_05, PEER_VERSION_EPOCH_2_1, PEER_VERSION_EPOCH_2_2,
37
    PEER_VERSION_EPOCH_2_3, PEER_VERSION_EPOCH_2_4, PEER_VERSION_EPOCH_2_5, PEER_VERSION_EPOCH_3_0,
38
    PEER_VERSION_EPOCH_3_1, PEER_VERSION_EPOCH_3_2, PEER_VERSION_EPOCH_3_3, PEER_VERSION_EPOCH_3_4,
39
};
40
use crate::types::chainstate::{StacksAddress, StacksPublicKey};
41
use crate::util::hash::Hash160;
42
use crate::util::secp256k1::{MessageSignature, Secp256k1PublicKey};
43

44
pub mod chainstate;
45
pub mod net;
46

47
#[cfg(test)]
48
pub mod tests;
49

50
/// A container for public keys (compressed secp256k1 public keys)
51
pub struct StacksPublicKeyBuffer(pub [u8; 33]);
52
impl_array_newtype!(StacksPublicKeyBuffer, u8, 33);
53
impl_array_hexstring_fmt!(StacksPublicKeyBuffer);
54
impl_byte_array_newtype!(StacksPublicKeyBuffer, u8, 33);
55
impl_byte_array_message_codec!(StacksPublicKeyBuffer, 33);
56
impl_byte_array_serde!(StacksPublicKeyBuffer);
57

58
impl StacksPublicKeyBuffer {
59
    pub fn from_public_key(pubkey: &Secp256k1PublicKey) -> StacksPublicKeyBuffer {
10,065,370✔
60
        let pubkey_bytes_vec = pubkey.to_bytes_compressed();
10,065,370✔
61
        let mut pubkey_bytes = [0u8; 33];
10,065,370✔
62
        pubkey_bytes.copy_from_slice(&pubkey_bytes_vec[..]);
10,065,370✔
63
        StacksPublicKeyBuffer(pubkey_bytes)
10,065,370✔
64
    }
10,065,370✔
65

66
    pub fn to_public_key(&self) -> Result<Secp256k1PublicKey, &'static str> {
7,584,290✔
67
        Secp256k1PublicKey::from_slice(&self.0)
7,584,290✔
68
            .map_err(|_e_str| "Failed to decode Stacks public key")
7,584,290✔
69
    }
7,584,290✔
70
}
71

72
pub trait PublicKey: Clone + fmt::Debug + serde::Serialize + serde::de::DeserializeOwned {
73
    fn to_bytes(&self) -> Vec<u8>;
74
    fn verify(&self, data_hash: &[u8], sig: &MessageSignature) -> Result<bool, &'static str>;
75
}
76

77
pub trait PrivateKey: Clone + fmt::Debug + serde::Serialize + serde::de::DeserializeOwned {
78
    fn to_bytes(&self) -> Vec<u8>;
79
    fn sign(&self, data_hash: &[u8]) -> Result<MessageSignature, &'static str>;
80
    #[cfg(any(test, feature = "testing"))]
81
    fn sign_with_noncedata(
82
        &self,
83
        data_hash: &[u8],
84
        noncedata: &[u8; 32],
85
    ) -> Result<MessageSignature, &'static str>;
86
}
87

88
pub trait Address: Clone + fmt::Debug + fmt::Display {
89
    fn to_bytes(&self) -> Vec<u8>;
90
    fn from_string(from: &str) -> Option<Self>
91
    where
92
        Self: Sized;
93
    fn is_burn(&self) -> bool;
94
}
95

96
// sliding burnchain window over which a miner's past block-commit payouts will be used to weight
97
// its current block-commit in a sortition.
98
// This is the value used in epoch 2.x
99
pub const MINING_COMMITMENT_WINDOW: u8 = 6;
100

101
// how often a miner must commit in its mining commitment window in order to even be considered for
102
// sortition.
103
// Only relevant for Nakamoto (epoch 3.x)
104
pub const MINING_COMMITMENT_FREQUENCY_NAKAMOTO: u8 = 3;
105

106
macro_rules! define_stacks_epochs {
107
    ($($variant:ident = $value:expr),* $(,)?) => {
108
        #[repr(u32)]
109
        #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
110
        pub enum StacksEpochId {
111
            $($variant = $value),*
112
        }
113

114
        impl StacksEpochId {
115
            pub const ALL: &'static [StacksEpochId] = &[
116
                $(StacksEpochId::$variant),*
117
            ];
118
        }
119
    };
120
}
121

122
define_stacks_epochs! {
123
    Epoch10 = 0x01000,
124
    Epoch20 = 0x02000,
125
    Epoch2_05 = 0x02005,
126
    Epoch21 = 0x0200a,
127
    Epoch22 = 0x0200f,
128
    Epoch23 = 0x02014,
129
    Epoch24 = 0x02019,
130
    Epoch25 = 0x0201a,
131
    Epoch30 = 0x03000,
132
    Epoch31 = 0x03001,
133
    Epoch32 = 0x03002,
134
    Epoch33 = 0x03003,
135
    Epoch34 = 0x03004,
136
}
137

138
#[derive(Debug)]
139
pub enum MempoolCollectionBehavior {
140
    ByStacksHeight,
141
    ByReceiveTime,
142
}
143

144
/// Struct describing an interval of time (measured in burnchain blocks) during which a coinbase is
145
/// allotted.  Applies to SIP-029 code paths and later.
146
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147
pub struct CoinbaseInterval {
148
    /// amount of uSTX to award
149
    pub coinbase: u128,
150
    /// height of the chain after Stacks chain genesis at which this coinbase interval starts
151
    pub effective_start_height: u64,
152
}
153

154
// From SIP-029:
155
//
156
// | Coinbase Interval  | Bitcoin Height | Offset Height       | Approx. Supply   | STX Reward | Annual Inflation |
157
// |--------------------|----------------|---------------------|------------------|------------|------------------|
158
// | Current            | -              | -                   | 1,552,452,847    | 1000       | -                |
159
// | 1st                |   945,000      |   278,950           | 1,627,352,847    | 500 (50%)  | 3.23%            |
160
// | 2nd                | 1,050,000      |   383,950           | 1,679,852,847    | 250 (50%)  | 1.57%            |
161
// | 3rd                | 1,260,000      |   593,950           | 1,732,352,847    | 125 (50%)  | 0.76%            |
162
// | 4th                | 1,470,000      |   803,950           | 1,758,602,847    | 62.5 (50%) | 0.37%            |
163
// | -                  | 2,197,560      | 1,531,510           | 1,804,075,347    | 62.5 (0%)  | 0.18%            |
164
//
165
// The above is for mainnet, which has a burnchain year of 52596 blocks and starts at burnchain height 666050.
166

167
/// Mainnet coinbase intervals, as of SIP-029
168
pub static COINBASE_INTERVALS_MAINNET: LazyLock<[CoinbaseInterval; 5]> = LazyLock::new(|| {
4✔
169
    let emissions_schedule = [
4✔
170
        CoinbaseInterval {
4✔
171
            coinbase: 1_000 * u128::from(MICROSTACKS_PER_STACKS),
4✔
172
            effective_start_height: 0,
4✔
173
        },
4✔
174
        CoinbaseInterval {
4✔
175
            coinbase: 500 * u128::from(MICROSTACKS_PER_STACKS),
4✔
176
            effective_start_height: 278_950,
4✔
177
        },
4✔
178
        CoinbaseInterval {
4✔
179
            coinbase: 250 * u128::from(MICROSTACKS_PER_STACKS),
4✔
180
            effective_start_height: 383_950,
4✔
181
        },
4✔
182
        CoinbaseInterval {
4✔
183
            coinbase: 125 * u128::from(MICROSTACKS_PER_STACKS),
4✔
184
            effective_start_height: 593_950,
4✔
185
        },
4✔
186
        CoinbaseInterval {
4✔
187
            coinbase: (625 * u128::from(MICROSTACKS_PER_STACKS)) / 10,
4✔
188
            effective_start_height: 803_950,
4✔
189
        },
4✔
190
    ];
4✔
191
    assert!(CoinbaseInterval::check_order(&emissions_schedule));
4✔
192
    emissions_schedule
4✔
193
});
4✔
194

195
/// Testnet coinbase intervals, as of SIP-029
196
pub static COINBASE_INTERVALS_TESTNET: LazyLock<[CoinbaseInterval; 5]> = LazyLock::new(|| {
3,613✔
197
    let emissions_schedule = [
3,613✔
198
        CoinbaseInterval {
3,613✔
199
            coinbase: 1_000 * u128::from(MICROSTACKS_PER_STACKS),
3,613✔
200
            effective_start_height: 0,
3,613✔
201
        },
3,613✔
202
        CoinbaseInterval {
3,613✔
203
            coinbase: 500 * u128::from(MICROSTACKS_PER_STACKS),
3,613✔
204
            effective_start_height: 77_777,
3,613✔
205
        },
3,613✔
206
        CoinbaseInterval {
3,613✔
207
            coinbase: 250 * u128::from(MICROSTACKS_PER_STACKS),
3,613✔
208
            effective_start_height: 77_777 * 7,
3,613✔
209
        },
3,613✔
210
        CoinbaseInterval {
3,613✔
211
            coinbase: 125 * u128::from(MICROSTACKS_PER_STACKS),
3,613✔
212
            effective_start_height: 77_777 * 14,
3,613✔
213
        },
3,613✔
214
        CoinbaseInterval {
3,613✔
215
            coinbase: (625 * u128::from(MICROSTACKS_PER_STACKS)) / 10,
3,613✔
216
            effective_start_height: 77_777 * 21,
3,613✔
217
        },
3,613✔
218
    ];
3,613✔
219
    assert!(CoinbaseInterval::check_order(&emissions_schedule));
3,613✔
220
    emissions_schedule
3,613✔
221
});
3,613✔
222

223
/// Used for testing to substitute a coinbase schedule
224
#[cfg(any(test, feature = "testing"))]
225
pub static COINBASE_INTERVALS_TEST: std::sync::Mutex<Option<Vec<CoinbaseInterval>>> =
226
    std::sync::Mutex::new(None);
227

228
#[cfg(any(test, feature = "testing"))]
229
pub fn set_test_coinbase_schedule(coinbase_schedule: Option<Vec<CoinbaseInterval>>) {
16✔
230
    match COINBASE_INTERVALS_TEST.lock() {
16✔
231
        Ok(mut schedule_guard) => {
16✔
232
            *schedule_guard = coinbase_schedule;
16✔
233
        }
16✔
234
        Err(_e) => {
×
235
            panic!("COINBASE_INTERVALS_TEST mutex poisoned");
×
236
        }
237
    }
238
}
16✔
239

240
impl CoinbaseInterval {
241
    /// Look up the value of a coinbase at an effective height.
242
    /// Precondition: `intervals` must be sorted in ascending order by `effective_start_height`
243
    pub fn get_coinbase_at_effective_height(
16,313✔
244
        intervals: &[CoinbaseInterval],
16,313✔
245
        effective_height: u64,
16,313✔
246
    ) -> u128 {
16,313✔
247
        if intervals.is_empty() {
16,313✔
248
            return 0;
×
249
        }
16,313✔
250
        if intervals.len() == 1 {
16,313✔
251
            if intervals[0].effective_start_height <= effective_height {
×
252
                return intervals[0].coinbase;
×
253
            } else {
254
                return 0;
×
255
            }
256
        }
16,313✔
257

258
        for i in 0..(intervals.len() - 1) {
16,982✔
259
            if intervals[i].effective_start_height <= effective_height
16,982✔
260
                && effective_height < intervals[i + 1].effective_start_height
16,982✔
261
            {
262
                return intervals[i].coinbase;
16,220✔
263
            }
762✔
264
        }
265

266
        // in last interval, which per the above checks is guaranteed to exist
267
        intervals.last().unwrap_or_else(|| unreachable!()).coinbase
93✔
268
    }
16,313✔
269

270
    /// Verify that a list of intervals is sorted in ascending order by `effective_start_height`
271
    pub fn check_order(intervals: &[CoinbaseInterval]) -> bool {
3,618✔
272
        if intervals.len() < 2 {
3,618✔
273
            return true;
×
274
        }
3,618✔
275

276
        let mut ht = intervals[0].effective_start_height;
3,618✔
277
        for interval in intervals.iter().skip(1) {
14,472✔
278
            if interval.effective_start_height < ht {
14,472✔
279
                return false;
×
280
            }
14,472✔
281
            ht = interval.effective_start_height;
14,472✔
282
        }
283
        true
3,618✔
284
    }
3,618✔
285
}
286

287
/// Struct describing the intervals in which SIP-031 emission are applied.
288
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289
pub struct SIP031EmissionInterval {
290
    /// amount of uSTX to emit
291
    pub amount: u128,
292
    /// height of the burn chain in which the interval starts
293
    pub start_height: u64,
294
}
295

296
// From SIP-031:
297
//
298
// | Bitcoin Height | STX Emission |
299
// |----------------|------------  |
300
// |   907,740      |     475      |
301
// |   960,300      |   1,140      |
302
// | 1,012,860      |   1,705      |
303
// | 1,065,420      |   1,305      |
304
// | 1,117,980      |   1,155      |
305
// | 1,170,540      |       0      |
306

307
/// Mainnet sip-031 emission intervals
308
pub static SIP031_EMISSION_INTERVALS_MAINNET: LazyLock<[SIP031EmissionInterval; 6]> =
309
    LazyLock::new(|| {
×
310
        let emissions_schedule = [
×
311
            SIP031EmissionInterval {
×
312
                amount: 0,
×
313
                start_height: 1_170_540,
×
314
            },
×
315
            SIP031EmissionInterval {
×
316
                amount: 1_155 * u128::from(MICROSTACKS_PER_STACKS),
×
317
                start_height: 1_117_980,
×
318
            },
×
319
            SIP031EmissionInterval {
×
320
                amount: 1_305 * u128::from(MICROSTACKS_PER_STACKS),
×
321
                start_height: 1_065_420,
×
322
            },
×
323
            SIP031EmissionInterval {
×
324
                amount: 1_705 * u128::from(MICROSTACKS_PER_STACKS),
×
325
                start_height: 1_012_860,
×
326
            },
×
327
            SIP031EmissionInterval {
×
328
                amount: 1_140 * u128::from(MICROSTACKS_PER_STACKS),
×
329
                start_height: 960_300,
×
330
            },
×
331
            SIP031EmissionInterval {
×
332
                amount: 475 * u128::from(MICROSTACKS_PER_STACKS),
×
333
                start_height: 907_740,
×
334
            },
×
335
        ];
×
336
        assert!(SIP031EmissionInterval::check_inversed_order(
×
337
            &emissions_schedule
×
338
        ));
339
        emissions_schedule
×
340
    });
×
341

342
/// Testnet sip-031 emission intervals (starting from 71_525, 1 interval every 360 bitcoin blocks)
343
pub static SIP031_EMISSION_INTERVALS_TESTNET: LazyLock<[SIP031EmissionInterval; 6]> =
344
    LazyLock::new(|| {
×
345
        let emissions_schedule = [
×
346
            SIP031EmissionInterval {
×
347
                amount: 0,
×
348
                start_height: 71_525 + (360 * 6),
×
349
            },
×
350
            SIP031EmissionInterval {
×
351
                amount: 5_000,
×
352
                start_height: 71_525 + (360 * 5),
×
353
            },
×
354
            SIP031EmissionInterval {
×
355
                amount: 4_000,
×
356
                start_height: 71_525 + (360 * 4),
×
357
            },
×
358
            SIP031EmissionInterval {
×
359
                amount: 3_000,
×
360
                start_height: 71_525 + (360 * 3),
×
361
            },
×
362
            SIP031EmissionInterval {
×
363
                amount: 2_000,
×
364
                start_height: 71_525 + (360 * 2),
×
365
            },
×
366
            SIP031EmissionInterval {
×
367
                amount: 1_000,
×
368
                start_height: 71_525 + 360,
×
369
            },
×
370
        ];
×
371
        assert!(SIP031EmissionInterval::check_inversed_order(
×
372
            &emissions_schedule
×
373
        ));
374
        emissions_schedule
×
375
    });
×
376

377
/// Used for testing to substitute a sip-031 emission schedule
378
#[cfg(any(test, feature = "testing"))]
379
pub static SIP031_EMISSION_INTERVALS_TEST: std::sync::Mutex<Option<Vec<SIP031EmissionInterval>>> =
380
    std::sync::Mutex::new(None);
381

382
#[cfg(any(test, feature = "testing"))]
383
pub fn set_test_sip_031_emission_schedule(emission_schedule: Option<Vec<SIP031EmissionInterval>>) {
84✔
384
    if let Some(emission_schedule_vec) = &emission_schedule {
84✔
385
        assert!(SIP031EmissionInterval::check_inversed_order(
42✔
386
            emission_schedule_vec
42✔
387
        ));
388
    }
42✔
389
    match SIP031_EMISSION_INTERVALS_TEST.lock() {
84✔
390
        Ok(mut schedule_guard) => {
84✔
391
            *schedule_guard = emission_schedule;
84✔
392
        }
84✔
393
        Err(_e) => {
×
394
            panic!("SIP031_EMISSION_INTERVALS_TEST mutex poisoned");
×
395
        }
396
    }
397
}
84✔
398

399
#[cfg(any(test, feature = "testing"))]
400
fn get_sip_031_emission_schedule(_mainnet: bool) -> Vec<SIP031EmissionInterval> {
27,272✔
401
    match SIP031_EMISSION_INTERVALS_TEST.lock() {
27,272✔
402
        Ok(schedule_opt) => {
27,272✔
403
            if let Some(schedule) = (*schedule_opt).as_ref() {
27,272✔
404
                info!("Use overridden SIP-031 emission schedule {:?}", &schedule);
5,054✔
405
                schedule.clone()
5,054✔
406
            } else {
407
                vec![]
22,218✔
408
            }
409
        }
410
        Err(_e) => {
×
411
            panic!("COINBASE_INTERVALS_TEST mutex poisoned");
×
412
        }
413
    }
414
}
27,272✔
415

416
#[cfg(not(any(test, feature = "testing")))]
417
fn get_sip_031_emission_schedule(mainnet: bool) -> Vec<SIP031EmissionInterval> {
418
    if mainnet {
419
        SIP031_EMISSION_INTERVALS_MAINNET.to_vec()
420
    } else {
421
        SIP031_EMISSION_INTERVALS_TESTNET.to_vec()
422
    }
423
}
424

425
impl SIP031EmissionInterval {
426
    /// Look up the amount of STX to emit at the start of the tenure at the specified height.
427
    /// Precondition: `intervals` must be sorted in descending order by `start_height`
428
    pub fn get_sip_031_emission_at_height(burn_height: u64, mainnet: bool) -> u128 {
27,272✔
429
        let intervals = get_sip_031_emission_schedule(mainnet);
27,272✔
430

431
        if intervals.is_empty() {
27,272✔
432
            return 0;
22,218✔
433
        }
5,054✔
434

435
        for interval in intervals {
13,328✔
436
            if burn_height >= interval.start_height {
13,328✔
437
                return interval.amount;
3,024✔
438
            }
10,304✔
439
        }
440

441
        // default emission (out of SIP-031 ranges)
442
        0
2,030✔
443
    }
27,272✔
444

445
    /// Verify that a list of intervals is sorted in descending order by `start_height`
446
    pub fn check_inversed_order(intervals: &[SIP031EmissionInterval]) -> bool {
42✔
447
        let Some(mut ht) = intervals.first().map(|x| x.start_height) else {
42✔
448
            // if the interval list is empty, its sorted
449
            return true;
×
450
        };
451

452
        for interval in intervals.iter().skip(1) {
98✔
453
            if interval.start_height > ht {
84✔
454
                return false;
×
455
            }
84✔
456
            ht = interval.start_height;
84✔
457
        }
458
        true
42✔
459
    }
42✔
460
}
461

462
impl StacksEpochId {
463
    /// Highest epoch enabled in release builds.
464
    /// Keep this in sync with `versions.toml` and `PEER_NETWORK_EPOCH`
465
    /// (validated in tests and `validate_epochs()`)
466
    pub const RELEASE_LATEST_EPOCH: StacksEpochId = StacksEpochId::Epoch34;
467

468
    #[cfg(any(test, feature = "testing"))]
469
    pub const fn latest() -> StacksEpochId {
12,995,794✔
470
        StacksEpochId::Epoch34
12,995,794✔
471
    }
12,995,794✔
472

473
    #[cfg(not(any(test, feature = "testing")))]
474
    pub const fn latest() -> StacksEpochId {
475
        StacksEpochId::RELEASE_LATEST_EPOCH
476
    }
477

478
    /// In this epoch, how should the mempool perform garbage collection?
479
    pub fn mempool_garbage_behavior(&self) -> MempoolCollectionBehavior {
8,340,480✔
480
        match self {
8,340,480✔
481
            StacksEpochId::Epoch10
482
            | StacksEpochId::Epoch20
483
            | StacksEpochId::Epoch2_05
484
            | StacksEpochId::Epoch21
485
            | StacksEpochId::Epoch22
486
            | StacksEpochId::Epoch23
487
            | StacksEpochId::Epoch24
488
            | StacksEpochId::Epoch25 => MempoolCollectionBehavior::ByStacksHeight,
5,940,050✔
489
            StacksEpochId::Epoch30
490
            | StacksEpochId::Epoch31
491
            | StacksEpochId::Epoch32
492
            | StacksEpochId::Epoch33
493
            | StacksEpochId::Epoch34 => MempoolCollectionBehavior::ByReceiveTime,
2,400,430✔
494
        }
495
    }
8,340,480✔
496

497
    /// Returns whether or not this Epoch should perform
498
    ///  memory checks during analysis
499
    pub fn analysis_memory(&self) -> bool {
44,393,180✔
500
        match self {
44,393,180✔
501
            StacksEpochId::Epoch10
502
            | StacksEpochId::Epoch20
503
            | StacksEpochId::Epoch2_05
504
            | StacksEpochId::Epoch21
505
            | StacksEpochId::Epoch22
506
            | StacksEpochId::Epoch23
507
            | StacksEpochId::Epoch24 => false,
27,755,962✔
508
            StacksEpochId::Epoch25
509
            | StacksEpochId::Epoch30
510
            | StacksEpochId::Epoch31
511
            | StacksEpochId::Epoch32
512
            | StacksEpochId::Epoch33
513
            | StacksEpochId::Epoch34 => true,
16,637,218✔
514
        }
515
    }
44,393,180✔
516

517
    /// Returns whether or not this Epoch should perform
518
    ///  Clarity value sanitization
519
    pub fn value_sanitizing(&self) -> bool {
348,346,115✔
520
        match self {
348,346,115✔
521
            StacksEpochId::Epoch10
522
            | StacksEpochId::Epoch20
523
            | StacksEpochId::Epoch2_05
524
            | StacksEpochId::Epoch21
525
            | StacksEpochId::Epoch22
526
            | StacksEpochId::Epoch23 => false,
17,476,004✔
527
            StacksEpochId::Epoch24
528
            | StacksEpochId::Epoch25
529
            | StacksEpochId::Epoch30
530
            | StacksEpochId::Epoch31
531
            | StacksEpochId::Epoch32
532
            | StacksEpochId::Epoch33
533
            | StacksEpochId::Epoch34 => true,
330,870,111✔
534
        }
535
    }
348,346,115✔
536

537
    pub fn supports_specific_budget_extends(&self) -> bool {
4,830✔
538
        match self {
4,830✔
539
            StacksEpochId::Epoch10
540
            | StacksEpochId::Epoch20
541
            | StacksEpochId::Epoch2_05
542
            | StacksEpochId::Epoch21
543
            | StacksEpochId::Epoch22
544
            | StacksEpochId::Epoch23
545
            | StacksEpochId::Epoch24
546
            | StacksEpochId::Epoch25
547
            | StacksEpochId::Epoch30
548
            | StacksEpochId::Epoch31
549
            | StacksEpochId::Epoch32 => false,
310✔
550
            StacksEpochId::Epoch33 | StacksEpochId::Epoch34 => true,
4,520✔
551
        }
552
    }
4,830✔
553

554
    /// Whether or not this epoch supports the punishment of PoX reward
555
    /// recipients using the bitvec scheme
556
    pub fn allows_pox_punishment(&self) -> bool {
1,466,640✔
557
        match self {
1,466,640✔
558
            StacksEpochId::Epoch10
559
            | StacksEpochId::Epoch20
560
            | StacksEpochId::Epoch2_05
561
            | StacksEpochId::Epoch21
562
            | StacksEpochId::Epoch22
563
            | StacksEpochId::Epoch23
564
            | StacksEpochId::Epoch24
565
            | StacksEpochId::Epoch25 => false,
1,375,230✔
566
            StacksEpochId::Epoch30
567
            | StacksEpochId::Epoch31
568
            | StacksEpochId::Epoch32
569
            | StacksEpochId::Epoch33
570
            | StacksEpochId::Epoch34 => true,
91,410✔
571
        }
572
    }
1,466,640✔
573

574
    /// Whether or not this epoch interprets block commit OPs block hash field
575
    ///  as a new block hash or the StacksBlockId of a new tenure's parent tenure.
576
    pub fn block_commits_to_parent(&self) -> bool {
63,850✔
577
        match self {
63,850✔
578
            StacksEpochId::Epoch10
579
            | StacksEpochId::Epoch20
580
            | StacksEpochId::Epoch2_05
581
            | StacksEpochId::Epoch21
582
            | StacksEpochId::Epoch22
583
            | StacksEpochId::Epoch23
584
            | StacksEpochId::Epoch24
585
            | StacksEpochId::Epoch25 => false,
40,040✔
586
            StacksEpochId::Epoch30
587
            | StacksEpochId::Epoch31
588
            | StacksEpochId::Epoch32
589
            | StacksEpochId::Epoch33
590
            | StacksEpochId::Epoch34 => true,
23,810✔
591
        }
592
    }
63,850✔
593

594
    /// Whether or not this epoch supports shadow blocks
595
    pub fn supports_shadow_blocks(&self) -> bool {
64,060✔
596
        match self {
64,060✔
597
            StacksEpochId::Epoch10
598
            | StacksEpochId::Epoch20
599
            | StacksEpochId::Epoch2_05
600
            | StacksEpochId::Epoch21
601
            | StacksEpochId::Epoch22
602
            | StacksEpochId::Epoch23
603
            | StacksEpochId::Epoch24
604
            | StacksEpochId::Epoch25 => false,
40,040✔
605
            StacksEpochId::Epoch30
606
            | StacksEpochId::Epoch31
607
            | StacksEpochId::Epoch32
608
            | StacksEpochId::Epoch33
609
            | StacksEpochId::Epoch34 => true,
24,020✔
610
        }
611
    }
64,060✔
612

613
    /// Does this epoch support unlocking PoX contributors that miss a slot?
614
    ///
615
    /// Epoch 2.0 - 2.05 didn't support this feature, but they weren't epoch-guarded on it. Instead,
616
    ///  the behavior never activates in those epochs because the Pox1 contract does not provide
617
    ///  `contibuted_stackers` information. This check maintains that exact semantics by returning
618
    ///  true for all epochs before 2.5. For 2.5 and after, this returns false.
619
    pub fn supports_pox_missed_slot_unlocks(&self) -> bool {
61,866✔
620
        self < &StacksEpochId::Epoch25
61,866✔
621
    }
61,866✔
622

623
    /// Whether `from-consensus-buff` treats unexpected serialization as `none` or causes
624
    /// an error that makes the transaction un-includable in a block.
625
    pub fn treats_unexpected_serialization_as_none(&self) -> bool {
15,050✔
626
        self >= &StacksEpochId::Epoch34
15,050✔
627
    }
15,050✔
628

629
    /// Whether or not this epoch rejects `SupertypeTooLarge` errors.
630
    pub fn rejects_supertype_too_large(&self) -> bool {
434✔
631
        self < &StacksEpochId::Epoch34
434✔
632
    }
434✔
633

634
    /// Whether or not this epoch rejects parse-depth errors.
635
    pub fn rejects_parse_depth_errors(&self) -> bool {
4,809,910✔
636
        self < &StacksEpochId::Epoch34
4,809,910✔
637
    }
4,809,910✔
638

639
    /// Whether or not this epoch pre-sanitizes contract variables at deploy
640
    /// and load time, allowing variable lookups to borrow directly.
641
    pub fn uses_pre_sanitized_variables(&self) -> bool {
402,081,836✔
642
        match self {
402,081,836✔
643
            StacksEpochId::Epoch10
644
            | StacksEpochId::Epoch20
645
            | StacksEpochId::Epoch2_05
646
            | StacksEpochId::Epoch21
647
            | StacksEpochId::Epoch22
648
            | StacksEpochId::Epoch23
649
            | StacksEpochId::Epoch24
650
            | StacksEpochId::Epoch25
651
            | StacksEpochId::Epoch30
652
            | StacksEpochId::Epoch31
653
            | StacksEpochId::Epoch32
654
            | StacksEpochId::Epoch33 => false,
251,044,040✔
655
            StacksEpochId::Epoch34 => true,
151,037,796✔
656
        }
657
    }
402,081,836✔
658

659
    /// What is the sortition mining commitment window for this epoch?
660
    pub fn mining_commitment_window(&self) -> u8 {
2,819,520✔
661
        MINING_COMMITMENT_WINDOW
2,819,520✔
662
    }
2,819,520✔
663

664
    /// How often must a miner mine in order to be considered for sortition in its commitment
665
    /// window?
666
    pub fn mining_commitment_frequency(&self) -> u8 {
42,230✔
667
        match self {
42,230✔
668
            StacksEpochId::Epoch10
669
            | StacksEpochId::Epoch20
670
            | StacksEpochId::Epoch2_05
671
            | StacksEpochId::Epoch21
672
            | StacksEpochId::Epoch22
673
            | StacksEpochId::Epoch23
674
            | StacksEpochId::Epoch24
675
            | StacksEpochId::Epoch25 => 0,
10✔
676
            StacksEpochId::Epoch30
677
            | StacksEpochId::Epoch31
678
            | StacksEpochId::Epoch32
679
            | StacksEpochId::Epoch33
680
            | StacksEpochId::Epoch34 => MINING_COMMITMENT_FREQUENCY_NAKAMOTO,
42,220✔
681
        }
682
    }
42,230✔
683

684
    /// Returns true for epochs which use Nakamoto blocks. These blocks use a
685
    /// different header format than the previous Stacks blocks, which among
686
    /// other changes includes a Stacks-specific timestamp.
687
    pub fn uses_nakamoto_blocks(&self) -> bool {
5,974,444✔
688
        self >= &StacksEpochId::Epoch30
5,974,444✔
689
    }
5,974,444✔
690

691
    /// Returns whether or not this epoch uses the tip for reading burn block
692
    /// info in Clarity (3.0+ behavior) or should use the parent block's burn
693
    /// block (behavior before 3.0).
694
    pub fn clarity_uses_tip_burn_block(&self) -> bool {
21,045,500✔
695
        self >= &StacksEpochId::Epoch30
21,045,500✔
696
    }
21,045,500✔
697

698
    /// Does this epoch use the nakamoto reward set, or the epoch2 reward set?
699
    /// We use the epoch2 reward set in all pre-3.0 epochs.
700
    /// We also use the epoch2 reward set in the first 3.0 reward cycle.
701
    /// After that, we use the nakamoto reward set.
702
    pub fn uses_nakamoto_reward_set(
×
703
        &self,
×
704
        cur_reward_cycle: u64,
×
705
        first_epoch30_reward_cycle: u64,
×
706
    ) -> bool {
×
707
        match self {
×
708
            StacksEpochId::Epoch10
709
            | StacksEpochId::Epoch20
710
            | StacksEpochId::Epoch2_05
711
            | StacksEpochId::Epoch21
712
            | StacksEpochId::Epoch22
713
            | StacksEpochId::Epoch23
714
            | StacksEpochId::Epoch24
715
            | StacksEpochId::Epoch25 => false,
×
716
            StacksEpochId::Epoch30
717
            | StacksEpochId::Epoch31
718
            | StacksEpochId::Epoch32
719
            | StacksEpochId::Epoch33
720
            | StacksEpochId::Epoch34 => cur_reward_cycle > first_epoch30_reward_cycle,
×
721
        }
722
    }
×
723

724
    /// Does this epoch support the post-condition enhancements from SIP-040?
725
    /// This includes support for `Originator` mode and the `MaySend` NFT condition.
726
    pub fn supports_sip040_post_conditions(&self) -> bool {
10,543,288✔
727
        self >= &StacksEpochId::Epoch34
10,543,288✔
728
    }
10,543,288✔
729

730
    /// What is the coinbase (in uSTX) to award for the given burnchain height?
731
    /// Applies prior to SIP-029
732
    fn coinbase_reward_pre_sip029(
1,387,024✔
733
        &self,
1,387,024✔
734
        first_burnchain_height: u64,
1,387,024✔
735
        current_burnchain_height: u64,
1,387,024✔
736
    ) -> u128 {
1,387,024✔
737
        /*
738
        From https://forum.stacks.org/t/pox-consensus-and-stx-future-supply
739

740
        """
741

742
        1000 STX for years 0-4
743
        500 STX for years 4-8
744
        250 STX for years 8-12
745
        125 STX in perpetuity
746

747

748
        From the Token Whitepaper:
749

750
        We expect that once native mining goes live, approximately 4383 blocks will be pro-
751
        cessed per month, or approximately 52,596 blocks will be processed per year.
752

753
        """
754
        */
755
        // this is saturating subtraction for the initial reward calculation
756
        //   where we are computing the coinbase reward for blocks that occur *before*
757
        //   the `first_burn_block_height`
758
        let effective_ht = current_burnchain_height.saturating_sub(first_burnchain_height);
1,387,024✔
759
        let blocks_per_year = 52596;
1,387,024✔
760
        let stx_reward = if effective_ht < blocks_per_year * 4 {
1,387,024✔
761
            1000
1,386,960✔
762
        } else if effective_ht < blocks_per_year * 8 {
64✔
763
            500
24✔
764
        } else if effective_ht < blocks_per_year * 12 {
40✔
765
            250
24✔
766
        } else {
767
            125
16✔
768
        };
769

770
        stx_reward * (u128::from(MICROSTACKS_PER_STACKS))
1,387,024✔
771
    }
1,387,024✔
772

773
    /// Get the coinbase intervals to use.
774
    /// Can be overriden by tests
775
    #[cfg(any(test, feature = "testing"))]
776
    pub(crate) fn get_coinbase_intervals(mainnet: bool) -> Vec<CoinbaseInterval> {
16,302✔
777
        match COINBASE_INTERVALS_TEST.lock() {
16,302✔
778
            Ok(schedule_opt) => {
16,302✔
779
                if let Some(schedule) = (*schedule_opt).as_ref() {
16,302✔
780
                    info!("Use overridden coinbase schedule {:?}", &schedule);
422✔
781
                    return schedule.clone();
422✔
782
                }
15,880✔
783
            }
784
            Err(_e) => {
×
785
                panic!("COINBASE_INTERVALS_TEST mutex poisoned");
×
786
            }
787
        }
788

789
        if mainnet {
15,880✔
790
            COINBASE_INTERVALS_MAINNET.to_vec()
30✔
791
        } else {
792
            COINBASE_INTERVALS_TESTNET.to_vec()
15,850✔
793
        }
794
    }
16,302✔
795

796
    #[cfg(not(any(test, feature = "testing")))]
797
    pub(crate) fn get_coinbase_intervals(mainnet: bool) -> Vec<CoinbaseInterval> {
798
        if mainnet {
799
            COINBASE_INTERVALS_MAINNET.to_vec()
800
        } else {
801
            COINBASE_INTERVALS_TESTNET.to_vec()
802
        }
803
    }
804

805
    /// what are the offsets after chain-start when coinbase reductions occur?
806
    /// Applies at and after SIP-029.
807
    /// Uses coinbase intervals defined by COINBASE_INTERVALS_MAINNET, unless overridden by a unit
808
    /// or integration test.
809
    fn coinbase_reward_sip029(
16,296✔
810
        &self,
16,296✔
811
        mainnet: bool,
16,296✔
812
        first_burnchain_height: u64,
16,296✔
813
        current_burnchain_height: u64,
16,296✔
814
    ) -> u128 {
16,296✔
815
        let effective_ht = current_burnchain_height.saturating_sub(first_burnchain_height);
16,296✔
816
        let coinbase_intervals = Self::get_coinbase_intervals(mainnet);
16,296✔
817
        CoinbaseInterval::get_coinbase_at_effective_height(&coinbase_intervals, effective_ht)
16,296✔
818
    }
16,296✔
819

820
    /// What is the coinbase to award?
821
    pub fn coinbase_reward(
1,403,320✔
822
        &self,
1,403,320✔
823
        mainnet: bool,
1,403,320✔
824
        first_burnchain_height: u64,
1,403,320✔
825
        current_burnchain_height: u64,
1,403,320✔
826
    ) -> u128 {
1,403,320✔
827
        match self {
1,403,320✔
828
            StacksEpochId::Epoch10 => {
829
                // Stacks is not active
830
                0
×
831
            }
832
            StacksEpochId::Epoch20
833
            | StacksEpochId::Epoch2_05
834
            | StacksEpochId::Epoch21
835
            | StacksEpochId::Epoch22
836
            | StacksEpochId::Epoch23
837
            | StacksEpochId::Epoch24
838
            | StacksEpochId::Epoch25
839
            | StacksEpochId::Epoch30 => {
840
                self.coinbase_reward_pre_sip029(first_burnchain_height, current_burnchain_height)
1,387,024✔
841
            }
842
            StacksEpochId::Epoch31
843
            | StacksEpochId::Epoch32
844
            | StacksEpochId::Epoch33
845
            | StacksEpochId::Epoch34 => self.coinbase_reward_sip029(
16,296✔
846
                mainnet,
16,296✔
847
                first_burnchain_height,
16,296✔
848
                current_burnchain_height,
16,296✔
849
            ),
850
        }
851
    }
1,403,320✔
852

853
    /// Whether or not this epoch is part of the SIP-031 schedule
854
    pub fn includes_sip_031(&self) -> bool {
101,600✔
855
        match self {
101,600✔
856
            StacksEpochId::Epoch10
857
            | StacksEpochId::Epoch20
858
            | StacksEpochId::Epoch2_05
859
            | StacksEpochId::Epoch21
860
            | StacksEpochId::Epoch22
861
            | StacksEpochId::Epoch23
862
            | StacksEpochId::Epoch24
863
            | StacksEpochId::Epoch25
864
            | StacksEpochId::Epoch30
865
            | StacksEpochId::Epoch31 => false,
84,650✔
866
            StacksEpochId::Epoch32 | StacksEpochId::Epoch33 | StacksEpochId::Epoch34 => true,
16,950✔
867
        }
868
    }
101,600✔
869

870
    pub fn uses_marfed_block_time(&self) -> bool {
1,012,011✔
871
        match self {
1,012,011✔
872
            StacksEpochId::Epoch10
873
            | StacksEpochId::Epoch20
874
            | StacksEpochId::Epoch2_05
875
            | StacksEpochId::Epoch21
876
            | StacksEpochId::Epoch22
877
            | StacksEpochId::Epoch23
878
            | StacksEpochId::Epoch24
879
            | StacksEpochId::Epoch25
880
            | StacksEpochId::Epoch30
881
            | StacksEpochId::Epoch31
882
            | StacksEpochId::Epoch32 => false,
807,222✔
883
            StacksEpochId::Epoch33 | StacksEpochId::Epoch34 => true,
204,789✔
884
        }
885
    }
1,012,011✔
886

887
    /// Before Epoch 3.3, the cost for arguments to functions was based on the
888
    /// parameter type, not the actual size of the argument passed in. This
889
    /// resulted in over-charging for arguments smaller than the maximum size
890
    /// permitted for the parameter.
891
    pub fn uses_arg_size_for_cost(&self) -> bool {
40,689,220✔
892
        match self {
40,689,220✔
893
            StacksEpochId::Epoch10
894
            | StacksEpochId::Epoch20
895
            | StacksEpochId::Epoch2_05
896
            | StacksEpochId::Epoch21
897
            | StacksEpochId::Epoch22
898
            | StacksEpochId::Epoch23
899
            | StacksEpochId::Epoch24
900
            | StacksEpochId::Epoch25
901
            | StacksEpochId::Epoch30
902
            | StacksEpochId::Epoch31
903
            | StacksEpochId::Epoch32 => false,
37,515,374✔
904
            StacksEpochId::Epoch33 | StacksEpochId::Epoch34 => true,
3,173,846✔
905
        }
906
    }
40,689,220✔
907

908
    /// In Epoch 3.3, limits are introduced on the number of parameters
909
    /// in function definitions and the number of methods in trait definitions.
910
    pub fn limits_parameter_and_method_count(&self) -> bool {
11,502,036✔
911
        match self {
11,502,036✔
912
            StacksEpochId::Epoch10
913
            | StacksEpochId::Epoch20
914
            | StacksEpochId::Epoch2_05
915
            | StacksEpochId::Epoch21
916
            | StacksEpochId::Epoch22
917
            | StacksEpochId::Epoch23
918
            | StacksEpochId::Epoch24
919
            | StacksEpochId::Epoch25
920
            | StacksEpochId::Epoch30
921
            | StacksEpochId::Epoch31
922
            | StacksEpochId::Epoch32 => false,
9,339,889✔
923
            StacksEpochId::Epoch33 | StacksEpochId::Epoch34 => true,
2,162,147✔
924
        }
925
    }
11,502,036✔
926

927
    pub fn handles_with_stx_combined_check(&self) -> bool {
663✔
928
        match self {
663✔
929
            StacksEpochId::Epoch10
930
            | StacksEpochId::Epoch20
931
            | StacksEpochId::Epoch2_05
932
            | StacksEpochId::Epoch21
933
            | StacksEpochId::Epoch22
934
            | StacksEpochId::Epoch23
935
            | StacksEpochId::Epoch24
936
            | StacksEpochId::Epoch25
937
            | StacksEpochId::Epoch30
938
            | StacksEpochId::Epoch31
939
            | StacksEpochId::Epoch32
940
            | StacksEpochId::Epoch33 => false,
117✔
941
            StacksEpochId::Epoch34 => true,
546✔
942
        }
943
    }
663✔
944

945
    pub fn supports_call_with_constant(&self) -> bool {
2,576✔
946
        self >= &StacksEpochId::Epoch34
2,576✔
947
    }
2,576✔
948

949
    /// Whether `at-block` is available in this epoch.
950
    pub fn supports_at_block(&self) -> bool {
13,188✔
951
        self < &StacksEpochId::Epoch34
13,188✔
952
    }
13,188✔
953

954
    /// Return the network epoch associated with the StacksEpochId
955
    pub fn network_epoch(epoch: StacksEpochId) -> u8 {
2,939✔
956
        match epoch {
2,939✔
957
            StacksEpochId::Epoch10 => PEER_VERSION_EPOCH_1_0,
226✔
958
            StacksEpochId::Epoch20 => PEER_VERSION_EPOCH_2_0,
226✔
959
            StacksEpochId::Epoch2_05 => PEER_VERSION_EPOCH_2_05,
226✔
960
            StacksEpochId::Epoch21 => PEER_VERSION_EPOCH_2_1,
226✔
961
            StacksEpochId::Epoch22 => PEER_VERSION_EPOCH_2_2,
226✔
962
            StacksEpochId::Epoch23 => PEER_VERSION_EPOCH_2_3,
226✔
963
            StacksEpochId::Epoch24 => PEER_VERSION_EPOCH_2_4,
226✔
964
            StacksEpochId::Epoch25 => PEER_VERSION_EPOCH_2_5,
226✔
965
            StacksEpochId::Epoch30 => PEER_VERSION_EPOCH_3_0,
226✔
966
            StacksEpochId::Epoch31 => PEER_VERSION_EPOCH_3_1,
226✔
967
            StacksEpochId::Epoch32 => PEER_VERSION_EPOCH_3_2,
226✔
968
            StacksEpochId::Epoch33 => PEER_VERSION_EPOCH_3_3,
226✔
969
            StacksEpochId::Epoch34 => PEER_VERSION_EPOCH_3_4,
227✔
970
        }
971
    }
2,939✔
972

973
    #[cfg(any(test, feature = "testing"))]
974
    pub fn since(epoch: StacksEpochId) -> &'static [StacksEpochId] {
2,968✔
975
        let idx = Self::ALL
2,968✔
976
            .iter()
2,968✔
977
            .position(|&e| e == epoch)
6,566✔
978
            .expect("epoch not found in ALL");
2,968✔
979

980
        &Self::ALL[idx..]
2,968✔
981
    }
2,968✔
982

983
    /// Returns all [`StacksEpochId`] from `start` to `end`, both inclusive.
984
    #[cfg(any(test, feature = "testing"))]
985
    pub fn between(start: StacksEpochId, end: StacksEpochId) -> &'static [StacksEpochId] {
42✔
986
        let start_idx = Self::ALL
42✔
987
            .iter()
42✔
988
            .position(|&e| e == start)
84✔
989
            .expect("start epoch not found in ALL");
42✔
990
        let end_idx = Self::ALL
42✔
991
            .iter()
42✔
992
            .position(|&e| e == end)
504✔
993
            .expect("end epoch not found in ALL");
42✔
994
        assert!(start_idx <= end_idx, "start epoch must be <= end epoch");
42✔
995

996
        &Self::ALL[start_idx..=end_idx]
42✔
997
    }
42✔
998
}
999

1000
impl std::fmt::Display for StacksEpochId {
1001
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423,472✔
1002
        match self {
423,472✔
1003
            StacksEpochId::Epoch10 => write!(f, "1.0"),
14✔
1004
            StacksEpochId::Epoch20 => write!(f, "2.0"),
24,290✔
1005
            StacksEpochId::Epoch2_05 => write!(f, "2.05"),
33,110✔
1006
            StacksEpochId::Epoch21 => write!(f, "2.1"),
54,824✔
1007
            StacksEpochId::Epoch22 => write!(f, "2.2"),
33,124✔
1008
            StacksEpochId::Epoch23 => write!(f, "2.3"),
36,344✔
1009
            StacksEpochId::Epoch24 => write!(f, "2.4"),
61,348✔
1010
            StacksEpochId::Epoch25 => write!(f, "2.5"),
81,298✔
1011
            StacksEpochId::Epoch30 => write!(f, "3.0"),
27,846✔
1012
            StacksEpochId::Epoch31 => write!(f, "3.1"),
11,970✔
1013
            StacksEpochId::Epoch32 => write!(f, "3.2"),
10,976✔
1014
            StacksEpochId::Epoch33 => write!(f, "3.3"),
35,518✔
1015
            StacksEpochId::Epoch34 => write!(f, "3.4"),
12,810✔
1016
        }
1017
    }
423,472✔
1018
}
1019

1020
impl FromStr for StacksEpochId {
1021
    type Err = &'static str;
1022

1023
    fn from_str(s: &str) -> Result<Self, Self::Err> {
14✔
1024
        match s {
14✔
1025
            "1.0" => Ok(StacksEpochId::Epoch10),
14✔
1026
            "2.0" => Ok(StacksEpochId::Epoch20),
14✔
1027
            "2.05" => Ok(StacksEpochId::Epoch2_05),
14✔
1028
            "2.1" => Ok(StacksEpochId::Epoch21),
14✔
1029
            "2.2" => Ok(StacksEpochId::Epoch22),
14✔
1030
            "2.3" => Ok(StacksEpochId::Epoch23),
14✔
1031
            "2.4" => Ok(StacksEpochId::Epoch24),
14✔
1032
            "2.5" => Ok(StacksEpochId::Epoch25),
14✔
1033
            "3.0" => Ok(StacksEpochId::Epoch30),
14✔
1034
            "3.1" => Ok(StacksEpochId::Epoch31),
14✔
1035
            "3.2" => Ok(StacksEpochId::Epoch32),
14✔
1036
            "3.3" => Ok(StacksEpochId::Epoch33),
14✔
1037
            "3.4" => Ok(StacksEpochId::Epoch34),
14✔
1038
            _ => Err("Invalid epoch string"),
×
1039
        }
1040
    }
14✔
1041
}
1042

1043
impl TryFrom<u32> for StacksEpochId {
1044
    type Error = &'static str;
1045

1046
    fn try_from(value: u32) -> Result<StacksEpochId, Self::Error> {
394,392,713✔
1047
        match value {
394,392,713✔
1048
            x if x == StacksEpochId::Epoch10 as u32 => Ok(StacksEpochId::Epoch10),
394,392,713✔
1049
            x if x == StacksEpochId::Epoch20 as u32 => Ok(StacksEpochId::Epoch20),
385,619,975✔
1050
            x if x == StacksEpochId::Epoch2_05 as u32 => Ok(StacksEpochId::Epoch2_05),
357,184,321✔
1051
            x if x == StacksEpochId::Epoch21 as u32 => Ok(StacksEpochId::Epoch21),
346,375,055✔
1052
            x if x == StacksEpochId::Epoch22 as u32 => Ok(StacksEpochId::Epoch22),
334,478,677✔
1053
            x if x == StacksEpochId::Epoch23 as u32 => Ok(StacksEpochId::Epoch23),
327,358,200✔
1054
            x if x == StacksEpochId::Epoch24 as u32 => Ok(StacksEpochId::Epoch24),
320,154,991✔
1055
            x if x == StacksEpochId::Epoch25 as u32 => Ok(StacksEpochId::Epoch25),
293,658,131✔
1056
            x if x == StacksEpochId::Epoch30 as u32 => Ok(StacksEpochId::Epoch30),
143,249,769✔
1057
            x if x == StacksEpochId::Epoch31 as u32 => Ok(StacksEpochId::Epoch31),
35,447,997✔
1058
            x if x == StacksEpochId::Epoch32 as u32 => Ok(StacksEpochId::Epoch32),
26,088,699✔
1059
            x if x == StacksEpochId::Epoch33 as u32 => Ok(StacksEpochId::Epoch33),
19,541,210✔
1060
            x if x == StacksEpochId::Epoch34 as u32 => Ok(StacksEpochId::Epoch34),
10,469,147✔
1061
            _ => Err("Invalid epoch"),
×
1062
        }
1063
    }
394,392,713✔
1064
}
1065

1066
impl PartialOrd for StacksAddress {
1067
    fn partial_cmp(&self, other: &StacksAddress) -> Option<Ordering> {
686✔
1068
        Some(self.cmp(other))
686✔
1069
    }
686✔
1070
}
1071

1072
impl Ord for StacksAddress {
1073
    fn cmp(&self, other: &StacksAddress) -> Ordering {
686✔
1074
        match self.version().cmp(&other.version()) {
686✔
1075
            Ordering::Equal => self.bytes().cmp(other.bytes()),
686✔
1076
            inequality => inequality,
×
1077
        }
1078
    }
686✔
1079
}
1080

1081
impl StacksAddress {
1082
    pub fn is_mainnet(&self) -> bool {
36,148✔
1083
        match self.version() {
36,148✔
1084
            C32_ADDRESS_VERSION_MAINNET_MULTISIG | C32_ADDRESS_VERSION_MAINNET_SINGLESIG => true,
×
1085
            C32_ADDRESS_VERSION_TESTNET_MULTISIG | C32_ADDRESS_VERSION_TESTNET_SINGLESIG => false,
36,148✔
1086
            _ => false,
×
1087
        }
1088
    }
36,148✔
1089

1090
    pub fn burn_address(mainnet: bool) -> StacksAddress {
87,169,026✔
1091
        Self::new(
87,169,026✔
1092
            if mainnet {
87,169,026✔
1093
                C32_ADDRESS_VERSION_MAINNET_SINGLESIG
5,752,894✔
1094
            } else {
1095
                C32_ADDRESS_VERSION_TESTNET_SINGLESIG
81,416,132✔
1096
            },
1097
            Hash160([0u8; 20]),
87,169,026✔
1098
        )
1099
        .unwrap_or_else(|_| panic!("FATAL: constant address versions are invalid"))
87,169,026✔
1100
        // infallible
1101
    }
87,169,026✔
1102

1103
    /// Generate an address from a given address hash mode, signature threshold, and list of public
1104
    /// keys.  Only return an address if the combination given is supported.
1105
    /// The version is may be arbitrary.
1106
    pub fn from_public_keys(
34,084,624✔
1107
        version: u8,
34,084,624✔
1108
        hash_mode: &AddressHashMode,
34,084,624✔
1109
        num_sigs: usize,
34,084,624✔
1110
        pubkeys: &Vec<StacksPublicKey>,
34,084,624✔
1111
    ) -> Option<StacksAddress> {
34,084,624✔
1112
        // must be sufficient public keys
1113
        if pubkeys.len() < num_sigs {
34,084,624✔
1114
            return None;
14✔
1115
        }
34,084,610✔
1116

1117
        // address hash mode must be consistent with the number of keys
1118
        match *hash_mode {
34,084,610✔
1119
            AddressHashMode::SerializeP2PKH | AddressHashMode::SerializeP2WPKH => {
1120
                // must be a single public key, and must require one signature
1121
                if num_sigs != 1 || pubkeys.len() != 1 {
33,231,422✔
1122
                    return None;
14✔
1123
                }
33,231,408✔
1124
            }
1125
            _ => {}
853,188✔
1126
        }
1127

1128
        // if segwit, then keys must all be compressed
1129
        match *hash_mode {
34,084,596✔
1130
            AddressHashMode::SerializeP2WPKH | AddressHashMode::SerializeP2WSH => {
1131
                for pubkey in pubkeys {
1,067,360✔
1132
                    if !pubkey.compressed() {
1,067,360✔
1133
                        return None;
266✔
1134
                    }
1,067,094✔
1135
                }
1136
            }
1137
            _ => {}
33,790,190✔
1138
        }
1139

1140
        let hash_bits = public_keys_to_address_hash(hash_mode, num_sigs, pubkeys);
34,084,330✔
1141
        StacksAddress::new(version, hash_bits).ok()
34,084,330✔
1142
    }
34,084,624✔
1143

1144
    /// Make a P2PKH StacksAddress
1145
    pub fn p2pkh(mainnet: bool, pubkey: &StacksPublicKey) -> StacksAddress {
25,835,922✔
1146
        let bytes = to_bits_p2pkh(pubkey);
25,835,922✔
1147
        Self::p2pkh_from_hash(mainnet, bytes)
25,835,922✔
1148
    }
25,835,922✔
1149

1150
    /// Make a P2PKH StacksAddress
1151
    pub fn p2pkh_from_hash(mainnet: bool, hash: Hash160) -> StacksAddress {
26,074,734✔
1152
        let version = if mainnet {
26,074,734✔
1153
            C32_ADDRESS_VERSION_MAINNET_SINGLESIG
56✔
1154
        } else {
1155
            C32_ADDRESS_VERSION_TESTNET_SINGLESIG
26,074,678✔
1156
        };
1157
        Self::new(version, hash)
26,074,734✔
1158
            .unwrap_or_else(|_| panic!("FATAL: constant address versions are invalid"))
26,074,734✔
1159
        // infallible
1160
    }
26,074,734✔
1161
}
1162

1163
impl std::fmt::Display for StacksAddress {
1164
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38,708,012✔
1165
        // the .unwrap_or_else() should be unreachable since StacksAddress is constructed to only
1166
        // accept a 5-bit value for its version
1167
        c32_address(self.version(), self.bytes().as_bytes())
38,708,012✔
1168
            .expect("Stacks version is not C32-encodable")
38,708,012✔
1169
            .fmt(f)
38,708,012✔
1170
    }
38,708,012✔
1171
}
1172

1173
impl Address for StacksAddress {
1174
    fn to_bytes(&self) -> Vec<u8> {
×
1175
        self.bytes().as_bytes().to_vec()
×
1176
    }
×
1177

1178
    fn from_string(s: &str) -> Option<StacksAddress> {
125,936,622✔
1179
        let (version, bytes) = c32_address_decode(s).ok()?;
125,936,622✔
1180

1181
        if bytes.len() != 20 {
125,936,622✔
1182
            return None;
×
1183
        }
125,936,622✔
1184

1185
        let mut hash_bytes = [0u8; 20];
125,936,622✔
1186
        hash_bytes.copy_from_slice(&bytes[..]);
125,936,622✔
1187
        StacksAddress::new(version, Hash160(hash_bytes)).ok()
125,936,622✔
1188
    }
125,936,622✔
1189

1190
    fn is_burn(&self) -> bool {
2,141,860✔
1191
        self.bytes() == &Hash160([0u8; 20])
2,141,860✔
1192
    }
2,141,860✔
1193
}
1194

1195
#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
1196
pub struct StacksEpoch<L> {
1197
    pub epoch_id: StacksEpochId,
1198
    pub start_height: u64,
1199
    pub end_height: u64,
1200
    pub block_limit: L,
1201
    pub network_epoch: u8,
1202
}
1203

1204
impl<L> StacksEpoch<L> {
1205
    /// Determine which epoch, if any, in a list of epochs, a given burnchain height falls into.
1206
    /// Returns Some(index) if there is such an epoch in the list.
1207
    /// Returns None if not.
1208
    pub fn find_epoch(epochs: &[StacksEpoch<L>], height: u64) -> Option<usize> {
12,573,986✔
1209
        for (i, epoch) in epochs.iter().enumerate() {
85,813,578✔
1210
            if epoch.start_height <= height && height < epoch.end_height {
85,813,578✔
1211
                return Some(i);
12,573,986✔
1212
            }
73,239,592✔
1213
        }
1214
        None
×
1215
    }
12,573,986✔
1216

1217
    /// Find an epoch by its ID
1218
    /// Returns Some(index) if the epoch is in the list
1219
    /// Returns None if not
1220
    pub fn find_epoch_by_id(epochs: &[StacksEpoch<L>], epoch_id: StacksEpochId) -> Option<usize> {
56,075,417✔
1221
        for (i, epoch) in epochs.iter().enumerate() {
256,308,744✔
1222
            if epoch.epoch_id == epoch_id {
256,308,744✔
1223
                return Some(i);
52,158,365✔
1224
            }
204,150,379✔
1225
        }
1226
        None
3,917,052✔
1227
    }
56,075,417✔
1228
}
1229

1230
// StacksEpochs are ordered by start block height
1231
impl<L: PartialEq> PartialOrd for StacksEpoch<L> {
1232
    fn partial_cmp(&self, other: &StacksEpoch<L>) -> Option<Ordering> {
41,528,232✔
1233
        self.epoch_id.partial_cmp(&other.epoch_id)
41,528,232✔
1234
    }
41,528,232✔
1235
}
1236

1237
impl<L: PartialEq + Eq> Ord for StacksEpoch<L> {
1238
    fn cmp(&self, other: &StacksEpoch<L>) -> Ordering {
41,528,285✔
1239
        self.epoch_id.cmp(&other.epoch_id)
41,528,285✔
1240
    }
41,528,285✔
1241
}
1242

1243
/// A wrapper for holding a list of Epochs, indexable by StacksEpochId
1244
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
1245
pub struct EpochList<L: Clone>(Vec<StacksEpoch<L>>);
1246

1247
impl<L: Clone> From<Vec<StacksEpoch<L>>> for EpochList<L> {
1248
    fn from(value: Vec<StacksEpoch<L>>) -> Self {
5,146,813✔
1249
        Self(value)
5,146,813✔
1250
    }
5,146,813✔
1251
}
1252

1253
impl<L: Clone> EpochList<L> {
1254
    pub fn new(epochs: &[StacksEpoch<L>]) -> EpochList<L> {
4,092,408✔
1255
        EpochList(epochs.to_vec())
4,092,408✔
1256
    }
4,092,408✔
1257

1258
    pub fn get(&self, index: StacksEpochId) -> Option<&StacksEpoch<L>> {
56,074,262✔
1259
        self.0.get(StacksEpoch::find_epoch_by_id(&self.0, index)?)
56,074,262✔
1260
    }
56,074,262✔
1261

1262
    pub fn get_mut(&mut self, index: StacksEpochId) -> Option<&mut StacksEpoch<L>> {
124✔
1263
        let index = StacksEpoch::find_epoch_by_id(&self.0, index)?;
124✔
1264
        self.0.get_mut(index)
124✔
1265
    }
124✔
1266

1267
    /// Truncates the list after the given epoch id
1268
    pub fn truncate_after(&mut self, epoch_id: StacksEpochId) {
7✔
1269
        if let Some(index) = StacksEpoch::find_epoch_by_id(&self.0, epoch_id) {
7✔
1270
            self.0.truncate(index + 1);
7✔
1271
        }
7✔
1272
    }
7✔
1273

1274
    /// Determine which epoch, if any, a given burnchain height falls into.
1275
    pub fn epoch_id_at_height(&self, height: u64) -> Option<StacksEpochId> {
64,726✔
1276
        StacksEpoch::find_epoch(self, height).map(|idx| self.0[idx].epoch_id)
64,726✔
1277
    }
64,726✔
1278

1279
    /// Determine which epoch, if any, a given burnchain height falls into.
1280
    pub fn epoch_at_height(&self, height: u64) -> Option<StacksEpoch<L>> {
11,989,261✔
1281
        StacksEpoch::find_epoch(self, height).map(|idx| self.0[idx].clone())
11,989,261✔
1282
    }
11,989,261✔
1283

1284
    /// Pushes a new `StacksEpoch` to the end of the list
1285
    pub fn push(&mut self, epoch: StacksEpoch<L>) {
20✔
1286
        if let Some(last) = self.0.last() {
20✔
1287
            assert!(
20✔
1288
                epoch.start_height == last.end_height && epoch.epoch_id > last.epoch_id,
20✔
1289
                "Epochs must be pushed in order"
1290
            );
1291
        }
×
1292
        self.0.push(epoch);
20✔
1293
    }
20✔
1294

1295
    pub fn to_vec(self) -> Vec<StacksEpoch<L>> {
12,173✔
1296
        self.0
12,173✔
1297
    }
12,173✔
1298
}
1299

1300
impl<L: Clone> Index<StacksEpochId> for EpochList<L> {
1301
    type Output = StacksEpoch<L>;
1302
    fn index(&self, index: StacksEpochId) -> &StacksEpoch<L> {
2,263,839✔
1303
        self.get(index)
2,263,839✔
1304
            .expect("Invalid StacksEpochId: could not find corresponding epoch")
2,263,839✔
1305
    }
2,263,839✔
1306
}
1307

1308
impl<L: Clone> IndexMut<StacksEpochId> for EpochList<L> {
1309
    fn index_mut(&mut self, index: StacksEpochId) -> &mut StacksEpoch<L> {
121✔
1310
        self.get_mut(index)
121✔
1311
            .expect("Invalid StacksEpochId: could not find corresponding epoch")
121✔
1312
    }
121✔
1313
}
1314

1315
impl<L: Clone> Deref for EpochList<L> {
1316
    type Target = [StacksEpoch<L>];
1317

1318
    fn deref(&self) -> &Self::Target {
33,016,980✔
1319
        &self.0
33,016,980✔
1320
    }
33,016,980✔
1321
}
1322

1323
impl<L: Clone> DerefMut for EpochList<L> {
1324
    fn deref_mut(&mut self) -> &mut Self::Target {
2✔
1325
        &mut self.0
2✔
1326
    }
2✔
1327
}
1328

1329
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Copy)]
1330
pub enum MiningReason {
1331
    BlockFound = 0,
1332
    Extended = 1,
1333
    ReadCountExtend = 2,
1334
}
1335

1336
impl TryFrom<u8> for MiningReason {
1337
    type Error = CodecError;
1338

1339
    fn try_from(value: u8) -> Result<Self, CodecError> {
298,228✔
1340
        match value {
298,228✔
1341
            x if x == MiningReason::BlockFound as u8 => Ok(MiningReason::BlockFound),
298,228✔
1342
            x if x == MiningReason::Extended as u8 => Ok(MiningReason::Extended),
18,228✔
1343
            x if x == MiningReason::ReadCountExtend as u8 => Ok(MiningReason::ReadCountExtend),
210✔
1344
            _ => Err(CodecError::DeserializeError(format!(
×
1345
                "unknown mining reason {value}"
×
1346
            ))),
×
1347
        }
1348
    }
298,228✔
1349
}
1350

1351
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1352
pub struct MinerDiagnosticData {
1353
    pub burnchain_tip_height: u64,
1354
    pub burnchain_tip_consensus_hash: chainstate::ConsensusHash,
1355
    pub burnchain_tip_header_hash: chainstate::BurnchainHeaderHash,
1356
    pub tenure_extend_time_stamp: u64,
1357
    pub read_count_extend_timestamp: u64,
1358
    pub mining_reason: MiningReason,
1359
}
1360

1361
impl StacksMessageCodec for MinerDiagnosticData {
1362
    fn consensus_serialize<W: Write>(&self, fd: &mut W) -> Result<(), CodecError> {
4,746✔
1363
        write_next(fd, &self.burnchain_tip_height)?;
4,746✔
1364
        write_next(fd, &self.burnchain_tip_consensus_hash)?;
4,746✔
1365
        write_next(fd, &self.burnchain_tip_header_hash)?;
4,746✔
1366
        write_next(fd, &self.tenure_extend_time_stamp)?;
4,746✔
1367
        write_next(fd, &self.read_count_extend_timestamp)?;
4,746✔
1368
        write_next(fd, &(self.mining_reason as u8))?;
4,746✔
1369
        Ok(())
4,746✔
1370
    }
4,746✔
1371

1372
    fn consensus_deserialize<R: Read>(fd: &mut R) -> Result<Self, CodecError> {
42,603✔
1373
        let burnchain_tip_height = read_next(fd)?;
42,603✔
1374
        let burnchain_tip_consensus_hash = read_next(fd)?;
42,603✔
1375
        let burnchain_tip_header_hash = read_next(fd)?;
42,603✔
1376
        let tenure_extend_time_stamp = read_next(fd)?;
42,603✔
1377
        let read_count_extend_timestamp = read_next(fd)?;
42,603✔
1378
        let mining_reason = read_next::<u8, _>(fd)?.try_into()?;
42,603✔
1379

1380
        Ok(MinerDiagnosticData {
42,603✔
1381
            burnchain_tip_height,
42,603✔
1382
            burnchain_tip_consensus_hash,
42,603✔
1383
            burnchain_tip_header_hash,
42,603✔
1384
            tenure_extend_time_stamp,
42,603✔
1385
            read_count_extend_timestamp,
42,603✔
1386
            mining_reason,
42,603✔
1387
        })
42,603✔
1388
    }
42,603✔
1389
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc