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

Split Modules from main function #17

Merged
merged 4 commits into from
Oct 10, 2022
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
4 changes: 2 additions & 2 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::ffi::OsString;
use std::fmt;
use std::num::ParseIntError;

use crate::{ScheduledChannel, ScheduledPayJoin};
use crate::scheduler::{ScheduledChannel, ScheduledPayJoin};

/// CLI argument errors.
#[derive(Debug)]
Expand Down Expand Up @@ -70,5 +70,5 @@ where
}
};

Ok(Some(ScheduledPayJoin { wallet_amount, channels, fee_rate }))
Ok(Some(ScheduledPayJoin::new(wallet_amount, channels, fee_rate)))
}
108 changes: 108 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use std::net::SocketAddr;

use bip78::receiver::*;
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, Server, StatusCode};

use crate::scheduler::{ScheduledPayJoin, Scheduler};

#[cfg(not(feature = "test_paths"))]
const STATIC_DIR: &str = "/usr/share/loin/static";

#[cfg(feature = "test_paths")]
const STATIC_DIR: &str = "static";

/// Serve requests to Schedule and execute PayJoins with given options.
pub async fn serve(sched: Scheduler, bind_addr: SocketAddr) -> Result<(), hyper::Error> {
let new_service = make_service_fn(move |_| {
let sched = sched.clone();
async move {
let handler = move |req| handle_web_req(sched.clone(), req);
Ok::<_, hyper::Error>(service_fn(handler))
}
});

let server = Server::bind(&bind_addr).serve(new_service);
println!("Listening on: http://{}", bind_addr);
server.await
}

async fn handle_web_req(
scheduler: Scheduler,
req: Request<Body>,
) -> Result<Response<Body>, hyper::Error> {
use std::path::Path;

match (req.method(), req.uri().path()) {
(&Method::GET, "/pj") => {
let index =
std::fs::read(Path::new(STATIC_DIR).join("index.html")).expect("can't open index");
Ok(Response::new(Body::from(index)))
}

(&Method::GET, path) if path.starts_with("/pj/static/") => {
let directory_traversal_vulnerable_path = &path[("/pj/static/".len())..];
let file =
std::fs::read(Path::new(STATIC_DIR).join(directory_traversal_vulnerable_path))
.expect("can't open static file");
Ok(Response::new(Body::from(file)))
}

(&Method::POST, "/pj") => {
dbg!(req.uri().query());

let headers = Headers(req.headers().to_owned());
let query = {
let uri = req.uri();
if let Some(query) = uri.query() {
Some(&query.to_owned());
}
None
};
let body = req.into_body();
let bytes = hyper::body::to_bytes(body).await?;
dbg!(&bytes); // this is correct by my accounts
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: dbg! usage doesn't need to be on a separate line.

let reader = &*bytes;
let original_request = UncheckedProposal::from_request(reader, query, headers).unwrap();

let proposal_psbt = scheduler.propose_payjoin(original_request).await.unwrap();

Ok(Response::new(Body::from(proposal_psbt)))
}

(&Method::POST, "/pj/schedule") => {
let bytes = hyper::body::to_bytes(req.into_body()).await?;
let request =
serde_json::from_slice::<ScheduledPayJoin>(&bytes).expect("invalid request");

let address = scheduler.schedule_payjoin(&request).await.unwrap();
let total_amount = request.total_amount();

// TODO: Don't hardcode pj endpoint
// * Optional cli flag or ENV for pj endpoint (in the case of port forwarding), otherwise
// we should determine the bip21 string using `api::ServeOptions`
let uri = format!(
"bitcoin:{}?amount={}&pj=https://localhost:3010/pj",
address,
total_amount.to_string_in(bitcoin::Denomination::Bitcoin)
);
let mut response = Response::new(Body::from(uri));
response
.headers_mut()
.insert(hyper::header::CONTENT_TYPE, "text/plain".parse().unwrap());
Ok(response)
}

// Return the 404 Not Found for other routes.
_ => {
let mut not_found = Response::default();
*not_found.status_mut() = StatusCode::NOT_FOUND;
Ok(not_found)
}
}
}

pub(crate) struct Headers(hyper::HeaderMap);
impl bip78::receiver::Headers for Headers {
fn get_header(&self, key: &str) -> Option<&str> { self.0.get(key)?.to_str().ok() }
}
47 changes: 46 additions & 1 deletion src/lnd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::Address;
use ln_types::P2PAddress;
use tokio::sync::Mutex as AsyncMutex;
use tonic_lnd::rpc::{FundingTransitionMsg, OpenChannelRequest, OpenStatusUpdate};
use tonic_lnd::rpc::funding_transition_msg::Trigger;
use tonic_lnd::rpc::{
FundingPsbtVerify, FundingTransitionMsg, OpenChannelRequest, OpenStatusUpdate,
};

use crate::scheduler::ChannelId;

#[derive(Debug)]
pub enum LndError {
Expand Down Expand Up @@ -62,6 +67,18 @@ impl From<tonic_lnd::ConnectError> for LndError {
pub struct LndClient(Arc<AsyncMutex<tonic_lnd::Client>>);

impl LndClient {
/// New [LndClient] from [Config].
pub async fn from_config(config: &crate::config::Config) -> Result<Self, LndError> {
let raw_client = tonic_lnd::connect(
config.lnd_address.clone(),
&config.lnd_cert_path,
&config.lnd_macaroon_path,
)
.await?;

Self::new(raw_client).await
}

pub async fn new(mut client: tonic_lnd::Client) -> Result<Self, LndError> {
let response = client
.get_info(tonic_lnd::rpc::GetInfoRequest {})
Expand Down Expand Up @@ -161,6 +178,34 @@ impl LndClient {
Ok(None)
}

/// Sends the `FundingPsbtVerify` message to remote lnd nodes to finalize channels of given
/// channel ids.
pub async fn verify_funding<I>(&self, funded_psbt: &[u8], chan_ids: I) -> Result<(), LndError>
where
I: IntoIterator<Item = ChannelId>,
{
let handles = chan_ids
.into_iter()
.map(|chan_id| {
let client = self.clone();
let req = FundingTransitionMsg {
trigger: Some(Trigger::PsbtVerify(FundingPsbtVerify {
pending_chan_id: chan_id.into(),
funded_psbt: funded_psbt.to_vec(),
skip_finalize: true,
})),
};
tokio::spawn(async move { client.funding_state_step(req).await })
})
.collect::<Vec<_>>();

for handle in handles {
handle.await.unwrap()?;
}

Ok(())
}

pub async fn funding_state_step(&self, req: FundingTransitionMsg) -> Result<(), LndError> {
let client = &mut *self.0.lock().await;
client.funding_state_step(req).await?;
Expand Down
Loading