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