depolymerization

wire gateway for Bitcoin/Ethereum
Log | Files | Refs | Submodules | README | LICENSE

commit ccade06e09b68cf7ee79a91ddf9413760925da14
parent b80fee7cc3a059eda7ae14d041d25e7b7b093733
Author: Antoine A <>
Date:   Tue, 16 Nov 2021 16:36:04 +0100

Use transaction listener to handle multipart msg

Diffstat:
Msrc/main.rs | 737+++++++++++++++++++++++++++++++++++++++++--------------------------------------
1 file changed, 382 insertions(+), 355 deletions(-)

diff --git a/src/main.rs b/src/main.rs @@ -1,355 +1,382 @@ -use std::{collections::HashSet, path::PathBuf, str::FromStr, time::Duration}; - -use bitcoincore_rpc::{ - bitcoin::{Amount, Txid}, - Auth, Client, RpcApi, -}; -use depolymerization::{utils::rand_key, ClientExtended}; - -const CLIENT: &str = "client"; -const WIRE: &str = "wire"; -const RPC_URL: &str = "http://localhost:18443"; - -/// Bitcoin networks -#[derive(Debug, Clone, Copy)] -pub enum Network { - MainNet, - TestNet, - RegTest, -} - -impl Network { - pub fn dir(&self) -> &'static str { - match self { - Network::MainNet => "main", - Network::TestNet => "testnet3", - Network::RegTest => "regtest", - } - } -} - -fn data_dir_path() -> PathBuf { - // https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#:~:text=By%20default%2C%20the%20configuration%20file,file%3E%20option%20in%20the%20bitcoin. - if cfg!(target_os = "windows") { - PathBuf::from_str(&std::env::var("APPDATA").unwrap()) - .unwrap() - .join("Bitcoin") - } else if cfg!(target_os = "linux") { - PathBuf::from_str(&std::env::var("HOME").unwrap()) - .unwrap() - .join(".bitcoin") - } else if cfg!(target_os = "macos") { - PathBuf::from_str(&std::env::var("HOME").unwrap()) - .unwrap() - .join("Library/Application Support/Bitcoin") - } else { - unimplemented!("Only windows, linux or macos") - } -} - -fn network_dir_path(network: Network) -> PathBuf { - data_dir_path().join(network.dir()) -} - -#[derive(argh::FromArgs)] -/// Bitcoin metadata tester -struct Args { - #[argh(subcommand)] - cmd: Cmd, -} - -#[derive(argh::FromArgs)] -/// Test library -#[argh(subcommand, name = "test")] -struct TestCmd {} - -#[derive(argh::FromArgs)] -/// Mine block for the given wallet -#[argh(subcommand, name = "mine")] -struct MineCmd { - #[argh(option, short = 'r')] - /// repeat every ? ms - repeat: Option<u64>, - - #[argh(positional)] - /// the wallet name - wallet: String, - - #[argh(positional, default = "1")] - /// repeat every ? ms - amount: u64, -} - -#[derive(argh::FromArgs)] - -/// Send message -#[argh(subcommand, name = "send")] -struct SendRole { - #[argh(switch, short = 'm')] - /// mine on send - mine: bool, -} - -#[derive(argh::FromArgs)] - -/// Receive message -#[argh(subcommand, name = "receive")] -struct ReceiveRole {} - -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Role { - Send(SendRole), - Receive(ReceiveRole), -} - -enum Metadata { - SegWit, - OpReturn, -} - -impl ToString for Metadata { - fn to_string(&self) -> String { - match self { - Metadata::SegWit => "SegWit", - Metadata::OpReturn => "OpReturn", - } - .to_string() - } -} - -impl FromStr for Metadata { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "SegWit" | "Segwit" | "segwit" | "seg" | "s" => Ok(Self::SegWit), - "OpReturn" | "Opreturn" | "opreturn" | "op" | "o" => Ok(Self::OpReturn), - _ => Err(format!("Unknown")), - } - } -} - -#[derive(argh::FromArgs)] -/// Msg exchange msg using metadata -#[argh(subcommand, name = "msg")] -struct MsgCmd { - #[argh(subcommand)] - role: Role, - - #[argh(positional)] - method: Option<Metadata>, -} - -#[derive(argh::FromArgs)] -#[argh(subcommand)] -enum Cmd { - Mine(MineCmd), - Test(TestCmd), - Msg(MsgCmd), -} - -fn common_rpc(network: Network) -> bitcoincore_rpc::Result<Client> { - Client::new( - RPC_URL, - Auth::CookieFile(network_dir_path(network).join(".cookie")), - ) -} - -fn wallet_rpc(network: Network, wallet: &str) -> Client { - Client::new( - &format!("{}/wallet/{}", RPC_URL, wallet), - Auth::CookieFile(network_dir_path(network).join(".cookie")), - ) - .expect(&format!("Failed to open wallet '{}' client", wallet)) -} - -fn last_transaction(rpc: &Client) -> bitcoincore_rpc::Result<Txid> { - Ok(rpc - .list_transactions(None, None, None, None)? - .last() - .unwrap() - .info - .txid) -} - -fn main() { - // Guess network by trying to connect to a JSON RPC server - let network = (|| { - let result_main = common_rpc(Network::MainNet).and_then(|rpc| rpc.get_network_info()); - if result_main.is_ok() { - println!("Connected to the main chain"); - return Network::MainNet; - } - let result_test = common_rpc(Network::TestNet).and_then(|rpc| rpc.get_network_info()); - if result_test.is_ok() { - println!("Connected to the test chain"); - return Network::TestNet; - } - let result_reg = common_rpc(Network::RegTest).and_then(|rpc| rpc.get_network_info()); - if result_reg.is_ok() { - println!("Connected to the reg chain"); - return Network::RegTest; - } - - unreachable!( - "Failed to connect to any chain\nmain: {:?}\ntest: {:?}\nreg: {:?}", - result_main, result_test, result_reg - ); - })(); - { - let existing_wallets: HashSet<String> = - std::fs::read_dir(network_dir_path(network).join("wallets")) - .unwrap() - .filter_map(|it| it.ok()) - .map(|it| it.file_name().to_string_lossy().to_string()) - .collect(); - - let rpc = common_rpc(network).expect("Failed to open common client"); - if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) { - println!("Generate new wallets"); - // Create wallets - rpc.create_wallet(&WIRE, None, None, None, None).unwrap(); - rpc.create_wallet(&CLIENT, None, None, None, None).unwrap(); - // Add 50 BTC to client wallet - let rpc = wallet_rpc(network, CLIENT); - let addr = rpc.get_new_address(None, None).unwrap(); - rpc.generate_to_address(101 /* Need 100 blocks to validate */, &addr) - .unwrap(); - } else { - rpc.load_wallet(&WIRE).ok(); - rpc.load_wallet(&CLIENT).ok(); - } - } - - let args: Args = argh::from_env(); - - match args.cmd { - Cmd::Mine(MineCmd { - repeat, - wallet, - amount, - }) => { - let rpc = wallet_rpc(network, &wallet); - let balance = rpc.get_balance(None, None).unwrap(); - let addr = rpc.get_new_address(None, None).unwrap(); - println!("{} {}", wallet, balance); - if amount == 0 { - return; - } - loop { - println!("Mine {} block", amount); - rpc.generate_to_address(amount, &addr).unwrap(); - - let balance = rpc.get_balance(None, None).unwrap(); - println!("{} {}", wallet, balance); - - if let Some(wait) = repeat { - std::thread::sleep(Duration::from_millis(wait)) - } else { - return; - } - } - } - Cmd::Test(_) => { - let client_rpc = wallet_rpc(network, CLIENT); - let wire_rpc = wallet_rpc(network, WIRE); - let client_addr = client_rpc.get_new_address(None, None).unwrap(); - let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); - // OP RETURN test - let msg = "J'aime le chocolat".as_bytes(); - client_rpc - .send_op_return(&wire_addr, Amount::from_sat(4200), msg) - .unwrap(); - client_rpc.generate_to_address(1, &client_addr).unwrap(); - let last = last_transaction(&wire_rpc).unwrap(); - let (_, decoded) = wire_rpc.get_tx_op_return(&last).unwrap(); - assert_eq!(&msg, &decoded.as_slice()); - - // Segwit test - let key = rand_key(); - client_rpc - .send_segwit_key(&wire_addr, Amount::from_sat(4200), &key) - .unwrap(); - client_rpc.generate_to_address(1, &client_addr).unwrap(); - let last = last_transaction(&wire_rpc).unwrap(); - let (_, decoded) = wire_rpc.get_tx_segwit_key(&last).unwrap(); - assert_eq!(key, decoded); - - println!("Test ok"); - } - Cmd::Msg(MsgCmd { role, method }) => { - println!("Initial state:"); - let client_rpc = wallet_rpc(network, CLIENT); - let wire_rpc = wallet_rpc(network, WIRE); - let client_addr = client_rpc.get_new_address(None, None).unwrap(); - let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); - println!("{} {}", WIRE, wire_rpc.get_balance(None, None).unwrap()); - println!("{} {}", CLIENT, client_rpc.get_balance(None, None).unwrap()); - let method = method.unwrap_or(Metadata::SegWit); - match role { - Role::Send(SendRole { mine }) => { - println!("Send message using {}", method.to_string()); - let mut rl = rustyline::Editor::<()>::new(); - loop { - let rl = rl.readline(">> "); - match rl { - Ok(line) => { - match method { - Metadata::SegWit => { - for chunk in line.as_bytes().chunks(32) { - let mut key = [0; 32]; - key[..chunk.len()].copy_from_slice(&chunk); - client_rpc - .send_segwit_key( - &wire_addr, - Amount::from_sat(4200), - &key, - ) - .unwrap(); - } - } - Metadata::OpReturn => { - for chunk in line.as_bytes().chunks(80) { - client_rpc - .send_op_return( - &wire_addr, - Amount::from_sat(4200), - chunk, - ) - .unwrap(); - } - } - } - - if mine { - client_rpc.generate_to_address(1, &client_addr).unwrap(); - } - } - Err(_) => break, - } - } - } - Role::Receive(_) => { - println!("Receive message using {}", method.to_string()); - loop { - wire_rpc.wait_for_new_block(60 * 60 * 1000).ok(); - println!("new block"); - let last = last_transaction(&wire_rpc).unwrap(); - let msg: String = match method { - Metadata::SegWit => { - let (_, decoded) = wire_rpc.get_tx_segwit_key(&last).unwrap(); - String::from_utf8_lossy(&decoded).to_string() - } - Metadata::OpReturn => { - let (_, decoded) = wire_rpc.get_tx_op_return(&last).unwrap(); - String::from_utf8_lossy(&decoded).to_string() - } - }; - println!("> {}", msg); - } - } - } - } - } -} +use std::{collections::HashSet, path::PathBuf, str::FromStr, time::Duration}; + +use bitcoincore_rpc::{ + bitcoin::{Amount, BlockHash, Transaction, Txid}, + json::{GetTransactionResultDetailCategory, ListSinceBlockResult, ListTransactionResult}, + jsonrpc::serde_json::error::Category, + Auth, Client, RpcApi, +}; +use depolymerization::{utils::rand_key, ClientExtended}; + +const CLIENT: &str = "client"; +const WIRE: &str = "wire"; +const RPC_URL: &str = "http://localhost:18443"; + +/// Bitcoin networks +#[derive(Debug, Clone, Copy)] +pub enum Network { + MainNet, + TestNet, + RegTest, +} + +impl Network { + pub fn dir(&self) -> &'static str { + match self { + Network::MainNet => "main", + Network::TestNet => "testnet3", + Network::RegTest => "regtest", + } + } +} + +fn data_dir_path() -> PathBuf { + // https://github.com/bitcoin/bitcoin/blob/master/doc/bitcoin-conf.md#:~:text=By%20default%2C%20the%20configuration%20file,file%3E%20option%20in%20the%20bitcoin. + if cfg!(target_os = "windows") { + PathBuf::from_str(&std::env::var("APPDATA").unwrap()) + .unwrap() + .join("Bitcoin") + } else if cfg!(target_os = "linux") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join(".bitcoin") + } else if cfg!(target_os = "macos") { + PathBuf::from_str(&std::env::var("HOME").unwrap()) + .unwrap() + .join("Library/Application Support/Bitcoin") + } else { + unimplemented!("Only windows, linux or macos") + } +} + +fn network_dir_path(network: Network) -> PathBuf { + data_dir_path().join(network.dir()) +} + +#[derive(argh::FromArgs)] +/// Bitcoin metadata tester +struct Args { + #[argh(subcommand)] + cmd: Cmd, +} + +#[derive(argh::FromArgs)] +/// Test library +#[argh(subcommand, name = "test")] +struct TestCmd {} + +#[derive(argh::FromArgs)] +/// Mine block for the given wallet +#[argh(subcommand, name = "mine")] +struct MineCmd { + #[argh(option, short = 'r')] + /// repeat every ? ms + repeat: Option<u64>, + + #[argh(positional)] + /// the wallet name + wallet: String, + + #[argh(positional, default = "1")] + /// repeat every ? ms + amount: u64, +} + +#[derive(argh::FromArgs)] + +/// Send message +#[argh(subcommand, name = "send")] +struct SendRole { + #[argh(switch, short = 'm')] + /// mine on send + mine: bool, +} + +#[derive(argh::FromArgs)] + +/// Receive message +#[argh(subcommand, name = "receive")] +struct ReceiveRole {} + +#[derive(argh::FromArgs)] +#[argh(subcommand)] +enum Role { + Send(SendRole), + Receive(ReceiveRole), +} + +enum Metadata { + SegWit, + OpReturn, +} + +impl ToString for Metadata { + fn to_string(&self) -> String { + match self { + Metadata::SegWit => "SegWit", + Metadata::OpReturn => "OpReturn", + } + .to_string() + } +} + +impl FromStr for Metadata { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "SegWit" | "Segwit" | "segwit" | "seg" | "s" => Ok(Self::SegWit), + "OpReturn" | "Opreturn" | "opreturn" | "op" | "o" => Ok(Self::OpReturn), + _ => Err(format!("Unknown")), + } + } +} + +#[derive(argh::FromArgs)] +/// Msg exchange msg using metadata +#[argh(subcommand, name = "msg")] +struct MsgCmd { + #[argh(subcommand)] + role: Role, + + #[argh(positional)] + method: Option<Metadata>, +} + +#[derive(argh::FromArgs)] +#[argh(subcommand)] +enum Cmd { + Mine(MineCmd), + Test(TestCmd), + Msg(MsgCmd), +} + +fn common_rpc(network: Network) -> bitcoincore_rpc::Result<Client> { + Client::new( + RPC_URL, + Auth::CookieFile(network_dir_path(network).join(".cookie")), + ) +} + +fn wallet_rpc(network: Network, wallet: &str) -> Client { + Client::new( + &format!("{}/wallet/{}", RPC_URL, wallet), + Auth::CookieFile(network_dir_path(network).join(".cookie")), + ) + .expect(&format!("Failed to open wallet '{}' client", wallet)) +} + +fn last_transaction(rpc: &Client) -> bitcoincore_rpc::Result<Txid> { + Ok(rpc + .list_transactions(None, None, None, None)? + .last() + .unwrap() + .info + .txid) +} + +fn received_since( + rpc: &Client, + hash: Option<&BlockHash>, +) -> bitcoincore_rpc::Result<(Vec<Txid>, BlockHash)> { + let result = rpc.list_since_block(hash, Some(1), None, None)?; + let mut received: Vec<&ListTransactionResult> = result + .transactions + .iter() + .filter(|it| { + it.info.confirmations > 0 + && it.detail.category == GetTransactionResultDetailCategory::Receive + }) + .collect(); + received.sort_unstable_by_key(|it| it.info.time); + received.reverse(); + Ok(( + received.into_iter().map(|it| it.info.txid).collect(), + result.lastblock, + )) +} + +fn main() { + // Guess network by trying to connect to a JSON RPC server + let network = (|| { + let result_main = common_rpc(Network::MainNet).and_then(|rpc| rpc.get_network_info()); + if result_main.is_ok() { + println!("Connected to the main chain"); + return Network::MainNet; + } + let result_test = common_rpc(Network::TestNet).and_then(|rpc| rpc.get_network_info()); + if result_test.is_ok() { + println!("Connected to the test chain"); + return Network::TestNet; + } + let result_reg = common_rpc(Network::RegTest).and_then(|rpc| rpc.get_network_info()); + if result_reg.is_ok() { + println!("Connected to the reg chain"); + return Network::RegTest; + } + + unreachable!( + "Failed to connect to any chain\nmain: {:?}\ntest: {:?}\nreg: {:?}", + result_main, result_test, result_reg + ); + })(); + { + let existing_wallets: HashSet<String> = + std::fs::read_dir(network_dir_path(network).join("wallets")) + .unwrap() + .filter_map(|it| it.ok()) + .map(|it| it.file_name().to_string_lossy().to_string()) + .collect(); + + let rpc = common_rpc(network).expect("Failed to open common client"); + if !existing_wallets.contains(CLIENT) || !existing_wallets.contains(WIRE) { + println!("Generate new wallets"); + // Create wallets + rpc.create_wallet(&WIRE, None, None, None, None).unwrap(); + rpc.create_wallet(&CLIENT, None, None, None, None).unwrap(); + // Add 50 BTC to client wallet + let rpc = wallet_rpc(network, CLIENT); + let addr = rpc.get_new_address(None, None).unwrap(); + rpc.generate_to_address(101 /* Need 100 blocks to validate */, &addr) + .unwrap(); + } else { + rpc.load_wallet(&WIRE).ok(); + rpc.load_wallet(&CLIENT).ok(); + } + } + + let args: Args = argh::from_env(); + + match args.cmd { + Cmd::Mine(MineCmd { + repeat, + wallet, + amount, + }) => { + let rpc = wallet_rpc(network, &wallet); + let balance = rpc.get_balance(None, None).unwrap(); + let addr = rpc.get_new_address(None, None).unwrap(); + println!("{} {}", wallet, balance); + if amount == 0 { + return; + } + loop { + println!("Mine {} block", amount); + rpc.generate_to_address(amount, &addr).unwrap(); + + let balance = rpc.get_balance(None, None).unwrap(); + println!("{} {}", wallet, balance); + + if let Some(wait) = repeat { + std::thread::sleep(Duration::from_millis(wait)) + } else { + return; + } + } + } + Cmd::Test(_) => { + let client_rpc = wallet_rpc(network, CLIENT); + let wire_rpc = wallet_rpc(network, WIRE); + let client_addr = client_rpc.get_new_address(None, None).unwrap(); + let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); + // OP RETURN test + let msg = "J'aime le chocolat".as_bytes(); + client_rpc + .send_op_return(&wire_addr, Amount::from_sat(4200), msg) + .unwrap(); + client_rpc.generate_to_address(1, &client_addr).unwrap(); + let last = last_transaction(&wire_rpc).unwrap(); + let (_, decoded) = wire_rpc.get_tx_op_return(&last).unwrap(); + assert_eq!(&msg, &decoded.as_slice()); + + // Segwit test + let key = rand_key(); + client_rpc + .send_segwit_key(&wire_addr, Amount::from_sat(4200), &key) + .unwrap(); + client_rpc.generate_to_address(1, &client_addr).unwrap(); + let last = last_transaction(&wire_rpc).unwrap(); + let (_, decoded) = wire_rpc.get_tx_segwit_key(&last).unwrap(); + assert_eq!(key, decoded); + + println!("Test ok"); + } + Cmd::Msg(MsgCmd { role, method }) => { + println!("Initial state:"); + let client_rpc = wallet_rpc(network, CLIENT); + let wire_rpc = wallet_rpc(network, WIRE); + let client_addr = client_rpc.get_new_address(None, None).unwrap(); + let wire_addr = wire_rpc.get_new_address(None, None).unwrap(); + println!("{} {}", WIRE, wire_rpc.get_balance(None, None).unwrap()); + println!("{} {}", CLIENT, client_rpc.get_balance(None, None).unwrap()); + let method = method.unwrap_or(Metadata::SegWit); + match role { + Role::Send(SendRole { mine }) => { + println!("Send message using {}", method.to_string()); + let mut rl = rustyline::Editor::<()>::new(); + loop { + let rl = rl.readline(">> "); + match rl { + Ok(line) => { + match method { + Metadata::SegWit => { + for chunk in line.as_bytes().chunks(32) { + let mut key = [0; 32]; + key[..chunk.len()].copy_from_slice(&chunk); + client_rpc + .send_segwit_key( + &wire_addr, + Amount::from_sat(4200), + &key, + ) + .unwrap(); + } + } + Metadata::OpReturn => { + for chunk in line.as_bytes().chunks(80) { + client_rpc + .send_op_return( + &wire_addr, + Amount::from_sat(4200), + chunk, + ) + .unwrap(); + } + } + } + + if mine { + client_rpc.generate_to_address(1, &client_addr).unwrap(); + } + } + Err(_) => break, + } + } + } + Role::Receive(_) => { + println!("Receive message using {}", method.to_string()); + let (_, mut hash) = received_since(&wire_rpc, None).unwrap(); + loop { + wire_rpc.wait_for_new_block(60 * 60 * 1000).ok(); + println!("new block"); + let (ids, nhash) = received_since(&wire_rpc, Some(&hash)).unwrap(); + hash = nhash; + for id in ids { + let msg: String = match method { + Metadata::SegWit => { + let (_, decoded) = wire_rpc.get_tx_segwit_key(&id).unwrap(); + String::from_utf8_lossy(&decoded).to_string() + } + Metadata::OpReturn => { + let (_, decoded) = wire_rpc.get_tx_op_return(&id).unwrap(); + String::from_utf8_lossy(&decoded).to_string() + } + }; + println!("> {}", msg); + } + } + } + } + } + } +}