init
This commit is contained in:
		
						commit
						3fc3666389
					
				
							
								
								
									
										1168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1168
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										22
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | |||||||
|  | [package] | ||||||
|  | name = "bal-server" | ||||||
|  | version = "0.1.0" | ||||||
|  | edition = "2021" | ||||||
|  | 
 | ||||||
|  | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||||
|  | [dependencies] | ||||||
|  | hyper = { version = "1.2", features = ["full"] } | ||||||
|  | tokio = { version = "1", features = ["full"] } | ||||||
|  | http-body-util = "0.1" | ||||||
|  | hyper-util = { version = "0.1", features = ["full"] } | ||||||
|  | bytes = "1.2" | ||||||
|  | serde = { version = "1.0.152", features = ["derive"] } # <- Only one serde version needed (serde or serde_derive)  | ||||||
|  | serde_json = "1.0.116" | ||||||
|  | confy = "0.6.1" | ||||||
|  | bitcoin = "0.31.0" | ||||||
|  | sqlite = "0.34.0" | ||||||
|  | hex-conservative = "0.1.1" | ||||||
|  | regex = "1.10.4" | ||||||
|  | log = "0.4.21" | ||||||
|  | env_logger = "0.11.5" | ||||||
|  | 
 | ||||||
							
								
								
									
										30
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | |||||||
|  | # bal-server | ||||||
|  | 
 | ||||||
|  | ## Installation | ||||||
|  | 
 | ||||||
|  | ```bash | ||||||
|  | $ git clone .... | ||||||
|  | $ cd bal-server | ||||||
|  | $ cargo build --release | ||||||
|  | $ sudo cp target/release/bal-server /usr/local/bin | ||||||
|  | $ bal-server | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## Configuration | ||||||
|  | 
 | ||||||
|  | The `bal-server` application can be configured using environment variables. The following variables are available: | ||||||
|  | 
 | ||||||
|  | | Variable | Description | Default | | ||||||
|  | | --- | --- | --- | | ||||||
|  | | `BAL_SERVER_CONFIG_FILE` | Path to the configuration file. If the file does not exist, a new one will be created. | `$HOME/.config/bal-server/default-config.toml` | | ||||||
|  | | `BAL_SERVER_DB_FILE` | Path to the SQLite3 database file. If the file does not exist, a new one will be created. | `bal.db` | | ||||||
|  | | `BAL_SERVER_BIND_ADDRESS` | Public address for listening to requests. | `127.0.0.1` | | ||||||
|  | | `BAL_SERVER_BIND_PORT` | Default port for listening to requests. | `9137` | | ||||||
|  | | `BAL_SERVER_REGTEST_ADDRESS` | Bitcoin address for the regtest environment. | - | | ||||||
|  | | `BAL_SERVER_REGTEST_FIXED_FEE` | Fixed fee for the regtest environment. | 50000 | | ||||||
|  | | `BAL_SERVER_SIGNET_ADDRESS` | Bitcoin address for the signet environment. | - | | ||||||
|  | | `BAL_SERVER_SIGNET_FIXED_FEE` | Fixed fee for the signet environment. | 50000 | | ||||||
|  | | `BAL_SERVER_TESTNET_ADDRESS` | Bitcoin address for the testnet environment. | - | | ||||||
|  | | `BAL_SERVER_TESTNET_FIXED_FEE` | Fixed fee for the testnet environment. | 50000 | | ||||||
|  | | `BAL_SERVER_BITCOIN_ADDRESS` | Bitcoin address for the mainnet environment. | - | | ||||||
|  | | `BAL_SERVER_BITCOIN_FIXED_FEE` | Fixed fee for the mainnet environment. | 50000 | | ||||||
							
								
								
									
										11
									
								
								bal-server.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								bal-server.env
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | RUST_LOG=info | ||||||
|  | BAL_SERVER_DB_FILE=/path/to/bal.db | ||||||
|  | BAL_SERVER_BIND_ADDRESS=127.0.0.1 | ||||||
|  | BAL_SERVER_BITCOIN_ADDRESS=your mainnet address | ||||||
|  | BAL_SERVER_BITCOIN_FEE=100000 | ||||||
|  | BAL_SERVER_REGTEST_ADDRESS= | ||||||
|  | BAL_SERVER_REGTEST_FEE=100000 | ||||||
|  | BAL_SERVER_TESTNET_ADDRESS= | ||||||
|  | BAL_SERVER_TESTNET_FEE=100000 | ||||||
|  | BAL_SERVER_SIGNET_ADDRESS= | ||||||
|  | BAL_SERVER_SIGNET_FEE=100000 | ||||||
							
								
								
									
										39
									
								
								bal-server.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								bal-server.service
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | |||||||
|  | [Unit] | ||||||
|  | Description=bal-server daemon | ||||||
|  | After=network.target | ||||||
|  | 
 | ||||||
|  | [Service] | ||||||
|  | 
 | ||||||
|  | EnvironmentFile=/etc/bal/bal-server.env | ||||||
|  | 
 | ||||||
|  | ExecStart=/usr/local/bin/bal-server  | ||||||
|  | 
 | ||||||
|  | StandardOutput=syslog | ||||||
|  | StandardError=syslog | ||||||
|  | SyslogIdentifier=bal-server | ||||||
|  | 
 | ||||||
|  | Type=simple | ||||||
|  | PIDFile=/run/bal-server/bal-server.pid | ||||||
|  | Restart=always | ||||||
|  | TimeoutSec=300 | ||||||
|  | RestartSec=30 | ||||||
|  | 
 | ||||||
|  | User=bal | ||||||
|  | UMask=0027 | ||||||
|  | 
 | ||||||
|  | RuntimeDirectory=bal-server | ||||||
|  | RuntimeDirectoryMode=0710 | ||||||
|  | 
 | ||||||
|  | PrivateTmp=true | ||||||
|  | 
 | ||||||
|  | ProtectSystem=full | ||||||
|  | 
 | ||||||
|  | NoNewPrivileges=true | ||||||
|  | 
 | ||||||
|  | PrivateDevices=true | ||||||
|  | 
 | ||||||
|  | MemoryDenyWriteExecute=true | ||||||
|  | 
 | ||||||
|  | [Install] | ||||||
|  | WantedBy=multi-user.target | ||||||
|  | 
 | ||||||
							
								
								
									
										501
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										501
									
								
								src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,501 @@ | |||||||
|  | 
 | ||||||
|  | use bytes::Bytes; | ||||||
|  | use http_body_util::{combinators::BoxBody, BodyExt, Empty, Full}; | ||||||
|  | use hyper::server::conn::http1; | ||||||
|  | use hyper::service::service_fn; | ||||||
|  | use hyper::{Method, Request, Response, StatusCode}; | ||||||
|  | use tokio::net::TcpListener; | ||||||
|  | use hyper_util::rt::TokioIo; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | use std::net::IpAddr; | ||||||
|  | use std::env; | ||||||
|  | 
 | ||||||
|  | use std::time::{SystemTime,UNIX_EPOCH}; | ||||||
|  | use std::sync::{Arc, Mutex, MutexGuard}; | ||||||
|  | use std::collections::HashMap; | ||||||
|  | use sqlite::{State,Connection}; | ||||||
|  | 
 | ||||||
|  | use bitcoin::{consensus, Transaction, Network}; | ||||||
|  | 
 | ||||||
|  | use hex_conservative::FromHex; | ||||||
|  | use regex::Regex; | ||||||
|  | use serde::{Serialize, Deserialize}; | ||||||
|  | use log::{info,error,trace,debug}; | ||||||
|  | use serde_json; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone,Serialize, Deserialize)] | ||||||
|  | struct NetConfig { | ||||||
|  |     address: String, | ||||||
|  |     fixed_fee: u64, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for NetConfig { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         NetConfig { | ||||||
|  |             address: "".to_string(), | ||||||
|  |             fixed_fee: 50000, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl NetConfig { | ||||||
|  |     fn default_regtest() -> Self { | ||||||
|  |         NetConfig { | ||||||
|  |             address: "".to_string(), | ||||||
|  |             fixed_fee: 50000, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Serialize, Deserialize,Clone)] | ||||||
|  | struct MyConfig { | ||||||
|  |     regtest: NetConfig, | ||||||
|  |     signet: NetConfig, | ||||||
|  |     testnet4: NetConfig, | ||||||
|  |     testnet3: NetConfig, | ||||||
|  |     mainnet: NetConfig, | ||||||
|  |     bind_address: String, | ||||||
|  |     bind_port: u16, // Changed to u16 for port numbers
 | ||||||
|  |     db_file: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Default for MyConfig { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         MyConfig { | ||||||
|  |             regtest: NetConfig::default_regtest(), | ||||||
|  |             signet: NetConfig::default(), // Use default for other networks
 | ||||||
|  |             testnet4: NetConfig::default(), | ||||||
|  |             testnet3: NetConfig::default(), | ||||||
|  |             mainnet: NetConfig::default(), | ||||||
|  |             bind_address: "127.0.0.1".to_string(), | ||||||
|  |             bind_port: 9137, // Ensure this is a u16
 | ||||||
|  |             db_file: "../bal.db".to_string(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | impl MyConfig { | ||||||
|  |     fn get_net_config(&self, param: &str) -> &NetConfig{ | ||||||
|  |         match param { | ||||||
|  |             "regtest" => &self.regtest, | ||||||
|  |             "testnet" => &self.testnet3, | ||||||
|  |             "signet" => &self.signet, | ||||||
|  |             _ => &self.mainnet, 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async fn echo_info( | ||||||
|  |                    param: &str, | ||||||
|  |                    cfg: &MyConfig, | ||||||
|  | ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { | ||||||
|  |         info!("echo info!!!{}",param); | ||||||
|  |         let netconfig=MyConfig::get_net_config(cfg,param); | ||||||
|  |         return Ok(Response::new(full("{\"address\":\"".to_owned()+&netconfig.address+"\",\"base_fee\":\""+&netconfig.fixed_fee.to_string()+"\"}"))); | ||||||
|  | } | ||||||
|  | async fn echo_search(whole_body: &Bytes, | ||||||
|  |                      cfg: &MyConfig, | ||||||
|  | ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { | ||||||
|  |     info!("echo search!!!"); | ||||||
|  |     let strbody = std::str::from_utf8(&whole_body).unwrap(); | ||||||
|  |     info!("{}",strbody); | ||||||
|  |     trace!("{}",strbody.len()); | ||||||
|  | 
 | ||||||
|  |     let mut response = Response::new(full("Bad data received".to_owned())); | ||||||
|  |     *response.status_mut() = StatusCode::BAD_REQUEST; 
 | ||||||
|  |     if strbody.len() >0 && strbody.len()<=70 { | ||||||
|  |         let db = sqlite::open(&cfg.db_file).unwrap(); | ||||||
|  |         trace!("qua ci arrivo"); | ||||||
|  |         let mut statement = db.prepare("SELECT * FROM tbl_tx WHERE txid = ?").unwrap(); | ||||||
|  |         statement.bind((1, strbody)).unwrap(); | ||||||
|  | 
 | ||||||
|  |         while let Ok(State::Row) = statement.next() { | ||||||
|  |             trace!("qua tutto ok"); | ||||||
|  |             let mut response_data = HashMap::new(); | ||||||
|  |             match statement.read::<String, _>("status") { | ||||||
|  |                 Ok(value) => response_data.insert("status", value), | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Error reading status: {}", e); | ||||||
|  |                     break; | ||||||
|  |                     //response_data.insert("status", "Error".to_string())
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // Read the transaction (tx)
 | ||||||
|  |             match statement.read::<String, _>("tx") { | ||||||
|  |                 Ok(value) => response_data.insert("tx", value), | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Error reading tx: {}", e); | ||||||
|  |                     break; | ||||||
|  |                     //response_data.insert("tx", "Error".to_string())
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             match statement.read::<String, _>("our_address") { | ||||||
|  |                 Ok(value) => response_data.insert("our_address", value), | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Error reading address: {}", e); | ||||||
|  |                     break; | ||||||
|  |                     //response_data.insert("tx", "Error".to_string())
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             match statement.read::<String, _>("our_fees") { | ||||||
|  |                 Ok(value) => response_data.insert("our_fees", value), | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Error reading fees: {}", e); | ||||||
|  |                     break; | ||||||
|  |                     //response_data.insert("tx", "Error".to_string())
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             // Read the request id (reqid)
 | ||||||
|  |             match statement.read::<String, _>("reqid") { | ||||||
|  |                 Ok(value) => response_data.insert("time", value), | ||||||
|  |                 Err(e) => { | ||||||
|  |                     error!("Error reading reqid: {}", e); | ||||||
|  |                     break; | ||||||
|  |                     //response_data.insert("time", "Error".to_string())
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             response = match serde_json::to_string(&response_data){ | ||||||
|  |                 Ok(json_data) => Response::new(full(json_data)), | ||||||
|  |                 Err(_) => {break;} | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             return Ok(response); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     Ok(response) | ||||||
|  | 
 | ||||||
|  |     
 | ||||||
|  | } | ||||||
|  | async fn echo_push(whole_body: &Bytes, | ||||||
|  |                    cfg: &MyConfig, | ||||||
|  |                    param: &str, | ||||||
|  | ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { | ||||||
|  |         //let whole_body = req.collect().await?.to_bytes();
 | ||||||
|  |         let strbody = std::str::from_utf8(&whole_body).unwrap(); | ||||||
|  |         let lines = strbody.split("\n"); | ||||||
|  | 
 | ||||||
|  |         let mut response = Response::new(full("Bad data received".to_owned())); | ||||||
|  |         *response.status_mut() = StatusCode::BAD_REQUEST; 
 | ||||||
|  |         debug!("network: {}", param); | ||||||
|  |         let network = match param{ | ||||||
|  |             "testnet" => Network::Testnet, | ||||||
|  |             "signet" => Network::Signet, | ||||||
|  |             "regtest" => Network::Regtest, | ||||||
|  |             &_ => Network::Bitcoin, | ||||||
|  |         }; | ||||||
|  |         
 | ||||||
|  |         let req_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_nanos(); | ||||||
|  |         
 | ||||||
|  |         
 | ||||||
|  |         let sqltxshead = "INSERT INTO tbl_tx (txid, wtxid, ntxid, tx, locktime, reqid, network, our_address, our_fees)".to_string(); | ||||||
|  |         let mut sqltxs = "".to_string(); | ||||||
|  |         //let mut sqlinps = "INSERT INTO tbl_input (txid, in_txid,in_vout)".to_string();
 | ||||||
|  |         //let mut sqlouts = "INSERT INTO tbl_output (txid, script_pubkey, address, amount)\n".to_string();
 | ||||||
|  |         let mut union_tx = true; | ||||||
|  |         let mut already_present = false; | ||||||
|  |         //let mut union_inps = true;
 | ||||||
|  |         //let mut union_outs = true;
 | ||||||
|  |         let db = sqlite::open(&cfg.db_file).unwrap(); | ||||||
|  |         let netconfig = MyConfig::get_net_config(cfg,param); | ||||||
|  |         for line in lines { | ||||||
|  |             if line.len() == 0{ | ||||||
|  |                 trace!("line len is: {}",line.len()); | ||||||
|  |                 continue
 | ||||||
|  |             } | ||||||
|  |             let linea = format!("{req_time}:{line}"); | ||||||
|  |             info!("New Tx: {}", linea); | ||||||
|  |             let raw_tx =  match Vec::<u8>::from_hex(line) { | ||||||
|  |                 Ok(raw_tx) => raw_tx, | ||||||
|  |                 Err(err) => { | ||||||
|  |                     error!("rawtx error: {}",err); | ||||||
|  |                     continue
 | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             if raw_tx.len() > 0 { | ||||||
|  |                 trace!("len: {}",raw_tx.len()); | ||||||
|  |                 let tx: Transaction = match consensus::deserialize(&raw_tx){ | ||||||
|  |                     Ok(tx) => tx, | ||||||
|  |                     Err(err) => {error!("error: unable to parse tx: {}\n{}",line,err);continue} | ||||||
|  |                 }; | ||||||
|  |                 let txid = tx.txid().to_string(); | ||||||
|  |                 trace!("txid: {}",txid); | ||||||
|  |                 let mut statement = db.prepare("SELECT * FROM tbl_tx WHERE txid = ?").unwrap(); | ||||||
|  |                 statement.bind((1,&txid[..])).unwrap(); | ||||||
|  |                 //statement.bind((1,"Bob")).unwrap();
 | ||||||
|  |                 if let Ok(State::Row) = statement.next() { | ||||||
|  |                     trace!("already present"); | ||||||
|  |                     already_present=true; | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 let ntxid = tx.ntxid(); | ||||||
|  |                 let wtxid = tx.wtxid(); | ||||||
|  |                 let mut found = false; | ||||||
|  |                 let locktime = tx.lock_time; | ||||||
|  |                 let mut our_fees = 0; | ||||||
|  |                 let mut our_address:String = "".to_string(); | ||||||
|  |                 //dbg!(netconfig.fixed_fee);
 | ||||||
|  |                 if netconfig.fixed_fee >0 { | ||||||
|  |                     for output in tx.output{ | ||||||
|  |                         let script_pubkey = output.script_pubkey; | ||||||
|  |                         let address = match bitcoin::Address::from_script(script_pubkey.as_script(), network){ | ||||||
|  |                             Ok(address) => address.to_string(), | ||||||
|  |                             Err(_) => String::new(), | ||||||
|  |                         }; | ||||||
|  |                         //dbg!(&address);
 | ||||||
|  |                         let amount = output.value; | ||||||
|  |                         //dbg!(&amount); 
 | ||||||
|  |                         //search wllexecutor output
 | ||||||
|  |                         if address == netconfig.address.to_string() && amount.to_sat() >= netconfig.fixed_fee{ | ||||||
|  |                             our_fees = amount.to_sat(); | ||||||
|  |                             our_address = netconfig.address.to_string(); | ||||||
|  |                             found = true; | ||||||
|  |                             trace!("address and fees are correct {}: {}",our_address,our_fees); | ||||||
|  |                             break; | ||||||
|  |                         }else { | ||||||
|  |                             trace!("address and fees not found {}: {}",address,amount.to_sat()); | ||||||
|  |                             trace!("address and fees not found {}: {}",netconfig.address.to_string(),netconfig.fixed_fee); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } else { found = true; } | ||||||
|  |                 if found == false{ | ||||||
|  |                     error!("willexecutor output not found "); | ||||||
|  |                     //return Ok(response)
 | ||||||
|  |                 } else { | ||||||
|  |                     if union_tx == false { | ||||||
|  |                         sqltxs = format!("{sqltxs} UNION ALL"); | ||||||
|  |                     }else{ | ||||||
|  |                         union_tx = false; | ||||||
|  |                     } | ||||||
|  |                     sqltxs = format!("{sqltxs}  SELECT '{txid}', '{wtxid}', '{ntxid}', '{line}', '{locktime}', '{req_time}', '{network}','{our_address}',{our_fees}"); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             }            
 | ||||||
|  |             else{ | ||||||
|  |                 trace!("rawTx len is: {}",raw_tx.len()); | ||||||
|  |                 debug!("{}",&sqltxs); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         debug!("SQL: {}",sqltxs); | ||||||
|  |         if sqltxs.len()== 0{ | ||||||
|  |             if already_present == true{ | ||||||
|  |                 //dbg!(already_present);
 | ||||||
|  |                 return Ok(Response::new(full("already present"))) | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         let _ = db.execute("BEGIN TRANSACTION"); | ||||||
|  |         let sql = format!("{}{}",sqltxshead,sqltxs); | ||||||
|  |         if let Err(err) = db.execute(&sql){ | ||||||
|  |             error!("error executing sql:{} - {}",&sql,err); | ||||||
|  |             let _ = db.execute("ROLLBACK"); | ||||||
|  |             //dbg!(&already_present);
 | ||||||
|  |             if already_present == true{ | ||||||
|  |                 trace!("already_present = True"); | ||||||
|  |                 return Ok(Response::new(full("already present"))) | ||||||
|  |             } | ||||||
|  |         
 | ||||||
|  |             return Ok(response) | ||||||
|  |         } | ||||||
|  |         //if !error {
 | ||||||
|  |         //    if let Err(_) = db.execute(sqlinps){
 | ||||||
|  |         //        let _ = db.execute("ROLLBACK");
 | ||||||
|  |         //        error = true
 | ||||||
|  |         //    }
 | ||||||
|  |         //}
 | ||||||
|  |         //if !error {
 | ||||||
|  |         //    if let Err(_) = db.execute(sqlouts){
 | ||||||
|  |         //        let _ = db.execute("ROLLBACK");
 | ||||||
|  |         //        error = true;
 | ||||||
|  |         //    }
 | ||||||
|  |         //}
 | ||||||
|  |         let _ = db.execute("COMMIT"); | ||||||
|  |         Ok(Response::new(full("thx"))) | ||||||
|  | } 
 | ||||||
|  | fn create_database(db: Connection){ | ||||||
|  |     info!("database sanity check"); | ||||||
|  |     let _ = db.execute("CREATE TABLE IF NOT EXISTS tbl_tx      (txid PRIMARY KEY, wtxid, ntxid, tx, locktime integer, network, network_fees, reqid, our_fees, our_address, status integer DEFAULT 0);"); | ||||||
|  |     let _ = db.execute("CREATE TABLE IF NOT EXISTS tbl_input   (txid, in_txid,in_vout, spend_txidi);"); | ||||||
|  |     let _ = db.execute("CREATE TABLE IF NOT EXISTS tbl_output  (txid, script_pubkey, address, amount);"); | ||||||
|  | 
 | ||||||
|  |     let _ = db.execute("CREATE UNIQUE INDEX idx_tbl_input   ON(txid, txid,in_txid,in_vout)"); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | fn match_uri<'a>(path: &str, uri: &'a str) -> Option<&'a str> { | ||||||
|  |     let re = Regex::new(path).unwrap(); | ||||||
|  |     if let Some(captures) = re.captures(uri) { | ||||||
|  |         if let Some(param) = captures.name("param") { | ||||||
|  |             return Some(param.as_str()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     None | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// This is our service handler. It receives a Request, routes on its
 | ||||||
|  | /// path, and returns a Future of a Response.
 | ||||||
|  | 
 | ||||||
|  | async fn echo( | ||||||
|  |     req: Request<hyper::body::Incoming>, | ||||||
|  |     cfg: &MyConfig, | ||||||
|  | ) -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> { | ||||||
|  | 
 | ||||||
|  |     let mut not_found = Response::new(empty()); | ||||||
|  |     *not_found.status_mut() = StatusCode::NOT_FOUND; | ||||||
|  |     let mut ret: Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> = Ok(not_found); | ||||||
|  | 
 | ||||||
|  |     let uri = req.uri().path().to_string(); | ||||||
|  |     //dbg!(&req);
 | ||||||
|  |     //dbg!(&uri);
 | ||||||
|  |     match req.method() { | ||||||
|  |         // Serve some instructions at /
 | ||||||
|  |         &Method::POST => { | ||||||
|  |             let whole_body = req.collect().await?.to_bytes(); | ||||||
|  |             if let Some(param) = match_uri(r"^?/?(?P<param>[^/]?+)?/pushtxs$",uri.as_str()) { | ||||||
|  |                 //let whole_body = collect_body(req,512_000).await?;
 | ||||||
|  |                 ret = echo_push(&whole_body,cfg,param).await; | ||||||
|  |             } | ||||||
|  |             if uri=="/searchtx"{ | ||||||
|  |                 //let whole_body = collect_body(req,64).await?;
 | ||||||
|  |                 ret = echo_search(&whole_body,cfg).await; | ||||||
|  |             } | ||||||
|  |             ret | ||||||
|  |         } | ||||||
|  |         &Method::GET => { | ||||||
|  |             if let Some(param) = match_uri(r"^?/?(?P<param>[^/]?+)?/info$",uri.as_str()) { | ||||||
|  |                 ret = echo_info(param,cfg).await; | ||||||
|  |             } | ||||||
|  |             ret | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Return the 404 Not Found for other routes.
 | ||||||
|  |         _ => ret | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn empty() -> BoxBody<Bytes, hyper::Error> { | ||||||
|  |     Empty::<Bytes>::new() | ||||||
|  |         .map_err(|never| match never {}) | ||||||
|  |         .boxed() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, hyper::Error> { | ||||||
|  |     Full::new(chunk.into()) | ||||||
|  |         .map_err(|never| match never {}) | ||||||
|  |         .boxed() | ||||||
|  | } | ||||||
|  | fn parse_env(cfg: &Arc<Mutex<MyConfig>>){ | ||||||
|  |     let mut cfg_lock = cfg.lock().unwrap(); | ||||||
|  |     match env::var("BAL_SERVER_DB_FILE") { | ||||||
|  |         Ok(value) => { | ||||||
|  |             cfg_lock.db_file = value;}, | ||||||
|  |         Err(_) => {}, | ||||||
|  |     } | ||||||
|  |     match env::var("BAL_SERVER_BIND_ADDRESS") { | ||||||
|  |         Ok(value) => { | ||||||
|  |             cfg_lock.bind_address = value;}, | ||||||
|  |         Err(_) => {}, | ||||||
|  |     } | ||||||
|  |     match env::var("BAL_SERVER_BIND_PORT") { | ||||||
|  |         Ok(value) => { | ||||||
|  |             cfg_lock.bind_address = value;}, | ||||||
|  |         Err(_) => {}, | ||||||
|  |     } | ||||||
|  |     cfg_lock = parse_env_netconfig(cfg_lock,"regtest");    
 | ||||||
|  |     cfg_lock = parse_env_netconfig(cfg_lock,"signet");    
 | ||||||
|  |     cfg_lock = parse_env_netconfig(cfg_lock,"testnet3");    
 | ||||||
|  |     cfg_lock = parse_env_netconfig(cfg_lock,"testnet");    
 | ||||||
|  |     drop(parse_env_netconfig(cfg_lock,"bitcoin")); | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | fn parse_env_netconfig<'a>(mut cfg_lock: MutexGuard<'a, MyConfig>, chain: &'a str) -> MutexGuard<'a, MyConfig>{ | ||||||
|  |     let cfg = match chain{ | ||||||
|  |         "regtest" => &mut cfg_lock.regtest, | ||||||
|  |         "signet" => &mut cfg_lock.signet, | ||||||
|  |         "testnet3" => &mut cfg_lock.testnet3, | ||||||
|  |         "testnet" => &mut cfg_lock.testnet4, | ||||||
|  |         &_ => &mut cfg_lock.mainnet, | ||||||
|  |     }; | ||||||
|  |     match env::var(format!("BAL_SERVER_{}_ADDRESS",chain.to_uppercase())) { | ||||||
|  |         Ok(value) => { cfg.address = value; }, | ||||||
|  |         Err(_) => {}, | ||||||
|  |     } | ||||||
|  |     match env::var(format!("BAL_SERVER_{}_FIXE_FEE",chain.to_uppercase())) { | ||||||
|  |         Ok(value) => { | ||||||
|  |             match value.parse::<u64>(){ | ||||||
|  |                 Ok(value) =>{ cfg.fixed_fee = value; }, | ||||||
|  |                 Err(_) => {}, | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Err(_) => {}, | ||||||
|  |     } | ||||||
|  |     cfg_lock | ||||||
|  | } | ||||||
|  | fn get_default_config()-> MyConfig { | ||||||
|  | 
 | ||||||
|  |     let file = confy::get_configuration_file_path("bal-server",None).expect("Error while getting path"); | ||||||
|  |     info!("Default configuration file path is: {:#?}", file); | ||||||
|  |     confy::load("bal-server",None).expect("cant_load") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #[tokio::main] | ||||||
|  | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|  |     env_logger::init(); | ||||||
|  |     let cfg: Arc<Mutex<MyConfig>> = match env::var("BAL_SERVER_CONFIG_FILE") { | ||||||
|  |         Ok(value) => { | ||||||
|  |             Arc::new(Mutex::new( | ||||||
|  |                 match confy::load_path(value.to_string()){ | ||||||
|  |                     Ok(val) => { | ||||||
|  |                         info!("The configuration file path is: {:#?}", value); | ||||||
|  |                         val | ||||||
|  |                     }, | ||||||
|  |                     Err(err) => { | ||||||
|  |                         error!("{}",err); | ||||||
|  |                         get_default_config() | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             )) | ||||||
|  |         }, | ||||||
|  |         Err(_) => { | ||||||
|  |             Arc::new(Mutex::new(get_default_config())) | ||||||
|  |         }, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     parse_env(&cfg); | ||||||
|  |     let cfg_lock = cfg.lock().unwrap(); | ||||||
|  | 
 | ||||||
|  |     let db = sqlite::open(&cfg_lock.db_file).unwrap(); | ||||||
|  |     create_database(db); | ||||||
|  | 
 | ||||||
|  |     let addr = cfg_lock.bind_address.to_string(); | ||||||
|  |     info!("bind address:{}",addr); | ||||||
|  |     let addr: IpAddr = addr.parse()?; | ||||||
|  | 
 | ||||||
|  |     let listener = TcpListener::bind((addr,cfg_lock.bind_port)).await?; | ||||||
|  |     info!("Listening on http://{}:{}", addr,cfg_lock.bind_port); | ||||||
|  |     
 | ||||||
|  | 
 | ||||||
|  |     loop { | ||||||
|  |         let (stream, _) = listener.accept().await?; | ||||||
|  |         let io = TokioIo::new(stream); | ||||||
|  |         
 | ||||||
|  |         tokio::task::spawn({ | ||||||
|  |             let cfg = cfg_lock.clone(); | ||||||
|  |             async move { | ||||||
|  |                 if let Err(err) = http1::Builder::new() | ||||||
|  |                     .serve_connection(io, service_fn(|req: Request<hyper::body::Incoming>| async { | ||||||
|  |                         echo(req,&cfg).await | ||||||
|  |                     })) | ||||||
|  |                     .await | ||||||
|  |                 { | ||||||
|  |                     error!("Error serving connection: {:?}", err); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 bitcoinafterlife
						bitcoinafterlife