Skip to content
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
190 changes: 183 additions & 7 deletions libs/gl-cli/src/signer.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::error::{Error, Result};
use crate::util;
use clap::Subcommand;
use clap::{Subcommand, ValueEnum};
use core::fmt::Debug;
use gl_client::signer::Signer;
use gl_client::signer::{
Signer, SignerConfig, StateSignatureMode, StateSignatureOverrideConfig,
};
use lightning_signer::bitcoin::Network;
use std::path::Path;
use tokio::{join, signal};
Expand All @@ -13,22 +15,69 @@ pub struct Config<P: AsRef<Path>> {
pub network: Network,
}

#[derive(Copy, Clone, Debug, Eq, PartialEq, ValueEnum)]
pub enum StateSignatureModeArg {
Off,
Soft,
Hard,
}

impl Default for StateSignatureModeArg {
fn default() -> Self {
Self::Soft
}
}

impl From<StateSignatureModeArg> for StateSignatureMode {
fn from(value: StateSignatureModeArg) -> Self {
match value {
StateSignatureModeArg::Off => StateSignatureMode::Off,
StateSignatureModeArg::Soft => StateSignatureMode::Soft,
StateSignatureModeArg::Hard => StateSignatureMode::Hard,
}
}
}

#[derive(Subcommand, Debug)]
pub enum Command {
/// Starts a signer that connects to greenlight
Run,
Run {
#[arg(long, value_enum, default_value_t = StateSignatureModeArg::Soft)]
state_signature_mode: StateSignatureModeArg,
#[arg(long = "state-override")]
state_override: Option<String>,
#[arg(long = "state-override-note")]
state_override_note: Option<String>,
},
/// Prints the version of the signer used
Version,
}

pub async fn command_handler<P: AsRef<Path>>(cmd: Command, config: Config<P>) -> Result<()> {
match cmd {
Command::Run => run_handler(config).await,
Command::Run {
state_signature_mode,
state_override,
state_override_note,
} => {
run_handler(
config,
state_signature_mode,
state_override,
state_override_note,
)
.await
}
Command::Version => version(config).await,
}
}

async fn run_handler<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
async fn run_handler<P: AsRef<Path>>(
config: Config<P>,
state_signature_mode: StateSignatureModeArg,
state_override: Option<String>,
state_override_note: Option<String>,
) -> Result<()> {
// Check if we can find a seed file, if we can not find one, we need to register first.
let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME);
let seed = util::read_seed(&seed_path);
Expand All @@ -53,8 +102,30 @@ async fn run_handler<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
)))
}
};
let signer = Signer::new(seed, config.network, creds.clone())
.map_err(|e| Error::custom(format!("Failed to create signer: {}", e)))?;

if state_override.is_none() && state_override_note.is_some() {
return Err(Error::custom(
"--state-override-note requires --state-override",
));
}

let state_signature_override = state_override.map(|ack| {
StateSignatureOverrideConfig {
ack,
note: state_override_note,
}
});

let signer = Signer::new_with_config(
seed,
config.network,
creds.clone(),
SignerConfig {
state_signature_mode: state_signature_mode.into(),
state_signature_override,
},
)
.map_err(|e| Error::custom(format!("Failed to create signer: {}", e)))?;

let (tx, rx) = tokio::sync::mpsc::channel(1);
let handle = tokio::spawn(async move {
Expand All @@ -69,6 +140,111 @@ async fn run_handler<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
Ok(())
}

#[cfg(test)]
mod tests {
use super::{Command, StateSignatureModeArg};
use clap::{Parser, Subcommand};

#[derive(Parser, Debug)]
struct TestCli {
#[command(subcommand)]
cmd: Command,
}

#[derive(Subcommand, Debug)]
enum RootCommand {
#[command(subcommand)]
Signer(Command),
}

#[test]
fn parse_run_mode_flag() {
let cli = TestCli::parse_from(["test", "run", "--state-signature-mode", "hard"]);
match cli.cmd {
Command::Run {
state_signature_mode,
state_override,
state_override_note,
} => {
assert_eq!(state_signature_mode, StateSignatureModeArg::Hard);
assert!(state_override.is_none());
assert!(state_override_note.is_none());
}
_ => panic!("expected run command"),
}
}

#[test]
fn run_mode_defaults_to_soft() {
let cli = TestCli::parse_from(["test", "run"]);
match cli.cmd {
Command::Run {
state_signature_mode,
state_override,
state_override_note,
} => {
assert_eq!(state_signature_mode, StateSignatureModeArg::Soft);
assert!(state_override.is_none());
assert!(state_override_note.is_none());
}
_ => panic!("expected run command"),
}
}

#[test]
fn signer_subcommand_parses_mode_flag() {
#[derive(Parser, Debug)]
struct WrapperCli {
#[command(subcommand)]
cmd: RootCommand,
}

let cli =
WrapperCli::parse_from(["test", "signer", "run", "--state-signature-mode", "off"]);
match cli.cmd {
RootCommand::Signer(Command::Run {
state_signature_mode,
state_override,
state_override_note,
}) => {
assert_eq!(state_signature_mode, StateSignatureModeArg::Off);
assert!(state_override.is_none());
assert!(state_override_note.is_none());
}
_ => panic!("expected signer run"),
}
}

#[test]
fn parse_override_flags() {
let cli = TestCli::parse_from([
"test",
"run",
"--state-signature-mode",
"hard",
"--state-override",
"I_ACCEPT_OPERATOR_ASSISTED_STATE_OVERRIDE",
"--state-override-note",
"debug session",
]);
match cli.cmd {
Command::Run {
state_signature_mode,
state_override,
state_override_note,
} => {
assert_eq!(state_signature_mode, StateSignatureModeArg::Hard);
assert_eq!(
state_override.as_deref(),
Some("I_ACCEPT_OPERATOR_ASSISTED_STATE_OVERRIDE")
);
assert_eq!(state_override_note.as_deref(), Some("debug session"));
}
_ => panic!("expected run command"),
}
}
}

async fn version<P: AsRef<Path>>(config: Config<P>) -> Result<()> {
// Check if we can find a seed file, if we can not find one, we need to register first.
let seed_path = config.data_dir.as_ref().join(SEED_FILE_NAME);
Expand Down
1 change: 1 addition & 0 deletions libs/gl-client-py/glclient/greenlight.proto
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ message SignerStateEntry {
uint64 version = 1;
string key = 2;
bytes value = 3;
bytes signature = 4;
}

// This represents a grpc request that is currently pending, along
Expand Down
Loading
Loading