Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplifies the cln plugin option parsing #170

Merged
merged 2 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions watchtower-plugin/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Collection of ENV variable names and values
pub const TOWERS_DATA_DIR: &str = "TOWERS_DATA_DIR";
pub const DEFAULT_TOWERS_DATA_DIR: &str = ".watchtower";

/// Collections of plugin option names, default values and descriptions

pub const WT_PORT: &str = "watchtower-port";
pub const DEFAULT_WT_PORT: i64 = 9814;
pub const WT_PORT_DESC: &str = "tower API port";
pub const WT_MAX_RETRY_TIME: &str = "watchtower-max-retry-time";
pub const DEFAULT_WT_MAX_RETRY_TIME: i64 = 900;
pub const WT_MAX_RETRY_TIME_DESC: &str = "the time (in seconds) after when a retrier will give up trying to send data to a temporary unreachable tower";
pub const WT_PROXY: &str = "watchtower-proxy";
pub const WT_PROXY_DESC: &str = "Socks v5 proxy IP address and port for the watchtower client";
pub const WT_AUTO_RETRY_DELAY: &str = "watchtower-auto-retry-delay";
pub const DEFAULT_WT_AUTO_RETRY_DELAY: i64 = 86400;
pub const WT_AUTO_RETRY_DELAY_DESC: &str = "the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day";
pub const DEV_WT_MAX_RETRY_INTERVAL: &str = "dev-watchtower-max-retry-interval";
pub const DEFAULT_DEV_WT_MAX_RETRY_INTERVAL: i64 = 60;
pub const DEV_WT_MAX_RETRY_INTERVAL_DESC: &str =
"the maximum time (in seconds) for a retrier wait interval";

/// Collections of rpc method names and descriptions

pub const RPC_REGISTER_TOWER: &str = "registertower";
pub const RPC_REGISTER_TOWER_DESC: &str =
"Registers the client public key (user id) with the tower";
pub const RPC_GET_REGISTRATION_RECEIPT: &str = "getregistrationreceipt";
pub const RPC_GET_REGISTRATION_RECEIPT_DESC: &str =
"Gets the latest registration receipt given a tower id";
pub const RPC_GET_APPOINTMENT: &str = "getappointment";
pub const RPC_GET_APPOINTMENT_DESC: &str =
"Gets appointment data from the tower given a tower id and a locator";
pub const RPC_GET_APPOINTMENT_RECEIPT: &str = "getappointmentreceipt";
pub const RPC_GET_APPOINTMENT_RECEIPT_DESC: &str =
"Gets a (local) appointment receipt given a tower id and a locator";
pub const RPC_GET_SUBSCRIPTION_INFO: &str = "getsubscriptioninfo";
pub const RPC_GET_SUBSCRIPTION_INFO_DESC: &str =
"Gets the subscription information directly from the tower";
pub const RPC_LIST_TOWERS: &str = "listtowers";
pub const RPC_LIST_TOWERS_DESC: &str = "Lists all registered towers";
pub const RPC_GET_TOWER_INFO: &str = "gettowerinfo";
pub const RPC_GET_TOWER_INFO_DESC: &str = "Shows the info about a tower given a tower id";
pub const RPC_RETRY_TOWER: &str = "retrytower";
pub const RPC_RETRY_TOWER_DESC: &str =
"Retries to send pending appointment to an unreachable tower";
pub const RPC_ABANDON_TOWER: &str = "abandontower";
pub const RPC_ABANDON_TOWER_DESC: &str = "Forgets about a tower and wipes all local data";

/// Collections of hook names

pub const HOOK_COMMITMENT_REVOCATION: &str = "commitment_revocation";
1 change: 1 addition & 0 deletions watchtower-plugin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use teos_common::appointment::{Appointment, Locator};
use teos_common::receipts::AppointmentReceipt;
use teos_common::TowerId;

pub mod constants;
pub mod convert;
pub mod dbm;
pub mod net;
Expand Down
173 changes: 92 additions & 81 deletions watchtower-plugin/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use watchtower_plugin::net::http::{
};
use watchtower_plugin::retrier::RetryManager;
use watchtower_plugin::wt_client::{RevocationData, WTClient};
use watchtower_plugin::TowerStatus;
use watchtower_plugin::{constants, TowerStatus};

fn to_cln_error(e: RequestError) -> Error {
let e = match e {
Expand Down Expand Up @@ -75,15 +75,9 @@ async fn register(
// Otherwise the tower could just generate a subscription starting far in the future. For this we need to access lightning RPC
// which is not available in the current version of `cln-plugin` (but already on master). Add it for the next release.

// FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap
// given that we are certain the option exists.
let port = params.port.unwrap_or(
if let Value::Integer(x) = plugin.option("watchtower-port").unwrap() {
x as u16
} else {
// We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme.
9814
},
u16::try_from(plugin.option(constants::WT_PORT).unwrap().as_i64().unwrap())
.map_err(|_| anyhow!("{} out of range", constants::WT_PORT))?,
);

let mut tower_net_addr = format!("{}:{}", host, port);
Expand Down Expand Up @@ -521,76 +515,86 @@ async fn on_commitment_revocation(

#[tokio::main]
async fn main() -> Result<(), Error> {
let data_dir = match env::var("TOWERS_DATA_DIR") {
let data_dir = match env::var(constants::TOWERS_DATA_DIR) {
Ok(v) => PathBuf::from(v),
Err(_) => home_dir().unwrap().join(".watchtower"),
Err(_) => home_dir().unwrap().join(constants::DEFAULT_TOWERS_DATA_DIR),
};

let builder = Builder::new(stdin(), stdout())
.option(ConfigOption::new(
"watchtower-port",
Value::Integer(9814),
"tower API port",
constants::WT_PORT,
Value::Integer(constants::DEFAULT_WT_PORT),
constants::WT_PORT_DESC,
))
.option(ConfigOption::new(
"watchtower-max-retry-time",
Value::Integer(900),
"the time (in seconds) after where the retrier will give up trying to send data to a temporary unreachable tower",
constants::WT_MAX_RETRY_TIME,
Value::Integer(constants::DEFAULT_WT_MAX_RETRY_TIME),
constants::WT_MAX_RETRY_TIME_DESC,
))
.option(ConfigOption::new(
"watchtower-proxy",
constants::WT_PROXY,
Value::OptString,
"Socks v5 proxy IP address and port for the watchtower client",
)).option(ConfigOption::new(
"watchtower-auto-retry-delay",
Value::Integer(86400),
"the time (in seconds) that a retrier will wait before auto-retrying a failed tower. Defaults to once a day",
))
constants::WT_PROXY_DESC,
))
.option(ConfigOption::new(
"dev-watchtower-max-retry-interval",
Value::Integer(60),
"the maximum time (in seconds) for a retrier wait interval",
constants::WT_AUTO_RETRY_DELAY,
Value::Integer(constants::DEFAULT_WT_AUTO_RETRY_DELAY),
constants::WT_AUTO_RETRY_DELAY_DESC,
))
.option(ConfigOption::new(
constants::DEV_WT_MAX_RETRY_INTERVAL,
Value::Integer(constants::DEFAULT_DEV_WT_MAX_RETRY_INTERVAL),
constants::DEV_WT_MAX_RETRY_INTERVAL_DESC,
))
.rpcmethod(
"registertower",
"Registers the client public key (user id) with the tower.",
constants::RPC_REGISTER_TOWER,
constants::RPC_REGISTER_TOWER_DESC,
register,
).rpcmethod(
"getregistrationreceipt",
"Gets the latest registration receipt given a tower id.",
)
.rpcmethod(
constants::RPC_GET_REGISTRATION_RECEIPT,
constants::RPC_GET_REGISTRATION_RECEIPT_DESC,
get_registration_receipt,
)
.rpcmethod(
"getappointment",
"Gets appointment data from the tower given the tower id and the locator.",
constants::RPC_GET_APPOINTMENT,
constants::RPC_GET_APPOINTMENT_DESC,
get_appointment,
).rpcmethod(
"getappointmentreceipt",
"Gets a (local) appointment receipt given a tower id and an locator.",
)
.rpcmethod(
constants::RPC_GET_APPOINTMENT_RECEIPT,
constants::RPC_GET_APPOINTMENT_RECEIPT_DESC,
get_appointment_receipt,
)
.rpcmethod(
"getsubscriptioninfo",
"Gets the subscription information directly from the tower.",
constants::RPC_GET_SUBSCRIPTION_INFO,
constants::RPC_GET_SUBSCRIPTION_INFO_DESC,
get_subscription_info,
)
.rpcmethod("listtowers", "Lists all registered towers.", list_towers)
.rpcmethod(
"gettowerinfo",
"Shows the info about a given tower.",
constants::RPC_LIST_TOWERS,
constants::RPC_LIST_TOWERS_DESC,
list_towers,
)
.rpcmethod(
constants::RPC_GET_TOWER_INFO,
constants::RPC_GET_TOWER_INFO_DESC,
get_tower_info,
)
.rpcmethod(
"retrytower",
"Retries to send pending appointment to an unreachable tower.",
constants::RPC_RETRY_TOWER,
constants::RPC_RETRY_TOWER_DESC,
retry_tower,
)
.rpcmethod(
"abandontower",
"Forgets about a tower and wipes all local data.",
constants::RPC_ABANDON_TOWER,
constants::RPC_ABANDON_TOWER_DESC,
abandon_tower,
)
.hook("commitment_revocation", on_commitment_revocation);
.hook(
constants::HOOK_COMMITMENT_REVOCATION,
on_commitment_revocation,
);

// We're unwrapping here given it does not seem we actually have anything to check at the moment.
// Change this so the plugin can be disabled soon if this happens not to be the case.
Expand All @@ -602,41 +606,48 @@ async fn main() -> Result<(), Error> {

let (tx, rx) = unbounded_channel();
let wt_client = Arc::new(Mutex::new(WTClient::new(data_dir, tx).await));
// FIXME: This is a workaround. Ideally, `cln_plugin::options::Value` will implement `as_u64` so we can simply call and unwrap
// given that we are certain the option exists.
wt_client.lock().unwrap().proxy =
if let Value::String(x) = midstate.option("watchtower-proxy").unwrap() {
if !x.is_empty() {
Some(x)
} else {
None
}
} else {
None
};
let max_elapsed_time =
if let Value::Integer(x) = midstate.option("watchtower-max-retry-time").unwrap() {
x as u16
} else {
// We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme.
900
};
let auto_retry_delay =
if let Value::Integer(x) = midstate.option("watchtower-auto-retry-delay").unwrap() {
x as u16
} else {
// We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme.
3600
};
let max_interval_time = if let Value::Integer(x) = midstate
.option("dev-watchtower-max-retry-interval")

wt_client.lock().unwrap().proxy = midstate
.option(constants::WT_PROXY)
.unwrap()
{
x as u16
} else {
// We will never end up here, but we need to define an else. Should be fixed alongside the previous fixme.
60
};
.as_str()
.map(|x| x.to_owned());

let max_elapsed_time = u16::try_from(
midstate
.option(constants::WT_MAX_RETRY_TIME)
.unwrap()
.as_i64()
.unwrap(),
)
.map_err(|e| {
log::error!("{} out of range", constants::WT_MAX_RETRY_TIME);
e
})?;

let auto_retry_delay = u32::try_from(
midstate
.option(constants::WT_AUTO_RETRY_DELAY)
.unwrap()
.as_i64()
.unwrap(),
)
.map_err(|e| {
log::error!("{} out of range", constants::WT_AUTO_RETRY_DELAY);
e
})?;

let max_interval_time = u16::try_from(
midstate
.option(constants::DEV_WT_MAX_RETRY_INTERVAL)
.unwrap()
.as_i64()
.unwrap(),
)
.map_err(|e| {
log::error!("{} out of range", constants::DEV_WT_MAX_RETRY_INTERVAL);
e
})?;

let plugin = midstate.start(wt_client.clone()).await?;
tokio::spawn(async move {
Expand Down
8 changes: 4 additions & 4 deletions watchtower-plugin/src/retrier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub struct RetryManager {
wt_client: Arc<Mutex<WTClient>>,
unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>,
max_elapsed_time_secs: u16,
auto_retry_delay: u16,
auto_retry_delay: u32,
max_interval_time_secs: u16,
retriers: HashMap<TowerId, Arc<Retrier>>,
}
Expand All @@ -59,7 +59,7 @@ impl RetryManager {
wt_client: Arc<Mutex<WTClient>>,
unreachable_towers: UnboundedReceiver<(TowerId, RevocationData)>,
max_elapsed_time_secs: u16,
auto_retry_delay: u16,
auto_retry_delay: u32,
max_interval_time_secs: u16,
) -> Self {
RetryManager {
Expand Down Expand Up @@ -595,8 +595,8 @@ mod tests {
use crate::net::http::ApiError;
use crate::test_utils::get_dummy_add_appointment_response;

const LONG_AUTO_RETRY_DELAY: u16 = 60;
const SHORT_AUTO_RETRY_DELAY: u16 = 3;
const LONG_AUTO_RETRY_DELAY: u32 = 60;
const SHORT_AUTO_RETRY_DELAY: u32 = 3;
const API_DELAY: f64 = 0.5;
const MAX_ELAPSED_TIME: u16 = 2;
const MAX_INTERVAL_TIME: u16 = 1;
Expand Down