hopr_strategy/
lib.rs

1//! This crate contains all the Strategies for HOPRd.
2//! Strategies are vital for (partial) automation of ticket and HOPR channel operations
3//! during node runtime.
4//!
5//! - [passive strategy](crate::strategy::MultiStrategy)
6//! - [auto funding strategy](crate::auto_funding)
7//! - [auto redeeming strategy](crate::auto_redeeming)
8//! - [multiple strategy chains](crate::strategy)
9//!
10//! HOPRd can be configured to use any of the above strategies.
11//!
12//! ## Configuring strategies in HOPRd
13//!
14//! There are two ways of configuring strategies in HOPRd: via CLI and via a YAML config file.
15//!
16//! The configuration through CLI allows only fairly primitive single-strategy setting, through the `defaultStrategy`
17//! parameter. It can be set to any of the above strategies, however, the strategy parameters are not further
18//! configurable via the CLI and will always have their default values.
19//! In addition, if the ` disableTicketAutoRedeem ` CLI argument is `false`, the default Auto Redeem strategy is added
20//! to the strategy configured via the `defaultStrategy` argument (they execute together as Multi strategy).
21//!
22//! For more complex strategy configurations, the YAML configuration method is recommended via the `strategy` YAML
23//! section. In this case, the top-most strategy is always assumed to be Multi strategy:
24//!
25//! ```yaml
26//! strategy:
27//!   on_fail_continue: true
28//!   allow_recursive: true
29//!   execution_interval: 60
30//!   strategies:
31//!     - !AutoFunding
32//!       funding_amount: 20
33//! ```
34
35use std::str::FromStr;
36
37use hopr_primitive_types::prelude::*;
38use serde::{Deserialize, Serialize};
39use strum::{Display, EnumString, VariantNames};
40
41use crate::{
42    Strategy::AutoRedeeming, auto_funding::AutoFundingStrategyConfig, auto_redeeming::AutoRedeemingStrategyConfig,
43    channel_finalizer::ClosureFinalizerStrategyConfig, strategy::MultiStrategyConfig,
44};
45
46pub mod auto_funding;
47pub mod auto_redeeming;
48pub mod channel_finalizer;
49pub mod errors;
50pub mod strategy;
51
52/// Lists all possible strategies with their respective configurations.
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Display, EnumString, VariantNames)]
54#[strum(serialize_all = "snake_case")]
55pub enum Strategy {
56    AutoRedeeming(AutoRedeemingStrategyConfig),
57    AutoFunding(AutoFundingStrategyConfig),
58    ClosureFinalizer(ClosureFinalizerStrategyConfig),
59    Multi(MultiStrategyConfig),
60    Passive,
61}
62
63/// Default HOPR node strategies (in order).
64///
65/// ## Auto-redeem Strategy
66/// - redeem single tickets on channel close if worth at least 1 wxHOPR
67pub fn hopr_default_strategies() -> MultiStrategyConfig {
68    MultiStrategyConfig {
69        on_fail_continue: true,
70        allow_recursive: false,
71        execution_interval: 60,
72        strategies: vec![
73            // AutoFunding(AutoFundingStrategyConfig {
74            // min_stake_threshold: Balance::new_from_str("1000000000000000000", BalanceType::HOPR),
75            // funding_amount: Balance::new_from_str("10000000000000000000", BalanceType::HOPR),
76            // }),
77            AutoRedeeming(AutoRedeemingStrategyConfig {
78                redeem_all_on_close: true,
79                minimum_redeem_ticket_value: HoprBalance::from_str("1 wxHOPR").unwrap(),
80                redeem_on_winning: true,
81            }),
82        ],
83    }
84}
85
86impl Default for Strategy {
87    fn default() -> Self {
88        Self::Multi(hopr_default_strategies())
89    }
90}
91
92/// An alias for the strategy configuration type.
93pub type StrategyConfig = MultiStrategyConfig;
94
95#[cfg(test)]
96pub(crate) mod tests {
97    use futures::{FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream};
98    use hopr_api::{
99        chain::{
100            ChainReadChannelOperations, ChainReceipt, ChainWriteChannelOperations, ChainWriteTicketOperations,
101            ChannelSelector,
102        },
103        db::TicketSelector,
104    };
105    use hopr_internal_types::{
106        channels::{ChannelEntry, ChannelId, ChannelStatus},
107        prelude::AcknowledgedTicket,
108    };
109    use hopr_primitive_types::{balance::HoprBalance, prelude::Address};
110
111    use crate::errors::StrategyError;
112
113    // Mock helper needs to be created, because ChainWriteTicketOperations and ChainReadChannelOperations
114    // cannot be mocked directly due to impossible lifetimes.
115    #[mockall::automock]
116    pub trait TestActions {
117        fn me(&self) -> &Address;
118        fn fund_channel(&self, channel_id: &ChannelId, amount: HoprBalance) -> Result<ChainReceipt, StrategyError>;
119        fn close_channel(&self, channel_id: &ChannelId) -> Result<(ChannelStatus, ChainReceipt), StrategyError>;
120        fn redeem_with_selector(&self, selector: TicketSelector) -> Vec<ChainReceipt>;
121        fn stream_channels(&self, selector: ChannelSelector) -> impl Stream<Item = ChannelEntry> + Send;
122        fn channel_by_id(&self, channel_id: &ChannelId) -> Option<ChannelEntry>;
123    }
124
125    pub struct MockChainActions<T>(pub std::sync::Arc<T>);
126
127    impl<T> Clone for MockChainActions<T> {
128        fn clone(&self) -> Self {
129            Self(self.0.clone())
130        }
131    }
132
133    #[async_trait::async_trait]
134    impl<T: TestActions + Send + Sync> ChainReadChannelOperations for MockChainActions<T> {
135        type Error = StrategyError;
136
137        fn me(&self) -> &Address {
138            self.0.me()
139        }
140
141        async fn channel_by_parties(&self, _: &Address, _: &Address) -> Result<Option<ChannelEntry>, Self::Error> {
142            unimplemented!()
143        }
144
145        async fn channel_by_id(&self, channel_id: &ChannelId) -> Result<Option<ChannelEntry>, Self::Error> {
146            Ok(self.0.channel_by_id(channel_id))
147        }
148
149        async fn stream_channels<'a>(
150            &'a self,
151            selector: ChannelSelector,
152        ) -> Result<BoxStream<'a, ChannelEntry>, Self::Error> {
153            Ok(self.0.stream_channels(selector).boxed())
154        }
155    }
156
157    #[async_trait::async_trait]
158    impl<T: TestActions + Send + Sync> ChainWriteChannelOperations for MockChainActions<T> {
159        type Error = StrategyError;
160
161        async fn open_channel<'a>(
162            &'a self,
163            _: &'a Address,
164            _: HoprBalance,
165        ) -> Result<BoxFuture<'a, Result<(ChannelId, ChainReceipt), Self::Error>>, Self::Error> {
166            unimplemented!()
167        }
168
169        async fn fund_channel<'a>(
170            &'a self,
171            channel_id: &'a ChannelId,
172            amount: HoprBalance,
173        ) -> Result<BoxFuture<'a, Result<ChainReceipt, Self::Error>>, Self::Error> {
174            Ok(futures::future::ready(self.0.fund_channel(channel_id, amount)).boxed())
175        }
176
177        async fn close_channel<'a>(
178            &'a self,
179            channel_id: &'a ChannelId,
180        ) -> Result<BoxFuture<'a, Result<(ChannelStatus, ChainReceipt), Self::Error>>, Self::Error> {
181            Ok(futures::future::ready(self.0.close_channel(channel_id)).boxed())
182        }
183    }
184
185    #[async_trait::async_trait]
186    impl<T: TestActions + Send + Sync> ChainWriteTicketOperations for MockChainActions<T> {
187        type Error = StrategyError;
188
189        async fn redeem_ticket(
190            &self,
191            _: AcknowledgedTicket,
192        ) -> Result<BoxFuture<'_, Result<ChainReceipt, Self::Error>>, Self::Error> {
193            unimplemented!()
194        }
195
196        async fn redeem_tickets_via_selector(
197            &self,
198            selector: TicketSelector,
199        ) -> Result<Vec<BoxFuture<'_, Result<ChainReceipt, Self::Error>>>, Self::Error> {
200            let receipts = self.0.redeem_with_selector(selector);
201            Ok(receipts
202                .into_iter()
203                .map(|r| futures::future::ready(Ok(r)).boxed())
204                .collect())
205        }
206    }
207}