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

Potential Offer Handling #85

Closed
wants to merge 3 commits into from
Closed
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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ tonic_lnd = { git = "https://github.com/orbitalturtle/tonic_lnd", branch = "upda
hex = "0.4.3"
configure_me = "0.4.0"
bytes = "1.4.0"
triggered = "0.1.2"

[dev-dependencies]
bitcoincore-rpc = { package="core-rpc", version = "0.17.0" }
Expand Down
256 changes: 166 additions & 90 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,122 +13,198 @@
use bitcoin::secp256k1::PublicKey;
use home::home_dir;
use lightning::ln::peer_handler::IgnoringMessageHandler;
use lightning::onion_message::{DefaultMessageRouter, OnionMessenger};
use lightning::offers::offer::Offer;
use lightning::onion_message::{
DefaultMessageRouter, OffersMessage, OffersMessageHandler, OnionMessenger, PendingOnionMessage,
};
use log::{error, info, LevelFilter};
use log4rs::append::console::ConsoleAppender;
use log4rs::append::file::FileAppender;
use log4rs::config::{Appender, Config as LogConfig, Root};
use log4rs::encode::pattern::PatternEncoder;
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Mutex;
use tonic_lnd::lnrpc::GetInfoRequest;
use triggered::{Listener, Trigger};

pub struct Cfg {
pub lnd: LndCfg,
pub log_dir: Option<String>,
// Use to externally trigger shutdown.
pub shutdown: Trigger,
// Used to listen for the signal to shutdown.
pub listener: Listener,
}

pub async fn run(args: Cfg) -> Result<(), ()> {
let log_dir = args.log_dir.unwrap_or_else(|| {
home_dir()
.unwrap()
.join(".lndk")
.join("lndk.log")
.as_path()
.to_str()
.unwrap()
.to_string()
});

// Log both to stdout and a log file.
let stdout = ConsoleAppender::builder().build();
let lndk_logs = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build(log_dir)
.unwrap();

let config = LogConfig::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("lndk_logs", Box::new(lndk_logs)))
.build(
Root::builder()
.appender("stdout")
.appender("lndk_logs")
.build(LevelFilter::Info),
)
.unwrap();

let _log_handle = log4rs::init_config(config).unwrap();
pub enum OfferError {
OfferAlreadyAdded,
}

let mut client = get_lnd_client(args.lnd).expect("failed to connect");
enum OfferState {
OfferAdded,

Check warning on line 45 in src/lib.rs

View workflow job for this annotation

GitHub Actions / LNDK Rust Build

variants `OfferAdded`, `InvoiceRequestSent`, `InvoiceReceived`, `InvoicePaymentDispatched`, and `InvoicePaid` are never constructed

Check failure on line 45 in src/lib.rs

View workflow job for this annotation

GitHub Actions / LNDK Rust Build

variants `OfferAdded`, `InvoiceRequestSent`, `InvoiceReceived`, `InvoicePaymentDispatched`, and `InvoicePaid` are never constructed
InvoiceRequestSent,
InvoiceReceived,
InvoicePaymentDispatched,
InvoicePaid,
}

let info = client
.lightning()
.get_info(GetInfoRequest {})
.await
.expect("failed to get info")
.into_inner();
pub struct OfferHandler {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think through a name that'll encompass that this struct is used both for forwarding onion messages (which might not be related to offers) and handling offer specific message. What about naming this something like... InternalOnionMessenger or OnionHandler so it doesn't conflict with LDK's OnionMessenger?

No rush to reply to this, I know you're on vacation haha. Just want to keep track of some of the questions I have here in the meantime...

active_offers: Mutex<HashMap<Offer, OfferState>>,

Check warning on line 53 in src/lib.rs

View workflow job for this annotation

GitHub Actions / LNDK Rust Build

field `active_offers` is never read

Check failure on line 53 in src/lib.rs

View workflow job for this annotation

GitHub Actions / LNDK Rust Build

field `active_offers` is never read
pending_messages: Mutex<Vec<PendingOnionMessage<OffersMessage>>>,
}

let mut network_str = None;
for chain in info.chains {
if chain.chain == "bitcoin" {
network_str = Some(chain.network.clone())
impl OfferHandler {
pub fn new() -> Self {
OfferHandler {
active_offers: Mutex::new(HashMap::new()),
pending_messages: Mutex::new(Vec::new()),
}
}
if network_str.is_none() {
error!("lnd node is not connected to bitcoin network as expected");
return Err(());

/// Adds an offer to be paid with the amount specified. May only be called once for a single offer.
pub fn pay_offer(&mut self, _offer: Offer, _amount: u64) -> Result<(), OfferError> {
/*
Check if we've already added offer -> error.
Add offer to state map.
Create invoice request and push to offer queue.
*/
Ok(())
}
let network = string_to_network(&network_str.unwrap());

let pubkey = PublicKey::from_str(&info.identity_pubkey).unwrap();
info!("Starting lndk for node: {pubkey}.");
pub async fn run(&self, args: Cfg) -> Result<(), ()> {
let log_dir = args.log_dir.unwrap_or_else(|| {
home_dir()
.unwrap()
.join(".lndk")
.join("lndk.log")
.as_path()
.to_str()
.unwrap()
.to_string()
});

// Log both to stdout and a log file.
let stdout = ConsoleAppender::builder().build();
let lndk_logs = FileAppender::builder()
.encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
.build(log_dir)
.unwrap();

let config = LogConfig::builder()
.appender(Appender::builder().build("stdout", Box::new(stdout)))
.appender(Appender::builder().build("lndk_logs", Box::new(lndk_logs)))
.build(
Root::builder()
.appender("stdout")
.appender("lndk_logs")
.build(LevelFilter::Info),
)
.unwrap();

let _log_handle = log4rs::init_config(config).unwrap();

let mut client = get_lnd_client(args.lnd).expect("failed to connect");

let info = client
.lightning()
.get_info(GetInfoRequest {})
.await
.expect("failed to get info")
.into_inner();

let mut network_str = None;
for chain in info.chains {
if chain.chain == "bitcoin" {
network_str = Some(chain.network.clone())
}
}
if network_str.is_none() {
error!("lnd node is not connected to bitcoin network as expected");
return Err(());
}
let network = string_to_network(&network_str.unwrap());

let pubkey = PublicKey::from_str(&info.identity_pubkey).unwrap();
info!("Starting lndk for node: {pubkey}.");

if !features_support_onion_messages(&info.features) {
error!("LND must support onion messaging to run LNDK.");
return Err(());
}
if !features_support_onion_messages(&info.features) {
error!("LND must support onion messaging to run LNDK.");
return Err(());
}

// On startup, we want to get a list of our currently online peers to notify the onion messenger that they are
// connected. This sets up our "start state" for the messenger correctly.
let current_peers = client
.lightning()
.list_peers(tonic_lnd::lnrpc::ListPeersRequest {
latest_error: false,
})
.await
.map_err(|e| {
error!("Could not lookup current peers: {e}.");
})?;

let mut peer_support = HashMap::new();
for peer in current_peers.into_inner().peers {
let pubkey = PublicKey::from_str(&peer.pub_key).unwrap();
let onion_support = features_support_onion_messages(&peer.features);
peer_support.insert(pubkey, onion_support);
}

// On startup, we want to get a list of our currently online peers to notify the onion messenger that they are
// connected. This sets up our "start state" for the messenger correctly.
let current_peers = client
.lightning()
.list_peers(tonic_lnd::lnrpc::ListPeersRequest {
latest_error: false,
})
// Create an onion messenger that depends on LND's signer client and consume related events.
let mut node_client = client.signer().clone();
let node_signer = LndNodeSigner::new(pubkey, &mut node_client);
let messenger_utils = MessengerUtilities::new();
let onion_messenger = OnionMessenger::new(
&messenger_utils,
&node_signer,
&messenger_utils,
&DefaultMessageRouter {},
self,
IgnoringMessageHandler {},
);

let mut peers_client = client.lightning().clone();
run_onion_messenger(
peer_support,
&mut peers_client,
onion_messenger,
network.unwrap(),
args.shutdown,
args.listener,
)
.await
.map_err(|e| {
error!("Could not lookup current peers: {e}.");
})?;

let mut peer_support = HashMap::new();
for peer in current_peers.into_inner().peers {
let pubkey = PublicKey::from_str(&peer.pub_key).unwrap();
let onion_support = features_support_onion_messages(&peer.features);
peer_support.insert(pubkey, onion_support);
}
}

impl OffersMessageHandler for OfferHandler {
fn handle_message(&self, message: OffersMessage) -> Option<OffersMessage> {
match message {
OffersMessage::InvoiceRequest(_) => {
log::error!("Invoice request received, payment not yet supported.");
None
}
OffersMessage::Invoice(_invoice) => {
// lookup corresponding invoice request / fail if not known
// Validate invoice for invoice request
// Progress state to invoice received
// Dispatch payment and update state
None
}
OffersMessage::InvoiceError(_error) => None,
}
}

fn release_pending_messages(&self) -> Vec<PendingOnionMessage<OffersMessage>> {
core::mem::take(&mut self.pending_messages.lock().unwrap())
}
}

// Create an onion messenger that depends on LND's signer client and consume related events.
let mut node_client = client.signer().clone();
let node_signer = LndNodeSigner::new(pubkey, &mut node_client);
let messenger_utils = MessengerUtilities::new();
let onion_messenger = OnionMessenger::new(
&messenger_utils,
&node_signer,
&messenger_utils,
&DefaultMessageRouter {},
IgnoringMessageHandler {},
IgnoringMessageHandler {},
);

let mut peers_client = client.lightning().clone();
run_onion_messenger(
peer_support,
&mut peers_client,
onion_messenger,
network.unwrap(),
)
.await
impl Default for OfferHandler {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
Expand Down
6 changes: 5 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@
.0;

let lnd_args = LndCfg::new(config.address, config.cert, config.macaroon);
let (shutdown, listener) = triggered::trigger();

Check warning on line 25 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L25

Added line #L25 was not covered by tests
let args = Cfg {
lnd: lnd_args,
log_dir: config.log_dir,
shutdown,
listener,

Check warning on line 30 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L29-L30

Added lines #L29 - L30 were not covered by tests
};

lndk::run(args).await
let handler = lndk::OfferHandler::new();
handler.run(args).await

Check warning on line 34 in src/main.rs

View check run for this annotation

Codecov / codecov/patch

src/main.rs#L33-L34

Added lines #L33 - L34 were not covered by tests
}
Loading
Loading