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)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12#[strum(serialize_all = "PascalCase")]
13pub enum ChannelStatus {
14 #[default]
16 Closed,
17 Open,
19 #[strum(serialize = "PendingToClose")]
22 PendingToClose(SystemTime),
23}
24
25impl From<ChannelStatus> for i8 {
27 fn from(value: ChannelStatus) -> Self {
28 match value {
29 ChannelStatus::Closed => 0,
30 ChannelStatus::Open => 1,
31 ChannelStatus::PendingToClose(_) => 2,
32 }
33 }
34}
35
36impl PartialEq for ChannelStatus {
38 fn eq(&self, other: &Self) -> bool {
39 match (self, other) {
41 (Self::Open, Self::Open) => true,
42 (Self::Closed, Self::Closed) => true,
43 (Self::PendingToClose(ct_1), Self::PendingToClose(ct_2)) => {
44 let diff = ct_1.max(ct_2).saturating_sub(*ct_1.min(ct_2));
45 diff.as_secs() == 0
46 }
47 _ => false,
48 }
49 }
50}
51impl Eq for ChannelStatus {}
52
53#[repr(u8)]
56#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display, strum::EnumString)]
57#[strum(serialize_all = "lowercase")]
58pub enum ChannelDirection {
59 Incoming = 0,
61 Outgoing = 1,
63}
64
65pub type ChannelId = Hash;
67
68#[derive(Copy, Clone, Debug, PartialEq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
71pub struct ChannelEntry {
72 pub source: Address,
73 pub destination: Address,
74 pub balance: HoprBalance,
75 pub ticket_index: U256,
76 pub status: ChannelStatus,
77 pub channel_epoch: U256,
78 id: ChannelId,
79}
80
81impl ChannelEntry {
82 pub const MAX_CHANNEL_BALANCE: u128 = 10_u128.pow(25);
84
85 pub fn new(
86 source: Address,
87 destination: Address,
88 balance: HoprBalance,
89 ticket_index: U256,
90 status: ChannelStatus,
91 channel_epoch: U256,
92 ) -> Self {
93 ChannelEntry {
94 source,
95 destination,
96 balance,
97 ticket_index,
98 status,
99 channel_epoch,
100 id: generate_channel_id(&source, &destination),
101 }
102 }
103
104 pub fn get_id(&self) -> ChannelId {
106 self.id
107 }
108
109 pub fn closure_time_passed(&self, current_time: SystemTime) -> bool {
113 match self.status {
114 ChannelStatus::Open => false,
115 ChannelStatus::PendingToClose(closure_time) => closure_time <= current_time,
116 ChannelStatus::Closed => true,
117 }
118 }
119
120 pub fn remaining_closure_time(&self, current_time: SystemTime) -> Option<Duration> {
123 match self.status {
124 ChannelStatus::Open => None,
125 ChannelStatus::PendingToClose(closure_time) => Some(closure_time.saturating_sub(current_time)),
126 ChannelStatus::Closed => Some(Duration::ZERO),
127 }
128 }
129
130 pub fn closure_time_at(&self) -> Option<SystemTime> {
133 match self.status {
134 ChannelStatus::PendingToClose(ct) => Some(ct),
135 _ => None,
136 }
137 }
138
139 pub fn direction(&self, me: &Address) -> Option<ChannelDirection> {
142 if self.source.eq(me) {
143 Some(ChannelDirection::Outgoing)
144 } else if self.destination.eq(me) {
145 Some(ChannelDirection::Incoming)
146 } else {
147 None
148 }
149 }
150
151 pub fn orientation(&self, me: &Address) -> Option<(ChannelDirection, Address)> {
154 if self.source.eq(me) {
155 Some((ChannelDirection::Outgoing, self.destination))
156 } else if self.destination.eq(me) {
157 Some((ChannelDirection::Incoming, self.source))
158 } else {
159 None
160 }
161 }
162}
163
164impl Display for ChannelEntry {
165 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
166 write!(f, "{} channel {}", self.status, self.get_id(),)
167 }
168}
169
170pub fn generate_channel_id(source: &Address, destination: &Address) -> Hash {
172 Hash::create(&[source.as_ref(), destination.as_ref()])
173}
174
175#[derive(Clone, Copy, Debug)]
177pub enum ChannelChange {
178 Status { left: ChannelStatus, right: ChannelStatus },
180
181 CurrentBalance { left: HoprBalance, right: HoprBalance },
183
184 Epoch { left: u32, right: u32 },
186
187 TicketIndex { left: u64, right: u64 },
189}
190
191impl Display for ChannelChange {
192 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
193 match self {
194 ChannelChange::Status { left, right } => {
195 write!(f, "Status: {left} -> {right}")
196 }
197
198 ChannelChange::CurrentBalance { left, right } => {
199 write!(f, "Balance: {left} -> {right}")
200 }
201
202 ChannelChange::Epoch { left, right } => {
203 write!(f, "Epoch: {left} -> {right}")
204 }
205
206 ChannelChange::TicketIndex { left, right } => {
207 write!(f, "TicketIndex: {left} -> {right}")
208 }
209 }
210 }
211}
212
213impl ChannelChange {
214 pub fn diff_channels(left: &ChannelEntry, right: &ChannelEntry) -> Vec<Self> {
220 assert_eq!(left.id, right.id, "must have equal ids"); let mut ret = Vec::with_capacity(4);
222 if left.status != right.status {
223 ret.push(ChannelChange::Status {
224 left: left.status,
225 right: right.status,
226 });
227 }
228
229 if left.balance != right.balance {
230 ret.push(ChannelChange::CurrentBalance {
231 left: left.balance,
232 right: right.balance,
233 });
234 }
235
236 if left.channel_epoch != right.channel_epoch {
237 ret.push(ChannelChange::Epoch {
238 left: left.channel_epoch.as_u32(),
239 right: right.channel_epoch.as_u32(),
240 });
241 }
242
243 if left.ticket_index != right.ticket_index {
244 ret.push(ChannelChange::TicketIndex {
245 left: left.ticket_index.as_u64(),
246 right: right.ticket_index.as_u64(),
247 })
248 }
249
250 ret
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use std::{
257 ops::Add,
258 str::FromStr,
259 time::{Duration, SystemTime},
260 };
261
262 use hex_literal::hex;
263 use hopr_crypto_types::prelude::*;
264 use hopr_primitive_types::prelude::*;
265
266 use crate::channels::{ChannelEntry, ChannelStatus, generate_channel_id};
267
268 lazy_static::lazy_static! {
269 static ref ALICE: ChainKeypair = ChainKeypair::from_secret(&hex!("492057cf93e99b31d2a85bc5e98a9c3aa0021feec52c227cc8170e8f7d047775")).expect("lazy static keypair should be constructible");
270 static ref BOB: ChainKeypair = ChainKeypair::from_secret(&hex!("48680484c6fc31bc881a0083e6e32b6dc789f9eaba0f8b981429fd346c697f8c")).expect("lazy static keypair should be constructible");
271
272 static ref ADDRESS_1: Address = "3829b806aea42200c623c4d6b9311670577480ed".parse().expect("lazy static address should be constructible");
273 static ref ADDRESS_2: Address = "1a34729c69e95d6e11c3a9b9be3ea0c62c6dc5b1".parse().expect("lazy static address should be constructible");
274 }
275
276 #[test]
277 pub fn test_generate_id() -> anyhow::Result<()> {
278 let from = Address::from_str("0xa460f2e47c641b64535f5f4beeb9ac6f36f9d27c")?;
279 let to = Address::from_str("0xb8b75fef7efdf4530cf1688c933d94e4e519ccd1")?;
280 let id = generate_channel_id(&from, &to).to_string();
281 assert_eq!("0x1a410210ce7265f3070bf0e8885705dce452efcfbd90a5467525d136fcefc64a", id);
282
283 Ok(())
284 }
285
286 #[test]
287 fn channel_status_names() {
288 assert_eq!("Open", ChannelStatus::Open.to_string());
289 assert_eq!("Closed", ChannelStatus::Closed.to_string());
290 assert_eq!(
291 "PendingToClose",
292 ChannelStatus::PendingToClose(SystemTime::now()).to_string()
293 );
294 }
295
296 #[test]
297 pub fn channel_entry_closure_time() {
298 let mut ce = ChannelEntry::new(
299 *ADDRESS_1,
300 *ADDRESS_2,
301 10.into(),
302 23u64.into(),
303 ChannelStatus::Open,
304 3u64.into(),
305 );
306
307 assert!(
308 !ce.closure_time_passed(SystemTime::now()),
309 "opened channel cannot pass closure time"
310 );
311 assert!(
312 ce.remaining_closure_time(SystemTime::now()).is_none(),
313 "opened channel cannot have remaining closure time"
314 );
315
316 let current_time = SystemTime::now();
317 ce.status = ChannelStatus::PendingToClose(current_time.add(Duration::from_secs(60)));
318
319 assert!(
320 !ce.closure_time_passed(current_time),
321 "must not have passed closure time"
322 );
323 assert_eq!(
324 60,
325 ce.remaining_closure_time(current_time)
326 .expect("must have closure time")
327 .as_secs()
328 );
329
330 let current_time = current_time.add(Duration::from_secs(120));
331
332 assert!(ce.closure_time_passed(current_time), "must have passed closure time");
333 assert_eq!(
334 Duration::ZERO,
335 ce.remaining_closure_time(current_time).expect("must have closure time")
336 );
337 }
338}