gear_common/gas_provider/
node.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use super::*;
20use core::ops::{Add, Index, IndexMut};
21use enum_iterator::cardinality;
22use gear_core::ids::ReservationId;
23use sp_runtime::{
24    codec::{self, MaxEncodedLen},
25    scale_info,
26    traits::Zero,
27};
28
29/// ID of the [`GasNode`].
30#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo)]
31#[codec(crate = codec)]
32#[scale_info(crate = scale_info)]
33pub enum GasNodeId<T, U> {
34    Node(T),
35    Reservation(U),
36}
37
38impl<T, U> GasNodeId<T, U> {
39    pub fn to_node_id(self) -> Option<T> {
40        match self {
41            GasNodeId::Node(message_id) => Some(message_id),
42            GasNodeId::Reservation(_) => None,
43        }
44    }
45
46    pub fn to_reservation_id(self) -> Option<U> {
47        match self {
48            GasNodeId::Node(_) => None,
49            GasNodeId::Reservation(reservation_id) => Some(reservation_id),
50        }
51    }
52}
53
54impl<T, U> fmt::Display for GasNodeId<T, U>
55where
56    T: fmt::Display,
57    U: fmt::Display,
58{
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        match self {
61            GasNodeId::Node(id) => fmt::Display::fmt(id, f),
62            GasNodeId::Reservation(id) => fmt::Display::fmt(id, f),
63        }
64    }
65}
66
67impl<U> From<MessageId> for GasNodeId<MessageId, U> {
68    fn from(id: MessageId) -> Self {
69        Self::Node(id)
70    }
71}
72
73impl<T> From<ReservationId> for GasNodeId<T, ReservationId> {
74    fn from(id: ReservationId) -> Self {
75        Self::Reservation(id)
76    }
77}
78
79#[derive(Clone, Copy, Decode, Encode, Debug, Default, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
80#[codec(crate = codec)]
81#[scale_info(crate = scale_info)]
82pub struct NodeLock<Balance>([Balance; cardinality::<LockId>()]);
83
84impl<Balance> Index<LockId> for NodeLock<Balance> {
85    type Output = Balance;
86
87    fn index(&self, index: LockId) -> &Self::Output {
88        &self.0[index as usize]
89    }
90}
91
92impl<Balance> IndexMut<LockId> for NodeLock<Balance> {
93    fn index_mut(&mut self, index: LockId) -> &mut Self::Output {
94        &mut self.0[index as usize]
95    }
96}
97
98impl<Balance: Zero + Copy> Zero for NodeLock<Balance> {
99    fn zero() -> Self {
100        Self([Balance::zero(); cardinality::<LockId>()])
101    }
102
103    fn is_zero(&self) -> bool {
104        self.0.iter().all(|x| x.is_zero())
105    }
106}
107
108impl<Balance: Add<Output = Balance> + Copy> Add<Self> for NodeLock<Balance> {
109    type Output = Self;
110
111    fn add(self, other: Self) -> Self::Output {
112        let NodeLock(mut inner) = self;
113        let NodeLock(other) = other;
114
115        for (i, elem) in inner.iter_mut().enumerate() {
116            *elem = *elem + other[i];
117        }
118
119        Self(inner)
120    }
121}
122
123// TODO: decide whether this method should stay or be removed as unused.
124// The only use case currently is to check Gas Tree migration upon runtime upgrade.
125impl<Balance: Zero + Copy + sp_runtime::traits::Saturating> NodeLock<Balance> {
126    pub fn total_locked(&self) -> Balance {
127        self.0
128            .iter()
129            .fold(Balance::zero(), |acc, v| acc.saturating_add(*v))
130    }
131}
132
133/// Node of the ['Tree'] gas tree
134#[derive(Clone, Decode, Debug, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
135#[codec(crate = codec)]
136#[scale_info(crate = scale_info)]
137pub enum GasNode<ExternalId: Clone, Id: Clone, Balance: Zero + Clone, Funds> {
138    /// A root node for each gas tree.
139    ///
140    /// Usually created when a new gasful logic started (i.e., message sent).
141    External {
142        id: ExternalId,
143        multiplier: GasMultiplier<Funds, Balance>,
144        value: Balance,
145        lock: NodeLock<Balance>,
146        system_reserve: Balance,
147        refs: ChildrenRefs,
148        consumed: bool,
149        deposit: bool,
150    },
151
152    /// A node created by "cutting" value from some other tree node.
153    ///
154    /// Such node types are detached and aren't part of the tree structure
155    /// (not node's parent, not node's child).
156    Cut {
157        id: ExternalId,
158        multiplier: GasMultiplier<Funds, Balance>,
159        value: Balance,
160        lock: NodeLock<Balance>,
161    },
162
163    /// A node used for gas reservation feature.
164    ///
165    /// Such node types are detached from initial tree and may act the a root of new tree.
166    Reserved {
167        id: ExternalId,
168        multiplier: GasMultiplier<Funds, Balance>,
169        value: Balance,
170        lock: NodeLock<Balance>,
171        refs: ChildrenRefs,
172        consumed: bool,
173    },
174
175    /// A node, which is a part of the tree structure, that can be
176    /// a parent and/or a child.
177    ///
178    /// As well as `External` node, it has an internal balance and can exist
179    /// while being consumed (see [`Tree::consume`] for details).
180    ///
181    /// However, it has a `parent` field pointing to the node,
182    /// from which that one was created.
183    SpecifiedLocal {
184        parent: Id,
185        root: Id,
186        value: Balance,
187        lock: NodeLock<Balance>,
188        system_reserve: Balance,
189        refs: ChildrenRefs,
190        consumed: bool,
191    },
192
193    /// Pretty same as `SpecifiedLocal`, but doesn't have internal balance,
194    /// so relies on its `parent`.
195    ///
196    /// Such nodes don't have children references.
197    UnspecifiedLocal {
198        parent: Id,
199        root: Id,
200        lock: NodeLock<Balance>,
201        system_reserve: Balance,
202    },
203}
204
205/// Children references convenience struct
206#[derive(Clone, Copy, Default, Decode, Debug, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
207#[codec(crate = codec)]
208#[scale_info(crate = scale_info)]
209pub struct ChildrenRefs {
210    spec_refs: u32,
211    unspec_refs: u32,
212}
213
214impl<
215        ExternalId: Clone,
216        Id: Clone + Copy,
217        Balance: Default + Zero + Clone + Copy + sp_runtime::traits::Saturating,
218        Funds: Clone,
219    > GasNode<ExternalId, Id, Balance, Funds>
220{
221    /// Returns total gas value inside GasNode.
222    pub fn total_value(&self) -> Balance {
223        self.value()
224            .unwrap_or_default()
225            .saturating_add(self.lock().total_locked())
226            .saturating_add(self.system_reserve().unwrap_or_default())
227    }
228}
229
230impl<ExternalId: Clone, Id: Clone + Copy, Balance: Default + Zero + Clone + Copy, Funds: Clone>
231    GasNode<ExternalId, Id, Balance, Funds>
232{
233    /// Creates a new `GasNode::External` root node for a new tree.
234    pub fn new(
235        id: ExternalId,
236        multiplier: GasMultiplier<Funds, Balance>,
237        value: Balance,
238        deposit: bool,
239    ) -> Self {
240        Self::External {
241            id,
242            multiplier,
243            value,
244            lock: Zero::zero(),
245            system_reserve: Zero::zero(),
246            refs: Default::default(),
247            consumed: false,
248            deposit,
249        }
250    }
251
252    /// Increases node's spec refs, if it can have any
253    pub fn increase_spec_refs(&mut self) {
254        self.adjust_refs(true, true);
255    }
256
257    /// Decreases node's spec refs, if it can have any
258    pub fn decrease_spec_refs(&mut self) {
259        self.adjust_refs(false, true);
260    }
261
262    /// Increases node's unspec refs, if it can have any
263    pub fn increase_unspec_refs(&mut self) {
264        self.adjust_refs(true, false);
265    }
266
267    /// Decreases node's unspec refs, if it can have any
268    pub fn decrease_unspec_refs(&mut self) {
269        self.adjust_refs(false, false);
270    }
271
272    /// Marks the node as consumed, if it has the flag
273    pub fn mark_consumed(&mut self) {
274        if let Self::External { consumed, .. }
275        | Self::SpecifiedLocal { consumed, .. }
276        | Self::Reserved { consumed, .. } = self
277        {
278            *consumed = true;
279        }
280    }
281
282    /// Returns whether the node is marked consumed or not
283    ///
284    /// Only `GasNode::External`, `GasNode::SpecifiedLocal`, `GasNode::Reserved` can be marked
285    /// consumed and not deleted. See [`Tree::consume`] for details.
286    pub fn is_consumed(&self) -> bool {
287        if let Self::External { consumed, .. }
288        | Self::SpecifiedLocal { consumed, .. }
289        | Self::Reserved { consumed, .. } = self
290        {
291            *consumed
292        } else {
293            false
294        }
295    }
296
297    /// Returns whether the node is patron or not.
298    ///
299    /// The flag signals whether the node isn't available
300    /// for the gas to be spent from it.
301    ///
302    /// These are nodes that have one of the following requirements:
303    /// 1. Have unspec refs (regardless of being consumed).
304    /// 2. Are not consumed.
305    ///
306    /// Patron nodes are those on which other nodes of the tree rely
307    /// (including the self node).
308    pub fn is_patron(&self) -> bool {
309        if let Self::External { refs, consumed, .. }
310        | Self::SpecifiedLocal { refs, consumed, .. }
311        | Self::Reserved { refs, consumed, .. } = self
312        {
313            !consumed || refs.unspec_refs != 0
314        } else {
315            false
316        }
317    }
318
319    /// Returns node's inner gas balance, if it can have any
320    pub fn value(&self) -> Option<Balance> {
321        match self {
322            Self::External { value, .. }
323            | Self::Cut { value, .. }
324            | Self::Reserved { value, .. }
325            | Self::SpecifiedLocal { value, .. } => Some(*value),
326            Self::UnspecifiedLocal { .. } => None,
327        }
328    }
329
330    /// Get's a mutable access to node's inner gas balance, if it can have any
331    pub fn value_mut(&mut self) -> Option<&mut Balance> {
332        match *self {
333            Self::External { ref mut value, .. }
334            | Self::Cut { ref mut value, .. }
335            | Self::Reserved { ref mut value, .. }
336            | Self::SpecifiedLocal { ref mut value, .. } => Some(value),
337            Self::UnspecifiedLocal { .. } => None,
338        }
339    }
340
341    /// Returns node's locked gas balance, if it can have any.
342    pub fn lock(&self) -> &NodeLock<Balance> {
343        match self {
344            Self::External { lock, .. }
345            | Self::UnspecifiedLocal { lock, .. }
346            | Self::SpecifiedLocal { lock, .. }
347            | Self::Reserved { lock, .. }
348            | Self::Cut { lock, .. } => lock,
349        }
350    }
351
352    /// Get's a mutable access to node's locked gas balance, if it can have any.
353    pub fn lock_mut(&mut self) -> &mut NodeLock<Balance> {
354        match *self {
355            Self::External { ref mut lock, .. }
356            | Self::UnspecifiedLocal { ref mut lock, .. }
357            | Self::SpecifiedLocal { ref mut lock, .. }
358            | Self::Reserved { ref mut lock, .. }
359            | Self::Cut { ref mut lock, .. } => lock,
360        }
361    }
362
363    /// Returns node's system reserved gas balance, if it can have any.
364    pub fn system_reserve(&self) -> Option<Balance> {
365        match self {
366            GasNode::External { system_reserve, .. }
367            | GasNode::SpecifiedLocal { system_reserve, .. }
368            | GasNode::UnspecifiedLocal { system_reserve, .. } => Some(*system_reserve),
369            GasNode::Cut { .. } | GasNode::Reserved { .. } => None,
370        }
371    }
372
373    /// Gets a mutable access to node's system reserved gas balance, if it can have any.
374    pub fn system_reserve_mut(&mut self) -> Option<&mut Balance> {
375        match self {
376            GasNode::External { system_reserve, .. }
377            | GasNode::SpecifiedLocal { system_reserve, .. }
378            | GasNode::UnspecifiedLocal { system_reserve, .. } => Some(system_reserve),
379            GasNode::Cut { .. } | GasNode::Reserved { .. } => None,
380        }
381    }
382
383    /// Returns node's parent, if it can have any.
384    ///
385    /// That is, `GasNode::External`, `GasNode::Cut`, 'GasNode::Reserved` nodes
386    /// don't have a parent, so a `None` is returned if the function is
387    /// called on them.
388    pub fn parent(&self) -> Option<Id> {
389        match self {
390            Self::External { .. } | Self::Cut { .. } | Self::Reserved { .. } => None,
391            Self::SpecifiedLocal { parent, .. } | Self::UnspecifiedLocal { parent, .. } => {
392                Some(*parent)
393            }
394        }
395    }
396
397    /// Returns node's total refs
398    pub fn refs(&self) -> u32 {
399        self.spec_refs().saturating_add(self.unspec_refs())
400    }
401
402    /// Returns node's spec refs
403    pub fn spec_refs(&self) -> u32 {
404        match self {
405            Self::External { refs, .. }
406            | Self::SpecifiedLocal { refs, .. }
407            | Self::Reserved { refs, .. } => refs.spec_refs,
408            _ => 0,
409        }
410    }
411
412    /// Returns node's unspec refs
413    pub fn unspec_refs(&self) -> u32 {
414        match self {
415            Self::External { refs, .. }
416            | Self::SpecifiedLocal { refs, .. }
417            | Self::Reserved { refs, .. } => refs.unspec_refs,
418            _ => 0,
419        }
420    }
421
422    /// Returns id of the root node.
423    pub fn root_id(&self) -> Option<Id> {
424        match self {
425            Self::SpecifiedLocal { root, .. } | Self::UnspecifiedLocal { root, .. } => Some(*root),
426            Self::External { .. } | Self::Cut { .. } | Self::Reserved { .. } => None,
427        }
428    }
429
430    /// Returns external origin and funds multiplier of the node if contains that data inside.
431    pub fn external_data(&self) -> Option<(ExternalId, GasMultiplier<Funds, Balance>)> {
432        match self {
433            Self::External { id, multiplier, .. }
434            | Self::Cut { id, multiplier, .. }
435            | Self::Reserved { id, multiplier, .. } => Some((id.clone(), multiplier.clone())),
436            Self::SpecifiedLocal { .. } | Self::UnspecifiedLocal { .. } => None,
437        }
438    }
439
440    /// Returns whether the node is of `External` type
441    pub(crate) fn is_external(&self) -> bool {
442        matches!(self, Self::External { .. })
443    }
444
445    /// Returns whether the node is of `SpecifiedLocal` type
446    pub(crate) fn is_specified_local(&self) -> bool {
447        matches!(self, Self::SpecifiedLocal { .. })
448    }
449
450    /// Returns whether the node is of `UnspecifiedLocal` type
451    pub(crate) fn is_unspecified_local(&self) -> bool {
452        matches!(self, Self::UnspecifiedLocal { .. })
453    }
454
455    /// Returns whether the node is of `Cut` type
456    pub(crate) fn is_cut(&self) -> bool {
457        matches!(self, Self::Cut { .. })
458    }
459
460    /// Returns whether the node is of `Reserve` type
461    pub(crate) fn is_reserved(&self) -> bool {
462        matches!(self, Self::Reserved { .. })
463    }
464
465    /// Returns whether the node has system reserved gas.
466    pub(crate) fn is_system_reservable(&self) -> bool {
467        self.system_reserve().is_some()
468    }
469
470    fn adjust_refs(&mut self, increase: bool, spec: bool) {
471        if let Self::External { refs, .. }
472        | Self::SpecifiedLocal { refs, .. }
473        | Self::Reserved { refs, .. } = self
474        {
475            match (increase, spec) {
476                (true, true) => refs.spec_refs = refs.spec_refs.saturating_add(1),
477                (true, false) => refs.unspec_refs = refs.unspec_refs.saturating_add(1),
478                (false, true) => refs.spec_refs = refs.spec_refs.saturating_sub(1),
479                (false, false) => refs.unspec_refs = refs.unspec_refs.saturating_sub(1),
480            }
481        }
482    }
483}
484
485#[cfg(test)]
486mod tests {
487    use super::*;
488
489    #[test]
490    // Checking node that have external data do not have root id
491    fn asserts_node_have_either_external_data_or_root_id() {
492        let nodes_with_external_data: [gas_provider::node::GasNode<i32, i32, i32, i32>; 3] = [
493            GasNode::External {
494                id: Default::default(),
495                multiplier: GasMultiplier::ValuePerGas(100),
496                value: Default::default(),
497                lock: Default::default(),
498                system_reserve: Default::default(),
499                refs: Default::default(),
500                consumed: Default::default(),
501                deposit: Default::default(),
502            },
503            GasNode::Cut {
504                id: Default::default(),
505                multiplier: GasMultiplier::ValuePerGas(100),
506                value: Default::default(),
507                lock: Default::default(),
508            },
509            GasNode::Reserved {
510                id: Default::default(),
511                multiplier: GasMultiplier::ValuePerGas(100),
512                value: Default::default(),
513                lock: Default::default(),
514                refs: Default::default(),
515                consumed: Default::default(),
516            },
517        ];
518
519        for node in nodes_with_external_data {
520            assert!(node.external_data().is_some() || node.root_id().is_none());
521        }
522
523        let nodes_with_root_id: [gas_provider::node::GasNode<i32, i32, i32, i32>; 2] = [
524            GasNode::SpecifiedLocal {
525                parent: Default::default(),
526                root: Default::default(),
527                value: Default::default(),
528                lock: Default::default(),
529                system_reserve: Default::default(),
530                refs: Default::default(),
531                consumed: Default::default(),
532            },
533            GasNode::UnspecifiedLocal {
534                parent: Default::default(),
535                root: Default::default(),
536                lock: Default::default(),
537                system_reserve: Default::default(),
538            },
539        ];
540
541        for node in nodes_with_root_id {
542            assert!(node.external_data().is_none() || node.root_id().is_some());
543        }
544    }
545}