commit ccade06e09b68cf7ee79a91ddf9413760925da14
parent b80fee7cc3a059eda7ae14d041d25e7b7b093733
Author: Antoine A <>
Date: Tue, 16 Nov 2021 16:36:04 +0100
Use transaction listener to handle multipart msg
Diffstat:
| M | src/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);
+ }
+ }
+ }
+ }
+ }
+ }
+}