From 47485623e4864607a3a99a3bd4ddacd3d45bd645 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Mon, 14 Oct 2024 21:27:27 +0000 Subject: [PATCH 01/13] Adding the blast_ldk model project --- blast_models/blast_ldk/blast_ldk/.gitignore | 2 ++ blast_models/blast_ldk/blast_ldk/Cargo.toml | 7 +++++++ blast_models/blast_ldk/blast_ldk/src/main.rs | 18 ++++++++++++++++++ blast_models/blast_ldk/build.sh | 3 ++- blast_models/blast_ldk/clean.sh | 4 +++- blast_models/blast_ldk/model.json | 4 +++- 6 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 blast_models/blast_ldk/blast_ldk/.gitignore create mode 100644 blast_models/blast_ldk/blast_ldk/Cargo.toml create mode 100644 blast_models/blast_ldk/blast_ldk/src/main.rs diff --git a/blast_models/blast_ldk/blast_ldk/.gitignore b/blast_models/blast_ldk/blast_ldk/.gitignore new file mode 100644 index 0000000..f2f9e58 --- /dev/null +++ b/blast_models/blast_ldk/blast_ldk/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/Cargo.toml b/blast_models/blast_ldk/blast_ldk/Cargo.toml new file mode 100644 index 0000000..5aeda16 --- /dev/null +++ b/blast_models/blast_ldk/blast_ldk/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "blast_ldk" +version = "0.1.0" +edition = "2021" + +[dependencies] +ldk-node = "0.3.0" diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs new file mode 100644 index 0000000..77dc6e4 --- /dev/null +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -0,0 +1,18 @@ +use ldk_node::Builder; +use ldk_node::bitcoin::Network; +use std::time::Duration; +use std::thread; + +fn main() { + let mut builder = Builder::new(); + builder.set_network(Network::Regtest); + builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); + //builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); + builder.set_gossip_source_p2p(); + + let node = builder.build().unwrap(); + + node.start().unwrap(); + thread::sleep(Duration::from_secs(10)); + node.stop().unwrap(); +} diff --git a/blast_models/blast_ldk/build.sh b/blast_models/blast_ldk/build.sh index 6957547..a2f8dff 100755 --- a/blast_models/blast_ldk/build.sh +++ b/blast_models/blast_ldk/build.sh @@ -1,4 +1,5 @@ #!/bin/bash set -e -echo "blast_ldk not implemented" \ No newline at end of file +cd blast_ldk +cargo build diff --git a/blast_models/blast_ldk/clean.sh b/blast_models/blast_ldk/clean.sh index 0974275..774918d 100755 --- a/blast_models/blast_ldk/clean.sh +++ b/blast_models/blast_ldk/clean.sh @@ -1,3 +1,5 @@ #!/bin/bash -echo "blast_ldk not implemented" \ No newline at end of file +cd blast_ldk +cargo clean +rm Cargo.lock diff --git a/blast_models/blast_ldk/model.json b/blast_models/blast_ldk/model.json index 77f67bd..cd454d7 100644 --- a/blast_models/blast_ldk/model.json +++ b/blast_models/blast_ldk/model.json @@ -1,3 +1,5 @@ { - "name": "blast_ldk" + "name": "blast_ldk", + "rpc": "localhost:5051", + "start": "blast_ldk/target/debug/blast_ldk" } \ No newline at end of file From 6c2479cde652cc367226cf8a8883bad0984891ea Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 15 Oct 2024 21:55:01 +0000 Subject: [PATCH 02/13] Adding some esplora specific instructions and script updates --- README.md | 13 ++++++++ blast_core/.gitignore | 1 + blast_core/start_bitcoind.sh | 33 ++++++++++++++++++++ blast_models/blast_ldk/blast_ldk/.gitignore | 1 + blast_models/blast_ldk/blast_ldk/src/main.rs | 26 ++++++++++++--- blast_models/blast_ldk/clean.sh | 1 + run_cli.sh | 1 + run_example.sh | 1 + run_web.sh | 1 + 9 files changed, 74 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8b24387..b902745 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,12 @@ tar xzf bitcoin-25.0-x86_64-linux-gnu.tar.gz sudo install -m 0755 -o root -g root -t /usr/local/bin bitcoin-25.0/bin/* ``` +### Install utils +```bash +sudo apt install bc +sudo apt install jq +``` + ### Install Rust ```bash curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh @@ -73,6 +79,13 @@ cd blast ./build.sh ``` +### Install electrs +```bash +git clone https://github.com/blockstream/electrs && cd electrs +git checkout new-index +cargo install --path . --root /blast_core +``` + # Run ### Run CLI ```bash diff --git a/blast_core/.gitignore b/blast_core/.gitignore index f2f9e58..c5ad121 100644 --- a/blast_core/.gitignore +++ b/blast_core/.gitignore @@ -1,2 +1,3 @@ target +bin Cargo.lock \ No newline at end of file diff --git a/blast_core/start_bitcoind.sh b/blast_core/start_bitcoind.sh index 8f8fe16..65f6513 100755 --- a/blast_core/start_bitcoind.sh +++ b/blast_core/start_bitcoind.sh @@ -1,4 +1,5 @@ #!/bin/bash + echo "server=1" > ~/.bitcoin/bitcoin.conf echo "regtest=1" >> ~/.bitcoin/bitcoin.conf echo "rpcuser=user" >> ~/.bitcoin/bitcoin.conf @@ -15,3 +16,35 @@ bitcoind -daemon sleep 5 bitcoin-cli createwallet test bitcoin-cli -generate 101 + +# https://bitcoin.stackexchange.com/questions/101153/setting-the-fee-rate-on-regtest-or-quickly-generating-full-blocks +cont=true +smartfee=$(bitcoin-cli estimatesmartfee 6) +if [[ "$smartfee" == *"\"feerate\":"* ]]; then + cont=false +fi +while $cont +do + counterb=0 + range=$(( $RANDOM % 11 + 20 )) + while [ $counterb -lt $range ] + do + power=$(( $RANDOM % 29 )) + randfee=`echo "scale=8; 0.00001 * (1.1892 ^ $power)" | bc` + newaddress=$(bitcoin-cli getnewaddress) + rawtx=$(bitcoin-cli createrawtransaction "[]" "[{\"$newaddress\":0.005}]") + fundedtx=$(bitcoin-cli fundrawtransaction "$rawtx" "{\"feeRate\": \"0$randfee\"}" | jq -r ".hex") + signedtx=$(bitcoin-cli signrawtransactionwithwallet "$fundedtx" | jq -r ".hex") + senttx=$(bitcoin-cli sendrawtransaction "$signedtx") + ((++counterb)) + echo "Created $counterb transactions this block" + done + bitcoin-cli generatetoaddress 1 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" + smartfee=$(bitcoin-cli estimatesmartfee 6) + if [[ "$smartfee" == *"\"feerate\":"* ]]; then + cont=false + fi +done +bitcoin-cli generatetoaddress 6 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" + +electrs -vvvv --daemon-dir ~/.bitcoin --network=regtest --cookie user:pass diff --git a/blast_models/blast_ldk/blast_ldk/.gitignore b/blast_models/blast_ldk/blast_ldk/.gitignore index f2f9e58..5f53793 100644 --- a/blast_models/blast_ldk/blast_ldk/.gitignore +++ b/blast_models/blast_ldk/blast_ldk/.gitignore @@ -1,2 +1,3 @@ target +blast_data Cargo.lock \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 77dc6e4..a32e4c1 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -1,18 +1,36 @@ -use ldk_node::Builder; +use ldk_node::{Builder, LogLevel}; use ldk_node::bitcoin::Network; +use ldk_node::Config; + use std::time::Duration; use std::thread; fn main() { - let mut builder = Builder::new(); - builder.set_network(Network::Regtest); - builder.set_esplora_server("https://blockstream.info/testnet/api".to_string()); + let config = Config { + storage_dir_path: String::from("./blast_data"), + log_dir_path: None, + network: Network::Regtest, + listening_addresses: None, + default_cltv_expiry_delta: 0, + onchain_wallet_sync_interval_secs: 2, + wallet_sync_interval_secs: 2, + fee_rate_cache_update_interval_secs: 2, + trusted_peers_0conf: Vec::new(), + probing_liquidity_limit_multiplier: 0, + log_level: LogLevel::Debug, + anchor_channels_config: None + }; + + let mut builder = Builder::from_config(config); + builder.set_esplora_server("http://localhost:3002".to_string()); //builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); builder.set_gossip_source_p2p(); let node = builder.build().unwrap(); node.start().unwrap(); + println!("Node Status: {:?}", node.status()); thread::sleep(Duration::from_secs(10)); node.stop().unwrap(); + println!("Node Status: {:?}", node.status()); } diff --git a/blast_models/blast_ldk/clean.sh b/blast_models/blast_ldk/clean.sh index 774918d..accc1c2 100755 --- a/blast_models/blast_ldk/clean.sh +++ b/blast_models/blast_ldk/clean.sh @@ -1,5 +1,6 @@ #!/bin/bash cd blast_ldk +rm -rf blast_data cargo clean rm Cargo.lock diff --git a/run_cli.sh b/run_cli.sh index 93d85b0..5d2b8c4 100755 --- a/run_cli.sh +++ b/run_cli.sh @@ -1,5 +1,6 @@ #!/bin/bash +export PATH="blast_core/bin:$PATH" cd blast_cli cargo run cd - \ No newline at end of file diff --git a/run_example.sh b/run_example.sh index 9d0642c..7df8fc7 100755 --- a/run_example.sh +++ b/run_example.sh @@ -1,5 +1,6 @@ #!/bin/bash +export PATH="blast_core/bin:$PATH" cd blast_example cargo run -- $1 cd - \ No newline at end of file diff --git a/run_web.sh b/run_web.sh index 547aee5..52f42d1 100755 --- a/run_web.sh +++ b/run_web.sh @@ -1,5 +1,6 @@ #!/bin/bash +export PATH="blast_core/bin:$PATH" cd blast_web cargo run cd - \ No newline at end of file From bd8768b64e5171a4cd88898f364dd35e0a5855a3 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Wed, 16 Oct 2024 19:29:49 +0000 Subject: [PATCH 03/13] Fixing dependency issue, auto-starting an electrs server for ldk-node to connect too --- README.md | 4 +++- blast_core/.gitignore | 2 +- blast_core/start_bitcoind.sh | 4 +++- blast_core/stop_bitcoind.sh | 7 +++++++ blast_models/blast_ldk/blast_ldk/Cargo.toml | 2 +- run_cli.sh | 1 - run_example.sh | 1 - run_web.sh | 1 - 8 files changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b902745..6d6c03e 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,9 @@ cd blast ```bash git clone https://github.com/blockstream/electrs && cd electrs git checkout new-index -cargo install --path . --root /blast_core +cargo install --path . --root /usr/local/bin +rm /usr/local/bin/.crates.toml +rm /usr/local/bin/crates2.json ``` # Run diff --git a/blast_core/.gitignore b/blast_core/.gitignore index c5ad121..a6622d1 100644 --- a/blast_core/.gitignore +++ b/blast_core/.gitignore @@ -1,3 +1,3 @@ target -bin +electrs_id.txt Cargo.lock \ No newline at end of file diff --git a/blast_core/start_bitcoind.sh b/blast_core/start_bitcoind.sh index 65f6513..7db8bdb 100755 --- a/blast_core/start_bitcoind.sh +++ b/blast_core/start_bitcoind.sh @@ -47,4 +47,6 @@ do done bitcoin-cli generatetoaddress 6 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" -electrs -vvvv --daemon-dir ~/.bitcoin --network=regtest --cookie user:pass +electrs -vvvv --daemon-dir ~/.bitcoin --network=regtest --cookie user:pass --db-dir ~/.electrs/db > /dev/null 2>&1 & +echo $! > electrs_id.txt +echo "Started electrs with PID: $(cat electrs_id.txt)" diff --git a/blast_core/stop_bitcoind.sh b/blast_core/stop_bitcoind.sh index 81b403e..5a48121 100755 --- a/blast_core/stop_bitcoind.sh +++ b/blast_core/stop_bitcoind.sh @@ -3,3 +3,10 @@ bitcoin-cli stop rm -rf ~/.bitcoin/regtest rm ~/.bitcoin/bitcoin.conf + +if [ -f electrs_id.txt ]; then + PID=$(cat electrs_id.txt) + kill -9 $PID + rm electrs_id.txt +fi +rm -rf ~/.electrs diff --git a/blast_models/blast_ldk/blast_ldk/Cargo.toml b/blast_models/blast_ldk/blast_ldk/Cargo.toml index 5aeda16..1969a3a 100644 --- a/blast_models/blast_ldk/blast_ldk/Cargo.toml +++ b/blast_models/blast_ldk/blast_ldk/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -ldk-node = "0.3.0" +ldk-node = { git = "https://github.com/bjohnson5/ldk-node", branch = "sim-ldk-node-v0.3.0" } diff --git a/run_cli.sh b/run_cli.sh index 5d2b8c4..93d85b0 100755 --- a/run_cli.sh +++ b/run_cli.sh @@ -1,6 +1,5 @@ #!/bin/bash -export PATH="blast_core/bin:$PATH" cd blast_cli cargo run cd - \ No newline at end of file diff --git a/run_example.sh b/run_example.sh index 7df8fc7..9d0642c 100755 --- a/run_example.sh +++ b/run_example.sh @@ -1,6 +1,5 @@ #!/bin/bash -export PATH="blast_core/bin:$PATH" cd blast_example cargo run -- $1 cd - \ No newline at end of file diff --git a/run_web.sh b/run_web.sh index 52f42d1..547aee5 100755 --- a/run_web.sh +++ b/run_web.sh @@ -1,6 +1,5 @@ #!/bin/bash -export PATH="blast_core/bin:$PATH" cd blast_web cargo run cd - \ No newline at end of file From ab51e43a2b7e150171106482d12a6d7d0e9132e8 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 22 Oct 2024 15:14:44 +0000 Subject: [PATCH 04/13] Adding the blast_ldk rpc server --- blast_cli/src/main.rs | 4 +- blast_core/src/blast_model_manager.rs | 10 +- blast_models/blast_ldk/blast_ldk/Cargo.toml | 10 +- blast_models/blast_ldk/blast_ldk/build.rs | 8 + blast_models/blast_ldk/blast_ldk/src/main.rs | 268 ++++++++++++++++--- 5 files changed, 267 insertions(+), 33 deletions(-) create mode 100644 blast_models/blast_ldk/blast_ldk/build.rs diff --git a/blast_cli/src/main.rs b/blast_cli/src/main.rs index 232817c..4376c34 100644 --- a/blast_cli/src/main.rs +++ b/blast_cli/src/main.rs @@ -170,7 +170,9 @@ async fn run(terminal: &mut Terminal, mut blast_cli: BlastCli) -> running.store(true, Ordering::SeqCst); let mut m = HashMap::new(); for model in models { - m.insert(model.name.clone(), model.num_nodes); + if model.num_nodes > 0 { + m.insert(model.name.clone(), model.num_nodes); + } } match blast_cli.blast.create_network("test", m, running.clone()).await { Ok(mut m) => { diff --git a/blast_core/src/blast_model_manager.rs b/blast_core/src/blast_model_manager.rs index 92da4ca..090df19 100644 --- a/blast_core/src/blast_model_manager.rs +++ b/blast_core/src/blast_model_manager.rs @@ -427,7 +427,15 @@ impl BlastModelManager { for (_, client) in &mut self.models { let request = tonic::Request::new(BlastGetModelChannelsRequest {}); - let response = match client.rpc_connection.as_mut().unwrap().get_model_channels(request).await { + let connection = match client.rpc_connection.as_mut() { + Some(c) => { + c + }, + None => { + continue; + } + }; + let response = match connection.get_model_channels(request).await { Ok(r) => r, Err(_) => { continue; diff --git a/blast_models/blast_ldk/blast_ldk/Cargo.toml b/blast_models/blast_ldk/blast_ldk/Cargo.toml index 1969a3a..35d5747 100644 --- a/blast_models/blast_ldk/blast_ldk/Cargo.toml +++ b/blast_models/blast_ldk/blast_ldk/Cargo.toml @@ -4,4 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -ldk-node = { git = "https://github.com/bjohnson5/ldk-node", branch = "sim-ldk-node-v0.3.0" } +ldk-node = "0.4.0" +tonic = "0.11.0" +tokio = { version = "1.37.0", features = ["full"] } +prost = "0.12" +serde = { version = "1.0.104", features = ["derive"] } +serde_json = "1.0.104" + +[build-dependencies] +tonic-build = "0.11.0" \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/build.rs b/blast_models/blast_ldk/blast_ldk/build.rs new file mode 100644 index 0000000..bc92378 --- /dev/null +++ b/blast_models/blast_ldk/blast_ldk/build.rs @@ -0,0 +1,8 @@ +use std::env; +fn main() -> Result<(), Box> { + let mut current_dir = env::current_dir()?; + current_dir.push("../../../blast_proto/blast_proto.proto"); + let current_dir_string = current_dir.to_string_lossy().into_owned(); + tonic_build::compile_protos(current_dir_string)?; + Ok(()) +} \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index a32e4c1..0d6cc4c 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -1,36 +1,244 @@ +use std::time::Duration; +use std::thread; +use std::sync::Arc; +use std::collections::HashMap; +use std::fs; + +use ldk_node::bip39::serde::{Deserialize, Serialize}; use ldk_node::{Builder, LogLevel}; use ldk_node::bitcoin::Network; -use ldk_node::Config; +use ldk_node::config::Config; -use std::time::Duration; -use std::thread; +use tonic::{transport::Server, Request, Response, Status}; +use tonic::Code; +use tokio::sync::Mutex; +use tokio::sync::oneshot; +use tokio::runtime::Runtime; + +use blast_rpc_server::BlastRpcServer; +use blast_rpc_server::BlastRpc; +use blast_proto::*; +pub mod blast_proto { + tonic::include_proto!("blast_proto"); +} + +// The data that is stored in the sim-ln sim.json file +#[derive(Serialize, Deserialize, Debug)] +struct SimLnNode { + id: String, + address: String, + macaroon: String, + cert: String +} + +// The sim.json file for a sim-ln simulation +#[derive(Serialize, Deserialize, Debug)] +struct SimJsonFile { + nodes: Vec +} + +struct BlastLdk { + nodes: HashMap>, + simln_data: String, + shutdown_sender: Option> +} -fn main() { - let config = Config { - storage_dir_path: String::from("./blast_data"), - log_dir_path: None, - network: Network::Regtest, - listening_addresses: None, - default_cltv_expiry_delta: 0, - onchain_wallet_sync_interval_secs: 2, - wallet_sync_interval_secs: 2, - fee_rate_cache_update_interval_secs: 2, - trusted_peers_0conf: Vec::new(), - probing_liquidity_limit_multiplier: 0, - log_level: LogLevel::Debug, - anchor_channels_config: None - }; - - let mut builder = Builder::from_config(config); - builder.set_esplora_server("http://localhost:3002".to_string()); - //builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); - builder.set_gossip_source_p2p(); - - let node = builder.build().unwrap(); +impl BlastLdk { + fn new() -> Self { + Self { + nodes: HashMap::new(), + simln_data: String::from(""), + shutdown_sender: None + } + } +} + +struct BlastLdkServer { + blast_ldk: Arc>, + runtime: Arc +} + +#[tonic::async_trait] +impl BlastRpc for BlastLdkServer { + async fn start_nodes(&self, request: Request) -> Result,Status> { + let num_nodes = request.get_ref().num_nodes; + let mut node_list = SimJsonFile{nodes: Vec::new()}; + let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); + data_dir.push_str("/blast_data/"); + for i in 0..num_nodes { + let node_id = prepend_and_pad("blast_ldk-", i); + let config = Config { + storage_dir_path: format!("{}{}", data_dir, node_id), + log_dir_path: None, + network: Network::Regtest, + listening_addresses: None, + node_alias: None, + sending_parameters: None, + trusted_peers_0conf: Vec::new(), + probing_liquidity_limit_multiplier: 0, + log_level: LogLevel::Debug, + anchor_channels_config: None + }; + + let mut builder = Builder::from_config(config); + builder.set_chain_source_esplora("http://localhost:3002".to_string(), None); + //builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); + builder.set_gossip_source_p2p(); + + let node = Arc::new(builder.build().unwrap()); + + node.start_with_runtime(Arc::clone(&self.runtime)).unwrap(); + println!("Node ({:?}) Status: {:?}", node_id, node.status()); + thread::sleep(Duration::from_secs(10)); + + let mut bldk = self.blast_ldk.lock().await; + bldk.nodes.insert(node_id.clone(), node.clone()); + + let n = SimLnNode{id: node_id.clone(), address: String::from(""), macaroon: String::from(""), cert: String::from("")}; + node_list.nodes.push(n); + } + + let mut bldk = self.blast_ldk.lock().await; + bldk.simln_data = match serde_json::to_string(&node_list) { + Ok(s) => s, + Err(_) => { + let start_response = BlastStartResponse { success: false }; + let response = Response::new(start_response); + return Ok(response); + } + }; + + let start_response = BlastStartResponse { success: true }; + let response = Response::new(start_response); + Ok(response) + } + + async fn get_sim_ln(&self, _request: Request) -> Result, Status> { + let bldk = self.blast_ldk.lock().await; + let simln_response = BlastSimlnResponse { simln_data: bldk.simln_data.clone().into() }; + let response = Response::new(simln_response); + Ok(response) + } - node.start().unwrap(); - println!("Node Status: {:?}", node.status()); - thread::sleep(Duration::from_secs(10)); - node.stop().unwrap(); - println!("Node Status: {:?}", node.status()); + async fn get_pub_key(&self, request: Request,) -> Result, Status> { + let node_id = &request.get_ref().node; + let bldk = self.blast_ldk.lock().await; + let node = bldk.nodes.get(node_id).unwrap(); + let pub_key = node.node_id().to_string(); + + let key_response = BlastPubKeyResponse { pub_key: pub_key }; + let response = Response::new(key_response); + Ok(response) + } + + async fn list_peers(&self, _request: Request,) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn wallet_balance(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn channel_balance(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn list_channels(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn open_channel(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn close_channel(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn get_model_channels(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn connect_peer(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn disconnect_peer(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn get_btc_address(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn get_listen_address(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn stop_model(&self, _request: Request) -> Result, Status> { + let mut bldk = self.blast_ldk.lock().await; + for (_, node) in &bldk.nodes { + node.stop().unwrap(); + } + let _ = bldk.shutdown_sender.take().unwrap().send(()); + + let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); + data_dir.push_str("/blast_data/"); + let _ = fs::remove_dir_all(data_dir); + + let stop_response = BlastStopModelResponse { success: true }; + let response = Response::new(stop_response); + Ok(response) + } + + async fn load(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } + + async fn save(&self, _request: Request) -> Result, Status> { + Err(Status::new(Code::InvalidArgument, "name is invalid")) + } +} + +fn main() -> Result<(), Box> { + let rt = Arc::new(tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap()); + + let addr = "127.0.0.1:5051".parse()?; + let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); + let mut bldk = BlastLdk::new(); + bldk.shutdown_sender = Some(shutdown_sender); + let blast_ldk = Arc::new(Mutex::new(bldk)); + let server = BlastLdkServer { + blast_ldk: Arc::clone(&blast_ldk), + runtime: Arc::clone(&rt) + }; + + println!("Starting gRPC server at {}", addr); + + let server = rt.spawn(async move { + Server::builder() + .add_service(BlastRpcServer::new(server)) + .serve_with_shutdown(addr, async { + // Wait for the shutdown signal + shutdown_receiver.await.ok(); + }) + .await + .unwrap(); + }); + + // Wait for the server task to finish + rt.block_on(async { + let _ = server.await; + }); + + println!("Shutting down gRPC server at {}", addr); + + Ok(()) } + +fn prepend_and_pad(input: &str, num: i32) -> String { + format!("{}{:04}", input, num) +} \ No newline at end of file From e0ed13e38ee74f7fd24981afcc66e5d9043b56b2 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Tue, 22 Oct 2024 15:23:14 +0000 Subject: [PATCH 05/13] Removing the electrs dependency after upgrading to ldk-node 0.4.0 --- README.md | 9 --------- blast_core/.gitignore | 1 - blast_core/start_bitcoind.sh | 4 ---- blast_core/stop_bitcoind.sh | 7 ------- blast_models/blast_ldk/blast_ldk/src/main.rs | 3 +-- 5 files changed, 1 insertion(+), 23 deletions(-) diff --git a/README.md b/README.md index 6d6c03e..97cad3c 100644 --- a/README.md +++ b/README.md @@ -79,15 +79,6 @@ cd blast ./build.sh ``` -### Install electrs -```bash -git clone https://github.com/blockstream/electrs && cd electrs -git checkout new-index -cargo install --path . --root /usr/local/bin -rm /usr/local/bin/.crates.toml -rm /usr/local/bin/crates2.json -``` - # Run ### Run CLI ```bash diff --git a/blast_core/.gitignore b/blast_core/.gitignore index a6622d1..f2f9e58 100644 --- a/blast_core/.gitignore +++ b/blast_core/.gitignore @@ -1,3 +1,2 @@ target -electrs_id.txt Cargo.lock \ No newline at end of file diff --git a/blast_core/start_bitcoind.sh b/blast_core/start_bitcoind.sh index 7db8bdb..af9d60f 100755 --- a/blast_core/start_bitcoind.sh +++ b/blast_core/start_bitcoind.sh @@ -46,7 +46,3 @@ do fi done bitcoin-cli generatetoaddress 6 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" - -electrs -vvvv --daemon-dir ~/.bitcoin --network=regtest --cookie user:pass --db-dir ~/.electrs/db > /dev/null 2>&1 & -echo $! > electrs_id.txt -echo "Started electrs with PID: $(cat electrs_id.txt)" diff --git a/blast_core/stop_bitcoind.sh b/blast_core/stop_bitcoind.sh index 5a48121..81b403e 100755 --- a/blast_core/stop_bitcoind.sh +++ b/blast_core/stop_bitcoind.sh @@ -3,10 +3,3 @@ bitcoin-cli stop rm -rf ~/.bitcoin/regtest rm ~/.bitcoin/bitcoin.conf - -if [ -f electrs_id.txt ]; then - PID=$(cat electrs_id.txt) - kill -9 $PID - rm electrs_id.txt -fi -rm -rf ~/.electrs diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 0d6cc4c..254ca76 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -81,8 +81,7 @@ impl BlastRpc for BlastLdkServer { }; let mut builder = Builder::from_config(config); - builder.set_chain_source_esplora("http://localhost:3002".to_string(), None); - //builder.set_gossip_source_rgs("https://rapidsync.lightningdevkit.org/testnet/snapshot".to_string()); + builder.set_chain_source_bitcoind_rpc(String::from("127.0.0.1"), 18443, String::from("user"), String::from("pass")); builder.set_gossip_source_p2p(); let node = Arc::new(builder.build().unwrap()); From 5d9aed212ebe1037911f0e98400ce02aea060b9d Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Wed, 23 Oct 2024 21:11:36 +0000 Subject: [PATCH 06/13] Implementing more of the LDK model rpc calls --- blast_cli/src/main.rs | 15 +- blast_models/blast_ldk/blast_ldk/Cargo.toml | 4 + blast_models/blast_ldk/blast_ldk/src/main.rs | 200 ++++++++++++++++--- 3 files changed, 188 insertions(+), 31 deletions(-) diff --git a/blast_cli/src/main.rs b/blast_cli/src/main.rs index 4376c34..4c870e6 100644 --- a/blast_cli/src/main.rs +++ b/blast_cli/src/main.rs @@ -551,7 +551,8 @@ async fn run_command(blast: &mut blast_core::Blast, cmd: String) -> Vec match blast.open_channel(source, dest, amount, push, chan_id, true).await { Ok(_) => {}, Err(e) => { - println!("{}", format!("Unable to open channel: {}", e)); + let msg = format!("Unable to open channel: {}", e); + output.push(msg); } } }, @@ -565,7 +566,8 @@ async fn run_command(blast: &mut blast_core::Blast, cmd: String) -> Vec match blast.close_channel(source, chan_id).await { Ok(_) => {}, Err(e) => { - println!("{}", format!("Unable to open channel: {}", e)); + let msg = format!("Unable to open channel: {}", e); + output.push(msg); } } }, @@ -575,7 +577,8 @@ async fn run_command(blast: &mut blast_core::Blast, cmd: String) -> Vec match blast.connect_peer(source, dest).await { Ok(_) => {}, Err(e) => { - println!("{}", format!("Unable to connect peers: {}", e)); + let msg = format!("Unable to connect peers: {}", e); + output.push(msg); } } }, @@ -585,7 +588,8 @@ async fn run_command(blast: &mut blast_core::Blast, cmd: String) -> Vec match blast.disconnect_peer(source, dest).await { Ok(_) => {}, Err(e) => { - println!("{}", format!("Unable to disconnect peers: {}", e)); + let msg = format!("Unable to disconnect peers: {}", e); + output.push(msg); } } }, @@ -594,7 +598,8 @@ async fn run_command(blast: &mut blast_core::Blast, cmd: String) -> Vec match blast.fund_node(source, true).await { Ok(_) => {}, Err(e) => { - println!("{}", format!("Unable to fund node: {}", e)); + let msg = format!("Unable to fund node: {}", e); + output.push(msg); } } }, diff --git a/blast_models/blast_ldk/blast_ldk/Cargo.toml b/blast_models/blast_ldk/blast_ldk/Cargo.toml index 35d5747..e94a919 100644 --- a/blast_models/blast_ldk/blast_ldk/Cargo.toml +++ b/blast_models/blast_ldk/blast_ldk/Cargo.toml @@ -10,6 +10,10 @@ tokio = { version = "1.37.0", features = ["full"] } prost = "0.12" serde = { version = "1.0.104", features = ["derive"] } serde_json = "1.0.104" +secp256k1 = "0.29.1" +hex = "0.4.3" +simplelog = "0.12.2" +log = "0.4.20" [build-dependencies] tonic-build = "0.11.0" \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 254ca76..aeee4e2 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -1,19 +1,29 @@ +use std::str::FromStr; use std::time::Duration; use std::thread; use std::sync::Arc; use std::collections::HashMap; use std::fs; +use std::fs::File; use ldk_node::bip39::serde::{Deserialize, Serialize}; use ldk_node::{Builder, LogLevel}; use ldk_node::bitcoin::Network; use ldk_node::config::Config; +use ldk_node::lightning::ln::msgs::SocketAddress; +use secp256k1::PublicKey; use tonic::{transport::Server, Request, Response, Status}; use tonic::Code; use tokio::sync::Mutex; use tokio::sync::oneshot; use tokio::runtime::Runtime; +use simplelog::WriteLogger; +use simplelog::Config as LogConfig; +use log::LevelFilter; +use std::path::PathBuf; +use std::env; +use std::net::TcpListener; use blast_rpc_server::BlastRpcServer; use blast_rpc_server::BlastRpc; @@ -58,6 +68,32 @@ struct BlastLdkServer { runtime: Arc } +impl BlastLdkServer { + async fn get_node(&self, id: String) -> Result, Status> { + let bldk = self.blast_ldk.lock().await; + let node = match bldk.nodes.get(&id) { + Some(n) => n, + None => { + return Err(Status::new(Code::NotFound, "Node not found.")) + } + }; + + Ok(node.clone()) + } + + fn get_available_port(&self) -> Option { + (8000..9000) + .find(|port| self.port_is_available(*port)) + } + + fn port_is_available(&self, port: u16) -> bool { + match TcpListener::bind(("127.0.0.1", port)) { + Ok(_) => true, + Err(_) => false, + } + } +} + #[tonic::async_trait] impl BlastRpc for BlastLdkServer { async fn start_nodes(&self, request: Request) -> Result,Status> { @@ -67,11 +103,21 @@ impl BlastRpc for BlastLdkServer { data_dir.push_str("/blast_data/"); for i in 0..num_nodes { let node_id = prepend_and_pad("blast_ldk-", i); + let mut listen_addr: Vec = Vec::new(); + let port = self.get_available_port().unwrap(); + let a = format!("127.0.0.1:{}", port); + let addr = match SocketAddress::from_str(&a) { + Ok(a) => a, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, "Could not create listen address.")); + } + }; + listen_addr.push(addr); let config = Config { storage_dir_path: format!("{}{}", data_dir, node_id), log_dir_path: None, network: Network::Regtest, - listening_addresses: None, + listening_addresses: Some(listen_addr), node_alias: None, sending_parameters: None, trusted_peers_0conf: Vec::new(), @@ -84,11 +130,22 @@ impl BlastRpc for BlastLdkServer { builder.set_chain_source_bitcoind_rpc(String::from("127.0.0.1"), 18443, String::from("user"), String::from("pass")); builder.set_gossip_source_p2p(); - let node = Arc::new(builder.build().unwrap()); + let ldknode = match builder.build() { + Ok(n) => n, + Err(_) => { + return Err(Status::new(Code::Unknown, "Could not create the ldk node.")); + } + }; + let node = Arc::new(ldknode); + + match node.start_with_runtime(Arc::clone(&self.runtime)) { + Ok(_) => {}, + Err(_) => { + return Err(Status::new(Code::Unknown, "Could not start the ldk node.")); + } + } - node.start_with_runtime(Arc::clone(&self.runtime)).unwrap(); - println!("Node ({:?}) Status: {:?}", node_id, node.status()); - thread::sleep(Duration::from_secs(10)); + thread::sleep(Duration::from_secs(2)); let mut bldk = self.blast_ldk.lock().await; bldk.nodes.insert(node_id.clone(), node.clone()); @@ -111,18 +168,17 @@ impl BlastRpc for BlastLdkServer { let response = Response::new(start_response); Ok(response) } - + async fn get_sim_ln(&self, _request: Request) -> Result, Status> { let bldk = self.blast_ldk.lock().await; let simln_response = BlastSimlnResponse { simln_data: bldk.simln_data.clone().into() }; let response = Response::new(simln_response); Ok(response) } - + async fn get_pub_key(&self, request: Request,) -> Result, Status> { let node_id = &request.get_ref().node; - let bldk = self.blast_ldk.lock().await; - let node = bldk.nodes.get(node_id).unwrap(); + let node = self.get_node(node_id.to_string()).await?; let pub_key = node.node_id().to_string(); let key_response = BlastPubKeyResponse { pub_key: pub_key }; @@ -130,20 +186,44 @@ impl BlastRpc for BlastLdkServer { Ok(response) } - async fn list_peers(&self, _request: Request,) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn list_peers(&self, request: Request,) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + let peers = format!("{:?}", node.list_peers()); + + let peers_response = BlastPeersResponse { peers: peers }; + let response = Response::new(peers_response); + Ok(response) } - async fn wallet_balance(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn wallet_balance(&self, request: Request) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + let balance = node.list_balances().total_onchain_balance_sats; + + let balance_response = BlastWalletBalanceResponse { balance: balance.to_string() }; + let response = Response::new(balance_response); + Ok(response) } - async fn channel_balance(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn channel_balance(&self, request: Request) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + let balance = node.list_balances().total_lightning_balance_sats; + + let balance_response = BlastChannelBalanceResponse { balance: balance.to_string() }; + let response = Response::new(balance_response); + Ok(response) } - async fn list_channels(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn list_channels(&self, request: Request) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + let chans = format!("{:?}", node.list_channels()); + + let chan_response = BlastListChannelsResponse { channels: chans }; + let response = Response::new(chan_response); + Ok(response) } async fn open_channel(&self, _request: Request) -> Result, Status> { @@ -158,20 +238,79 @@ impl BlastRpc for BlastLdkServer { Err(Status::new(Code::InvalidArgument, "name is invalid")) } - async fn connect_peer(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn connect_peer(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let node_id = &req.node; + let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { + Ok(k) => { k }, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); + } + }; + let addr = req.peer_addr.clone(); + let converted_addr = addr.replace("localhost", "127.0.0.1"); + let peer_addr = match SocketAddress::from_str(&converted_addr) { + Ok(a) => { a }, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer address: {:?}", &req.peer_addr))); + } + }; + let node = self.get_node(node_id.to_string()).await?; + match node.connect(peer_pub, peer_addr, true) { + Ok(_) => { + let connect_response = BlastConnectResponse { success: true }; + let response = Response::new(connect_response); + Ok(response) + }, + Err(_) => { + let connect_response = BlastConnectResponse { success: false }; + let response = Response::new(connect_response); + Ok(response) + } + } } - async fn disconnect_peer(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn disconnect_peer(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let node_id = &req.node; + let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { + Ok(k) => { k }, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); + } + }; + let node = self.get_node(node_id.to_string()).await?; + match node.disconnect(peer_pub) { + Ok(_) => { + let connect_response = BlastDisconnectResponse { success: true }; + let response = Response::new(connect_response); + Ok(response) + }, + Err(_) => { + let connect_response = BlastDisconnectResponse { success: false }; + let response = Response::new(connect_response); + Ok(response) + } + } } async fn get_btc_address(&self, _request: Request) -> Result, Status> { Err(Status::new(Code::InvalidArgument, "name is invalid")) } - async fn get_listen_address(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn get_listen_address(&self, request: Request) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + let addr = match node.config().listening_addresses { + Some(a) => { a }, + None => { + return Err(Status::new(Code::InvalidArgument, "Could not get listening address.")); + } + }; + + let listen_response = BlastListenAddressResponse { address: addr.clone().get(0).unwrap().clone().to_string() }; + let response = Response::new(listen_response); + Ok(response) } async fn stop_model(&self, _request: Request) -> Result, Status> { @@ -200,6 +339,15 @@ impl BlastRpc for BlastLdkServer { } fn main() -> Result<(), Box> { + let home = env::var("HOME").expect("HOME environment variable not set"); + let folder_path = PathBuf::from(home).join(".blast/blast_ldk.log"); + std::fs::create_dir_all(folder_path.parent().unwrap()).unwrap(); + let _ = WriteLogger::init( + LevelFilter::Info, + LogConfig::default(), + File::create(folder_path).unwrap(), + ); + let rt = Arc::new(tokio::runtime::Builder::new_multi_thread() .enable_all() .build() @@ -215,7 +363,7 @@ fn main() -> Result<(), Box> { runtime: Arc::clone(&rt) }; - println!("Starting gRPC server at {}", addr); + log::info!("Starting gRPC server at {}", addr); let server = rt.spawn(async move { Server::builder() @@ -233,11 +381,11 @@ fn main() -> Result<(), Box> { let _ = server.await; }); - println!("Shutting down gRPC server at {}", addr); + log::info!("Shutting down gRPC server at {}", addr); Ok(()) } fn prepend_and_pad(input: &str, num: i32) -> String { format!("{}{:04}", input, num) -} \ No newline at end of file +} From 78ea209dbd3e131cf0bab4ae343ffcae13284d6f Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 24 Oct 2024 15:19:31 +0000 Subject: [PATCH 07/13] Implementing open/close channels and onchain address for the ldk model --- blast_models/blast_ldk/blast_ldk/src/main.rs | 92 ++++++++++++++++++-- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index aeee4e2..3f25595 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -11,6 +11,8 @@ use ldk_node::{Builder, LogLevel}; use ldk_node::bitcoin::Network; use ldk_node::config::Config; use ldk_node::lightning::ln::msgs::SocketAddress; +use ldk_node::lightning::routing::gossip::NodeAlias; +use ldk_node::UserChannelId; use secp256k1::PublicKey; use tonic::{transport::Server, Request, Response, Status}; @@ -47,9 +49,15 @@ struct SimJsonFile { nodes: Vec } +struct Channel { + id: UserChannelId, + pk: PublicKey +} + struct BlastLdk { nodes: HashMap>, simln_data: String, + open_channels: HashMap, shutdown_sender: Option> } @@ -58,6 +66,7 @@ impl BlastLdk { Self { nodes: HashMap::new(), simln_data: String::from(""), + open_channels: HashMap::new(), shutdown_sender: None } } @@ -103,6 +112,12 @@ impl BlastRpc for BlastLdkServer { data_dir.push_str("/blast_data/"); for i in 0..num_nodes { let node_id = prepend_and_pad("blast_ldk-", i); + let alias = node_id.as_bytes(); + // Create an array and fill it with values from the slice + let mut alias_array = [0u8; 32]; // Fill with default value 0 + let len = alias.len().min(alias_array.len()); // Get the minimum length + alias_array[..len].copy_from_slice(alias); // Copy the slice into the array + let node_alias = NodeAlias(alias_array); let mut listen_addr: Vec = Vec::new(); let port = self.get_available_port().unwrap(); let a = format!("127.0.0.1:{}", port); @@ -118,7 +133,7 @@ impl BlastRpc for BlastLdkServer { log_dir_path: None, network: Network::Regtest, listening_addresses: Some(listen_addr), - node_alias: None, + node_alias: Some(node_alias), sending_parameters: None, trusted_peers_0conf: Vec::new(), probing_liquidity_limit_multiplier: 0, @@ -226,12 +241,59 @@ impl BlastRpc for BlastLdkServer { Ok(response) } - async fn open_channel(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn open_channel(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let node_id = &req.node; + let node = self.get_node(node_id.to_string()).await?; + let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { + Ok(k) => { k }, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); + } + }; + let amount = req.amount; + let push = req.push_amout; + let id = req.channel_id; + + let chan_id = match node.open_announced_channel(peer_pub, SocketAddress::from_str("127.0.0.1:8000").unwrap(), amount as u64, Some(push as u64), None) { + Ok(id) => id, + Err(_) => { + return Err(Status::new(Code::Unknown, format!("Could not open channel."))); + } + }; + + let mut bldk = self.blast_ldk.lock().await; + bldk.open_channels.insert(id, Channel{id: chan_id, pk: peer_pub}); + + let chan_response = BlastOpenChannelResponse { success: true }; + let response = Response::new(chan_response); + Ok(response) } - async fn close_channel(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn close_channel(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let node_id = &req.node; + let node = self.get_node(node_id.to_string()).await?; + let id = req.channel_id; + + let bldk = self.blast_ldk.lock().await; + let channel = match bldk.open_channels.get(&id) { + Some(c) => c, + None => { + return Err(Status::new(Code::Unknown, format!("Could not close channel."))); + } + }; + + match node.close_channel(&channel.id, channel.pk) { + Ok(_) => {}, + Err(_) => { + return Err(Status::new(Code::Unknown, format!("Could not close channel."))); + } + } + + let chan_response = BlastCloseChannelResponse { success: true }; + let response = Response::new(chan_response); + Ok(response) } async fn get_model_channels(&self, _request: Request) -> Result, Status> { @@ -294,17 +356,29 @@ impl BlastRpc for BlastLdkServer { } } - async fn get_btc_address(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn get_btc_address(&self, request: Request) -> Result, Status> { + let node_id = &request.get_ref().node; + let node = self.get_node(node_id.to_string()).await?; + + let address = match node.onchain_payment().new_address() { + Ok(address) => address, + Err(_) => { + return Err(Status::new(Code::Unknown, "Could not get bitcoin address.")); + } + }; + + let addr_response = BlastBtcAddressResponse { address: address.to_string() }; + let response = Response::new(addr_response); + Ok(response) } async fn get_listen_address(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; let addr = match node.config().listening_addresses { - Some(a) => { a }, + Some(a) => a, None => { - return Err(Status::new(Code::InvalidArgument, "Could not get listening address.")); + return Err(Status::new(Code::Unknown, "Could not get listening address.")); } }; From 33bb24c16c859a4f7b28e5ebd6332efa6f818698 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 24 Oct 2024 15:27:54 +0000 Subject: [PATCH 08/13] Adding peer address to the open channel blast rpc --- blast_core/src/blast_model_manager.rs | 2 ++ blast_models/blast_ldk/blast_ldk/src/main.rs | 8 +++++++- blast_proto/blast_proto.proto | 7 ++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/blast_core/src/blast_model_manager.rs b/blast_core/src/blast_model_manager.rs index 090df19..f414b41 100644 --- a/blast_core/src/blast_model_manager.rs +++ b/blast_core/src/blast_model_manager.rs @@ -455,6 +455,7 @@ impl BlastModelManager { /// Open a channel from node with source_id to node with dest_id for the given amount and with the given chan_id pub async fn open_channel(&mut self, source_id: String, dest_id: String, amount: i64, push_amount: i64, chan_id: i64) -> Result<(), String> { let pub_key = self.get_pub_key(dest_id.clone()).await?; + let address = self.get_listen_address(dest_id.clone()).await?; // Get the model name from the node_id (example node_id: model_name-0000) let model_name: String = get_model_from_node(source_id.clone()); @@ -466,6 +467,7 @@ impl BlastModelManager { let request = tonic::Request::new(BlastOpenChannelRequest { node: source_id, peer_pub_key: pub_key, + peer_address: address, amount: amount, push_amout: push_amount, channel_id: chan_id diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 3f25595..e2343af 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -251,11 +251,17 @@ impl BlastRpc for BlastLdkServer { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); } }; + let address = match SocketAddress::from_str(&req.peer_address) { + Ok(a) => a, + Err(_) => { + return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer address: {:?}", &req.peer_address))); + } + }; let amount = req.amount; let push = req.push_amout; let id = req.channel_id; - let chan_id = match node.open_announced_channel(peer_pub, SocketAddress::from_str("127.0.0.1:8000").unwrap(), amount as u64, Some(push as u64), None) { + let chan_id = match node.open_announced_channel(peer_pub, address, amount as u64, Some(push as u64), None) { Ok(id) => id, Err(_) => { return Err(Status::new(Code::Unknown, format!("Could not open channel."))); diff --git a/blast_proto/blast_proto.proto b/blast_proto/blast_proto.proto index 630f589..e497665 100644 --- a/blast_proto/blast_proto.proto +++ b/blast_proto/blast_proto.proto @@ -81,9 +81,10 @@ message BlastListChannelsResponse { message BlastOpenChannelRequest { string node = 1; string peer_pub_key = 2; - int64 amount = 3; - int64 push_amout = 4; - int64 channel_id = 5; + string peer_address = 3; + int64 amount = 4; + int64 push_amout = 5; + int64 channel_id = 6; } message BlastOpenChannelResponse { From 8a755f745bbd16e2dbefce56c2b6dc15d46d4268 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 24 Oct 2024 16:13:22 +0000 Subject: [PATCH 09/13] Fixing some channel bugs in the LDK model --- blast_models/blast_ldk/blast_ldk/src/main.rs | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index e2343af..0d379bb 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -50,6 +50,7 @@ struct SimJsonFile { } struct Channel { + source: String, id: UserChannelId, pk: PublicKey } @@ -251,8 +252,10 @@ impl BlastRpc for BlastLdkServer { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); } }; - let address = match SocketAddress::from_str(&req.peer_address) { - Ok(a) => a, + let addr = req.peer_address.clone(); + let converted_addr = addr.replace("localhost", "127.0.0.1"); + let peer_addr = match SocketAddress::from_str(&converted_addr) { + Ok(a) => { a }, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer address: {:?}", &req.peer_address))); } @@ -261,7 +264,7 @@ impl BlastRpc for BlastLdkServer { let push = req.push_amout; let id = req.channel_id; - let chan_id = match node.open_announced_channel(peer_pub, address, amount as u64, Some(push as u64), None) { + let chan_id = match node.open_announced_channel(peer_pub, peer_addr, amount as u64, Some(push as u64), None) { Ok(id) => id, Err(_) => { return Err(Status::new(Code::Unknown, format!("Could not open channel."))); @@ -269,7 +272,7 @@ impl BlastRpc for BlastLdkServer { }; let mut bldk = self.blast_ldk.lock().await; - bldk.open_channels.insert(id, Channel{id: chan_id, pk: peer_pub}); + bldk.open_channels.insert(id, Channel{source: node_id.to_string(), id: chan_id, pk: peer_pub}); let chan_response = BlastOpenChannelResponse { success: true }; let response = Response::new(chan_response); @@ -303,7 +306,15 @@ impl BlastRpc for BlastLdkServer { } async fn get_model_channels(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + let mut result = String::new(); + let bldk = self.blast_ldk.lock().await; + for (key, value) in &bldk.open_channels { + result.push_str(&format!("{}: {} -> {},", key, &value.source, value.pk.to_string())); + } + + let chan_response = BlastGetModelChannelsResponse { channels: result }; + let response = Response::new(chan_response); + Ok(response) } async fn connect_peer(&self, request: Request) -> Result, Status> { From e3d861260ac38a79f8920359964cb5c0b24792e5 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 24 Oct 2024 19:38:35 +0000 Subject: [PATCH 10/13] Code cleanup for the cli and ldk model --- blast_cli/src/blast_cli.rs | 4 + blast_cli/src/configure.rs | 11 +- blast_cli/src/load.rs | 25 +++- blast_cli/src/main.rs | 10 +- blast_cli/src/new.rs | 25 +++- blast_cli/src/run.rs | 8 ++ blast_cli/src/shared.rs | 10 ++ blast_cli/src/wait.rs | 8 ++ blast_models/blast_ldk/blast_ldk/src/main.rs | 141 ++++++++++++++----- 9 files changed, 191 insertions(+), 51 deletions(-) diff --git a/blast_cli/src/blast_cli.rs b/blast_cli/src/blast_cli.rs index 0afa76c..efbe6d6 100644 --- a/blast_cli/src/blast_cli.rs +++ b/blast_cli/src/blast_cli.rs @@ -7,6 +7,7 @@ use crate::load::*; use crate::configure::*; use crate::run::*; +// The BLAST CLI, which is comprised of 4 tabs pub struct BlastCli { pub new: NewTab, pub load: LoadTab, @@ -20,6 +21,7 @@ impl BlastCli { // Create the blast core object let blast = Blast::new(); + // Get a list of the available models let mut model_list: Vec = Vec::new(); match blast.get_available_models() { Ok(models) => { @@ -30,6 +32,7 @@ impl BlastCli { Err(_) => {} } + // Get a list of saved sims that can be loaded let mut sim_list: Vec = Vec::new(); match blast.get_available_sims() { Ok(sims) => { @@ -40,6 +43,7 @@ impl BlastCli { Err(_) => {} } + // Create the tabs let nt = NewTab{models: StatefulList::with_items(model_list)}; let lt = LoadTab{sims: StatefulList::with_items(sim_list)}; let ct = ConfigureTab::new(); diff --git a/blast_cli/src/configure.rs b/blast_cli/src/configure.rs index 8ce0478..b93d34b 100644 --- a/blast_cli/src/configure.rs +++ b/blast_cli/src/configure.rs @@ -1,13 +1,17 @@ +// TUI libraries use ratatui::{ crossterm::event::{KeyCode, KeyEvent, KeyEventKind}, prelude::*, widgets::*, }; +// Extra Dependencies use textwrap; +// BLAST libraries use crate::shared::*; +// The sections of the Configure page #[derive(PartialEq,Clone)] pub enum ConfigureSection { Command, @@ -16,6 +20,7 @@ pub enum ConfigureSection { Activity } +// The Configure Tab structure pub struct ConfigureTab { pub input: String, pub history: Vec, @@ -28,6 +33,7 @@ pub struct ConfigureTab { pub current_section: ConfigureSection } +// The Configure Tab is a window that displays a CLI and the current state of the test network impl ConfigureTab { pub fn new() -> Self { Self { @@ -192,10 +198,12 @@ impl BlastTab for ConfigureTab { frame.render_widget(messages, messages_area); } + /// This is called when the configure tab is first displayed fn init(&mut self) { self.current_section = ConfigureSection::Command; } + /// This is called when the configure tab is closed fn close(&mut self) { self.messages.clear(); self.input.clear(); @@ -206,6 +214,7 @@ impl BlastTab for ConfigureTab { self.history_index = 0; } + /// This is called when a key is pressed while on the configure tab fn process(&mut self, key: KeyEvent) -> ProcessResult { if key.kind == KeyEventKind::Press { match key.code { @@ -302,7 +311,7 @@ impl BlastTab for ConfigureTab { KeyCode::Down => { match self.current_section { ConfigureSection::Command => { - if self.history_index <= self.history.len() - 1 { + if self.history.len() > 0 && self.history_index <= self.history.len() - 1 { self.history_index += 1; self.input = self.history.get(self.history_index).unwrap_or(&String::from("")).to_string(); self.character_index = self.input.len(); diff --git a/blast_cli/src/load.rs b/blast_cli/src/load.rs index 725b400..7bae8ff 100644 --- a/blast_cli/src/load.rs +++ b/blast_cli/src/load.rs @@ -1,23 +1,34 @@ +// TUI libraries use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, prelude::*, widgets::*, }; +// BLAST libraries use crate::shared::*; +// The Load Tab structure pub struct LoadTab { pub sims: StatefulList } +// The Load Tab is a window that displays the saved simulations and lets the user select one to load impl BlastTab for LoadTab { + /// Draw the tab fn draw(&mut self, frame: &mut Frame, area: Rect) { let layout = Layout::new( Direction::Vertical, [Constraint::Percentage(25), Constraint::Percentage(5), Constraint::Percentage(70)], ) .split(area); - + + // layout[0] The top of the window show the BLAST banner + frame.render_widget( + Paragraph::new(BANNER), layout[0] + ); + + // layout[1] The help message let msg = vec![ "Press ".into(), "q".bold(), @@ -30,11 +41,8 @@ impl BlastTab for LoadTab { let text = Text::from(Line::from(msg)).patch_style(Style::default()); let help_message = Paragraph::new(text); frame.render_widget(help_message, layout[1]); - - frame.render_widget( - Paragraph::new(BANNER), layout[0] - ); - + + // layout[2] The list of simulations let tasks: Vec = self .sims .items @@ -50,14 +58,17 @@ impl BlastTab for LoadTab { frame.render_stateful_widget(tasks, layout[2], &mut self.sims.state); } + /// This is called when the load tab is first displayed fn init(&mut self) { self.sims.next(); } + /// This is called when the load tab is closing fn close(&mut self) { self.sims.clear(); } + /// This is called when a key is pressed while on the load tab fn process(&mut self, key: KeyEvent) -> ProcessResult { match key.code { // Scroll the list of simulations @@ -68,9 +79,11 @@ impl BlastTab for LoadTab { KeyCode::Up => { self.sims.previous(); } + // Load the selected simulation KeyCode::Enter => { return ProcessResult::LoadNetwork(self.sims.items[self.sims.state.selected().unwrap()].clone()); } + // Ignore all other keys _ => {} } diff --git a/blast_cli/src/main.rs b/blast_cli/src/main.rs index 4c870e6..3db887e 100644 --- a/blast_cli/src/main.rs +++ b/blast_cli/src/main.rs @@ -1,3 +1,4 @@ +// Standard libraries use std::{error::Error, io, time::Instant, time::Duration}; use std::collections::HashMap; use std::sync::atomic::{AtomicBool, Ordering}; @@ -7,12 +8,14 @@ use std::fs::File; use std::path::PathBuf; use std::env; +// Extra Dependencies use simplelog::WriteLogger; use simplelog::Config; use log::LevelFilter; use tokio::task::JoinSet; use anyhow::Error as AnyError; +// TUI libraries use ratatui::{ crossterm::{ event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode}, @@ -23,6 +26,7 @@ use ratatui::{ widgets::*, }; +// BLAST libraries mod shared; mod new; mod load; @@ -49,18 +53,18 @@ async fn main() -> Result<(), Box> { File::create(folder_path).unwrap(), ); - // setup terminal + // Setup terminal enable_raw_mode()?; let mut stdout = io::stdout(); execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; - // create app and run it + // Create app and run it let cli = BlastCli::new(); let res = run(&mut terminal, cli).await; - // restore terminal + // Restore terminal disable_raw_mode()?; execute!( terminal.backend_mut(), diff --git a/blast_cli/src/new.rs b/blast_cli/src/new.rs index ce73486..493a6c2 100644 --- a/blast_cli/src/new.rs +++ b/blast_cli/src/new.rs @@ -1,23 +1,34 @@ +// TUI libraries use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, prelude::*, widgets::*, }; +// BLAST libraries use crate::shared::*; +// The New Tab structure pub struct NewTab { pub models: StatefulList } +// The New Tab is a window that displays the available models and lets the user select the number of nodes for each impl BlastTab for NewTab { + /// Draw the tab fn draw(&mut self, frame: &mut Frame, area: Rect) { let layout = Layout::new( Direction::Vertical, [Constraint::Percentage(25), Constraint::Percentage(5), Constraint::Percentage(70)], ) .split(area); - + + // layout[0] The top of the window show the BLAST banner + frame.render_widget( + Paragraph::new(BANNER), layout[0] + ); + + // layout[1] The help message let msg = vec![ "Press ".into(), "q".bold(), @@ -30,11 +41,8 @@ impl BlastTab for NewTab { let text = Text::from(Line::from(msg)).patch_style(Style::default()); let help_message = Paragraph::new(text); frame.render_widget(help_message, layout[1]); - - frame.render_widget( - Paragraph::new(BANNER), layout[0] - ); - + + // layout[2] The list of available models let tasks: Vec = self .models .items @@ -50,14 +58,17 @@ impl BlastTab for NewTab { frame.render_stateful_widget(tasks, layout[2], &mut self.models.state); } + /// This is called when the new tab is first displayed fn init(&mut self) { self.models.next(); } + /// This is called when the new tab is closing fn close(&mut self) { self.models.clear(); } + /// This is called when a key is pressed while on the new tab fn process(&mut self, key: KeyEvent) -> ProcessResult { match key.code { // Scroll the list of models @@ -83,9 +94,11 @@ impl BlastTab for NewTab { } } } + // Start the network KeyCode::Enter => { return ProcessResult::StartNetwork(self.models.items.clone()); } + // Ignore all other keys _ => {} } diff --git a/blast_cli/src/run.rs b/blast_cli/src/run.rs index c1ec735..0e3b503 100644 --- a/blast_cli/src/run.rs +++ b/blast_cli/src/run.rs @@ -1,17 +1,21 @@ +// TUI libraries use ratatui::{ crossterm::event::{KeyCode, KeyEvent}, prelude::*, widgets::*, }; +// BLAST libraries use crate::shared::*; +// The different sections of the Run page pub enum RunSection { Events, Activity, Stats } +// The Run Tab structure pub struct RunTab { pub events: StatefulList, pub activity: StatefulList, @@ -23,6 +27,7 @@ pub struct RunTab { pub points: usize } +// The Run Tab is a windows that displays the runtime data to the user as the simulation is executing impl RunTab { pub fn new() -> Self { Self { @@ -151,11 +156,13 @@ impl BlastTab for RunTab { frame.render_widget(chart, layout2[1]); } + /// This is called when the run tab is first displayed fn init(&mut self) { self.current_section = RunSection::Events; self.events.next(); } + /// This is called when the run tab is closed fn close(&mut self) { self.events.clear(); self.activity.clear(); @@ -167,6 +174,7 @@ impl BlastTab for RunTab { self.points = 0; } + /// This is called when a key is pressed while on the run tab fn process(&mut self, key: KeyEvent) -> ProcessResult { match key.code { // The Run page is mainly readonly and will show the status of the running simulation, use `s` to stop the simulation and go back to the Configure page diff --git a/blast_cli/src/shared.rs b/blast_cli/src/shared.rs index 98520f1..e400079 100644 --- a/blast_cli/src/shared.rs +++ b/blast_cli/src/shared.rs @@ -1,9 +1,11 @@ +// TUI libraries use ratatui::{ crossterm::event::KeyEvent, prelude::*, widgets::*, }; +// BLAST constants pub const BANNER: &str = r" ____ _ _____ _______ | _ \| | /\ / ____|__ __| @@ -14,6 +16,7 @@ pub const BANNER: &str = r" pub const TAB_TITLES: [&'static str; 4] = ["New", "Load", "Configure", "Run"]; +// A list that stores a selected state pub struct StatefulList { pub state: ListState, pub items: Vec, @@ -60,6 +63,7 @@ impl StatefulList { } } +// The return values for a tab when a key event is processed pub enum ProcessResult { StartNetwork(Vec), LoadNetwork(String), @@ -72,6 +76,10 @@ pub enum ProcessResult { NoOp, } +// The TUI mode +// Menu: selecting the top level tabs +// Page: working on a particular tab +// Error: displaying an error #[derive(PartialEq,Clone)] pub enum Mode { Menu, @@ -79,6 +87,7 @@ pub enum Mode { Error } +// An available model within BLAST #[derive(Clone)] pub struct Model { pub name: String, @@ -91,6 +100,7 @@ impl<'a> Into> for Model { } } +// The trait that all tabs on the TUI must implement pub trait BlastTab { fn draw(&mut self, frame: &mut Frame, area: Rect); fn init(&mut self); diff --git a/blast_cli/src/wait.rs b/blast_cli/src/wait.rs index 19cc37e..26121e2 100644 --- a/blast_cli/src/wait.rs +++ b/blast_cli/src/wait.rs @@ -1,15 +1,19 @@ +// TUI libraries use ratatui::{ crossterm::event::KeyEvent, prelude::*, widgets::*, }; +// BLAST libraries use crate::shared::*; +// The New Tab structure pub struct WaitTab { pub message: String } +// The Wait Tab is a window that displays some status while waiting for a task to complete impl BlastTab for WaitTab { fn draw(&mut self, frame: &mut Frame, area: Rect) { frame.render_widget( @@ -18,15 +22,19 @@ impl BlastTab for WaitTab { ); } + /// This is called when the wait tab is first displayed fn init(&mut self) { return; } + /// This is called when the wait tab is closing fn close(&mut self) { return; } + /// This is called when a key is pressed while on the wait tab fn process(&mut self, key: KeyEvent) -> ProcessResult { + // Ignore all keys match key.code { _ => {} } diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 0d379bb..792c515 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -1,3 +1,4 @@ +// Standard libraries use std::str::FromStr; use std::time::Duration; use std::thread; @@ -5,7 +6,11 @@ use std::sync::Arc; use std::collections::HashMap; use std::fs; use std::fs::File; +use std::path::PathBuf; +use std::env; +use std::net::TcpListener; +// LDK Node libraries use ldk_node::bip39::serde::{Deserialize, Serialize}; use ldk_node::{Builder, LogLevel}; use ldk_node::bitcoin::Network; @@ -13,7 +18,9 @@ use ldk_node::config::Config; use ldk_node::lightning::ln::msgs::SocketAddress; use ldk_node::lightning::routing::gossip::NodeAlias; use ldk_node::UserChannelId; +use ldk_node::Node; +// Extra dependencies use secp256k1::PublicKey; use tonic::{transport::Server, Request, Response, Status}; use tonic::Code; @@ -23,10 +30,8 @@ use tokio::runtime::Runtime; use simplelog::WriteLogger; use simplelog::Config as LogConfig; use log::LevelFilter; -use std::path::PathBuf; -use std::env; -use std::net::TcpListener; +// Blast libraries use blast_rpc_server::BlastRpcServer; use blast_rpc_server::BlastRpc; use blast_proto::*; @@ -49,19 +54,22 @@ struct SimJsonFile { nodes: Vec } +// The data that the LDK model will store about an open channel struct Channel { source: String, id: UserChannelId, pk: PublicKey } +// The main data structure for the LDK model struct BlastLdk { - nodes: HashMap>, + nodes: HashMap>, simln_data: String, open_channels: HashMap, shutdown_sender: Option> } +// Constructor for the LDK model data structure impl BlastLdk { fn new() -> Self { Self { @@ -73,13 +81,16 @@ impl BlastLdk { } } +// The RPC server that implements the BLAST model interface struct BlastLdkServer { blast_ldk: Arc>, runtime: Arc } +// Helper functions for the RPC server impl BlastLdkServer { - async fn get_node(&self, id: String) -> Result, Status> { + // Get an ldk-node "Node" object from an id + async fn get_node(&self, id: String) -> Result, Status> { let bldk = self.blast_ldk.lock().await; let node = match bldk.nodes.get(&id) { Some(n) => n, @@ -91,11 +102,12 @@ impl BlastLdkServer { Ok(node.clone()) } + // Get an available port that can be used for listening fn get_available_port(&self) -> Option { - (8000..9000) - .find(|port| self.port_is_available(*port)) + (8000..9000).find(|port| self.port_is_available(*port)) } + // Check if a port is available fn port_is_available(&self, port: u16) -> bool { match TcpListener::bind(("127.0.0.1", port)) { Ok(_) => true, @@ -104,36 +116,44 @@ impl BlastLdkServer { } } +// The RPC server that the blast framework will connect to #[tonic::async_trait] impl BlastRpc for BlastLdkServer { + /// Start a certain number of nodes async fn start_nodes(&self, request: Request) -> Result,Status> { let num_nodes = request.get_ref().num_nodes; let mut node_list = SimJsonFile{nodes: Vec::new()}; let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); data_dir.push_str("/blast_data/"); + + // Start the requested number of ldk nodes for i in 0..num_nodes { - let node_id = prepend_and_pad("blast_ldk-", i); + // Create a node id and alias + let node_id = format!("{}{:04}", "blast_ldk-", i); let alias = node_id.as_bytes(); - // Create an array and fill it with values from the slice - let mut alias_array = [0u8; 32]; // Fill with default value 0 - let len = alias.len().min(alias_array.len()); // Get the minimum length - alias_array[..len].copy_from_slice(alias); // Copy the slice into the array + let mut alias_array = [0u8; 32]; + let len = alias.len().min(alias_array.len()); + alias_array[..len].copy_from_slice(alias); let node_alias = NodeAlias(alias_array); - let mut listen_addr: Vec = Vec::new(); + + // Set up the listening address for this node + let mut listen_addr_list: Vec = Vec::new(); let port = self.get_available_port().unwrap(); - let a = format!("127.0.0.1:{}", port); - let addr = match SocketAddress::from_str(&a) { + let addr = format!("127.0.0.1:{}", port); + let address = match SocketAddress::from_str(&addr) { Ok(a) => a, Err(_) => { return Err(Status::new(Code::InvalidArgument, "Could not create listen address.")); } }; - listen_addr.push(addr); + listen_addr_list.push(address); + + // Create the config for this node let config = Config { storage_dir_path: format!("{}{}", data_dir, node_id), log_dir_path: None, network: Network::Regtest, - listening_addresses: Some(listen_addr), + listening_addresses: Some(listen_addr_list), node_alias: Some(node_alias), sending_parameters: None, trusted_peers_0conf: Vec::new(), @@ -142,10 +162,10 @@ impl BlastRpc for BlastLdkServer { anchor_channels_config: None }; + // Build the ldk node let mut builder = Builder::from_config(config); builder.set_chain_source_bitcoind_rpc(String::from("127.0.0.1"), 18443, String::from("user"), String::from("pass")); builder.set_gossip_source_p2p(); - let ldknode = match builder.build() { Ok(n) => n, Err(_) => { @@ -154,6 +174,7 @@ impl BlastRpc for BlastLdkServer { }; let node = Arc::new(ldknode); + // Start the node match node.start_with_runtime(Arc::clone(&self.runtime)) { Ok(_) => {}, Err(_) => { @@ -161,15 +182,18 @@ impl BlastRpc for BlastLdkServer { } } + // Let the node get started up thread::sleep(Duration::from_secs(2)); + // Add the node to the model's list of nodes and to the SimLn data list let mut bldk = self.blast_ldk.lock().await; bldk.nodes.insert(node_id.clone(), node.clone()); - + // TODO: Once and RPC is added to LDK-node, fill in the config for that connection here so that SimLn will be able to connect and generate payments let n = SimLnNode{id: node_id.clone(), address: String::from(""), macaroon: String::from(""), cert: String::from("")}; node_list.nodes.push(n); } + // Serialize the SimLn data into a json string let mut bldk = self.blast_ldk.lock().await; bldk.simln_data = match serde_json::to_string(&node_list) { Ok(s) => s, @@ -180,11 +204,13 @@ impl BlastRpc for BlastLdkServer { } }; + // Return the response to start_nodes let start_response = BlastStartResponse { success: true }; let response = Response::new(start_response); Ok(response) } + /// Get the sim-ln data for this model async fn get_sim_ln(&self, _request: Request) -> Result, Status> { let bldk = self.blast_ldk.lock().await; let simln_response = BlastSimlnResponse { simln_data: bldk.simln_data.clone().into() }; @@ -192,9 +218,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests the pub key of a node that is controlled by this model async fn get_pub_key(&self, request: Request,) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let pub_key = node.node_id().to_string(); let key_response = BlastPubKeyResponse { pub_key: pub_key }; @@ -202,9 +230,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests the list of peers for a node that is controlled by this model async fn list_peers(&self, request: Request,) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let peers = format!("{:?}", node.list_peers()); let peers_response = BlastPeersResponse { peers: peers }; @@ -212,9 +242,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests the wallet balance of a node that is controlled by this model async fn wallet_balance(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let balance = node.list_balances().total_onchain_balance_sats; let balance_response = BlastWalletBalanceResponse { balance: balance.to_string() }; @@ -222,9 +254,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests the channel balance of a node that is controlled by this model async fn channel_balance(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let balance = node.list_balances().total_lightning_balance_sats; let balance_response = BlastChannelBalanceResponse { balance: balance.to_string() }; @@ -232,9 +266,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests the list of channels for a node that is controlled by this model async fn list_channels(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let chans = format!("{:?}", node.list_channels()); let chan_response = BlastListChannelsResponse { channels: chans }; @@ -242,28 +278,38 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests that a node controlled by this model opens a channel async fn open_channel(&self, request: Request) -> Result, Status> { let req = &request.get_ref(); + + // Get the source node from the id let node_id = &req.node; let node = self.get_node(node_id.to_string()).await?; + + // Get the peer public key from the request and convert it to a PublicKey object let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { - Ok(k) => { k }, + Ok(k) => k, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); } }; + + // Get the peer address from the request and convert it to a SocketAddress object let addr = req.peer_address.clone(); let converted_addr = addr.replace("localhost", "127.0.0.1"); let peer_addr = match SocketAddress::from_str(&converted_addr) { - Ok(a) => { a }, + Ok(a) => a, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer address: {:?}", &req.peer_address))); } }; + + // Get the other parameters from the request let amount = req.amount; let push = req.push_amout; let id = req.channel_id; + // Attempt to open a channel from this node let chan_id = match node.open_announced_channel(peer_pub, peer_addr, amount as u64, Some(push as u64), None) { Ok(id) => id, Err(_) => { @@ -271,28 +317,35 @@ impl BlastRpc for BlastLdkServer { } }; + // Add the channel to the model's list of open channels let mut bldk = self.blast_ldk.lock().await; bldk.open_channels.insert(id, Channel{source: node_id.to_string(), id: chan_id, pk: peer_pub}); + // Respond to the open channel request let chan_response = BlastOpenChannelResponse { success: true }; let response = Response::new(chan_response); Ok(response) } + /// Blast requests that a node controlled by this model closes a channel async fn close_channel(&self, request: Request) -> Result, Status> { let req = &request.get_ref(); + + // Get the source node from the id let node_id = &req.node; let node = self.get_node(node_id.to_string()).await?; - let id = req.channel_id; + // Get the channel from the model's open channel map + let id = req.channel_id; let bldk = self.blast_ldk.lock().await; let channel = match bldk.open_channels.get(&id) { Some(c) => c, None => { - return Err(Status::new(Code::Unknown, format!("Could not close channel."))); + return Err(Status::new(Code::Unknown, format!("Could not find the channel."))); } }; + // Attempt to close the channel match node.close_channel(&channel.id, channel.pk) { Ok(_) => {}, Err(_) => { @@ -300,11 +353,13 @@ impl BlastRpc for BlastLdkServer { } } + // Respond to the close channel request let chan_response = BlastCloseChannelResponse { success: true }; let response = Response::new(chan_response); Ok(response) } + /// Create a comma separated list of open channels that this model has control over async fn get_model_channels(&self, _request: Request) -> Result, Status> { let mut result = String::new(); let bldk = self.blast_ldk.lock().await; @@ -317,23 +372,30 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Blast requests that a node controlled by this model connects to a peer async fn connect_peer(&self, request: Request) -> Result, Status> { let req = &request.get_ref(); - let node_id = &req.node; + + // Get the peer public key from the request and convert it to a PublicKey object let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { - Ok(k) => { k }, + Ok(k) => k, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); } }; + + // Get the peer address from the request and convert it to a SocketAddress object let addr = req.peer_addr.clone(); let converted_addr = addr.replace("localhost", "127.0.0.1"); let peer_addr = match SocketAddress::from_str(&converted_addr) { - Ok(a) => { a }, + Ok(a) => a, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer address: {:?}", &req.peer_addr))); } }; + + // Attempt to connect to the peer from this node + let node_id = &req.node; let node = self.get_node(node_id.to_string()).await?; match node.connect(peer_pub, peer_addr, true) { Ok(_) => { @@ -349,15 +411,20 @@ impl BlastRpc for BlastLdkServer { } } + /// Blast requests that a node controlled by this model disconnects from a peer async fn disconnect_peer(&self, request: Request) -> Result, Status> { let req = &request.get_ref(); - let node_id = &req.node; + + // Get the peer public key from the request and convert it to a PublicKey object let peer_pub = match PublicKey::from_slice(hex::decode(&req.peer_pub_key).unwrap().as_slice()) { - Ok(k) => { k }, + Ok(k) => k, Err(_) => { return Err(Status::new(Code::InvalidArgument, format!("Could not parse peer pub key: {:?}", req.peer_pub_key))); } }; + + // Attempt to disconnect from the peer + let node_id = &req.node; let node = self.get_node(node_id.to_string()).await?; match node.disconnect(peer_pub) { Ok(_) => { @@ -373,12 +440,13 @@ impl BlastRpc for BlastLdkServer { } } + /// Get a BTC address for a node async fn get_btc_address(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; let address = match node.onchain_payment().new_address() { - Ok(address) => address, + Ok(a) => a, Err(_) => { return Err(Status::new(Code::Unknown, "Could not get bitcoin address.")); } @@ -389,9 +457,11 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Get the listen address for a node async fn get_listen_address(&self, request: Request) -> Result, Status> { let node_id = &request.get_ref().node; let node = self.get_node(node_id.to_string()).await?; + let addr = match node.config().listening_addresses { Some(a) => a, None => { @@ -404,6 +474,7 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Shutdown the nodes async fn stop_model(&self, _request: Request) -> Result, Status> { let mut bldk = self.blast_ldk.lock().await; for (_, node) in &bldk.nodes { @@ -420,16 +491,19 @@ impl BlastRpc for BlastLdkServer { Ok(response) } + /// Load a previous state of this model async fn load(&self, _request: Request) -> Result, Status> { Err(Status::new(Code::InvalidArgument, "name is invalid")) } + /// Save this models current state async fn save(&self, _request: Request) -> Result, Status> { Err(Status::new(Code::InvalidArgument, "name is invalid")) } } fn main() -> Result<(), Box> { + // Set up the logger for this model let home = env::var("HOME").expect("HOME environment variable not set"); let folder_path = PathBuf::from(home).join(".blast/blast_ldk.log"); std::fs::create_dir_all(folder_path.parent().unwrap()).unwrap(); @@ -439,11 +513,13 @@ fn main() -> Result<(), Box> { File::create(folder_path).unwrap(), ); + // Create a multi-thread runtime that the LDK-nodes will run on let rt = Arc::new(tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap()); + // Create the BlastLdkServer object let addr = "127.0.0.1:5051".parse()?; let (shutdown_sender, shutdown_receiver) = oneshot::channel::<()>(); let mut bldk = BlastLdk::new(); @@ -454,13 +530,12 @@ fn main() -> Result<(), Box> { runtime: Arc::clone(&rt) }; + // Start the RPC server log::info!("Starting gRPC server at {}", addr); - let server = rt.spawn(async move { Server::builder() .add_service(BlastRpcServer::new(server)) .serve_with_shutdown(addr, async { - // Wait for the shutdown signal shutdown_receiver.await.ok(); }) .await @@ -476,7 +551,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -fn prepend_and_pad(input: &str, num: i32) -> String { - format!("{}{:04}", input, num) -} From 106c65693678bc9280aee65855fe5f5a9e95905b Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Thu, 24 Oct 2024 20:42:58 +0000 Subject: [PATCH 11/13] Small bug fixes --- blast_core/src/blast_model_manager.rs | 2 +- blast_core/src/blast_simln_manager.rs | 3 +++ blast_models/blast_ldk/blast_ldk/src/main.rs | 2 ++ blast_models/blast_lnd/blast_rpc_server.go | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/blast_core/src/blast_model_manager.rs b/blast_core/src/blast_model_manager.rs index f414b41..6d05554 100644 --- a/blast_core/src/blast_model_manager.rs +++ b/blast_core/src/blast_model_manager.rs @@ -443,7 +443,7 @@ impl BlastModelManager { }; let chan_string = response.get_ref().channels.clone(); - if chan_string != "" { + if !chan_string.is_empty() { let mut c: Vec = chan_string.split(',').map(|s| s.trim().to_string()).collect(); chans.append(&mut c); } diff --git a/blast_core/src/blast_simln_manager.rs b/blast_core/src/blast_simln_manager.rs index a08dc9e..f948d8b 100644 --- a/blast_core/src/blast_simln_manager.rs +++ b/blast_core/src/blast_simln_manager.rs @@ -129,6 +129,9 @@ impl BlastSimLnManager { for connection in nodes { let node: Arc> = match connection { NodeConnection::LND(c) => { + if c.address.is_empty() { + continue; + } Arc::new(Mutex::new(LndNode::new(c).await?)) }, NodeConnection::CLN(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)), diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 792c515..016a5d0 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -367,6 +367,8 @@ impl BlastRpc for BlastLdkServer { result.push_str(&format!("{}: {} -> {},", key, &value.source, value.pk.to_string())); } + result.pop(); + let chan_response = BlastGetModelChannelsResponse { channels: result }; let response = Response::new(chan_response); Ok(response) diff --git a/blast_models/blast_lnd/blast_rpc_server.go b/blast_models/blast_lnd/blast_rpc_server.go index 66faf25..8553330 100644 --- a/blast_models/blast_lnd/blast_rpc_server.go +++ b/blast_models/blast_lnd/blast_rpc_server.go @@ -245,7 +245,7 @@ func (s *BlastRpcServer) GetModelChannels(ctx context.Context, request *pb.Blast result := sb.String() if len(result) > 0 { - result = result[:len(result)-2] + result = result[:len(result)-1] } response := &pb.BlastGetModelChannelsResponse{ From 9e02ced75c451097bd4f49e24fbe6fd73bf66561 Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Fri, 25 Oct 2024 15:43:59 +0000 Subject: [PATCH 12/13] Implementing save function for ldk model --- blast_models/blast_ldk/blast_ldk/Cargo.toml | 2 + blast_models/blast_ldk/blast_ldk/src/main.rs | 97 +++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/blast_models/blast_ldk/blast_ldk/Cargo.toml b/blast_models/blast_ldk/blast_ldk/Cargo.toml index e94a919..3ef0662 100644 --- a/blast_models/blast_ldk/blast_ldk/Cargo.toml +++ b/blast_models/blast_ldk/blast_ldk/Cargo.toml @@ -14,6 +14,8 @@ secp256k1 = "0.29.1" hex = "0.4.3" simplelog = "0.12.2" log = "0.4.20" +tar = "0.4" +flate2 = "1.0" [build-dependencies] tonic-build = "0.11.0" \ No newline at end of file diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index 016a5d0..e1ec2f5 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -9,6 +9,7 @@ use std::fs::File; use std::path::PathBuf; use std::env; use std::net::TcpListener; +use std::path::Path; // LDK Node libraries use ldk_node::bip39::serde::{Deserialize, Serialize}; @@ -30,6 +31,12 @@ use tokio::runtime::Runtime; use simplelog::WriteLogger; use simplelog::Config as LogConfig; use log::LevelFilter; +use flate2::write::GzEncoder; +use flate2::Compression; +use tar::Builder as TarBuilder; +use serde::Serializer; +use serde::Deserializer; +use serde::ser::SerializeStruct; // Blast libraries use blast_rpc_server::BlastRpcServer; @@ -39,6 +46,15 @@ pub mod blast_proto { tonic::include_proto!("blast_proto"); } +// The name of this model (should match the name in model.json) +pub const MODEL_NAME: &str = "blast_ldk"; + +// The directory to save simulations +pub const SIM_DIR: &str = "/.blast/blast_sims/"; + +// The temporary directory to save runtime ldk data +pub const DATA_DIR: &str = "/blast_data/"; + // The data that is stored in the sim-ln sim.json file #[derive(Serialize, Deserialize, Debug)] struct SimLnNode { @@ -61,6 +77,41 @@ struct Channel { pk: PublicKey } +impl Serialize for Channel { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut s = serializer.serialize_struct("Channel", 3)?; + s.serialize_field("source", &self.source)?; + s.serialize_field("id", &self.id.0)?; + s.serialize_field("pk", &self.pk)?; + s.end() + } +} + +impl<'de> Deserialize<'de> for Channel { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct ChannelHelper { + source: String, + id: u128, + pk: PublicKey + } + + let helper = ChannelHelper::deserialize(deserializer)?; + + Ok(Channel { + source: helper.source, + id: UserChannelId(helper.id), + pk: helper.pk + }) + } +} + // The main data structure for the LDK model struct BlastLdk { nodes: HashMap>, @@ -106,7 +157,7 @@ impl BlastLdkServer { fn get_available_port(&self) -> Option { (8000..9000).find(|port| self.port_is_available(*port)) } - + // Check if a port is available fn port_is_available(&self, port: u16) -> bool { match TcpListener::bind(("127.0.0.1", port)) { @@ -124,7 +175,7 @@ impl BlastRpc for BlastLdkServer { let num_nodes = request.get_ref().num_nodes; let mut node_list = SimJsonFile{nodes: Vec::new()}; let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); - data_dir.push_str("/blast_data/"); + data_dir.push_str(DATA_DIR); // Start the requested number of ldk nodes for i in 0..num_nodes { @@ -495,12 +546,48 @@ impl BlastRpc for BlastLdkServer { /// Load a previous state of this model async fn load(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + // untar data_dir into correct location + // count the number of nodes in the sim + // start_nodes(num_nodes) + // deserialize channels + // set the open_channels to the deserialized channels + + let load_response = BlastLoadResponse { success: true }; + let response = Response::new(load_response); + Ok(response) } /// Save this models current state - async fn save(&self, _request: Request) -> Result, Status> { - Err(Status::new(Code::InvalidArgument, "name is invalid")) + async fn save(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let sim_name = &req.sim; + let home_dir = env::var("HOME").expect("HOME environment variable not set"); + let sim_dir = String::from(SIM_DIR); + let sim_model_dir = format!("{}{}{}/{}/", home_dir, sim_dir, sim_name, MODEL_NAME); + + // Set paths for the archive and JSON file + let archive_path = Path::new(&sim_model_dir).join(format!("{}.tar.gz", sim_name)); + let json_path = Path::new(&sim_model_dir).join(format!("{}_channels.json", sim_name)); + + // Create the .tar.gz archive + let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); + data_dir.push_str("/blast_data/"); + if let Some(parent) = archive_path.parent() { + fs::create_dir_all(parent)?; + } + let tar_gz = File::create(&archive_path).unwrap(); + let enc = GzEncoder::new(tar_gz, Compression::default()); + let mut tar = TarBuilder::new(enc); + tar.append_dir_all(".", data_dir).unwrap(); + + // Serialize the HashMap to JSON and write to a file + let bldk = self.blast_ldk.lock().await; + let json_string = serde_json::to_string_pretty(&bldk.open_channels).unwrap(); + fs::write(&json_path, json_string)?; + + let save_response = BlastSaveResponse { success: true }; + let response = Response::new(save_response); + Ok(response) } } From b7c0180058ff9435d1721abf33a9ffbf4bd0292c Mon Sep 17 00:00:00 2001 From: Blake Johnson Date: Mon, 28 Oct 2024 17:41:06 +0000 Subject: [PATCH 13/13] Implementing the load function for ldk model --- blast_core/load_bitcoind.sh | 30 +++++++ blast_models/blast_ldk/blast_ldk/src/main.rs | 89 ++++++++++++++++++-- 2 files changed, 111 insertions(+), 8 deletions(-) diff --git a/blast_core/load_bitcoind.sh b/blast_core/load_bitcoind.sh index 9c12318..69cfd44 100755 --- a/blast_core/load_bitcoind.sh +++ b/blast_core/load_bitcoind.sh @@ -14,3 +14,33 @@ echo "rpcport=18443" >> ~/.bitcoin/bitcoin.conf bitcoind -daemon -reindex sleep 5 bitcoin-cli loadwallet test + +# https://bitcoin.stackexchange.com/questions/101153/setting-the-fee-rate-on-regtest-or-quickly-generating-full-blocks +cont=true +smartfee=$(bitcoin-cli estimatesmartfee 6) +if [[ "$smartfee" == *"\"feerate\":"* ]]; then + cont=false +fi +while $cont +do + counterb=0 + range=$(( $RANDOM % 11 + 20 )) + while [ $counterb -lt $range ] + do + power=$(( $RANDOM % 29 )) + randfee=`echo "scale=8; 0.00001 * (1.1892 ^ $power)" | bc` + newaddress=$(bitcoin-cli getnewaddress) + rawtx=$(bitcoin-cli createrawtransaction "[]" "[{\"$newaddress\":0.005}]") + fundedtx=$(bitcoin-cli fundrawtransaction "$rawtx" "{\"feeRate\": \"0$randfee\"}" | jq -r ".hex") + signedtx=$(bitcoin-cli signrawtransactionwithwallet "$fundedtx" | jq -r ".hex") + senttx=$(bitcoin-cli sendrawtransaction "$signedtx") + ((++counterb)) + echo "Created $counterb transactions this block" + done + bitcoin-cli generatetoaddress 1 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" + smartfee=$(bitcoin-cli estimatesmartfee 6) + if [[ "$smartfee" == *"\"feerate\":"* ]]; then + cont=false + fi +done +bitcoin-cli generatetoaddress 6 "mp76nrashrCCYLy3a8cAc5HufEas11yHbh" diff --git a/blast_models/blast_ldk/blast_ldk/src/main.rs b/blast_models/blast_ldk/blast_ldk/src/main.rs index e1ec2f5..00e77a1 100644 --- a/blast_models/blast_ldk/blast_ldk/src/main.rs +++ b/blast_models/blast_ldk/blast_ldk/src/main.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use std::env; use std::net::TcpListener; use std::path::Path; +use std::io::BufReader; // LDK Node libraries use ldk_node::bip39::serde::{Deserialize, Serialize}; @@ -37,6 +38,8 @@ use tar::Builder as TarBuilder; use serde::Serializer; use serde::Deserializer; use serde::ser::SerializeStruct; +use flate2::read::GzDecoder; +use tar::Archive; // Blast libraries use blast_rpc_server::BlastRpcServer; @@ -388,7 +391,7 @@ impl BlastRpc for BlastLdkServer { // Get the channel from the model's open channel map let id = req.channel_id; - let bldk = self.blast_ldk.lock().await; + let mut bldk = self.blast_ldk.lock().await; let channel = match bldk.open_channels.get(&id) { Some(c) => c, None => { @@ -404,6 +407,9 @@ impl BlastRpc for BlastLdkServer { } } + // Remove the channel from the model's list of open channels + bldk.open_channels.remove(&id); + // Respond to the close channel request let chan_response = BlastCloseChannelResponse { success: true }; let response = Response::new(chan_response); @@ -545,12 +551,79 @@ impl BlastRpc for BlastLdkServer { } /// Load a previous state of this model - async fn load(&self, _request: Request) -> Result, Status> { - // untar data_dir into correct location - // count the number of nodes in the sim - // start_nodes(num_nodes) - // deserialize channels - // set the open_channels to the deserialized channels + async fn load(&self, request: Request) -> Result, Status> { + let req = &request.get_ref(); + let sim_name = &req.sim; + let home_dir = env::var("HOME").expect("HOME environment variable not set"); + let sim_dir = String::from(SIM_DIR); + let sim_model_dir = format!("{}{}{}/{}/", home_dir, sim_dir, sim_name, MODEL_NAME); + + // Set paths for the archive and JSON file + let archive_path = Path::new(&sim_model_dir).join(format!("{}.tar.gz", sim_name)); + let json_path = Path::new(&sim_model_dir).join(format!("{}_channels.json", sim_name)); + + // Open the .tar.gz file + let tar_gz = File::open(archive_path)?; + let decompressor = GzDecoder::new(tar_gz); + let mut archive = Archive::new(decompressor); + // Extract the archive into the specified directory + let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); + data_dir.push_str(DATA_DIR); + let data_path = Path::new(&data_dir); + fs::create_dir_all(data_path).unwrap(); + archive.unpack(data_path).unwrap(); + + + // Count the number of nodes to start and remove the old symlink + let mut count = 0; + for entry in fs::read_dir(data_path).unwrap() { + let entry = match entry { + Ok(e) => e, + Err(_) => { + return Err(Status::new(Code::Unknown, "Could not read the data directory")); + } + }; + let path = entry.path(); + + // Check if the entry is a directory and if its name starts with blast_ldk- + if path.is_dir() { + if let Some(dir_name) = path.file_name().and_then(|n| n.to_str()) { + if dir_name.starts_with("blast_ldk-") { + count += 1; + + // Construct the path to the file to remove + let file_path = path.join("logs/ldk_node_latest.log"); + + // Attempt to remove the file if it exists + if file_path.exists() { + match fs::remove_file(&file_path) { + Ok(_) => {}, + Err(_) => {} + } + } + } + } + } + } + + // Attempt to start the nodes + let request = BlastStartRequest { num_nodes: count }; + let start_req = Request::new(request); + match self.start_nodes(start_req).await { + Ok(_) => {}, + Err(_) => { + return Err(Status::new(Code::Unknown, "Could not start nodes.")); + } + } + + // Open the JSON file + let file = File::open(json_path).unwrap(); + let reader = BufReader::new(file); + + // Deserialize JSON to Channel map + let chans: HashMap = serde_json::from_reader(reader).unwrap(); + let mut bldk = self.blast_ldk.lock().await; + bldk.open_channels = chans; let load_response = BlastLoadResponse { success: true }; let response = Response::new(load_response); @@ -573,7 +646,7 @@ impl BlastRpc for BlastLdkServer { let mut data_dir = env!("CARGO_MANIFEST_DIR").to_owned(); data_dir.push_str("/blast_data/"); if let Some(parent) = archive_path.parent() { - fs::create_dir_all(parent)?; + fs::create_dir_all(parent).unwrap(); } let tar_gz = File::create(&archive_path).unwrap(); let enc = GzEncoder::new(tar_gz, Compression::default());