kaspa_consensus/processes/
coinbase.rs

1use kaspa_consensus_core::{
2    coinbase::*,
3    errors::coinbase::{CoinbaseError, CoinbaseResult},
4    subnets,
5    tx::{ScriptPublicKey, ScriptVec, Transaction, TransactionOutput},
6    BlockHashMap, BlockHashSet,
7};
8use std::convert::TryInto;
9
10use crate::{constants, model::stores::ghostdag::GhostdagData};
11
12const LENGTH_OF_BLUE_SCORE: usize = size_of::<u64>();
13const LENGTH_OF_SUBSIDY: usize = size_of::<u64>();
14const LENGTH_OF_SCRIPT_PUB_KEY_VERSION: usize = size_of::<u16>();
15const LENGTH_OF_SCRIPT_PUB_KEY_LENGTH: usize = size_of::<u8>();
16
17const MIN_PAYLOAD_LENGTH: usize =
18    LENGTH_OF_BLUE_SCORE + LENGTH_OF_SUBSIDY + LENGTH_OF_SCRIPT_PUB_KEY_VERSION + LENGTH_OF_SCRIPT_PUB_KEY_LENGTH;
19
20// We define a year as 365.25 days and a month as 365.25 / 12 = 30.4375
21// SECONDS_PER_MONTH = 30.4375 * 24 * 60 * 60
22const SECONDS_PER_MONTH: u64 = 2629800;
23
24pub const SUBSIDY_BY_MONTH_TABLE_SIZE: usize = 426;
25pub type SubsidyByMonthTable = [u64; SUBSIDY_BY_MONTH_TABLE_SIZE];
26
27#[derive(Clone)]
28pub struct CoinbaseManager {
29    coinbase_payload_script_public_key_max_len: u8,
30    max_coinbase_payload_len: usize,
31    deflationary_phase_daa_score: u64,
32    pre_deflationary_phase_base_subsidy: u64,
33    target_time_per_block: u64,
34
35    /// Precomputed number of blocks per month
36    blocks_per_month: u64,
37
38    /// Precomputed subsidy by month table
39    subsidy_by_month_table: SubsidyByMonthTable,
40}
41
42/// Struct used to streamline payload parsing
43struct PayloadParser<'a> {
44    remaining: &'a [u8], // The unparsed remainder
45}
46
47impl<'a> PayloadParser<'a> {
48    fn new(data: &'a [u8]) -> Self {
49        Self { remaining: data }
50    }
51
52    /// Returns a slice with the first `n` bytes of `remaining`, while setting `remaining` to the remaining part
53    fn take(&mut self, n: usize) -> &[u8] {
54        let (segment, remaining) = self.remaining.split_at(n);
55        self.remaining = remaining;
56        segment
57    }
58}
59
60impl CoinbaseManager {
61    pub fn new(
62        coinbase_payload_script_public_key_max_len: u8,
63        max_coinbase_payload_len: usize,
64        deflationary_phase_daa_score: u64,
65        pre_deflationary_phase_base_subsidy: u64,
66        target_time_per_block: u64,
67    ) -> Self {
68        assert!(1000 % target_time_per_block == 0);
69        let bps = 1000 / target_time_per_block;
70        let blocks_per_month = SECONDS_PER_MONTH * bps;
71
72        // Precomputed subsidy by month table for the actual block per second rate
73        // Here values are rounded up so that we keep the same number of rewarding months as in the original 1 BPS table.
74        // In a 10 BPS network, the induced increase in total rewards is 51 KAS (see tests::calc_high_bps_total_rewards_delta())
75        let subsidy_by_month_table: SubsidyByMonthTable = core::array::from_fn(|i| (SUBSIDY_BY_MONTH_TABLE[i] + bps - 1) / bps);
76        Self {
77            coinbase_payload_script_public_key_max_len,
78            max_coinbase_payload_len,
79            deflationary_phase_daa_score,
80            pre_deflationary_phase_base_subsidy,
81            target_time_per_block,
82            blocks_per_month,
83            subsidy_by_month_table,
84        }
85    }
86
87    #[cfg(test)]
88    #[inline]
89    pub fn bps(&self) -> u64 {
90        1000 / self.target_time_per_block
91    }
92
93    pub fn expected_coinbase_transaction<T: AsRef<[u8]>>(
94        &self,
95        daa_score: u64,
96        miner_data: MinerData<T>,
97        ghostdag_data: &GhostdagData,
98        mergeset_rewards: &BlockHashMap<BlockRewardData>,
99        mergeset_non_daa: &BlockHashSet,
100    ) -> CoinbaseResult<CoinbaseTransactionTemplate> {
101        let mut outputs = Vec::with_capacity(ghostdag_data.mergeset_blues.len() + 1); // + 1 for possible red reward
102
103        // Add an output for each mergeset blue block (∩ DAA window), paying to the script reported by the block.
104        // Note that combinatorically it is nearly impossible for a blue block to be non-DAA
105        for blue in ghostdag_data.mergeset_blues.iter().filter(|h| !mergeset_non_daa.contains(h)) {
106            let reward_data = mergeset_rewards.get(blue).unwrap();
107            if reward_data.subsidy + reward_data.total_fees > 0 {
108                outputs
109                    .push(TransactionOutput::new(reward_data.subsidy + reward_data.total_fees, reward_data.script_public_key.clone()));
110            }
111        }
112
113        // Collect all rewards from mergeset reds ∩ DAA window and create a
114        // single output rewarding all to the current block (the "merging" block)
115        let mut red_reward = 0u64;
116        for red in ghostdag_data.mergeset_reds.iter().filter(|h| !mergeset_non_daa.contains(h)) {
117            let reward_data = mergeset_rewards.get(red).unwrap();
118            red_reward += reward_data.subsidy + reward_data.total_fees;
119        }
120        if red_reward > 0 {
121            outputs.push(TransactionOutput::new(red_reward, miner_data.script_public_key.clone()));
122        }
123
124        // Build the current block's payload
125        let subsidy = self.calc_block_subsidy(daa_score);
126        let payload = self.serialize_coinbase_payload(&CoinbaseData { blue_score: ghostdag_data.blue_score, subsidy, miner_data })?;
127
128        Ok(CoinbaseTransactionTemplate {
129            tx: Transaction::new(constants::TX_VERSION, vec![], outputs, 0, subnets::SUBNETWORK_ID_COINBASE, 0, payload),
130            has_red_reward: red_reward > 0,
131        })
132    }
133
134    pub fn serialize_coinbase_payload<T: AsRef<[u8]>>(&self, data: &CoinbaseData<T>) -> CoinbaseResult<Vec<u8>> {
135        let script_pub_key_len = data.miner_data.script_public_key.script().len();
136        if script_pub_key_len > self.coinbase_payload_script_public_key_max_len as usize {
137            return Err(CoinbaseError::PayloadScriptPublicKeyLenAboveMax(
138                script_pub_key_len,
139                self.coinbase_payload_script_public_key_max_len,
140            ));
141        }
142        let payload: Vec<u8> = data.blue_score.to_le_bytes().iter().copied()                    // Blue score                   (u64)
143            .chain(data.subsidy.to_le_bytes().iter().copied())                                  // Subsidy                      (u64)
144            .chain(data.miner_data.script_public_key.version().to_le_bytes().iter().copied())   // Script public key version    (u16)
145            .chain((script_pub_key_len as u8).to_le_bytes().iter().copied())                    // Script public key length     (u8)
146            .chain(data.miner_data.script_public_key.script().iter().copied())                  // Script public key            
147            .chain(data.miner_data.extra_data.as_ref().iter().copied())                         // Extra data
148            .collect();
149
150        Ok(payload)
151    }
152
153    pub fn modify_coinbase_payload<T: AsRef<[u8]>>(&self, mut payload: Vec<u8>, miner_data: &MinerData<T>) -> CoinbaseResult<Vec<u8>> {
154        let script_pub_key_len = miner_data.script_public_key.script().len();
155        if script_pub_key_len > self.coinbase_payload_script_public_key_max_len as usize {
156            return Err(CoinbaseError::PayloadScriptPublicKeyLenAboveMax(
157                script_pub_key_len,
158                self.coinbase_payload_script_public_key_max_len,
159            ));
160        }
161
162        // Keep only blue score and subsidy. Note that truncate does not modify capacity, so
163        // the usual case where the payloads are the same size will not trigger a reallocation
164        payload.truncate(LENGTH_OF_BLUE_SCORE + LENGTH_OF_SUBSIDY);
165        payload.extend(
166            miner_data.script_public_key.version().to_le_bytes().iter().copied() // Script public key version (u16)
167                .chain((script_pub_key_len as u8).to_le_bytes().iter().copied()) // Script public key length  (u8)
168                .chain(miner_data.script_public_key.script().iter().copied())    // Script public key
169                .chain(miner_data.extra_data.as_ref().iter().copied()), // Extra data
170        );
171
172        Ok(payload)
173    }
174
175    pub fn deserialize_coinbase_payload<'a>(&self, payload: &'a [u8]) -> CoinbaseResult<CoinbaseData<&'a [u8]>> {
176        if payload.len() < MIN_PAYLOAD_LENGTH {
177            return Err(CoinbaseError::PayloadLenBelowMin(payload.len(), MIN_PAYLOAD_LENGTH));
178        }
179
180        if payload.len() > self.max_coinbase_payload_len {
181            return Err(CoinbaseError::PayloadLenAboveMax(payload.len(), self.max_coinbase_payload_len));
182        }
183
184        let mut parser = PayloadParser::new(payload);
185
186        let blue_score = u64::from_le_bytes(parser.take(LENGTH_OF_BLUE_SCORE).try_into().unwrap());
187        let subsidy = u64::from_le_bytes(parser.take(LENGTH_OF_SUBSIDY).try_into().unwrap());
188        let script_pub_key_version = u16::from_le_bytes(parser.take(LENGTH_OF_SCRIPT_PUB_KEY_VERSION).try_into().unwrap());
189        let script_pub_key_len = u8::from_le_bytes(parser.take(LENGTH_OF_SCRIPT_PUB_KEY_LENGTH).try_into().unwrap());
190
191        if script_pub_key_len > self.coinbase_payload_script_public_key_max_len {
192            return Err(CoinbaseError::PayloadScriptPublicKeyLenAboveMax(
193                script_pub_key_len as usize,
194                self.coinbase_payload_script_public_key_max_len,
195            ));
196        }
197
198        if parser.remaining.len() < script_pub_key_len as usize {
199            return Err(CoinbaseError::PayloadCantContainScriptPublicKey(
200                payload.len(),
201                MIN_PAYLOAD_LENGTH + script_pub_key_len as usize,
202            ));
203        }
204
205        let script_public_key =
206            ScriptPublicKey::new(script_pub_key_version, ScriptVec::from_slice(parser.take(script_pub_key_len as usize)));
207        let extra_data = parser.remaining;
208
209        Ok(CoinbaseData { blue_score, subsidy, miner_data: MinerData { script_public_key, extra_data } })
210    }
211
212    pub fn calc_block_subsidy(&self, daa_score: u64) -> u64 {
213        if daa_score < self.deflationary_phase_daa_score {
214            return self.pre_deflationary_phase_base_subsidy;
215        }
216
217        let months_since_deflationary_phase_started =
218            ((daa_score - self.deflationary_phase_daa_score) / self.blocks_per_month) as usize;
219        if months_since_deflationary_phase_started >= self.subsidy_by_month_table.len() {
220            *(self.subsidy_by_month_table).last().unwrap()
221        } else {
222            self.subsidy_by_month_table[months_since_deflationary_phase_started]
223        }
224    }
225
226    #[cfg(test)]
227    pub fn legacy_calc_block_subsidy(&self, daa_score: u64) -> u64 {
228        if daa_score < self.deflationary_phase_daa_score {
229            return self.pre_deflationary_phase_base_subsidy;
230        }
231
232        // Note that this calculation implicitly assumes that block per second = 1 (by assuming daa score diff is in second units).
233        let months_since_deflationary_phase_started = (daa_score - self.deflationary_phase_daa_score) / SECONDS_PER_MONTH;
234        assert!(months_since_deflationary_phase_started <= usize::MAX as u64);
235        let months_since_deflationary_phase_started: usize = months_since_deflationary_phase_started as usize;
236        if months_since_deflationary_phase_started >= SUBSIDY_BY_MONTH_TABLE.len() {
237            *SUBSIDY_BY_MONTH_TABLE.last().unwrap()
238        } else {
239            SUBSIDY_BY_MONTH_TABLE[months_since_deflationary_phase_started]
240        }
241    }
242}
243
244/*
245    This table was pre-calculated by calling `calcDeflationaryPeriodBlockSubsidyFloatCalc` (in kaspad-go) for all months until reaching 0 subsidy.
246    To regenerate this table, run `TestBuildSubsidyTable` in coinbasemanager_test.go (note the `deflationaryPhaseBaseSubsidy` therein).
247    These values apply to 1 block per second.
248*/
249#[rustfmt::skip]
250const SUBSIDY_BY_MONTH_TABLE: [u64; 426] = [
251	44000000000, 41530469757, 39199543598, 36999442271, 34922823143, 32962755691, 31112698372, 29366476791, 27718263097, 26162556530, 24694165062, 23308188075, 22000000000, 20765234878, 19599771799, 18499721135, 17461411571, 16481377845, 15556349186, 14683238395, 13859131548, 13081278265, 12347082531, 11654094037, 11000000000,
252	10382617439, 9799885899, 9249860567, 8730705785, 8240688922, 7778174593, 7341619197, 6929565774, 6540639132, 6173541265, 5827047018, 5500000000, 5191308719, 4899942949, 4624930283, 4365352892, 4120344461, 3889087296, 3670809598, 3464782887, 3270319566, 3086770632, 2913523509, 2750000000, 2595654359,
253	2449971474, 2312465141, 2182676446, 2060172230, 1944543648, 1835404799, 1732391443, 1635159783, 1543385316, 1456761754, 1375000000, 1297827179, 1224985737, 1156232570, 1091338223, 1030086115, 972271824, 917702399, 866195721, 817579891, 771692658, 728380877, 687500000, 648913589, 612492868,
254	578116285, 545669111, 515043057, 486135912, 458851199, 433097860, 408789945, 385846329, 364190438, 343750000, 324456794, 306246434, 289058142, 272834555, 257521528, 243067956, 229425599, 216548930, 204394972, 192923164, 182095219, 171875000, 162228397, 153123217, 144529071,
255	136417277, 128760764, 121533978, 114712799, 108274465, 102197486, 96461582, 91047609, 85937500, 81114198, 76561608, 72264535, 68208638, 64380382, 60766989, 57356399, 54137232, 51098743, 48230791, 45523804, 42968750, 40557099, 38280804, 36132267, 34104319,
256	32190191, 30383494, 28678199, 27068616, 25549371, 24115395, 22761902, 21484375, 20278549, 19140402, 18066133, 17052159, 16095095, 15191747, 14339099, 13534308, 12774685, 12057697, 11380951, 10742187, 10139274, 9570201, 9033066, 8526079, 8047547,
257	7595873, 7169549, 6767154, 6387342, 6028848, 5690475, 5371093, 5069637, 4785100, 4516533, 4263039, 4023773, 3797936, 3584774, 3383577, 3193671, 3014424, 2845237, 2685546, 2534818, 2392550, 2258266, 2131519, 2011886, 1898968,
258	1792387, 1691788, 1596835, 1507212, 1422618, 1342773, 1267409, 1196275, 1129133, 1065759, 1005943, 949484, 896193, 845894, 798417, 753606, 711309, 671386, 633704, 598137, 564566, 532879, 502971, 474742, 448096,
259	422947, 399208, 376803, 355654, 335693, 316852, 299068, 282283, 266439, 251485, 237371, 224048, 211473, 199604, 188401, 177827, 167846, 158426, 149534, 141141, 133219, 125742, 118685, 112024, 105736,
260	99802, 94200, 88913, 83923, 79213, 74767, 70570, 66609, 62871, 59342, 56012, 52868, 49901, 47100, 44456, 41961, 39606, 37383, 35285, 33304, 31435, 29671, 28006, 26434, 24950,
261	23550, 22228, 20980, 19803, 18691, 17642, 16652, 15717, 14835, 14003, 13217, 12475, 11775, 11114, 10490, 9901, 9345, 8821, 8326, 7858, 7417, 7001, 6608, 6237, 5887,
262	5557, 5245, 4950, 4672, 4410, 4163, 3929, 3708, 3500, 3304, 3118, 2943, 2778, 2622, 2475, 2336, 2205, 2081, 1964, 1854, 1750, 1652, 1559, 1471, 1389,
263	1311, 1237, 1168, 1102, 1040, 982, 927, 875, 826, 779, 735, 694, 655, 618, 584, 551, 520, 491, 463, 437, 413, 389, 367, 347, 327,
264	309, 292, 275, 260, 245, 231, 218, 206, 194, 183, 173, 163, 154, 146, 137, 130, 122, 115, 109, 103, 97, 91, 86, 81, 77,
265	73, 68, 65, 61, 57, 54, 51, 48, 45, 43, 40, 38, 36, 34, 32, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18,
266	17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4,
267	4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
268	0,
269];
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use crate::params::MAINNET_PARAMS;
275    use kaspa_consensus_core::{
276        config::params::{Params, TESTNET11_PARAMS},
277        constants::SOMPI_PER_KASPA,
278        network::NetworkId,
279        tx::scriptvec,
280    };
281
282    #[test]
283    fn calc_high_bps_total_rewards_delta() {
284        const SECONDS_PER_MONTH: u64 = 2629800;
285
286        let legacy_cbm = create_legacy_manager();
287        let pre_deflationary_rewards = legacy_cbm.pre_deflationary_phase_base_subsidy * legacy_cbm.deflationary_phase_daa_score;
288        let total_rewards: u64 = pre_deflationary_rewards + SUBSIDY_BY_MONTH_TABLE.iter().map(|x| x * SECONDS_PER_MONTH).sum::<u64>();
289        let testnet_11_bps = TESTNET11_PARAMS.bps();
290        let total_high_bps_rewards_rounded_up: u64 = pre_deflationary_rewards
291            + SUBSIDY_BY_MONTH_TABLE
292                .iter()
293                .map(|x| ((x + testnet_11_bps - 1) / testnet_11_bps * testnet_11_bps) * SECONDS_PER_MONTH)
294                .sum::<u64>();
295
296        let cbm = create_manager(&TESTNET11_PARAMS);
297        let total_high_bps_rewards: u64 =
298            pre_deflationary_rewards + cbm.subsidy_by_month_table.iter().map(|x| x * cbm.blocks_per_month).sum::<u64>();
299        assert_eq!(total_high_bps_rewards_rounded_up, total_high_bps_rewards, "subsidy adjusted to bps must be rounded up");
300
301        let delta = total_high_bps_rewards as i64 - total_rewards as i64;
302
303        println!("Total rewards: {} sompi => {} KAS", total_rewards, total_rewards / SOMPI_PER_KASPA);
304        println!("Total high bps rewards: {} sompi => {} KAS", total_high_bps_rewards, total_high_bps_rewards / SOMPI_PER_KASPA);
305        println!("Delta: {} sompi => {} KAS", delta, delta / SOMPI_PER_KASPA as i64);
306    }
307
308    #[test]
309    fn subsidy_by_month_table_test() {
310        let cbm = create_legacy_manager();
311        cbm.subsidy_by_month_table.iter().enumerate().for_each(|(i, x)| {
312            assert_eq!(SUBSIDY_BY_MONTH_TABLE[i], *x, "for 1 BPS, const table and precomputed values must match");
313        });
314
315        for network_id in NetworkId::iter() {
316            let cbm = create_manager(&network_id.into());
317            cbm.subsidy_by_month_table.iter().enumerate().for_each(|(i, x)| {
318                assert_eq!(
319                    (SUBSIDY_BY_MONTH_TABLE[i] + cbm.bps() - 1) / cbm.bps(),
320                    *x,
321                    "{}: locally computed and precomputed values must match",
322                    network_id
323                );
324            });
325        }
326    }
327
328    #[test]
329    fn subsidy_test() {
330        const PRE_DEFLATIONARY_PHASE_BASE_SUBSIDY: u64 = 50000000000;
331        const DEFLATIONARY_PHASE_INITIAL_SUBSIDY: u64 = 44000000000;
332        const SECONDS_PER_MONTH: u64 = 2629800;
333        const SECONDS_PER_HALVING: u64 = SECONDS_PER_MONTH * 12;
334
335        for network_id in NetworkId::iter() {
336            let params = &network_id.into();
337            let cbm = create_manager(params);
338
339            let pre_deflationary_phase_base_subsidy = PRE_DEFLATIONARY_PHASE_BASE_SUBSIDY / params.bps();
340            let deflationary_phase_initial_subsidy = DEFLATIONARY_PHASE_INITIAL_SUBSIDY / params.bps();
341            let blocks_per_halving = SECONDS_PER_HALVING * params.bps();
342
343            struct Test {
344                name: &'static str,
345                daa_score: u64,
346                expected: u64,
347            }
348
349            let tests = vec![
350                Test { name: "first mined block", daa_score: 1, expected: pre_deflationary_phase_base_subsidy },
351                Test {
352                    name: "before deflationary phase",
353                    daa_score: params.deflationary_phase_daa_score - 1,
354                    expected: pre_deflationary_phase_base_subsidy,
355                },
356                Test {
357                    name: "start of deflationary phase",
358                    daa_score: params.deflationary_phase_daa_score,
359                    expected: deflationary_phase_initial_subsidy,
360                },
361                Test {
362                    name: "after one halving",
363                    daa_score: params.deflationary_phase_daa_score + blocks_per_halving,
364                    expected: deflationary_phase_initial_subsidy / 2,
365                },
366                Test {
367                    name: "after 2 halvings",
368                    daa_score: params.deflationary_phase_daa_score + 2 * blocks_per_halving,
369                    expected: deflationary_phase_initial_subsidy / 4,
370                },
371                Test {
372                    name: "after 5 halvings",
373                    daa_score: params.deflationary_phase_daa_score + 5 * blocks_per_halving,
374                    expected: deflationary_phase_initial_subsidy / 32,
375                },
376                Test {
377                    name: "after 32 halvings",
378                    daa_score: params.deflationary_phase_daa_score + 32 * blocks_per_halving,
379                    expected: ((DEFLATIONARY_PHASE_INITIAL_SUBSIDY / 2_u64.pow(32)) + cbm.bps() - 1) / cbm.bps(),
380                },
381                Test {
382                    name: "just before subsidy depleted",
383                    daa_score: params.deflationary_phase_daa_score + 35 * blocks_per_halving,
384                    expected: 1,
385                },
386                Test {
387                    name: "after subsidy depleted",
388                    daa_score: params.deflationary_phase_daa_score + 36 * blocks_per_halving,
389                    expected: 0,
390                },
391            ];
392
393            for t in tests {
394                assert_eq!(cbm.calc_block_subsidy(t.daa_score), t.expected, "{} test '{}' failed", network_id, t.name);
395                if params.bps() == 1 {
396                    assert_eq!(cbm.legacy_calc_block_subsidy(t.daa_score), t.expected, "{} test '{}' failed", network_id, t.name);
397                }
398            }
399        }
400    }
401
402    #[test]
403    fn payload_serialization_test() {
404        let cbm = create_manager(&MAINNET_PARAMS);
405
406        let script_data = [33u8, 255];
407        let extra_data = [2u8, 3];
408        let data = CoinbaseData {
409            blue_score: 56,
410            subsidy: 44000000000,
411            miner_data: MinerData {
412                script_public_key: ScriptPublicKey::new(0, ScriptVec::from_slice(&script_data)),
413                extra_data: &extra_data as &[u8],
414            },
415        };
416
417        let payload = cbm.serialize_coinbase_payload(&data).unwrap();
418        let deserialized_data = cbm.deserialize_coinbase_payload(&payload).unwrap();
419
420        assert_eq!(data, deserialized_data);
421
422        // Test an actual mainnet payload
423        let payload_hex =
424            "b612c90100000000041a763e07000000000022202b32443ff740012157716d81216d09aebc39e5493c93a7181d92cb756c02c560ac302e31322e382f";
425        let mut payload = vec![0u8; payload_hex.len() / 2];
426        faster_hex::hex_decode(payload_hex.as_bytes(), &mut payload).unwrap();
427        let deserialized_data = cbm.deserialize_coinbase_payload(&payload).unwrap();
428
429        let expected_data = CoinbaseData {
430            blue_score: 29954742,
431            subsidy: 31112698372,
432            miner_data: MinerData {
433                script_public_key: ScriptPublicKey::new(
434                    0,
435                    scriptvec![
436                        32, 43, 50, 68, 63, 247, 64, 1, 33, 87, 113, 109, 129, 33, 109, 9, 174, 188, 57, 229, 73, 60, 147, 167, 24,
437                        29, 146, 203, 117, 108, 2, 197, 96, 172,
438                    ],
439                ),
440                extra_data: &[48u8, 46, 49, 50, 46, 56, 47] as &[u8],
441            },
442        };
443        assert_eq!(expected_data, deserialized_data);
444    }
445
446    #[test]
447    fn modify_payload_test() {
448        let cbm = create_manager(&MAINNET_PARAMS);
449
450        let script_data = [33u8, 255];
451        let extra_data = [2u8, 3, 23, 98];
452        let data = CoinbaseData {
453            blue_score: 56345,
454            subsidy: 44000000000,
455            miner_data: MinerData {
456                script_public_key: ScriptPublicKey::new(0, ScriptVec::from_slice(&script_data)),
457                extra_data: &extra_data,
458            },
459        };
460
461        let data2 = CoinbaseData {
462            blue_score: data.blue_score,
463            subsidy: data.subsidy,
464            miner_data: MinerData {
465                // Modify only miner data
466                script_public_key: ScriptPublicKey::new(0, ScriptVec::from_slice(&[33u8, 255, 33])),
467                extra_data: &[2u8, 3, 23, 98, 34, 34] as &[u8],
468            },
469        };
470
471        let mut payload = cbm.serialize_coinbase_payload(&data).unwrap();
472        payload = cbm.modify_coinbase_payload(payload, &data2.miner_data).unwrap(); // Update the payload with the modified miner data
473        let deserialized_data = cbm.deserialize_coinbase_payload(&payload).unwrap();
474
475        assert_eq!(data2, deserialized_data);
476    }
477
478    fn create_manager(params: &Params) -> CoinbaseManager {
479        CoinbaseManager::new(
480            params.coinbase_payload_script_public_key_max_len,
481            params.max_coinbase_payload_len,
482            params.deflationary_phase_daa_score,
483            params.pre_deflationary_phase_base_subsidy,
484            params.target_time_per_block,
485        )
486    }
487
488    /// Return a CoinbaseManager with legacy golang 1 BPS properties
489    fn create_legacy_manager() -> CoinbaseManager {
490        CoinbaseManager::new(150, 204, 15778800 - 259200, 50000000000, 1000)
491    }
492}