diff --git a/frontend/src/routes/KitchenSink.tsx b/frontend/src/routes/KitchenSink.tsx index 6f9388cee..b47d028a7 100644 --- a/frontend/src/routes/KitchenSink.tsx +++ b/frontend/src/routes/KitchenSink.tsx @@ -28,8 +28,9 @@ function App() { const [amount, setAmount] = useState("") const [destinationAddress, setDestinationAddress] = useState("") - const [proxyAddress, setProxyAddress] = useState("wss://websocket-tcp-proxy-fywbx.ondigitalocean.app") + const [proxyAddress, setProxyAddress] = useState("ws://127.0.0.1:3001") const [connectPeer, setConnectPeer] = useState("") + const [disconnectPeer, setDisconnectPeer] = useState("") function handleAmountChange(e: React.ChangeEvent) { setAmount(e.target.value); @@ -55,6 +56,10 @@ function App() { setConnectPeer(e.target.value); } + function handleDisconnectPeerChange(e: React.ChangeEvent) { + setDisconnectPeer(e.target.value); + } + function handleProxyAddressChange(e: React.ChangeEvent) { setProxyAddress(e.target.value); } @@ -71,6 +76,16 @@ function App() { if (nodeManager) { try { await nodeManager.sync() + await updateBalance() + } catch (e) { + console.error(e); + } + } + } + + async function updateBalance() { + if (nodeManager) { + try { let balance = await nodeManager.get_balance(); let str = `confirmed: ${balance.confirmed?.toLocaleString()} sats, unconfirmed: ${balance.unconfirmed?.toLocaleString()} sats, ln: ${balance.lightning.toLocaleString()} sats` setBalance(str) @@ -138,7 +153,7 @@ function App() { async function sendKeysend(e: React.SyntheticEvent) { e.preventDefault() try { - await nodeManager?.keysend(currentNode, keysend, BigInt(50)); + await nodeManager?.keysend(currentNode, keysend, BigInt(5000)); } catch (e) { console.error(e); } @@ -153,6 +168,14 @@ function App() { } } + async function disconnect_peer(e: React.SyntheticEvent) { + e.preventDefault() + try { + await nodeManager?.disconnect_peer(currentNode, disconnectPeer) + } catch (e) { + console.error(e); + } + } async function new_node() { if (nodeManager) { @@ -185,6 +208,9 @@ function App() {

{`Wallet Balance: ${balance}`}

+

+ +

@@ -227,6 +253,11 @@ function App() { +
+

Disconnect Peer:

+ + +

Open Channel:

diff --git a/node-manager/src/chain.rs b/node-manager/src/chain.rs index db76650d8..59770ef6f 100644 --- a/node-manager/src/chain.rs +++ b/node-manager/src/chain.rs @@ -8,7 +8,7 @@ use lightning::chain::chaininterface::{ BroadcasterInterface, ConfirmationTarget, FeeEstimator, FEERATE_FLOOR_SATS_PER_KW, }; use lightning::chain::{Confirm, Filter, WatchedOutput}; -use log::error; +use log::{debug, error, info, warn}; use std::collections::HashSet; use std::sync::{Arc, Mutex}; use wasm_bindgen_futures::spawn_local; @@ -171,6 +171,10 @@ impl MutinyChain { unconfirmed_registered_txs: HashSet, unspent_registered_outputs: HashSet, ) { + for c in &confirmed_txs { + debug!("confirming tx! {}", c.tx.txid()) + } + for ctx in confirmed_txs { for c in confirmables { c.transactions_confirmed( @@ -202,10 +206,14 @@ impl MutinyChain { // Remember all registered but unconfirmed transactions for future processing. let mut unconfirmed_registered_txs = HashSet::new(); + info!("registered tx size: {}", registered_txs.len()); for txid in registered_txs { + info!("registered tx: {}", txid); if let Some(confirmed_tx) = self.get_confirmed_tx(&txid, None, None).await? { + info!("confirmed tx: {}", txid); confirmed_txs.push(confirmed_tx); } else { + warn!("unconfirmed tx: {}", txid); unconfirmed_registered_txs.insert(txid); } } @@ -311,11 +319,14 @@ impl MutinyChain { .iter() .flat_map(|c| c.get_relevant_txids()) .collect::>(); + let mut to_watch: HashSet = HashSet::new(); for txid in relevant_txids { + debug!("unconfirmed: {}", txid); match client.get_tx_status(&txid).await { Ok(Some(status)) => { // Skip if the tx in question is still confirmed. if status.confirmed { + to_watch.insert(txid); continue; } } @@ -330,6 +341,8 @@ impl MutinyChain { } } + *self.watched_transactions.lock().unwrap() = to_watch; + Ok(()) } } diff --git a/node-manager/src/invoice.rs b/node-manager/src/invoice.rs index da9be5b3c..d7d939f14 100644 --- a/node-manager/src/invoice.rs +++ b/node-manager/src/invoice.rs @@ -5,6 +5,7 @@ use bitcoin::bech32::ToBase32; use bitcoin_hashes::Hash; use core::ops::Deref; use core::time::Duration; +use instant::SystemTime; use lightning::chain::keysinterface::{KeysInterface, Recipient, Sign}; use lightning::ln::channelmanager::{ChannelDetails, MIN_FINAL_CLTV_EXPIRY}; use lightning::ln::channelmanager::{PhantomRouteHints, MIN_CLTV_EXPIRY_DELTA}; @@ -56,6 +57,11 @@ where )); } + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); + let invoice = InvoiceBuilder::new(network).description(description); // If we ever see performance here being too slow then we should probably take this ExpandedKey as a parameter instead. @@ -66,7 +72,7 @@ where amt_msat, payment_hash, invoice_expiry_delta_secs, - 1000 * instant::now() as u64, + now, ) .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))?; (payment_hash, payment_secret) @@ -76,13 +82,13 @@ where amt_msat, invoice_expiry_delta_secs, &keys_manager, - 1000 * instant::now() as u64, + now, ) .map_err(|_| SignOrCreationError::CreationError(CreationError::InvalidAmount))? }; let mut invoice = invoice - .duration_since_epoch(Duration::from_secs(1000 * instant::now() as u64)) + .duration_since_epoch(Duration::from_secs(now)) .payment_hash(Hash::from_slice(&payment_hash.0).unwrap()) .payment_secret(payment_secret) .min_final_cltv_expiry(MIN_FINAL_CLTV_EXPIRY.into()) diff --git a/node-manager/src/keymanager.rs b/node-manager/src/keymanager.rs index 2b42b9799..8533389a4 100644 --- a/node-manager/src/keymanager.rs +++ b/node-manager/src/keymanager.rs @@ -2,6 +2,7 @@ use crate::error::MutinyError; use bip32::XPrv; use bip39::Mnemonic; use bitcoin::secp256k1::{PublicKey, Secp256k1}; +use instant::SystemTime; use lightning::chain::keysinterface::{KeysInterface, PhantomKeysManager, Recipient}; pub(crate) fn generate_seed(num_words: u8) -> Result { @@ -43,11 +44,15 @@ pub(crate) fn create_keys_manager(mnemonic: Mnemonic, child_index: u32) -> Phant .unwrap() .derive_child(bip32::ChildNumber::new(child_index, true).unwrap()) .unwrap(); - let current_time = instant::now(); + + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap(); + PhantomKeysManager::new( &xpriv.to_bytes(), - (current_time / 1000.0).round() as u64, - (current_time * 1000.0).round() as u32, + now.as_secs(), + now.as_nanos() as u32, &shared_key.to_bytes(), ) } diff --git a/node-manager/src/ldkstorage.rs b/node-manager/src/ldkstorage.rs index e95867b96..33319c76f 100644 --- a/node-manager/src/ldkstorage.rs +++ b/node-manager/src/ldkstorage.rs @@ -164,7 +164,7 @@ impl MutinyNodePersister { mutiny_logger: Arc, keys_manager: Arc, mut channel_monitors: Vec<(BlockHash, ChannelMonitor)>, - ) -> Result { + ) -> Result<(PhantomChannelManager, bool), MutinyError> { match self.read_value(CHANNEL_MANAGER_KEY) { Ok(kv_value) => { let mut channel_monitor_mut_references = Vec::new(); @@ -184,7 +184,7 @@ impl MutinyNodePersister { let Ok((_, channel_manager)) = <(BlockHash, PhantomChannelManager)>::read(&mut readable_kv_value, read_args) else { return Err(MutinyError::ReadError { source: error::MutinyStorageError::Other(anyhow!("could not read manager")) }) }; - Ok(channel_manager) + Ok((channel_manager, true)) } Err(_) => { // no key manager stored, start a new one @@ -212,7 +212,7 @@ impl MutinyNodePersister { chain_params, ); - Ok(fresh_channel_manager) + Ok((fresh_channel_manager, false)) } } } diff --git a/node-manager/src/logging.rs b/node-manager/src/logging.rs index ad986235c..19b8a1792 100644 --- a/node-manager/src/logging.rs +++ b/node-manager/src/logging.rs @@ -22,7 +22,7 @@ impl Logger for MutinyLogger { match record.level { Level::Gossip => trace!("{}", log), - Level::Trace => trace!("{}", log), + Level::Trace => debug!("{}", log), Level::Debug => debug!("{}", log), Level::Info => info!("{}", log), Level::Warn => warn!("{}", log), diff --git a/node-manager/src/node.rs b/node-manager/src/node.rs index 462d0a3cd..80fc0d1c0 100644 --- a/node-manager/src/node.rs +++ b/node-manager/src/node.rs @@ -8,7 +8,7 @@ use bitcoin::hashes::Hash; use bitcoin::Network; use futures::StreamExt; use gloo_net::websocket::Message; -use lightning::chain::{chainmonitor, Filter}; +use lightning::chain::{chainmonitor, Filter, Watch}; use lightning::ln::channelmanager::PhantomRouteHints; use lightning::ln::msgs::NetAddress; use lightning::ln::PaymentHash; @@ -32,6 +32,7 @@ use anyhow::Context; use bip39::Mnemonic; use bitcoin::blockdata::constants::genesis_block; use bitcoin::secp256k1::PublicKey; +use instant::SystemTime; use lightning::chain::keysinterface::{ InMemorySigner, KeysInterface, PhantomKeysManager, Recipient, }; @@ -132,7 +133,7 @@ impl Node { })?; // init channel manager - let channel_manager = persister + let (channel_manager, restarting_node) = persister .read_channel_manager( network, chain_monitor.clone(), @@ -167,6 +168,41 @@ impl Node { logger.clone(), )); + // fixme dont read from storage twice lol + // read channelmonitor state from disk again + let mut channel_monitors = persister + .read_channel_monitors(keys_manager.clone()) + .map_err(|e| MutinyError::ReadError { + source: MutinyStorageError::Other(e.into()), + })?; + + // sync to chain tip + let mut chain_listener_channel_monitors = Vec::new(); + if restarting_node { + for (blockhash, channel_monitor) in channel_monitors.drain(..) { + let outpoint = channel_monitor.get_funding_txo().0; + chain_listener_channel_monitors.push(( + blockhash, + ( + channel_monitor, + chain.clone(), + chain.clone(), + logger.clone(), + ), + outpoint, + )); + } + } + + // give channel monitors to chain monitor + for item in chain_listener_channel_monitors.drain(..) { + let channel_monitor = item.1 .0; + let funding_outpoint = item.2; + chain_monitor + .clone() + .watch_channel(funding_outpoint, channel_monitor); + } + // todo use RGS // get network graph let genesis_hash = genesis_block(network).block_hash(); @@ -273,7 +309,7 @@ impl Node { route_hints: Vec, ) -> Result { let invoice = match create_phantom_invoice::>( - Some(amount_sat * 1000), + Some(amount_sat * 1), None, description, 1500, @@ -462,7 +498,10 @@ pub(crate) fn create_peer_manager( lightning_msg_handler: MessageHandler, logger: Arc, ) -> PeerManager { - let current_time = instant::now(); + let now = SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs(); let mut ephemeral_bytes = [0u8; 32]; getrandom::getrandom(&mut ephemeral_bytes).expect("Failed to generate entropy"); @@ -470,7 +509,7 @@ pub(crate) fn create_peer_manager( lightning_msg_handler, km.get_node_secret(Recipient::Node) .expect("Failed to get node secret"), - current_time as u32, + now as u32, &ephemeral_bytes, logger, Arc::new(IgnoringMessageHandler {}), diff --git a/node-manager/src/nodemanager.rs b/node-manager/src/nodemanager.rs index 5ce63a5e6..7614aca1a 100644 --- a/node-manager/src/nodemanager.rs +++ b/node-manager/src/nodemanager.rs @@ -408,7 +408,7 @@ impl NodeManager { .collect(); self.chain.sync(confirmables).await?; - Ok(()) + Ok(info!("We are synced!")) } Err(e) => Err(e.into()), } @@ -449,6 +449,27 @@ impl NodeManager { Err(MutinyError::WalletOperationFailed.into()) } + #[wasm_bindgen] + pub async fn disconnect_peer( + &self, + self_node_pubkey: String, + peer: String, + ) -> Result<(), MutinyJsError> { + if let Some(node) = self.nodes.lock().await.get(&self_node_pubkey) { + let node_id = match PublicKey::from_str(peer.as_str()) { + Ok(node_id) => Ok(node_id.inner), + Err(_) => Err(MutinyJsError::PubkeyInvalid), + }?; + + node.peer_manager.disconnect_by_node_id(node_id, false); + info!("disconnected from peer: {peer}"); + Ok(()) + } else { + error!("could not find internal node {self_node_pubkey}"); + Err(MutinyError::WalletOperationFailed.into()) + } + } + // all values in sats #[wasm_bindgen] @@ -509,6 +530,7 @@ impl NodeManager { amt_sats: u64, ) -> Result<(), MutinyJsError> { let nodes = self.nodes.lock().await; + debug!("Keysending to {to_node}"); let node = nodes.get(from_node.as_str()).unwrap(); let node_id = match PublicKey::from_str(to_node.as_str()) { @@ -521,12 +543,26 @@ impl NodeManager { let preimage = PaymentPreimage(entropy); let amt_msats = amt_sats * 1000; + + let chans = node.channel_manager.list_channels(); + let chan = chans.first().unwrap(); + let usable = node.channel_manager.list_usable_channels(); + + info!( + "confirmations_required: {}", + chan.confirmations_required.unwrap_or(69) + ); + info!("is_channel_ready: {}", chan.is_channel_ready); + info!("is_usable: {}", chan.is_usable); + info!("is_public: {}", chan.is_public); + info!("usable: {}", usable.len()); + let _ = node .invoice_payer .pay_pubkey(node_id, preimage, amt_msats, 40) .unwrap(); - Ok(()) + Ok(info!("Keysend successful!")) } #[wasm_bindgen]