1use std::{
2 fmt::{Display, Formatter},
3 time::{Duration, SystemTime},
4};
5
6use hopr_crypto_types::prelude::*;
7use hopr_primitive_types::prelude::*;
8
9#[derive(Copy, Clone, Debug, smart_default::SmartDefault, strum::Display, strum::EnumDiscriminants)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[strum_discriminants(vis(pub))]
13#[strum_discriminants(derive(strum::FromRepr, strum::EnumCount), repr(i8))]
14#[strum(serialize_all = "PascalCase")]
15pub enum ChannelStatus {
16 #[default]
18 Closed,
19 Open,
21 #[strum(serialize = "PendingToClose")]
24 PendingToClose(SystemTime),
25}
26
27impl From<ChannelStatus> for i8 {
29 fn from(value: ChannelStatus) -> Self {
30 match value {
31 ChannelStatus::Closed => 0,
32 ChannelStatus::Open => 1,
33 ChannelStatus::PendingToClose(_) => 2,
34 }
35 }
36}
37
38impl PartialEq for ChannelStatus {
40 fn eq(&self, other: &Self) -> bool {
41 match (self, other) {
43 (Self::Open, Self::Open) => true,
44 (Self::Closed, Self::Closed) => true,
45 (Self::PendingToClose(ct_1), Self::PendingToClose(ct_2)) => {
46 let diff = ct_1.max(ct_2).saturating_sub(*ct_1.min(ct_2));
47 diff.as_secs() == 0
48 }
49 _ => false,
50 }
51 }
52}
53impl Eq for ChannelStatus {}
54
55#[repr(u8)]
58#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display, strum::EnumString)]
59#[strum(serialize_all = "lowercase")]
60pub enum ChannelDirection {
61 Incoming = 0,
63 Outgoing = 1,
65}
66
67pub type ChannelId = Hash;
69
70#[derive(Copy, Clone, Debug, PartialEq)]
72#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
73pub struct ChannelEntry {
74 pub source: Address,
75 pub destination: Address,
76 pub balance: HoprBalance,
77 pub ticket_index: U256,
78 pub status: ChannelStatus,
79 pub channel_epoch: U256,
80 id: ChannelId,
81}
82
83impl ChannelEntry {
84 pub const MAX_CHANNEL_BALANCE: u128 = 10_u128.pow(25);
86
87 pub fn new(
88 source: Address,
89 destination: Address,
90 balance: HoprBalance,
91 ticket_index: U256,
92 status: ChannelStatus,
93 channel_epoch: U256,
94 ) -> Self {
95 ChannelEntry {
96 source,
97 destination,
98 balance,
99 ticket_index,
100 status,
101 channel_epoch,
102 id: generate_channel_id(&source, &destination),
103 }
104 }
105
106 pub fn get_id(&self) -> ChannelId {
108 self.id
109 }
110
111 pub fn closure_time_passed(&self, current_time: SystemTime) -> bool {
115 match self.status {
116 ChannelStatus::Open => false,
117 ChannelStatus::PendingToClose(closure_time) => closure_time <= current_time,
118 ChannelStatus::Closed => true,
119 }
120 }
121
122 pub fn remaining_closure_time(&self, current_time: SystemTime) -> Option<Duration> {
125 match self.status {
126 ChannelStatus::Open => None,
127 ChannelStatus::PendingToClose(closure_time) => Some(closure_time.saturating_sub(current_time)),
128 ChannelStatus::Closed => Some(Duration::ZERO),
129 }
130 }
131
132 pub fn closure_time_at(&self) -> Option<SystemTime> {
135 match self.status {
136 ChannelStatus::PendingToClose(ct) => Some(ct),
137 _ => None,
138 }
139 }
140
141 pub fn direction(&self, me: &Address) -> Option<ChannelDirection> {
144 if self.source.eq(me) {
145 Some(ChannelDirection::Outgoing)
146 } else if self.destination.eq(me) {
147 Some(ChannelDirection::Incoming)
148 } else {
149 None
150 }
151 }
152
153 pub fn orientation(&self, me: &Address) -> Option<(ChannelDirection, Address)> {
156 if self.source.eq(me) {
157 Some((ChannelDirection::Outgoing, self.destination))
158 } else if self.destination.eq(me) {
159 Some((ChannelDirection::Incoming, self.source))
160 } else {
161 None
162 }
163 }
164}
165
166impl Display for ChannelEntry {
167 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
168 write!(f, "{} channel {}", self.status, self.get_id(),)
169 }
170}
171
172pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
174 Hash::create(&[source.as_ref(), destination.as_ref()])
175}
176
177#[derive(Clone, Copy, Debug)]
179pub enum ChannelChange {
180 Status { left: ChannelStatus, right: ChannelStatus },
182
183 CurrentBalance { left: HoprBalance, right: HoprBalance },
185
186 Epoch { left: u32, right: u32 },
188
189 TicketIndex { left: u64, right: u64 },
191}
192
193impl Display for ChannelChange {
194 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
195 match self {
196 ChannelChange::Status { left, right } => {
197 write!(f, "Status: {left} -> {right}")
198 }
199
200 ChannelChange::CurrentBalance { left, right } => {
201 write!(f, "Balance: {left} -> {right}")
202 }
203
204 ChannelChange::Epoch { left, right } => {
205 write!(f, "Epoch: {left} -> {right}")
206 }
207
208 ChannelChange::TicketIndex { left, right } => {
209 write!(f, "TicketIndex: {left} -> {right}")
210 }
211 }
212 }
213}
214
215impl ChannelChange {
216 pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
222 assert_eq!(left.id, right.id, "must have equal ids"); let mut ret = Vec::with_capacity(4);
224 if left.status != right.status {
225 ret.push(ChannelChange::Status {
226 left: left.status,
227 right: right.status,
228 });
229 }
230
231 if left.balance != right.balance {
232 ret.push(ChannelChange::CurrentBalance {
233 left: left.balance,
234 right: right.balance,
235 });
236 }
237
238 if left.channel_epoch != right.channel_epoch {
239 ret.push(ChannelChange::Epoch {
240 left: left.channel_epoch.as_u32(),
241 right: right.channel_epoch.as_u32(),
242 });
243 }
244
245 if left.ticket_index != right.ticket_index {
246 ret.push(ChannelChange::TicketIndex {
247 left: left.ticket_index.as_u64(),
248 right: right.ticket_index.as_u64(),
249 })
250 }
251
252 ret
253 }
254}
255
256#[derive(Copy, Clone, Debug, PartialEq)]
258#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
259pub struct CorruptedChannelEntry(ChannelId);
260
261impl From<ChannelId> for CorruptedChannelEntry {
262 fn from(value: ChannelId) -> Self {
263 CorruptedChannelEntry(value)
264 }
265}
266
267impl CorruptedChannelEntry {
268 pub fn channel_id(&self) -> &ChannelId {
270 &self.0
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
276pub struct SrcDstPair(Address, Address);
277
278impl From<ChannelEntry> for SrcDstPair {
279 fn from(channel: ChannelEntry) -> Self {
280 SrcDstPair(channel.source, channel.destination)
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use std::{
287 ops::Add,
288 str::FromStr,
289 time::{Duration, SystemTime},
290 };
291
292 use hex_literal::hex;
293
294 use super::*;
295
296 lazy_static::lazy_static! {
297 static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775")).expect("lazy static keypair should be constructible");
298 static ref BOB: ChainKeypair = ChainKeypair::from_secret(&hex!("48680484c6fc31bc881a0083e6e32b6dc789f9eaba0f8b981429fd346c697f8c")).expect("lazy static keypair should be constructible");
299
300 static ref ADDRESS_1: Address = "3829b806aea42200c623c4d6b9311670577480ed".parse().expect("lazy static address should be constructible");
301 static ref ADDRESS_2: Address = "1a34729c69e95d6e11c3a9b9be3ea0c62c6dc5b1".parse().expect("lazy static address should be constructible");
302 }
303
304 #[test]
305 pub fn test_generate_id() -> anyhow::Result<()> {
306 let from = Address::from_str("0xa460f2e47c641b64535f5f4beeb9ac6f36f9d27c")?;
307 let to = Address::from_str("0xb8b75fef7efdf4530cf1688c933d94e4e519ccd1")?;
308 let id = generate_channel_id(&from, &to).to_string();
309 assert_eq!("0x1a410210ce7265f3070bf0e8885705dce452efcfbd90a5467525d136fcefc64a", id);
310
311 Ok(())
312 }
313
314 #[test]
315 fn channel_status_names() {
316 assert_eq!("Open", ChannelStatus::Open.to_string());
317 assert_eq!("Closed", ChannelStatus::Closed.to_string());
318 assert_eq!(
319 "PendingToClose",
320 ChannelStatus::PendingToClose(SystemTime::now()).to_string()
321 );
322 }
323
324 #[test]
325 fn channel_status_repr_compat() {
326 assert_eq!(ChannelStatusDiscriminants::Open as i8, i8::from(ChannelStatus::Open));
327 assert_eq!(
328 ChannelStatusDiscriminants::Closed as i8,
329 i8::from(ChannelStatus::Closed)
330 );
331 assert_eq!(
332 ChannelStatusDiscriminants::PendingToClose as i8,
333 i8::from(ChannelStatus::PendingToClose(SystemTime::now()))
334 );
335 }
336
337 #[test]
338 pub fn channel_entry_closure_time() {
339 let mut ce = ChannelEntry::new(
340 *ADDRESS_1,
341 *ADDRESS_2,
342 10.into(),
343 23u64.into(),
344 ChannelStatus::Open,
345 3u64.into(),
346 );
347
348 assert!(
349 !ce.closure_time_passed(SystemTime::now()),
350 "opened channel cannot pass closure time"
351 );
352 assert!(
353 ce.remaining_closure_time(SystemTime::now()).is_none(),
354 "opened channel cannot have remaining closure time"
355 );
356
357 let current_time = SystemTime::now();
358 ce.status = ChannelStatus::PendingToClose(current_time.add(Duration::from_secs(60)));
359
360 assert!(
361 !ce.closure_time_passed(current_time),
362 "must not have passed closure time"
363 );
364 assert_eq!(
365 60,
366 ce.remaining_closure_time(current_time)
367 .expect("must have closure time")
368 .as_secs()
369 );
370
371 let current_time = current_time.add(Duration::from_secs(120));
372
373 assert!(ce.closure_time_passed(current_time), "must have passed closure time");
374 assert_eq!(
375 Duration::ZERO,
376 ce.remaining_closure_time(current_time).expect("must have closure time")
377 );
378 }
379}