Skip to content

Commit

Permalink
Multistage cargo build for x86 and ARM
Browse files Browse the repository at this point in the history
 - Conditionally `cargo build` for platforms of x86_64 or ARM.
 - In the final Docker stage we copy the built binary to alpine, and run with environment:
 - $LND_HOST, $LND_GRPC_PORT, $TLS_FILE, $MACAROON_FILE"

Don't copy everything! Copy static later

fix onion endpoint to http
  • Loading branch information
nickfarrow committed Oct 28, 2022
1 parent 943cb87 commit 00b766d
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 45 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Test

on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]

env:
CARGO_TERM_COLOR: always

jobs:
build:

runs-on: ubuntu-latest

steps:
- uses: KengoTODA/actions-setup-docker-compose@main
with:
version: '2.10.2'
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose -- --nocapture
63 changes: 63 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Multistage Build for Loin
#
# x86_64-unknown-linux-musl
# aarch64-unknown-linux-musl
#
# Conditionally `cargo build` for platforms of x86_64 or ARM.
# Use musl for static linking, producing a standalone executable with no dependencies.
# In the final Docker stage we copy the built binary to alpine, and run with environment:
# $LND_HOST, $LND_GRPC_PORT, $TLS_FILE, $MACAROON_FILE"

## Initial build Stage
FROM rustlang/rust:nightly AS builder
# Target architecture argument used to change build
ARG TARGETARCH
# Some nicer rust debugging
ENV RUSTFLAGS="-Z macro-backtrace"
ENV RUST_BACKTRACE=1
# Copy the required build files. In this case, these are all the files that
# are used for both architectures.
WORKDIR /usr/src/loin/
COPY Cargo.toml Cargo.lock build.rs config_spec.toml ./
COPY src/ ./src/
# COPY static/ /usr/share/loin/static/

## x86_64
FROM builder AS branch-version-amd64
RUN echo "Preparing to cargo build for x86_64 (${TARGETARCH})"
# Install the required dependencies to build for `musl` static linking
RUN apt-get update && apt-get install -y musl-tools musl-dev
# Add our x86 target to rust, then compile and install
RUN rustup target add x86_64-unknown-linux-musl
RUN cargo install --target x86_64-unknown-linux-musl --path .

# ARM
FROM builder AS branch-version-arm64
RUN echo "Preparing to cargo build for arm (${TARGETARCH})"
# Install the required dependencies to build for `musl` static linking for arm.
RUN apt-get update && apt-get install musl-tools clang llvm -y
# Add our arm target to rust, some build variables, then compile and install
RUN rustup target add aarch64-unknown-linux-musl
ENV CC_aarch64_unknown_linux_musl=clang
ENV AR_aarch64_unknown_linux_musl=llvm-ar
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_RUSTFLAGS="-Clink-self-contained=yes -Clinker=rust-lld"
RUN cargo install --target aarch64-unknown-linux-musl --path .

# We build for either x86_64 or ARM from above options using the docker $TARGETARCH
FROM branch-version-${TARGETARCH} AS chosen_builder
RUN echo "Called build!"

# Run Loin from a final debian container
FROM debian:buster-slim
# COPY --chown=1000:1000 . .
USER 1000

# Copy just the binary from our build stage
COPY --from=chosen_builder /usr/local/cargo/bin/loin /usr/local/bin/loin
COPY run_loin /usr/local/bin/run_loin
COPY static/ /usr/share/loin/static/

# Expose any necessary ports
EXPOSE 4444
# Run
CMD ["run_loin"]
19 changes: 19 additions & 0 deletions run_loin
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/sh

# Old: create loin config (we may want some persistent & modifyable config in the future, will need a volume)
# CONF=loin.conf
# if [ ! -f $CONF ]
# then
# touch $CONF
# echo "bind_port=4444" >> $CONF
# echo "lnd_address=\"https://lightning_lnd_1:$LND_GRPC_PORT\"" >> $CONF
# echo "lnd_cert_path=\"$TLS_FILE\"" >> $CONF
# echo "lnd_macaroon_path=\"$MACAROON_FILE\"" >> $CONF
# echo "endpoint=\"https://$APP_HIDDEN_SERVICE\"" >> $CONF
# fi
# cat $CONF
#loin --conf $CONF

echo "Running loin"
loin --bind-port=4444 --lnd-address=https://lightning_lnd_1:$LND_GRPC_PORT --lnd-cert-path=$TLS_FILE --lnd-macaroon-path=$MACAROON_FILE --endpoint=http://$APP_HIDDEN_SERVICE
echo "STOPPED RUNNING LOIN"
11 changes: 5 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

if let Some(payjoin) = scheduled_pj {
let address = scheduler.schedule_payjoin(&payjoin).await?;
println!("{}", scheduler::format_bip21(
address,
payjoin.total_amount(),
secure_endpoint.clone()
));
println!(
"{}",
scheduler::format_bip21(address, payjoin.total_amount(), secure_endpoint.clone())
);
}

let bind_addr = ([127, 0, 0, 1], config.bind_port).into();
let bind_addr = ([0, 0, 0, 0], config.bind_port).into();
http::serve(scheduler, bind_addr, secure_endpoint).await?;

Ok(())
Expand Down
108 changes: 69 additions & 39 deletions tests/integration.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#[cfg(test)]
mod integration {
use std::thread::sleep;
use std::time::Duration;
use std::{
env,
Expand Down Expand Up @@ -33,43 +34,62 @@ mod integration {
let tmp_path = fixture.tmp_path();

// wait for bitcoind to start and for lnd to be fully initialized with secrets
std::thread::sleep(std::time::Duration::from_secs(10));

// sanity check
let bitcoin_rpc = Client::new(
"http://localhost:43782",
Auth::UserPass("ceiwHEbqWI83".to_string(), "DwubwWsoo3".to_string()),
)
.unwrap();
assert!(&bitcoin_rpc.get_best_block_hash().is_ok());

Command::new("docker")
.arg("cp")
.arg("compose-merchant_lnd-1:/root/.lnd/tls.cert")
.arg(format!("{}/merchant-tls.cert", tmp_path))
.output()
.expect("failed to copy tls.cert");
println!("copied merchant-tls.cert");
let timeout = 6;
let bitcoin_rpc = loop {
if --timeout < 0 {
panic!("can't connect to bitcoin rpc");
}
sleep(Duration::from_secs(1));
if let Ok(btcrpc) = Client::new("http://localhost:43782", Auth::UserPass("ceiwHEbqWI83".to_string(), "DwubwWsoo3".to_string())) {
match btcrpc.get_best_block_hash() {
Ok(_) => break btcrpc,
Err(e) => println!("Attempting to contact btcrpc: {}", e),
}
}

Command::new("docker")
.arg("cp")
.arg("compose-merchant_lnd-1:/data/chain/bitcoin/regtest/admin.macaroon")
.arg(format!("{}/merchant-admin.macaroon", &tmp_path))
.output()
.expect("failed to copy admin.macaroon");
println!("copied merchant-admin.macaroon");
};

// merchant lnd loin configuration
let address_str = "https://localhost:53281";
let cert_file = format!("{}/merchant-tls.cert", &tmp_path).to_string();
let macaroon_file = format!("{}/merchant-admin.macaroon", &tmp_path).to_string();

// Connecting to LND requires only address, cert file, and macaroon file
let mut merchant_client =
tonic_lnd::connect(address_str, &cert_file, &macaroon_file).await.unwrap();
let timeout = 6;
let mut merchant_client = loop {
if --timeout < 0 {
panic!("can't connect to merchant_client");
}
sleep(Duration::from_secs(1));

Command::new("docker")
.arg("cp")
.arg("compose-merchant_lnd-1:/root/.lnd/tls.cert")
.arg(format!("{}/merchant-tls.cert", tmp_path))
.output()
.expect("failed to copy tls.cert");
println!("copied merchant-tls.cert");

// Just test the node rpc
merchant_client.get_info(tonic_lnd::rpc::GetInfoRequest {}).await.unwrap();
Command::new("docker")
.arg("cp")
.arg("compose-merchant_lnd-1:/data/chain/bitcoin/regtest/admin.macaroon")
.arg(format!("{}/merchant-admin.macaroon", &tmp_path))
.output()
.expect("failed to copy admin.macaroon");
println!("copied merchant-admin.macaroon");

// Connecting to LND requires only address, cert file, and macaroon file
let client =
tonic_lnd::connect(address_str, &cert_file, &macaroon_file).await;

if let Ok(mut client) = client {
match client.get_info(tonic_lnd::rpc::GetInfoRequest {}).await {
Ok(_) => break client,
Err(e) => println!("Attempting to connect lnd: {}", e),
}
}
};

// conf to merchant
let endpoint: url::Url = "https://localhost:3010".parse().expect("not a valid Url");
Expand Down Expand Up @@ -112,7 +132,7 @@ mod integration {

let source_address = bitcoin_rpc.get_new_address(None, None).unwrap();
bitcoin_rpc.generate_to_address(101, &source_address).unwrap();
std::thread::sleep(std::time::Duration::from_secs(5));
std::thread::sleep(Duration::from_secs(5));
println!("SLEPT");
// connect one to the next
let connected = merchant_client
Expand Down Expand Up @@ -144,11 +164,25 @@ mod integration {
let bip21 = scheduler::format_bip21(address, pj.total_amount(), endpoint.clone());
println!("{}", &bip21);

let loop_til_open_channel = tokio::spawn(async move {
let channel_update = peer_client.subscribe_channel_events(tonic_lnd::rpc::ChannelEventSubscription {});
let mut res = channel_update.await.unwrap().into_inner();
loop {
if let Ok(Some(channel_event)) = res.message().await {
if channel_event.r#type() == tonic_lnd::rpc::channel_event_update::UpdateType::OpenChannel {
break;
}
}
};
});

let bind_addr = ([127, 0, 0, 1], 3000).into();
let loin_server = http::serve(scheduler, bind_addr, endpoint.clone());
// trigger payjoin-client
let payjoin_channel_open = tokio::spawn(async move {
// if we don't wait for loin server to run we'll make requests to a closed port
std::thread::sleep(std::time::Duration::from_secs(2));
std::thread::sleep(Duration::from_secs(2));
// TODO loop on ping 3000 until it the server is live

let link = bip78::Uri::try_from(bip21).expect("bad bip78 uri");

Expand Down Expand Up @@ -216,25 +250,21 @@ mod integration {
let tx = bitcoin_rpc.finalize_psbt(&psbt, Some(true)).unwrap().hex.expect("incomplete psbt");
bitcoin_rpc.send_raw_transaction(&tx).unwrap();

// Open channel on newly created payjoin
// Confirm the newly opene transaction in new blocks
bitcoin_rpc.generate_to_address(8, &source_address).unwrap();
std::thread::sleep(Duration::from_secs(1));
});

let loin_server = http::serve(scheduler, bind_addr, endpoint.clone());

tokio::select! {
_ = payjoin_channel_open => println!("payjoin-client completed first"),
_ = loin_server => println!("loin server stopped first. This shouldn't happen"),
_ = tokio::time::sleep(std::time::Duration::from_secs(20)) => println!("payjoin timed out after 20 seconds"),
_ = tokio::time::sleep(Duration::from_secs(20)) => println!("payjoin timed out after 20 seconds"),
};

let bal_res = peer_client.channel_balance(tonic_lnd::rpc::ChannelBalanceRequest::default()).await?;
println!("{:?}",bal_res);
let merchant_side_channel_balance = bal_res.into_inner().remote_balance.unwrap().sat;
println!("{:?}",merchant_side_channel_balance);
tokio::select! {
_ = loop_til_open_channel => println!("Channel opened!"),
_ = tokio::time::sleep(Duration::from_secs(6)) => println!("Channel open upate listener timed out"),
};

assert!(merchant_side_channel_balance != 0);
Ok(())
}
struct Fixture {
Expand Down

0 comments on commit 00b766d

Please sign in to comment.