drive/fees/
op.rs

1use crate::util::batch::GroveDbOpBatch;
2use grovedb_costs::storage_cost::removal::Identifier;
3use grovedb_costs::storage_cost::removal::StorageRemovedBytes::{
4    BasicStorageRemoval, NoStorageRemoval, SectionedStorageRemoval,
5};
6use std::collections::BTreeMap;
7
8use enum_map::Enum;
9use grovedb::batch::key_info::KeyInfo;
10use grovedb::batch::KeyInfoPath;
11use grovedb::element::reference_path::ReferencePathType;
12use grovedb::element::MaxReferenceHop;
13use grovedb::{batch::QualifiedGroveDbOp, Element, ElementFlags, TreeType};
14use grovedb_costs::OperationCost;
15use itertools::Itertools;
16
17use crate::error::drive::DriveError;
18use crate::error::Error;
19use crate::fees::get_overflow_error;
20use crate::fees::op::LowLevelDriveOperation::{
21    CalculatedCostOperation, FunctionOperation, GroveOperation, PreCalculatedFeeResult,
22};
23use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods;
24use crate::util::storage_flags::StorageFlags;
25use dpp::block::epoch::Epoch;
26use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
27use dpp::fee::fee_result::refunds::FeeRefunds;
28use dpp::fee::fee_result::FeeResult;
29use dpp::fee::Credits;
30use platform_version::version::fee::FeeVersion;
31
32/// Base ops
33#[derive(Debug, Enum)]
34pub enum BaseOp {
35    /// Stop
36    Stop,
37    /// Add
38    Add,
39    /// Multiply
40    Mul,
41    /// Subtract
42    Sub,
43    /// Divide
44    Div,
45    /// Sdiv
46    Sdiv,
47    /// Modulo
48    Mod,
49    /// Smod
50    Smod,
51    /// Addmod
52    Addmod,
53    /// Mulmod
54    Mulmod,
55    /// Signextend
56    Signextend,
57    /// Less than
58    Lt,
59    /// Greater than
60    Gt,
61    /// Slt
62    Slt,
63    /// Sgt
64    Sgt,
65    /// Equals
66    Eq,
67    /// Is zero
68    Iszero,
69    /// And
70    And,
71    /// Or
72    Or,
73    /// Xor
74    Xor,
75    /// Not
76    Not,
77    /// Byte
78    Byte,
79}
80
81impl BaseOp {
82    /// Match the op and get the cost
83    pub fn cost(&self) -> u64 {
84        match self {
85            BaseOp::Stop => 0,
86            BaseOp::Add => 12,
87            BaseOp::Mul => 20,
88            BaseOp::Sub => 12,
89            BaseOp::Div => 20,
90            BaseOp::Sdiv => 20,
91            BaseOp::Mod => 20,
92            BaseOp::Smod => 20,
93            BaseOp::Addmod => 32,
94            BaseOp::Mulmod => 32,
95            BaseOp::Signextend => 20,
96            BaseOp::Lt => 12,
97            BaseOp::Gt => 12,
98            BaseOp::Slt => 12,
99            BaseOp::Sgt => 12,
100            BaseOp::Eq => 12,
101            BaseOp::Iszero => 12,
102            BaseOp::And => 12,
103            BaseOp::Or => 12,
104            BaseOp::Xor => 12,
105            BaseOp::Not => 12,
106            BaseOp::Byte => 12,
107        }
108    }
109}
110
111/// Supported Hash Functions
112#[derive(Debug, Enum, PartialEq, Eq)]
113pub enum HashFunction {
114    /// Used for crypto addresses
115    Sha256RipeMD160,
116    /// Single Sha256
117    Sha256,
118    /// Double Sha256
119    Sha256_2,
120    /// Single Blake3
121    Blake3,
122}
123
124impl HashFunction {
125    fn block_size(&self) -> u16 {
126        match self {
127            HashFunction::Sha256 => 64,
128            HashFunction::Sha256_2 => 64,
129            HashFunction::Blake3 => 64,
130            HashFunction::Sha256RipeMD160 => 64,
131        }
132    }
133
134    fn rounds(&self) -> u16 {
135        match self {
136            HashFunction::Sha256 => 1,
137            HashFunction::Sha256_2 => 2,
138            HashFunction::Blake3 => 1,
139            HashFunction::Sha256RipeMD160 => 1,
140        }
141    }
142
143    fn block_cost(&self, fee_version: &FeeVersion) -> u64 {
144        match self {
145            HashFunction::Sha256 => fee_version.hashing.sha256_per_block,
146            HashFunction::Sha256_2 => fee_version.hashing.sha256_per_block,
147            HashFunction::Blake3 => fee_version.hashing.blake3_per_block,
148            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_per_block,
149        }
150    }
151
152    fn base_cost(&self, fee_version: &FeeVersion) -> u64 {
153        match self {
154            HashFunction::Sha256 => fee_version.hashing.single_sha256_base,
155            // It's normal that the base cost for a sha256 will have a single sha256 base
156            // But it has an extra block
157            HashFunction::Sha256_2 => fee_version.hashing.single_sha256_base,
158            HashFunction::Blake3 => fee_version.hashing.blake3_base,
159            HashFunction::Sha256RipeMD160 => fee_version.hashing.sha256_ripe_md160_base,
160        }
161    }
162}
163
164/// A Hash Function Operation
165#[derive(Debug, PartialEq, Eq)]
166pub struct FunctionOp {
167    /// hash
168    pub(crate) hash: HashFunction,
169    /// rounds
170    pub(crate) rounds: u32,
171}
172
173impl FunctionOp {
174    /// The cost of the function
175    fn cost(&self, fee_version: &FeeVersion) -> Credits {
176        let block_cost = (self.rounds as u64).saturating_mul(self.hash.block_cost(fee_version));
177        self.hash.base_cost(fee_version).saturating_add(block_cost)
178    }
179
180    /// Create a new function operation with the following hash knowing the rounds it will take
181    /// in advance
182    pub fn new_with_round_count(hash: HashFunction, rounds: u32) -> Self {
183        FunctionOp { hash, rounds }
184    }
185
186    /// Create a new function operation with the following hash knowing the number of bytes
187    /// it will hash
188    pub fn new_with_byte_count(hash: HashFunction, byte_count: u16) -> Self {
189        let blocks = byte_count / hash.block_size() + 1;
190        let rounds = blocks + hash.rounds() - 1;
191        FunctionOp {
192            hash,
193            rounds: rounds as u32,
194        }
195    }
196}
197
198/// Drive operation
199#[derive(Debug, Eq, PartialEq)]
200pub enum LowLevelDriveOperation {
201    /// Grove operation
202    GroveOperation(QualifiedGroveDbOp),
203    /// A drive operation
204    FunctionOperation(FunctionOp),
205    /// Calculated cost operation
206    CalculatedCostOperation(OperationCost),
207    /// Pre Calculated Fee Result
208    PreCalculatedFeeResult(FeeResult),
209}
210
211impl LowLevelDriveOperation {
212    /// Returns a list of the costs of the Drive operations.
213    /// Should only be used by Calculate fee
214    pub fn consume_to_fees_v0(
215        drive_operations: Vec<LowLevelDriveOperation>,
216        epoch: &Epoch,
217        epochs_per_era: u16,
218        fee_version: &FeeVersion,
219        previous_fee_versions: Option<&CachedEpochIndexFeeVersions>,
220    ) -> Result<Vec<FeeResult>, Error> {
221        drive_operations
222            .into_iter()
223            .map(|operation| match operation {
224                PreCalculatedFeeResult(f) => Ok(f),
225                FunctionOperation(op) => Ok(FeeResult {
226                    processing_fee: op.cost(fee_version),
227                    ..Default::default()
228                }),
229                _ => {
230                    let cost = operation.operation_cost()?;
231                    // There is no need for a checked multiply here because added bytes are u64 and
232                    // storage disk usage credit per byte should never be high enough to cause an overflow
233                    let storage_fee = cost.storage_cost.added_bytes as u64 * fee_version.storage.storage_disk_usage_credit_per_byte;
234                    let processing_fee = cost.ephemeral_cost(fee_version)?;
235                    let (fee_refunds, removed_bytes_from_system) =
236                        match cost.storage_cost.removed_bytes {
237                            NoStorageRemoval => (FeeRefunds::default(), 0),
238                            BasicStorageRemoval(amount) => {
239                                // this is not always considered an error
240                                (FeeRefunds::default(), amount)
241                            }
242                            SectionedStorageRemoval(mut removal_per_epoch_by_identifier) => {
243
244                                let system_amount = removal_per_epoch_by_identifier
245                                    .remove(&Identifier::default())
246                                    .map_or(0, |a| a.values().sum());
247                                if fee_version.fee_version_number == 1 {
248                                    (
249                                        FeeRefunds::from_storage_removal(
250                                            removal_per_epoch_by_identifier,
251                                            epoch.index,
252                                            epochs_per_era,
253                                            &BTreeMap::default(),
254                                        )?,
255                                        system_amount,
256                                    )
257                                } else {
258                                    let previous_fee_versions = previous_fee_versions.ok_or(Error::Drive(DriveError::CorruptedCodeExecution("expected previous epoch index fee versions to be able to offer refunds")))?;
259                                    (
260                                        FeeRefunds::from_storage_removal(
261                                            removal_per_epoch_by_identifier,
262                                            epoch.index,
263                                            epochs_per_era,
264                                            previous_fee_versions,
265                                        )?,
266                                        system_amount,
267                                    )
268                                }
269                            }
270                        };
271                    Ok(FeeResult {
272                        storage_fee,
273                        processing_fee,
274                        fee_refunds,
275                        removed_bytes_from_system,
276                    })
277                }
278            })
279            .collect()
280    }
281
282    /// Returns the cost of this operation
283    pub fn operation_cost(self) -> Result<OperationCost, Error> {
284        match self {
285            GroveOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
286                "grove operations must be executed, not directly transformed to costs",
287            ))),
288            CalculatedCostOperation(c) => Ok(c),
289            PreCalculatedFeeResult(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
290                "pre calculated fees should not be requested by operation costs",
291            ))),
292            FunctionOperation(_) => Err(Error::Drive(DriveError::CorruptedCodeExecution(
293                "function operations should not be requested by operation costs",
294            ))),
295        }
296    }
297
298    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
299    pub fn combine_cost_operations(operations: &[LowLevelDriveOperation]) -> OperationCost {
300        let mut cost = OperationCost::default();
301        operations.iter().for_each(|op| {
302            if let CalculatedCostOperation(operation_cost) = op {
303                cost += operation_cost.clone()
304            }
305        });
306        cost
307    }
308
309    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
310    pub fn grovedb_operations_batch(
311        insert_operations: &[LowLevelDriveOperation],
312    ) -> GroveDbOpBatch {
313        let operations = insert_operations
314            .iter()
315            .filter_map(|op| match op {
316                GroveOperation(grovedb_op) => Some(grovedb_op.clone()),
317                _ => None,
318            })
319            .collect();
320        GroveDbOpBatch::from_operations(operations)
321    }
322
323    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
324    pub fn grovedb_operations_batch_consume(
325        insert_operations: Vec<LowLevelDriveOperation>,
326    ) -> GroveDbOpBatch {
327        let operations = insert_operations
328            .into_iter()
329            .filter_map(|op| match op {
330                GroveOperation(grovedb_op) => Some(grovedb_op),
331                _ => None,
332            })
333            .collect();
334        GroveDbOpBatch::from_operations(operations)
335    }
336
337    /// Filters the groveDB ops from a list of operations and puts them in a `GroveDbOpBatch`.
338    pub fn grovedb_operations_batch_consume_with_leftovers(
339        insert_operations: Vec<LowLevelDriveOperation>,
340    ) -> (GroveDbOpBatch, Vec<LowLevelDriveOperation>) {
341        let (grove_operations, other_operations): (Vec<_>, Vec<_>) =
342            insert_operations.into_iter().partition_map(|op| match op {
343                GroveOperation(grovedb_op) => itertools::Either::Left(grovedb_op),
344                _ => itertools::Either::Right(op),
345            });
346
347        (
348            GroveDbOpBatch::from_operations(grove_operations),
349            other_operations,
350        )
351    }
352
353    /// Filters the groveDB ops from a list of operations and collects them in a `Vec<QualifiedGroveDbOp>`.
354    pub fn grovedb_operations_consume(
355        insert_operations: Vec<LowLevelDriveOperation>,
356    ) -> Vec<QualifiedGroveDbOp> {
357        insert_operations
358            .into_iter()
359            .filter_map(|op| match op {
360                GroveOperation(grovedb_op) => Some(grovedb_op),
361                _ => None,
362            })
363            .collect()
364    }
365
366    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
367    pub fn for_known_path_key_empty_tree(
368        path: Vec<Vec<u8>>,
369        key: Vec<u8>,
370        storage_flags: Option<&StorageFlags>,
371    ) -> Self {
372        let tree = match storage_flags {
373            Some(storage_flags) => {
374                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
375            }
376            None => Element::empty_tree(),
377        };
378
379        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
380    }
381
382    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
383    pub fn for_known_path_key_empty_sum_tree(
384        path: Vec<Vec<u8>>,
385        key: Vec<u8>,
386        storage_flags: Option<&StorageFlags>,
387    ) -> Self {
388        let tree = match storage_flags {
389            Some(storage_flags) => {
390                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
391            }
392            None => Element::empty_sum_tree(),
393        };
394
395        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
396    }
397
398    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
399    pub fn for_known_path_key_empty_big_sum_tree(
400        path: Vec<Vec<u8>>,
401        key: Vec<u8>,
402        storage_flags: Option<&StorageFlags>,
403    ) -> Self {
404        let tree = match storage_flags {
405            Some(storage_flags) => {
406                Element::new_big_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
407            }
408            None => Element::empty_big_sum_tree(),
409        };
410
411        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
412    }
413
414    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
415    pub fn for_known_path_key_empty_count_tree(
416        path: Vec<Vec<u8>>,
417        key: Vec<u8>,
418        storage_flags: Option<&StorageFlags>,
419    ) -> Self {
420        let tree = match storage_flags {
421            Some(storage_flags) => {
422                Element::new_count_tree_with_flags(None, storage_flags.to_some_element_flags())
423            }
424            None => Element::empty_count_tree(),
425        };
426
427        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
428    }
429
430    /// Sets `GroveOperation` for inserting an empty count tree at the given path and key
431    pub fn for_known_path_key_empty_count_sum_tree(
432        path: Vec<Vec<u8>>,
433        key: Vec<u8>,
434        storage_flags: Option<&StorageFlags>,
435    ) -> Self {
436        let tree = match storage_flags {
437            Some(storage_flags) => {
438                Element::new_count_sum_tree_with_flags(None, storage_flags.to_some_element_flags())
439            }
440            None => Element::new_count_sum_tree(None),
441        };
442
443        LowLevelDriveOperation::insert_for_known_path_key_element(path, key, tree)
444    }
445
446    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
447    pub fn for_estimated_path_key_empty_tree(
448        path: KeyInfoPath,
449        key: KeyInfo,
450        storage_flags: Option<&StorageFlags>,
451    ) -> Self {
452        let tree = match storage_flags {
453            Some(storage_flags) => {
454                Element::empty_tree_with_flags(storage_flags.to_some_element_flags())
455            }
456            None => Element::empty_tree(),
457        };
458
459        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
460    }
461
462    /// Sets `GroveOperation` for inserting an empty sum tree at the given path and key
463    pub fn for_estimated_path_key_empty_sum_tree(
464        path: KeyInfoPath,
465        key: KeyInfo,
466        storage_flags: Option<&StorageFlags>,
467    ) -> Self {
468        let tree = match storage_flags {
469            Some(storage_flags) => {
470                Element::empty_sum_tree_with_flags(storage_flags.to_some_element_flags())
471            }
472            None => Element::empty_sum_tree(),
473        };
474
475        LowLevelDriveOperation::insert_for_estimated_path_key_element(path, key, tree)
476    }
477
478    /// Sets `GroveOperation` for inserting an element at the given path and key
479    pub fn insert_for_known_path_key_element(
480        path: Vec<Vec<u8>>,
481        key: Vec<u8>,
482        element: Element,
483    ) -> Self {
484        GroveOperation(QualifiedGroveDbOp::insert_or_replace_op(path, key, element))
485    }
486
487    /// Sets `GroveOperation` for replacement of an element at the given path and key
488    pub fn replace_for_known_path_key_element(
489        path: Vec<Vec<u8>>,
490        key: Vec<u8>,
491        element: Element,
492    ) -> Self {
493        GroveOperation(QualifiedGroveDbOp::replace_op(path, key, element))
494    }
495
496    /// Sets `GroveOperation` for patching of an element at the given path and key
497    /// This is different from replacement which does not add or delete bytes
498    pub fn patch_for_known_path_key_element(
499        path: Vec<Vec<u8>>,
500        key: Vec<u8>,
501        element: Element,
502        change_in_bytes: i32,
503    ) -> Self {
504        GroveOperation(QualifiedGroveDbOp::patch_op(
505            path,
506            key,
507            element,
508            change_in_bytes,
509        ))
510    }
511
512    /// Sets `GroveOperation` for inserting an element at an unknown estimated path and key
513    pub fn insert_for_estimated_path_key_element(
514        path: KeyInfoPath,
515        key: KeyInfo,
516        element: Element,
517    ) -> Self {
518        GroveOperation(QualifiedGroveDbOp::insert_estimated_op(path, key, element))
519    }
520
521    /// Sets `GroveOperation` for replacement of an element at an unknown estimated path and key
522    pub fn replace_for_estimated_path_key_element(
523        path: KeyInfoPath,
524        key: KeyInfo,
525        element: Element,
526    ) -> Self {
527        GroveOperation(QualifiedGroveDbOp::replace_estimated_op(path, key, element))
528    }
529
530    /// Sets `GroveOperation` for refresh of a reference at the given path and key
531    pub fn refresh_reference_for_known_path_key_reference_info(
532        path: Vec<Vec<u8>>,
533        key: Vec<u8>,
534        reference_path_type: ReferencePathType,
535        max_reference_hop: MaxReferenceHop,
536        flags: Option<ElementFlags>,
537        trust_refresh_reference: bool,
538    ) -> Self {
539        GroveOperation(QualifiedGroveDbOp::refresh_reference_op(
540            path,
541            key,
542            reference_path_type,
543            max_reference_hop,
544            flags,
545            trust_refresh_reference,
546        ))
547    }
548}
549
550/// A trait for getting an empty tree operation based on the tree type
551pub trait LowLevelDriveOperationTreeTypeConverter {
552    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
553    fn empty_tree_operation_for_known_path_key(
554        &self,
555        path: Vec<Vec<u8>>,
556        key: Vec<u8>,
557        storage_flags: Option<&StorageFlags>,
558    ) -> Result<LowLevelDriveOperation, Error>;
559}
560
561impl LowLevelDriveOperationTreeTypeConverter for TreeType {
562    /// Sets `GroveOperation` for inserting an empty tree at the given path and key
563    fn empty_tree_operation_for_known_path_key(
564        &self,
565        path: Vec<Vec<u8>>,
566        key: Vec<u8>,
567        storage_flags: Option<&StorageFlags>,
568    ) -> Result<LowLevelDriveOperation, Error> {
569        let element_flags = storage_flags.map(|storage_flags| storage_flags.to_element_flags());
570        let element = match self {
571            TreeType::NormalTree => Element::empty_tree_with_flags(element_flags),
572            TreeType::SumTree => Element::empty_sum_tree_with_flags(element_flags),
573            TreeType::BigSumTree => Element::empty_big_sum_tree_with_flags(element_flags),
574            TreeType::CountTree => Element::empty_count_tree_with_flags(element_flags),
575            TreeType::CountSumTree => Element::empty_count_sum_tree_with_flags(element_flags),
576            TreeType::ProvableCountTree => {
577                Element::empty_provable_count_tree_with_flags(element_flags)
578            }
579            TreeType::ProvableCountSumTree => {
580                Element::empty_provable_count_sum_tree_with_flags(element_flags)
581            }
582            TreeType::CommitmentTree(chunk_power) => {
583                Element::empty_commitment_tree_with_flags(*chunk_power, element_flags)?
584            }
585            TreeType::MmrTree => Element::empty_mmr_tree_with_flags(element_flags),
586            TreeType::BulkAppendTree(chunk_power) => {
587                Element::empty_bulk_append_tree_with_flags(*chunk_power, element_flags)?
588            }
589            TreeType::DenseAppendOnlyFixedSizeTree(chunk_power) => {
590                Element::empty_dense_tree_with_flags(*chunk_power, element_flags)
591            }
592        };
593
594        Ok(LowLevelDriveOperation::insert_for_known_path_key_element(
595            path, key, element,
596        ))
597    }
598}
599
600/// Drive cost trait
601pub trait DriveCost {
602    /// Ephemeral cost
603    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<u64, Error>;
604}
605
606impl DriveCost for OperationCost {
607    /// Return the ephemeral cost from the operation
608    fn ephemeral_cost(&self, fee_version: &FeeVersion) -> Result<Credits, Error> {
609        let OperationCost {
610            seek_count,
611            storage_cost,
612            storage_loaded_bytes,
613            hash_node_calls,
614            sinsemilla_hash_calls,
615        } = self;
616        let epoch_cost_for_processing_credit_per_byte =
617            fee_version.storage.storage_processing_credit_per_byte;
618        let seek_cost = (*seek_count as u64)
619            .checked_mul(fee_version.storage.storage_seek_cost)
620            .ok_or_else(|| get_overflow_error("seek cost overflow"))?;
621        let storage_added_bytes_ephemeral_cost = (storage_cost.added_bytes as u64)
622            .checked_mul(epoch_cost_for_processing_credit_per_byte)
623            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
624        let storage_replaced_bytes_ephemeral_cost = (storage_cost.replaced_bytes as u64)
625            .checked_mul(epoch_cost_for_processing_credit_per_byte)
626            .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
627        let storage_removed_bytes_ephemeral_cost =
628            (storage_cost.removed_bytes.total_removed_bytes() as u64)
629                .checked_mul(epoch_cost_for_processing_credit_per_byte)
630                .ok_or_else(|| get_overflow_error("storage written bytes cost overflow"))?;
631        // not accessible
632        let storage_loaded_bytes_cost = { *storage_loaded_bytes }
633            .checked_mul(fee_version.storage.storage_load_credit_per_byte)
634            .ok_or_else(|| get_overflow_error("storage loaded cost overflow"))?;
635
636        // There is one block per hash node call
637        let blake3_total = fee_version.hashing.blake3_base + fee_version.hashing.blake3_per_block;
638        // this can't overflow
639        let hash_node_cost = blake3_total * (*hash_node_calls as u64);
640        let sinsemilla_cost = fee_version.hashing.sinsemilla_base * (*sinsemilla_hash_calls as u64);
641        seek_cost
642            .checked_add(storage_added_bytes_ephemeral_cost)
643            .and_then(|c| c.checked_add(storage_replaced_bytes_ephemeral_cost))
644            .and_then(|c| c.checked_add(storage_loaded_bytes_cost))
645            .and_then(|c| c.checked_add(storage_removed_bytes_ephemeral_cost))
646            .and_then(|c| c.checked_add(hash_node_cost))
647            .and_then(|c| c.checked_add(sinsemilla_cost))
648            .ok_or_else(|| get_overflow_error("ephemeral cost addition overflow"))
649    }
650}