massa_models/
ledger.rs

1// Copyright (c) 2022 MASSA LABS <info@massa.net>
2
3use crate::{
4    address::Address,
5    amount::{Amount, AmountDeserializer, AmountSerializer},
6    error::ModelsError,
7    error::ModelsResult as Result,
8    prehash::{PreHashMap, PreHashSet},
9};
10use massa_serialization::{Deserializer, SerializeError, Serializer};
11use nom::{
12    error::{context, ContextError, ParseError},
13    IResult, Parser,
14};
15use serde::{Deserialize, Serialize};
16use std::{collections::hash_map, ops::Bound::Included};
17
18/// a consensus ledger entry
19#[derive(Debug, Default, Deserialize, Clone, Copy, Serialize)]
20pub struct LedgerData {
21    /// the balance in coins
22    pub balance: Amount,
23}
24
25/// Basic serializer for `LedgerData`
26#[derive(Default)]
27pub struct LedgerDataSerializer {
28    amount_serializer: AmountSerializer,
29}
30
31impl LedgerDataSerializer {
32    /// Creates a `LedgerDataSerializer`
33    pub fn new() -> Self {
34        Self {
35            amount_serializer: AmountSerializer::new(),
36        }
37    }
38}
39
40impl Serializer<LedgerData> for LedgerDataSerializer {
41    /// ## Example:
42    /// ```rust
43    /// use massa_models::amount::Amount;
44    /// use massa_serialization::Serializer;
45    /// use std::str::FromStr;
46    /// use massa_models::ledger::{LedgerData, LedgerDataSerializer};
47    ///
48    /// let ledger_data = LedgerData {
49    ///    balance: Amount::from_str("1349").unwrap(),
50    /// };
51    /// let mut buffer = Vec::new();
52    /// LedgerDataSerializer::new().serialize(&ledger_data, &mut buffer).unwrap();
53    /// ```
54    fn serialize(&self, value: &LedgerData, buffer: &mut Vec<u8>) -> Result<(), SerializeError> {
55        self.amount_serializer.serialize(&value.balance, buffer)?;
56        Ok(())
57    }
58}
59
60/// Basic deserializer for `LedgerData`
61pub struct LedgerDataDeserializer {
62    amount_deserializer: AmountDeserializer,
63}
64
65impl LedgerDataDeserializer {
66    /// Creates a `LedgerDataDeserializer`
67    pub fn new() -> Self {
68        Self {
69            amount_deserializer: AmountDeserializer::new(
70                Included(Amount::MIN),
71                Included(Amount::MAX),
72            ),
73        }
74    }
75}
76
77impl Default for LedgerDataDeserializer {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl Deserializer<LedgerData> for LedgerDataDeserializer {
84    /// ## Example:
85    /// ```rust
86    /// use massa_models::amount::Amount;
87    /// use massa_serialization::{Serializer, Deserializer, DeserializeError};
88    /// use std::str::FromStr;
89    /// use massa_models::ledger::{LedgerData, LedgerDataDeserializer, LedgerDataSerializer};
90    ///
91    /// let ledger_data = LedgerData {
92    ///    balance: Amount::from_str("1349").unwrap(),
93    /// };
94    /// let mut buffer = Vec::new();
95    /// LedgerDataSerializer::new().serialize(&ledger_data, &mut buffer).unwrap();
96    /// let (rest, ledger_data_deserialized) = LedgerDataDeserializer::new().deserialize::<DeserializeError>(&buffer).unwrap();
97    /// assert_eq!(rest.len(), 0);
98    /// assert_eq!(ledger_data.balance, ledger_data_deserialized.balance);
99    /// ```
100    fn deserialize<'a, E: ParseError<&'a [u8]> + ContextError<&'a [u8]>>(
101        &self,
102        buffer: &'a [u8],
103    ) -> IResult<&'a [u8], LedgerData, E> {
104        context("Failed LedgerData deserialization", |input| {
105            self.amount_deserializer.deserialize(input)
106        })
107        .map(|balance| LedgerData { balance })
108        .parse(buffer)
109    }
110}
111
112impl LedgerData {
113    /// new `LedgerData` from an initial balance
114    pub fn new(starting_balance: Amount) -> LedgerData {
115        LedgerData {
116            balance: starting_balance,
117        }
118    }
119
120    /// apply a `LedgerChange` for an entry
121    /// Can fail if an overflow or underflow occurs
122    pub fn apply_change(&mut self, change: &LedgerChange) -> Result<()> {
123        if change.balance_increment {
124            self.balance = self
125                .balance
126                .checked_add(change.balance_delta)
127                .ok_or_else(|| {
128                    ModelsError::InvalidLedgerChange(
129                        "balance overflow in LedgerData::apply_change".into(),
130                    )
131                })?;
132        } else {
133            self.balance = self
134                .balance
135                .checked_sub(change.balance_delta)
136                .ok_or_else(|| {
137                    ModelsError::InvalidLedgerChange(
138                        "balance underflow in LedgerData::apply_change".into(),
139                    )
140                })?;
141        }
142        Ok(())
143    }
144
145    /// returns true if the balance is zero
146    pub fn is_nil(&self) -> bool {
147        self.balance == Amount::default()
148    }
149}
150
151/// A balance change that can be applied to an address
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct LedgerChange {
154    /// Amount to add or subtract
155    pub balance_delta: Amount,
156    /// whether to increment or decrements balance of delta
157    pub balance_increment: bool,
158}
159
160impl Default for LedgerChange {
161    fn default() -> Self {
162        LedgerChange {
163            balance_delta: Amount::default(),
164            balance_increment: true,
165        }
166    }
167}
168
169impl LedgerChange {
170    /// Applies another ledger change on top of self
171    pub fn chain(&mut self, change: &LedgerChange) -> Result<(), ModelsError> {
172        if self.balance_increment == change.balance_increment {
173            self.balance_delta = self
174                .balance_delta
175                .checked_add(change.balance_delta)
176                .ok_or_else(|| {
177                    ModelsError::InvalidLedgerChange("overflow in LedgerChange::chain".into())
178                })?;
179        } else if change.balance_delta > self.balance_delta {
180            self.balance_delta = change
181                .balance_delta
182                .checked_sub(self.balance_delta)
183                .ok_or_else(|| {
184                    ModelsError::InvalidLedgerChange("underflow in LedgerChange::chain".into())
185                })?;
186            self.balance_increment = !self.balance_increment;
187        } else {
188            self.balance_delta = self
189                .balance_delta
190                .checked_sub(change.balance_delta)
191                .ok_or_else(|| {
192                    ModelsError::InvalidLedgerChange("underflow in LedgerChange::chain".into())
193                })?;
194        }
195        if self.balance_delta == Amount::default() {
196            self.balance_increment = true;
197        }
198        Ok(())
199    }
200
201    /// true if the change is 0
202    pub fn is_nil(&self) -> bool {
203        self.balance_delta == Amount::default()
204    }
205}
206
207/// Map an address to a `LedgerChange`
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
209pub struct LedgerChanges(pub PreHashMap<Address, LedgerChange>);
210
211impl LedgerChanges {
212    /// addresses that are impacted by these ledger changes
213    pub fn get_involved_addresses(&self) -> PreHashSet<Address> {
214        self.0.keys().copied().collect()
215    }
216
217    /// applies a `LedgerChange`
218    pub fn apply(&mut self, addr: &Address, change: &LedgerChange) -> Result<()> {
219        match self.0.entry(*addr) {
220            hash_map::Entry::Occupied(mut occ) => {
221                occ.get_mut().chain(change)?;
222                if occ.get().is_nil() {
223                    occ.remove();
224                }
225            }
226            hash_map::Entry::Vacant(vac) => {
227                let mut res = LedgerChange::default();
228                res.chain(change)?;
229                if !res.is_nil() {
230                    vac.insert(res);
231                }
232            }
233        }
234        Ok(())
235    }
236
237    /// chain with another `LedgerChange`
238    pub fn chain(&mut self, other: &LedgerChanges) -> Result<()> {
239        // We avoid mutating self directly to ensure atomicity in error cases.
240        let mut updated = self.clone();
241        for (addr, change) in other.0.iter() {
242            updated.apply(addr, change)?;
243        }
244        *self = updated;
245        Ok(())
246    }
247
248    /// merge another ledger changes into self, overwriting existing data
249    /// addresses that are in not other are removed from self
250    pub fn sync_from(&mut self, addrs: &PreHashSet<Address>, mut other: LedgerChanges) {
251        for addr in addrs.iter() {
252            if let Some(new_val) = other.0.remove(addr) {
253                self.0.insert(*addr, new_val);
254            } else {
255                self.0.remove(addr);
256            }
257        }
258    }
259
260    /// clone subset
261    #[must_use]
262    pub fn clone_subset(&self, addrs: &PreHashSet<Address>) -> Self {
263        LedgerChanges(
264            self.0
265                .iter()
266                .filter_map(|(a, dta)| {
267                    if addrs.contains(a) {
268                        Some((*a, dta.clone()))
269                    } else {
270                        None
271                    }
272                })
273                .collect(),
274        )
275    }
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281
282    #[test]
283    fn apply_change_reports_balance_underflow() {
284        let mut ledger_data = LedgerData::new(Amount::zero());
285        let change = LedgerChange {
286            balance_delta: Amount::from_raw(1),
287            balance_increment: false,
288        };
289
290        let error = ledger_data.apply_change(&change).unwrap_err();
291
292        assert!(matches!(
293            error,
294            ModelsError::InvalidLedgerChange(message)
295                if message == "balance underflow in LedgerData::apply_change"
296        ));
297    }
298}