saito_core/core/consensus/
burnfee.rs

1use std::cmp::max;
2
3use log::debug;
4
5use crate::core::defs::{Currency, Timestamp};
6
7//
8// our target blocktime
9// #[cfg(not(test))]
10// pub const HEARTBEAT: Timestamp = Duration::from_secs(5).as_millis() as Timestamp;
11//
12// #[cfg(test)]
13// pub const HEARTBEAT: Timestamp = Duration::from_millis(100).as_millis() as Timestamp;
14//
15// Burn Fee
16//
17// The burn fee algorithms determine how much ROUTING WORK needs to be in any block
18// in order for that block to be valid according to consensus rules. There are two
19// functions that are needed.
20//
21// - determine routing work needed (to produce block)
22// - determine burnfee variable (to include in block)
23//
24// Both of these functions are
25pub struct BurnFee {}
26
27impl BurnFee {
28    ///
29    /// Returns the amount of work needed to produce a block given the timestamp of
30    /// the previous block, the current timestamp, and the y-axis of the burn fee
31    /// curve. This is used both in the creation of blocks (mempool) as well as
32    /// during block validation.
33    ///
34    /// * `start` - burn fee value (y-axis) for curve determination ("start")
35    /// * `current_block_timestamp`- candidate timestamp
36    /// * `previous_block_timestamp` - timestamp of previous block
37    ///
38    pub fn return_routing_work_needed_to_produce_block_in_nolan(
39        burn_fee_previous_block: Currency,
40        current_block_timestamp_in_ms: Timestamp,
41        previous_block_timestamp_in_ms: Timestamp,
42        heartbeat: Timestamp,
43    ) -> Currency {
44        // impossible if times misordered
45        //
46        if previous_block_timestamp_in_ms >= current_block_timestamp_in_ms {
47            return 10_000_000_000_000_000_000;
48        }
49
50        let elapsed_time = max(
51            current_block_timestamp_in_ms - previous_block_timestamp_in_ms,
52            1,
53        );
54
55        if elapsed_time >= (2 * heartbeat) {
56            return 0;
57        }
58
59        // convert to float for division
60        let elapsed_time_float = elapsed_time as f64;
61        let burn_fee_previous_block_as_float: f64 = burn_fee_previous_block as f64 / 100_000_000.0;
62        let work_needed_float: f64 = burn_fee_previous_block_as_float / elapsed_time_float;
63
64        // convert back to nolan for rounding / safety
65        (work_needed_float * 100_000_000.0).round() as Currency
66    }
67
68    /// Returns an adjusted burnfee based on the start value provided
69    /// and the difference between the current block timestamp and the
70    /// previous block timestamp
71    ///
72    /// * `start` - The starting burn fee
73    /// * `current_block_timestamp` - The timestamp of the current `Block`
74    /// * `previous_block_timestamp` - The timestamp of the previous `Block`
75    ///
76    pub fn calculate_burnfee_for_block(
77        burn_fee_previous_block: Currency,
78        current_block_timestamp_in_ms: Timestamp,
79        previous_block_timestamp_in_ms: Timestamp,
80        heartbeat: Timestamp,
81    ) -> Currency {
82        debug!("calculate burnfee : previous block burn fee = {:?} current timestamp = {:?} prev block timestamp : {:?}",
83            burn_fee_previous_block, current_block_timestamp_in_ms, previous_block_timestamp_in_ms);
84        // impossible if times misordered
85        if previous_block_timestamp_in_ms >= current_block_timestamp_in_ms {
86            return 10_000_000_000_000_000_000;
87        }
88        let timestamp_difference = max(
89            1,
90            current_block_timestamp_in_ms - previous_block_timestamp_in_ms,
91        );
92
93        // algorithm fails if burn fee last block is 0, so default to low value
94        if burn_fee_previous_block == 0 {
95            return 50_000_000;
96        }
97
98        let burn_fee_previous_block_as_float: f64 = burn_fee_previous_block as f64 / 100_000_000.0;
99
100        let res0 = heartbeat as f64 / timestamp_difference as f64;
101        let res1 = res0.sqrt();
102        let res2: f64 = burn_fee_previous_block_as_float * res1;
103        let new_burnfee: Currency = (res2 * 100_000_000.0).round() as Currency;
104
105        new_burnfee
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use crate::core::defs::Currency;
112
113    use super::*;
114
115    #[test]
116    fn burnfee_return_work_needed_test() {
117        // if our elapsed time is twice our heartbeat, return 0
118        assert_eq!(
119            BurnFee::return_routing_work_needed_to_produce_block_in_nolan(10, 2 * 2_000, 0, 1_000),
120            0
121        );
122
123        // if their is no difference, the value should be the start value * 10^8
124        assert_eq!(
125            BurnFee::return_routing_work_needed_to_produce_block_in_nolan(
126                10_0000_0000,
127                0,
128                0,
129                1_000
130            ),
131            10_000_000_000_000_000_000,
132        );
133    }
134
135    #[test]
136    fn burnfee_burn_fee_adjustment_test() {
137        // if the difference in timestamps is equal to HEARTBEAT, our start value should not change
138        let mut new_start_burnfee =
139            BurnFee::calculate_burnfee_for_block(100_000_000, 1_000, 0, 1_000);
140        assert_eq!(new_start_burnfee, 100_000_000);
141
142        // the difference should be the square root of HEARBEAT over the difference in timestamps
143        new_start_burnfee = BurnFee::calculate_burnfee_for_block(100_000_000, 1_000 / 10, 0, 1_000);
144        assert_eq!(
145            new_start_burnfee,
146            (100_000_000.0 * 10f64.sqrt()).round() as Currency
147        );
148    }
149
150    #[test]
151    fn burnfee_slr_match() {
152        let burn_fee_previous_block = 50000000;
153        let current_block_timestamp: Timestamp = 1658821423;
154        let previous_block_timestamp: Timestamp = 1658821412;
155
156        let burnfee = BurnFee::calculate_burnfee_for_block(
157            burn_fee_previous_block,
158            current_block_timestamp,
159            previous_block_timestamp,
160            100,
161        );
162        assert_eq!(burnfee, 150755672);
163    }
164}