diff --git a/Cargo.lock b/Cargo.lock index 7d057c721..140a9599d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,6 +68,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0320167c3655e83f0415d52f39618902e449186ffc7dfb090f922f79675c316" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bech32" version = "0.10.0-beta" @@ -85,6 +91,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "bitcoin" +version = "0.29.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" +dependencies = [ + "bech32 0.9.1", + "bitcoin_hashes 0.11.0", + "secp256k1 0.24.3", +] + [[package]] name = "bitcoin" version = "0.31.0" @@ -92,12 +109,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5973a027b341b462105675962214dfe3c938ad9afd395d84b28602608bdcec7b" dependencies = [ "base64 0.21.5", - "bech32", + "bech32 0.10.0-beta", "bitcoin-internals", "bitcoin_hashes 0.13.0", "hex-conservative", "hex_lit", - "secp256k1", + "secp256k1 0.28.0", "serde", ] @@ -169,6 +186,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "crossbeam-channel" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + [[package]] name = "dirs" version = "5.0.0" @@ -202,6 +234,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + [[package]] name = "fern" version = "0.6.2" @@ -259,6 +300,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.10" @@ -291,6 +341,7 @@ dependencies = [ "libc", "log", "miniscript", + "nakamoto", "rdrand", "rusqlite", "serde", @@ -338,14 +389,34 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "microserde" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73d64402c57ce913a4a4788ca4e0c60329a2e29782f8e88d510c1c951fac46" +dependencies = [ + "microserde-derive", +] + +[[package]] +name = "microserde-derive" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3afa8ee462bb9ed735b544476a9e4ece0f10be76864ff9b80a6a525b837232" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "miniscript" version = "11.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86a23dd3ad145a980e231185d114399f25a0a307d2cd918010ddda6334323df9" dependencies = [ - "bech32", - "bitcoin", + "bech32 0.10.0-beta", + "bitcoin 0.31.0", "bitcoin-internals", "serde", ] @@ -370,6 +441,104 @@ dependencies = [ "serde_json", ] +[[package]] +name = "nakamoto" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "nakamoto-chain", + "nakamoto-client", + "nakamoto-common", + "nakamoto-net", + "nakamoto-net-poll", + "nakamoto-p2p", +] + +[[package]] +name = "nakamoto-chain" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "log", + "nakamoto-common", + "thiserror", +] + +[[package]] +name = "nakamoto-client" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "crossbeam-channel", + "fastrand", + "log", + "microserde", + "nakamoto-chain", + "nakamoto-common", + "nakamoto-net", + "nakamoto-p2p", + "thiserror", +] + +[[package]] +name = "nakamoto-common" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "bitcoin 0.29.2", + "bitcoin_hashes 0.11.0", + "fastrand", + "log", + "microserde", + "nakamoto-net", + "nonempty", + "thiserror", +] + +[[package]] +name = "nakamoto-net" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "crossbeam-channel", + "fastrand", + "log", + "thiserror", +] + +[[package]] +name = "nakamoto-net-poll" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "crossbeam-channel", + "libc", + "log", + "nakamoto-net", + "popol", + "socket2", +] + +[[package]] +name = "nakamoto-p2p" +version = "0.4.0" +source = "git+https://github.com/vincenzopalazzo/nakamoto.git?branch=macros/master#83a746621c08a6ac1177f086e337697f9a8f57dd" +dependencies = [ + "crossbeam-channel", + "fastrand", + "log", + "microserde", + "nakamoto-common", + "nakamoto-net", + "thiserror", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "object" version = "0.31.1" @@ -397,6 +566,15 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +[[package]] +name = "popol" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953dd115cbe3b9d10340d47e6825a9700059a2fee12cee722975776caf3c531" +dependencies = [ + "libc", +] + [[package]] name = "proc-macro2" version = "1.0.74" @@ -479,6 +657,16 @@ version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "secp256k1" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" +dependencies = [ + "bitcoin_hashes 0.11.0", + "secp256k1-sys 0.6.1", +] + [[package]] name = "secp256k1" version = "0.28.0" @@ -486,10 +674,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" dependencies = [ "bitcoin_hashes 0.12.0", - "secp256k1-sys", + "secp256k1-sys 0.9.1", "serde", ] +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + [[package]] name = "secp256k1-sys" version = "0.9.1" @@ -516,7 +713,7 @@ checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.46", ] [[package]] @@ -536,6 +733,27 @@ version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.46" @@ -564,7 +782,7 @@ checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.46", ] [[package]] @@ -624,6 +842,28 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.48.0" @@ -707,5 +947,5 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.46", ] diff --git a/Cargo.toml b/Cargo.toml index 21c21949b..850f64c3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ nonblocking_shutdown = [] [dependencies] # For managing transactions (it re-exports the bitcoin crate) miniscript = { version = "11.0", features = ["serde", "compiler", "base64"] } +nakamoto = { git = "https://github.com/vincenzopalazzo/nakamoto.git", branch = "macros/master", features = ["nakamoto-client", "nakamoto-net-poll"] } # Coin selection algorithms for spend transaction creation. bdk_coin_select = { version = "0.1.0" } diff --git a/contrib/lianad_config_example.toml b/contrib/lianad_config_example.toml index 462ac8ccb..262fc477b 100644 --- a/contrib/lianad_config_example.toml +++ b/contrib/lianad_config_example.toml @@ -20,7 +20,7 @@ log_level = "debug" # # YOUR DESCRIPTOR IS UNIQUE AND MUST BE BACKED UP, WITHOUT IT YOU WONT BE ABLE TO RECOVER YOUR FUNDS. # -main_descriptor = "wsh(or_d(pk([92162c45]tpubD6NzVbkrYhZ4WzTf9SsD6h7AH7oQEippXK2KP8qvhMMqFoNeN5YFVi7vRyeRSDGtgd2bPyMxUNmHui8t5yCgszxPPxMafu1VVzDpg9aruYW/<0;1>/*),and_v(v:pkh(tpubD6NzVbkrYhZ4Wdgu2yfdmrce5g4fiH1ZLmKhewsnNKupbi4sxjH1ZVAorkBLWSkhsjhg8kiq8C4BrBjMy3SjAKDyDdbuvUa1ToAHbiR98js/<0;1>/*),older(2))))#uact7s3g" +#main_descriptor = "wsh(or_d(pk([92162c45]tpubD6NzVbkrYhZ4WzTf9SsD6h7AH7oQEippXK2KP8qvhMMqFoNeN5YFVi7vRyeRSDGtgd2bPyMxUNmHui8t5yCgszxPPxMafu1VVzDpg9aruYW/<0;1>/*),and_v(v:pkh(tpubD6NzVbkrYhZ4Wdgu2yfdmrce5g4fiH1ZLmKhewsnNKupbi4sxjH1ZVAorkBLWSkhsjhg8kiq8C4BrBjMy3SjAKDyDdbuvUa1ToAHbiR98js/<0;1>/*),older(2))))#uact7s3g" # This section is the configuration related to the Bitcoin backend. # On what network shall it operate? @@ -35,6 +35,6 @@ poll_interval_secs = 30 # how to authenticate, either by specifying the cookie location with "cookie_path" or otherwise # passing a colon-separated user and password with "auth". [bitcoind_config] -addr = "127.0.0.1:18332" -cookie_path = "/home/wizardsardine/.bitcoin/testnet3/.cookie" -# auth = "my_user:my_password" +addr = "192.168.178.30:18332" +#cookie_path = "/home/wizardsardine/.bitcoin/testnet3/.cookie" +auth = "vincent:vincent-home-pi" diff --git a/src/bin/daemon.rs b/src/bin/daemon.rs index 51e3c1347..3f9ed03b3 100644 --- a/src/bin/daemon.rs +++ b/src/bin/daemon.rs @@ -70,7 +70,7 @@ fn main() { process::exit(1); }); - let daemon = DaemonHandle::start_default(config).unwrap_or_else(|e| { + let daemon = DaemonHandle::start_nakamoto(config).unwrap_or_else(|e| { log::error!("Error starting Liana daemon: {}", e); process::exit(1); }); diff --git a/src/bitcoin/d/mod.rs b/src/bitcoin/d/mod.rs index a1b1317c4..43d95bdbd 100644 --- a/src/bitcoin/d/mod.rs +++ b/src/bitcoin/d/mod.rs @@ -2,7 +2,10 @@ //! //! We use the RPC interface and a watchonly descriptor wallet. +// FIXME(vincenzopalazzo): move this outside of the bitcoind crate +pub(crate) mod nakamoto; mod utils; + use crate::{ bitcoin::{Block, BlockChainTip}, config, @@ -59,6 +62,7 @@ pub enum BitcoindError { NetworkMismatch(String /*config*/, String /*bitcoind*/), StartRescan, RescanPastPruneHeight, + GenericError, } impl BitcoindError { @@ -151,7 +155,8 @@ impl std::fmt::Display for BitcoindError { f, "Trying to rescan the block chain past the prune block height." ) - } + }, + BitcoindError::GenericError => write!(f, "Generic error"), } } } diff --git a/src/bitcoin/d/nakamoto/mod.rs b/src/bitcoin/d/nakamoto/mod.rs new file mode 100644 index 000000000..5638009c1 --- /dev/null +++ b/src/bitcoin/d/nakamoto/mod.rs @@ -0,0 +1,172 @@ +//! Nakamoto Backend implementation +//! +//! Author: Vincenzo Palazzo +use std::net; +use std::path::PathBuf; +use std::str::FromStr; +use std::thread::JoinHandle; + +use nakamoto::client::traits::Handle; +use nakamoto::client; +use nakamoto::common::bitcoin_hashes::hex::FromHex; +use nakamoto::net::poll::{Waker, Reactor}; +use miniscript::bitcoin::hashes::Hash; +use miniscript::bitcoin; + +use crate::BitcoindError; +use crate::bitcoin::BitcoinInterface; + +use super::SyncProgress; + +/// Nakamoto client +pub struct Nakamoto { + /// Nakamoto handler used interact with the client. + handler: client::Handle, + /// Nakamoto main worked to avoid leave a pending project. + _worker: JoinHandle>, + _network: client::Network, +} + +impl Nakamoto { + /// Create a new instance of nakamoto. + pub fn new(network: &bitcoin::Network, connect: &[net::SocketAddr], data_dir: PathBuf) -> Result { + let network = client::Network::from_str(&network.to_string()).map_err(|_| ())?; + let mut config = client::Config::new(network); + config.root = data_dir; + config.connect = connect.to_vec(); + config.user_agent = "Liana-Nakamoto-v1"; + let client = client::Client::>::new().unwrap(); + let handler = client.handle(); + let worker = std::thread::spawn(|| client.run(config)); + Ok(Self{ handler, _worker: worker, _network: network }) + } + + /// Stop the nakamoto node + #[allow(dead_code)] + pub fn stop(self) -> Result<(), BitcoindError> { + self.handler.shutdown().map_err(|_| BitcoindError::GenericError)?; + let _ = self._worker.join().map_err(|_| BitcoindError::GenericError)?; + Ok(()) + } +} + +impl BitcoinInterface for Nakamoto { + fn mempool_spenders(&self, outpoints: &[miniscript::bitcoin::OutPoint]) -> Vec { + unimplemented!() + } + + fn chain_tip(&self) -> crate::bitcoin::BlockChainTip { + // FIXME: we should check the error and maybe return + // it to the caller. + let (height, header, _) = self.handler.get_tip().unwrap(); + let block_hash = header.block_hash(); + let hash = bitcoin::BlockHash::from_slice(&block_hash.as_hash().to_vec()).unwrap(); + crate::bitcoin::BlockChainTip{ height: height as i32, hash } + } + + fn broadcast_tx(&self, tx: &miniscript::bitcoin::Transaction) -> Result<(), String> { + use miniscript::bitcoin::consensus::serialize; + use nakamoto::common::bitcoin::consensus::deserialize; + + let tx = serialize(tx); + let tx: nakamoto::common::bitcoin::Transaction = deserialize(&tx).map_err(|err| format!("{err}"))?; + self.handler.submit_transaction(tx).map_err(|err| format!("{err}"))?; + Ok(()) + } + + fn common_ancestor(&self, tip: &crate::bitcoin::BlockChainTip) -> Option { + None + } + + fn is_in_chain(&self, tip: &crate::bitcoin::BlockChainTip) -> bool { + true + } + + fn block_before_date(&self, timestamp: u32) -> Option { + None + } + + fn confirmed_coins( + &self, + outpoints: &[miniscript::bitcoin::OutPoint], + ) -> (Vec<(miniscript::bitcoin::OutPoint, i32, u32)>, Vec) { + unimplemented!() + } + + fn genesis_block(&self) -> crate::bitcoin::BlockChainTip { + let height = 0; + let block = self.handler.get_block_by_height(height).unwrap(); + let block = block.unwrap(); + let block_hash = block.block_hash().as_hash().to_string(); + let block_hash = miniscript::bitcoin::BlockHash::from_str(&block_hash).unwrap(); + crate::bitcoin::BlockChainTip{ height: height as i32, hash: block_hash } + } + + fn received_coins( + &self, + tip: &crate::bitcoin::BlockChainTip, + descs: &[crate::descriptors::SinglePathLianaDesc], + ) -> Vec { + unimplemented!() + } + + fn rescan_progress(&self) -> Option { + None + } + + fn spending_coins( + &self, + outpoints: &[miniscript::bitcoin::OutPoint], + ) -> Vec<(miniscript::bitcoin::OutPoint, miniscript::bitcoin::Txid)> { + unimplemented!() + } + + fn start_rescan( + &self, + desc: &crate::descriptors::LianaDescriptor, + timestamp: u32, + ) -> Result<(), String> { + // We do not care for the moment, because we are tracking with nakamoto + // all the transactions submitted + Ok(()) + } + + fn wallet_transaction( + &self, + txid: &miniscript::bitcoin::Txid, + ) -> Option<(miniscript::bitcoin::Transaction, Option)> { + use nakamoto::common::bitcoin::consensus::serialize; + use miniscript::bitcoin::consensus::deserialize; + + let txid = txid.to_string(); + let txid = nakamoto::common::bitcoin::Txid::from_hex(&txid).unwrap(); + let Ok(Some(tx)) = self.handler.get_submitted_transaction(&txid) else { + return None; + }; + let tx = serialize(&tx); + let tx: miniscript::bitcoin::Transaction = deserialize(&tx).unwrap(); + // FIXME: we do not know what is the block that it is confirmed, we should + // keep this in our db maybe? + Some((tx, None)) + } + + fn spent_coins( + &self, + outpoints: &[(miniscript::bitcoin::OutPoint, miniscript::bitcoin::Txid)], + ) -> ( + Vec<(miniscript::bitcoin::OutPoint, miniscript::bitcoin::Txid, crate::bitcoin::Block)>, + Vec, + ) { + unimplemented!() + } + + fn sync_progress(&self) -> super::SyncProgress { + // FIXME: call tip and try to simulate the bitcoin intergace + SyncProgress::new(100.0, 0, 0) + } + + fn tip_time(&self) -> Option { + // This is a little bit hard to track at the moment + None + } +} diff --git a/src/lib.rs b/src/lib.rs index a58b6b30a..bddd4c7d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ pub mod spend; mod testutils; pub use bip39; +use bitcoin::d::nakamoto::Nakamoto; pub use miniscript; pub use crate::bitcoin::d::{BitcoinD, BitcoindError, WalletError}; @@ -373,6 +374,11 @@ impl DaemonHandle { DaemonHandle::start(config, Option::::None, Option::::None) } + pub fn start_nakamoto(config: Config) -> Result { + let nakamoto = Nakamoto::new(&config.bitcoin_config.network, &[], config.data_dir().unwrap()).unwrap(); + DaemonHandle::start(config, Some(nakamoto), Option::::None) + } + /// Start the JSONRPC server and listen for incoming commands until we die. /// Like DaemonHandle::shutdown(), this stops the Bitcoin poller at teardown. #[cfg(feature = "daemon")]