1use crate::prelude::BlockHeight;
12use crate::ProtocolError;
13use integer_encoding::VarInt;
14use std::collections::BTreeMap;
15use std::convert::TryFrom;
16
17pub type Duffs = u64;
19
20pub type Credits = u64;
22
23pub type RemainingCredits = Credits;
25
26pub type TokenAmount = u64;
28
29pub type SignedTokenAmount = i64;
31
32pub type SumTokenAmount = i128;
34
35pub type SignedCredits = i64;
38
39pub const MAX_CREDITS: Credits = 9223372036854775807 as Credits; pub const CREDITS_PER_DUFF: Credits = 1000;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, bincode::Encode, bincode::Decode)]
46pub enum CreditOperation {
47 SetCredits(Credits),
49 AddToCredits(Credits),
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, bincode::Encode, bincode::Decode)]
55pub enum BlockAwareCreditOperation {
56 SetCredits(Credits),
58 AddToCreditsOperations(BTreeMap<BlockHeight, Credits>),
60}
61
62impl BlockAwareCreditOperation {
63 pub fn merge(&mut self, block_height: BlockHeight, operation: &CreditOperation) {
69 match (self, operation) {
70 (
72 BlockAwareCreditOperation::SetCredits(current),
73 CreditOperation::SetCredits(new_val),
74 ) => {
75 *current = *new_val;
76 }
77 (
79 BlockAwareCreditOperation::SetCredits(current),
80 CreditOperation::AddToCredits(add_val),
81 ) => {
82 *current = current.saturating_add(*add_val);
83 }
84 (
86 this @ BlockAwareCreditOperation::AddToCreditsOperations(_),
87 CreditOperation::SetCredits(new_val),
88 ) => {
89 *this = BlockAwareCreditOperation::SetCredits(*new_val);
92 }
93 (
95 BlockAwareCreditOperation::AddToCreditsOperations(map),
96 CreditOperation::AddToCredits(add_val),
97 ) => {
98 map.entry(block_height)
99 .and_modify(|existing| *existing = existing.saturating_add(*add_val))
100 .or_insert(*add_val);
101 }
102 }
103 }
104
105 pub fn from_operation(block_height: BlockHeight, operation: &CreditOperation) -> Self {
107 match operation {
108 CreditOperation::SetCredits(value) => BlockAwareCreditOperation::SetCredits(*value),
109 CreditOperation::AddToCredits(value) => {
110 let mut map = BTreeMap::new();
111 map.insert(block_height, *value);
112 BlockAwareCreditOperation::AddToCreditsOperations(map)
113 }
114 }
115 }
116}
117
118impl CreditOperation {
119 pub fn merge(&self, other: &CreditOperation) -> CreditOperation {
127 match (self, other) {
128 (_, CreditOperation::SetCredits(value)) => CreditOperation::SetCredits(*value),
130 (CreditOperation::SetCredits(set_val), CreditOperation::AddToCredits(add_val)) => {
132 CreditOperation::SetCredits(set_val.saturating_add(*add_val))
133 }
134 (CreditOperation::AddToCredits(val1), CreditOperation::AddToCredits(val2)) => {
136 CreditOperation::AddToCredits(val1.saturating_add(*val2))
137 }
138 }
139 }
140}
141
142pub trait Creditable {
144 fn to_signed(&self) -> Result<SignedCredits, ProtocolError>;
146 fn to_unsigned(&self) -> Credits;
148
149 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError>
153 where
154 Self: Sized;
155 fn to_vec_bytes(&self) -> Vec<u8>;
157}
158
159impl Creditable for Credits {
160 fn to_signed(&self) -> Result<SignedCredits, ProtocolError> {
161 SignedCredits::try_from(*self)
162 .map_err(|_| ProtocolError::Overflow("credits are too big to convert to signed value"))
163 }
164
165 fn to_unsigned(&self) -> Credits {
166 *self
167 }
168
169 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError> {
170 Self::decode_var(vec.as_slice()).map(|(n, _)| n).ok_or(
171 ProtocolError::CorruptedSerialization(
172 "pending refunds epoch index for must be u16".to_string(),
173 ),
174 )
175 }
176
177 fn to_vec_bytes(&self) -> Vec<u8> {
178 self.encode_var_vec()
179 }
180}
181
182impl Creditable for SignedCredits {
183 fn to_signed(&self) -> Result<SignedCredits, ProtocolError> {
184 Ok(*self)
185 }
186
187 fn to_unsigned(&self) -> Credits {
188 self.unsigned_abs()
189 }
190
191 fn from_vec_bytes(vec: Vec<u8>) -> Result<Self, ProtocolError> {
192 Self::decode_var(vec.as_slice()).map(|(n, _)| n).ok_or(
193 ProtocolError::CorruptedSerialization(
194 "pending refunds epoch index for must be u16".to_string(),
195 ),
196 )
197 }
198
199 fn to_vec_bytes(&self) -> Vec<u8> {
200 self.encode_var_vec()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207
208 mod block_aware_credit_operation {
209 use super::*;
210
211 #[test]
212 fn from_operation_set_credits() {
213 let op =
214 BlockAwareCreditOperation::from_operation(100, &CreditOperation::SetCredits(1000));
215 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1000));
216 }
217
218 #[test]
219 fn from_operation_add_to_credits() {
220 let op =
221 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
222 let expected: BTreeMap<BlockHeight, Credits> = [(100, 500)].into_iter().collect();
223 assert_eq!(
224 op,
225 BlockAwareCreditOperation::AddToCreditsOperations(expected)
226 );
227 }
228
229 #[test]
230 fn merge_set_then_set_takes_latest() {
231 let mut op = BlockAwareCreditOperation::SetCredits(1000);
232 op.merge(101, &CreditOperation::SetCredits(2000));
233 assert_eq!(op, BlockAwareCreditOperation::SetCredits(2000));
234 }
235
236 #[test]
237 fn merge_set_then_add_adds_to_set() {
238 let mut op = BlockAwareCreditOperation::SetCredits(1000);
239 op.merge(101, &CreditOperation::AddToCredits(500));
240 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1500));
241 }
242
243 #[test]
244 fn merge_set_then_multiple_adds() {
245 let mut op = BlockAwareCreditOperation::SetCredits(1000);
246 op.merge(101, &CreditOperation::AddToCredits(500));
247 op.merge(102, &CreditOperation::AddToCredits(300));
248 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1800));
249 }
250
251 #[test]
252 fn merge_add_then_set_becomes_set() {
253 let mut op =
254 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
255 op.merge(101, &CreditOperation::SetCredits(2000));
256 assert_eq!(op, BlockAwareCreditOperation::SetCredits(2000));
257 }
258
259 #[test]
260 fn merge_add_then_add_preserves_block_heights() {
261 let mut op =
262 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
263 op.merge(101, &CreditOperation::AddToCredits(300));
264 op.merge(102, &CreditOperation::AddToCredits(200));
265
266 let expected: BTreeMap<BlockHeight, Credits> =
267 [(100, 500), (101, 300), (102, 200)].into_iter().collect();
268 assert_eq!(
269 op,
270 BlockAwareCreditOperation::AddToCreditsOperations(expected)
271 );
272 }
273
274 #[test]
275 fn merge_multiple_adds_at_same_block_combines() {
276 let mut op =
277 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
278 op.merge(100, &CreditOperation::AddToCredits(300)); let expected: BTreeMap<BlockHeight, Credits> = [(100, 800)].into_iter().collect();
281 assert_eq!(
282 op,
283 BlockAwareCreditOperation::AddToCreditsOperations(expected)
284 );
285 }
286
287 #[test]
288 fn merge_add_then_set_then_add() {
289 let mut op =
291 BlockAwareCreditOperation::from_operation(100, &CreditOperation::AddToCredits(500));
292 op.merge(101, &CreditOperation::SetCredits(1000));
294 op.merge(102, &CreditOperation::AddToCredits(200));
296
297 assert_eq!(op, BlockAwareCreditOperation::SetCredits(1200));
299 }
300
301 #[test]
302 fn client_sync_scenario() {
303 let mut op =
308 BlockAwareCreditOperation::from_operation(400, &CreditOperation::AddToCredits(100));
309 op.merge(450, &CreditOperation::AddToCredits(200));
310 op.merge(500, &CreditOperation::AddToCredits(300));
311 op.merge(550, &CreditOperation::AddToCredits(400));
312 op.merge(600, &CreditOperation::AddToCredits(500));
313
314 if let BlockAwareCreditOperation::AddToCreditsOperations(map) = &op {
316 assert_eq!(map.len(), 5);
317
318 let to_apply: Credits = map
320 .iter()
321 .filter(|(block, _)| **block > 550)
322 .map(|(_, credits)| *credits)
323 .sum();
324
325 assert_eq!(to_apply, 500);
327
328 let to_apply_from_400: Credits = map
330 .iter()
331 .filter(|(block, _)| **block > 400)
332 .map(|(_, credits)| *credits)
333 .sum();
334
335 assert_eq!(to_apply_from_400, 1400);
337 } else {
338 panic!("Expected AddToCreditsOperations");
339 }
340 }
341
342 #[test]
343 fn set_credits_followed_by_adds_scenario() {
344 let mut op =
350 BlockAwareCreditOperation::from_operation(400, &CreditOperation::SetCredits(10000));
351 op.merge(500, &CreditOperation::AddToCredits(100));
352 op.merge(600, &CreditOperation::AddToCredits(200));
353
354 assert_eq!(op, BlockAwareCreditOperation::SetCredits(10300));
356
357 }
360 }
361}