gemachain_program/
fee_calculator.rs

1#![allow(clippy::integer_arithmetic)]
2use crate::clock::DEFAULT_MS_PER_SLOT;
3use crate::ed25519_program;
4use crate::message::Message;
5use crate::secp256k1_program;
6use log::*;
7
8#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
9#[serde(rename_all = "camelCase")]
10pub struct FeeCalculator {
11    // The current cost of a signature  This amount may increase/decrease over time based on
12    // cluster processing load.
13    pub carats_per_signature: u64,
14}
15
16impl Default for FeeCalculator {
17    fn default() -> Self {
18        Self {
19            carats_per_signature: 0,
20        }
21    }
22}
23
24impl FeeCalculator {
25    pub fn new(carats_per_signature: u64) -> Self {
26        Self {
27            carats_per_signature,
28        }
29    }
30
31    #[deprecated(
32        since = "1.8.0",
33        note = "Please do not use, will no longer be available in the future"
34    )]
35    pub fn calculate_fee(&self, message: &Message) -> u64 {
36        let mut num_signatures: u64 = 0;
37        for instruction in &message.instructions {
38            let program_index = instruction.program_id_index as usize;
39            // Message may not be sanitized here
40            if program_index < message.account_keys.len() {
41                let id = message.account_keys[program_index];
42                if (secp256k1_program::check_id(&id) || ed25519_program::check_id(&id))
43                    && !instruction.data.is_empty()
44                {
45                    num_signatures += instruction.data[0] as u64;
46                }
47            }
48        }
49
50        self.carats_per_signature
51            * (u64::from(message.header.num_required_signatures) + num_signatures)
52    }
53}
54
55#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, AbiExample)]
56#[serde(rename_all = "camelCase")]
57pub struct FeeRateGovernor {
58    // The current cost of a signature  This amount may increase/decrease over time based on
59    // cluster processing load.
60    #[serde(skip)]
61    pub carats_per_signature: u64,
62
63    // The target cost of a signature when the cluster is operating around target_signatures_per_slot
64    // signatures
65    pub target_carats_per_signature: u64,
66
67    // Used to estimate the desired processing capacity of the cluster.  As the signatures for
68    // recent slots are fewer/greater than this value, carats_per_signature will decrease/increase
69    // for the next slot.  A value of 0 disables carats_per_signature fee adjustments
70    pub target_signatures_per_slot: u64,
71
72    pub min_carats_per_signature: u64,
73    pub max_carats_per_signature: u64,
74
75    // What portion of collected fees are to be destroyed, as a fraction of std::u8::MAX
76    pub burn_percent: u8,
77}
78
79pub const DEFAULT_TARGET_CARATS_PER_SIGNATURE: u64 = 10_000;
80pub const DEFAULT_TARGET_SIGNATURES_PER_SLOT: u64 = 50 * DEFAULT_MS_PER_SLOT;
81
82// Percentage of tx fees to burn
83pub const DEFAULT_BURN_PERCENT: u8 = 50;
84
85impl Default for FeeRateGovernor {
86    fn default() -> Self {
87        Self {
88            carats_per_signature: 0,
89            target_carats_per_signature: DEFAULT_TARGET_CARATS_PER_SIGNATURE,
90            target_signatures_per_slot: DEFAULT_TARGET_SIGNATURES_PER_SLOT,
91            min_carats_per_signature: 0,
92            max_carats_per_signature: 0,
93            burn_percent: DEFAULT_BURN_PERCENT,
94        }
95    }
96}
97
98impl FeeRateGovernor {
99    pub fn new(target_carats_per_signature: u64, target_signatures_per_slot: u64) -> Self {
100        let base_fee_rate_governor = Self {
101            target_carats_per_signature,
102            carats_per_signature: target_carats_per_signature,
103            target_signatures_per_slot,
104            ..FeeRateGovernor::default()
105        };
106
107        Self::new_derived(&base_fee_rate_governor, 0)
108    }
109
110    pub fn new_derived(
111        base_fee_rate_governor: &FeeRateGovernor,
112        latest_signatures_per_slot: u64,
113    ) -> Self {
114        let mut me = base_fee_rate_governor.clone();
115
116        if me.target_signatures_per_slot > 0 {
117            // carats_per_signature can range from 50% to 1000% of
118            // target_carats_per_signature
119            me.min_carats_per_signature = std::cmp::max(1, me.target_carats_per_signature / 2);
120            me.max_carats_per_signature = me.target_carats_per_signature * 10;
121
122            // What the cluster should charge at `latest_signatures_per_slot`
123            let desired_carats_per_signature =
124                me.max_carats_per_signature
125                    .min(me.min_carats_per_signature.max(
126                        me.target_carats_per_signature
127                            * std::cmp::min(latest_signatures_per_slot, std::u32::MAX as u64)
128                                as u64
129                            / me.target_signatures_per_slot as u64,
130                    ));
131
132            trace!(
133                "desired_carats_per_signature: {}",
134                desired_carats_per_signature
135            );
136
137            let gap = desired_carats_per_signature as i64
138                - base_fee_rate_governor.carats_per_signature as i64;
139
140            if gap == 0 {
141                me.carats_per_signature = desired_carats_per_signature;
142            } else {
143                // Adjust fee by 5% of target_carats_per_signature to produce a smooth
144                // increase/decrease in fees over time.
145                let gap_adjust =
146                    std::cmp::max(1, me.target_carats_per_signature / 20) as i64 * gap.signum();
147
148                trace!(
149                    "carats_per_signature gap is {}, adjusting by {}",
150                    gap,
151                    gap_adjust
152                );
153
154                me.carats_per_signature =
155                    me.max_carats_per_signature
156                        .min(me.min_carats_per_signature.max(
157                            (base_fee_rate_governor.carats_per_signature as i64 + gap_adjust)
158                                as u64,
159                        ));
160            }
161        } else {
162            me.carats_per_signature = base_fee_rate_governor.target_carats_per_signature;
163            me.min_carats_per_signature = me.target_carats_per_signature;
164            me.max_carats_per_signature = me.target_carats_per_signature;
165        }
166        debug!(
167            "new_derived(): carats_per_signature: {}",
168            me.carats_per_signature
169        );
170        me
171    }
172
173    /// calculate unburned fee from a fee total, returns (unburned, burned)
174    pub fn burn(&self, fees: u64) -> (u64, u64) {
175        let burned = fees * u64::from(self.burn_percent) / 100;
176        (fees - burned, burned)
177    }
178
179    /// create a FeeCalculator based on current cluster signature throughput
180    pub fn create_fee_calculator(&self) -> FeeCalculator {
181        FeeCalculator::new(self.carats_per_signature)
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::{pubkey::Pubkey, system_instruction};
189
190    #[test]
191    fn test_fee_rate_governor_burn() {
192        let mut fee_rate_governor = FeeRateGovernor::default();
193        assert_eq!(fee_rate_governor.burn(2), (1, 1));
194
195        fee_rate_governor.burn_percent = 0;
196        assert_eq!(fee_rate_governor.burn(2), (2, 0));
197
198        fee_rate_governor.burn_percent = 100;
199        assert_eq!(fee_rate_governor.burn(2), (0, 2));
200    }
201
202    #[test]
203    #[allow(deprecated)]
204    fn test_fee_calculator_calculate_fee() {
205        // Default: no fee.
206        let message = Message::default();
207        assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
208
209        // No signature, no fee.
210        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
211
212        // One signature, a fee.
213        let pubkey0 = Pubkey::new(&[0; 32]);
214        let pubkey1 = Pubkey::new(&[1; 32]);
215        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
216        let message = Message::new(&[ix0], Some(&pubkey0));
217        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
218
219        // Two signatures, double the fee.
220        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
221        let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
222        let message = Message::new(&[ix0, ix1], Some(&pubkey0));
223        assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
224    }
225
226    #[test]
227    #[allow(deprecated)]
228    fn test_fee_calculator_calculate_fee_secp256k1() {
229        use crate::instruction::Instruction;
230        let pubkey0 = Pubkey::new(&[0; 32]);
231        let pubkey1 = Pubkey::new(&[1; 32]);
232        let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
233        let mut secp_instruction = Instruction {
234            program_id: crate::secp256k1_program::id(),
235            accounts: vec![],
236            data: vec![],
237        };
238        let mut secp_instruction2 = Instruction {
239            program_id: crate::secp256k1_program::id(),
240            accounts: vec![],
241            data: vec![1],
242        };
243
244        let message = Message::new(
245            &[
246                ix0.clone(),
247                secp_instruction.clone(),
248                secp_instruction2.clone(),
249            ],
250            Some(&pubkey0),
251        );
252        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
253
254        secp_instruction.data = vec![0];
255        secp_instruction2.data = vec![10];
256        let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
257        assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
258    }
259
260    #[test]
261    fn test_fee_rate_governor_derived_default() {
262        gemachain_logger::setup();
263
264        let f0 = FeeRateGovernor::default();
265        assert_eq!(
266            f0.target_signatures_per_slot,
267            DEFAULT_TARGET_SIGNATURES_PER_SLOT
268        );
269        assert_eq!(
270            f0.target_carats_per_signature,
271            DEFAULT_TARGET_CARATS_PER_SIGNATURE
272        );
273        assert_eq!(f0.carats_per_signature, 0);
274
275        let f1 = FeeRateGovernor::new_derived(&f0, DEFAULT_TARGET_SIGNATURES_PER_SLOT);
276        assert_eq!(
277            f1.target_signatures_per_slot,
278            DEFAULT_TARGET_SIGNATURES_PER_SLOT
279        );
280        assert_eq!(
281            f1.target_carats_per_signature,
282            DEFAULT_TARGET_CARATS_PER_SIGNATURE
283        );
284        assert_eq!(
285            f1.carats_per_signature,
286            DEFAULT_TARGET_CARATS_PER_SIGNATURE / 2
287        ); // min
288    }
289
290    #[test]
291    fn test_fee_rate_governor_derived_adjust() {
292        gemachain_logger::setup();
293
294        let mut f = FeeRateGovernor {
295            target_carats_per_signature: 100,
296            target_signatures_per_slot: 100,
297            ..FeeRateGovernor::default()
298        };
299        f = FeeRateGovernor::new_derived(&f, 0);
300
301        // Ramp fees up
302        let mut count = 0;
303        loop {
304            let last_carats_per_signature = f.carats_per_signature;
305
306            f = FeeRateGovernor::new_derived(&f, std::u64::MAX);
307            info!("[up] f.carats_per_signature={}", f.carats_per_signature);
308
309            // some maximum target reached
310            if f.carats_per_signature == last_carats_per_signature {
311                break;
312            }
313            // shouldn't take more than 1000 steps to get to minimum
314            assert!(count < 1000);
315            count += 1;
316        }
317
318        // Ramp fees down
319        let mut count = 0;
320        loop {
321            let last_carats_per_signature = f.carats_per_signature;
322            f = FeeRateGovernor::new_derived(&f, 0);
323
324            info!(
325                "[down] f.carats_per_signature={}",
326                f.carats_per_signature
327            );
328
329            // some minimum target reached
330            if f.carats_per_signature == last_carats_per_signature {
331                break;
332            }
333
334            // shouldn't take more than 1000 steps to get to minimum
335            assert!(count < 1000);
336            count += 1;
337        }
338
339        // Arrive at target rate
340        let mut count = 0;
341        while f.carats_per_signature != f.target_carats_per_signature {
342            f = FeeRateGovernor::new_derived(&f, f.target_signatures_per_slot);
343            info!(
344                "[target] f.carats_per_signature={}",
345                f.carats_per_signature
346            );
347            // shouldn't take more than 100 steps to get to target
348            assert!(count < 100);
349            count += 1;
350        }
351    }
352}