1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
use crate::prelude::*;
use core::cmp::min;
use core::fmt::{self, Debug, Formatter};

/// Limit velocity per unit time.
///
/// We track velocity in intervals instead of tracking each send, to keep
/// storage requirements constant.
#[derive(Clone)]
pub struct VelocityControl {
    /// start second for the current velocity epoch
    pub start_sec: u64,
    /// the number of seconds represented by a bucket
    pub bucket_interval: u32,
    /// each bucket entry is the total velocity detected in that interval, in satoshi
    pub buckets: Vec<u64>,
    /// the limit, or MAX if the control is disabled
    pub limit: u64,
}

impl Debug for VelocityControl {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_struct("VelocityControl")
            .field("start_sec", &self.start_sec)
            .field("bucket_interval", &self.bucket_interval)
            .field("buckets", &format_args!("{:?}", self.buckets))
            .field("limit", &self.limit)
            .finish()
    }
}

/// The total interval in which to track velocity
#[derive(Clone, Copy, Debug)]
pub enum VelocityControlIntervalType {
    /// Tracked in 5 minute sub-intervals
    Hourly,
    /// Tracked in 1 hour sub-intervals
    Daily,
    /// Unlimited velocity
    Unlimited,
}

/// A specifier for creating velocity controls
#[derive(Clone, Copy, Debug)]
pub struct VelocityControlSpec {
    /// The limit per interval in msat
    pub limit_msat: u64,
    /// The interval type
    pub interval_type: VelocityControlIntervalType,
}

impl VelocityControlSpec {
    /// A velocity control spec for controls which don't limit velocity
    pub const UNLIMITED: VelocityControlSpec = VelocityControlSpec {
        limit_msat: 0,
        interval_type: VelocityControlIntervalType::Unlimited,
    };
}

impl VelocityControl {
    /// Create a velocity control with arbitrary specified intervals
    /// current_sec: the current second
    /// num_buckets: how many buckets to keep track of
    /// bucket_interval: each bucket represents this number of seconds
    /// limit: the total velocity limit when summing the buckets
    pub fn new_with_intervals(limit_msat: u64, bucket_interval: u32, num_buckets: usize) -> Self {
        assert!(bucket_interval > 0 && num_buckets > 0);
        let mut buckets = Vec::new();
        buckets.resize(num_buckets, 0);
        VelocityControl { start_sec: 0, bucket_interval, buckets, limit: limit_msat }
    }

    /// Create an unlimited velocity control (i.e. no actual control)
    pub fn new_unlimited(bucket_interval: u32, num_buckets: usize) -> Self {
        assert!(bucket_interval > 0 && num_buckets > 0);
        let mut buckets = Vec::new();
        buckets.resize(num_buckets, 0);
        VelocityControl { start_sec: 0, bucket_interval, buckets, limit: u64::MAX }
    }

    /// Create a velocity control with the given interval type
    pub fn new(spec: VelocityControlSpec) -> Self {
        let (limit, bucket_interval, num_buckets) = Self::spec_to_triple(&spec);
        Self::new_with_intervals(limit, bucket_interval, num_buckets)
    }

    /// Whether the spec matches this control
    pub fn spec_matches(&self, spec: &VelocityControlSpec) -> bool {
        let (limit, bucket_interval, num_buckets) = Self::spec_to_triple(spec);
        self.limit == limit
            && self.bucket_interval == bucket_interval
            && self.buckets.len() == num_buckets
    }

    /// Update this control to match the given spec.  If the spec does not
    /// match the previous spec, the control is reset.
    pub fn update_spec(&mut self, spec: &VelocityControlSpec) {
        if !self.spec_matches(spec) {
            let (limit, bucket_interval, num_buckets) = Self::spec_to_triple(spec);
            self.limit = limit;
            self.bucket_interval = bucket_interval;
            self.buckets = Vec::new();
            self.buckets.resize(num_buckets, 0);
            self.start_sec = 0;
        }
    }

    // Convert a spec to a (limit, bucket_interval, num_buckets) triple
    fn spec_to_triple(spec: &VelocityControlSpec) -> (u64, u32, usize) {
        match spec.interval_type {
            VelocityControlIntervalType::Hourly => (spec.limit_msat, 300, 12),
            VelocityControlIntervalType::Daily => (spec.limit_msat, 3600, 24),
            VelocityControlIntervalType::Unlimited => (u64::MAX, 300, 12),
        }
    }

    /// Load from persistence
    pub fn load_from_state(spec: VelocityControlSpec, state: (u64, Vec<u64>)) -> Self {
        let control = Self::new(spec);
        control.with_state(state)
    }

    fn with_state(mut self, state: (u64, Vec<u64>)) -> VelocityControl {
        self.start_sec = state.0;
        self.buckets = state.1;
        self
    }

    /// Get the state for persistence
    pub fn get_state(&self) -> (u64, Vec<u64>) {
        (self.start_sec, self.buckets.clone())
    }

    /// Whether this instance is unlimited (no control)
    pub fn is_unlimited(&self) -> bool {
        self.limit == u64::MAX
    }

    /// Update the velocity given the passage of time given by `current_sec`
    /// and the given velocity.  If the limit would be exceeded, the given velocity
    /// is not inserted and false is returned.
    pub fn insert(&mut self, current_sec: u64, velocity_msat: u64) -> bool {
        let nshift = (current_sec - self.start_sec) / self.bucket_interval as u64;
        let len = self.buckets.len();
        let nshift = min(len, nshift as usize);
        self.buckets.resize(len - nshift, 0);
        for _ in 0..nshift {
            self.buckets.insert(0, 0);
        }
        self.start_sec = current_sec - (current_sec % self.bucket_interval as u64);
        let current_velocity = self.velocity();
        if current_velocity.saturating_add(velocity_msat) > self.limit {
            false
        } else {
            self.buckets[0] = self.buckets[0].saturating_add(velocity_msat);
            true
        }
    }

    /// Clear the control (e.g. when the user manually approves)
    pub fn clear(&mut self) {
        for bucket in self.buckets.iter_mut() {
            *bucket = 0;
        }
    }

    /// The total msat velocity in the tracked interval.
    ///
    /// If this is an unlimited control, zero is always returned.
    pub fn velocity(&self) -> u64 {
        let mut sum = 0u64;
        for bucket in self.buckets.iter() {
            sum = sum.saturating_add(*bucket)
        }
        sum
    }
}

#[cfg(test)]
mod tests {
    use crate::util::velocity::VelocityControl;

    #[test]
    fn test_velocity() {
        let mut c = VelocityControl::new_with_intervals(100, 10, 4);
        assert_eq!(c.velocity(), 0);
        assert!(c.insert(1100, 90));
        assert_eq!(c.velocity(), 90);
        assert!(!c.insert(1101, 11));
        assert_eq!(c.velocity(), 90);
        assert!(c.insert(1101, 10));
        assert_eq!(c.velocity(), 100);
        assert!(!c.insert(1139, 90));
        assert_eq!(c.velocity(), 100);
        assert!(c.insert(1140, 90));
        assert_eq!(c.velocity(), 90);
        assert!(c.insert(1150, 5));
        assert_eq!(c.velocity(), 95);
        assert!(c.insert(1180, 80));
        assert_eq!(c.velocity(), 85);
        assert!(c.insert(1190, 1));
        assert_eq!(c.velocity(), 81);
    }

    #[test]
    fn test_unlimited() {
        let mut c = VelocityControl::new_unlimited(10, 4);
        assert!(c.insert(0, u64::MAX - 1));
        assert_eq!(c.velocity(), u64::MAX - 1);
        assert!(c.insert(0, 1));
        assert_eq!(c.velocity(), u64::MAX);
        assert!(c.insert(0, 1));
        assert_eq!(c.velocity(), u64::MAX);
    }
}