From 2408dd69fe18a4796d0fff2153ce1b82c5165576 Mon Sep 17 00:00:00 2001 From: Ademar Alves de Oliveira Date: Fri, 7 Jul 2023 09:18:18 -0300 Subject: [PATCH] Get the on chain and off chain balance --- libs/sdk-core/src/breez_services.rs | 38 +++---- libs/sdk-core/src/greenlight/node_api.rs | 128 ++++++++++++++++------- libs/sdk-core/src/models.rs | 19 +++- libs/sdk-core/src/test_utils.rs | 30 +++++- 4 files changed, 157 insertions(+), 58 deletions(-) diff --git a/libs/sdk-core/src/breez_services.rs b/libs/sdk-core/src/breez_services.rs index 7ddec4c29..81386b1f0 100644 --- a/libs/sdk-core/src/breez_services.rs +++ b/libs/sdk-core/src/breez_services.rs @@ -5,43 +5,40 @@ use std::time::{SystemTime, UNIX_EPOCH}; use anyhow::{anyhow, Result}; use bip39::*; -use bitcoin::hashes::{Hash, sha256}; +use bitcoin::hashes::{sha256, Hash}; use bitcoin::util::bip32::ChildNumber; use tokio::runtime::Runtime; -use tokio::sync::{mpsc, Mutex, RwLock, watch}; -use tokio::time::{Duration, sleep}; -use tonic::{Request, Status}; +use tokio::sync::{mpsc, watch, Mutex, RwLock}; +use tokio::time::{sleep, Duration}; use tonic::codegen::InterceptedService; use tonic::metadata::{Ascii, MetadataValue}; use tonic::service::Interceptor; use tonic::transport::{Channel, Uri}; +use tonic::{Request, Status}; -use crate::*; -use crate::{BuyBitcoinProvider, LnUrlAuthRequestData, LnUrlWithdrawRequestData, PaymentResponse}; use crate::backup::{BackupRequest, BackupTransport, BackupWatcher}; use crate::boltzswap::BoltzApi; -use crate::BuyBitcoinProvider::Moonpay; use crate::chain::{ChainService, MempoolSpace, RecommendedFees}; use crate::fiat::{FiatCurrency, Rate}; use crate::greenlight::{GLBackupTransport, Greenlight}; use crate::grpc::channel_opener_client::ChannelOpenerClient; use crate::grpc::fund_manager_client::FundManagerClient; use crate::grpc::information_client::InformationClient; -use crate::grpc::PaymentInformation; use crate::grpc::signer_client::SignerClient; +use crate::grpc::PaymentInformation; use crate::input_parser::LnUrlPayRequestData; -use crate::invoice::{add_lsp_routing_hints, LNInvoice, parse_invoice, RouteHint, RouteHintHop}; +use crate::invoice::{add_lsp_routing_hints, parse_invoice, LNInvoice, RouteHint, RouteHintHop}; use crate::lnurl::auth::perform_lnurl_auth; +use crate::lnurl::pay::model::SuccessAction::Aes; use crate::lnurl::pay::model::{ LnUrlPayResult, SuccessAction, SuccessActionProcessed, ValidatedCallbackResponse, }; -use crate::lnurl::pay::model::SuccessAction::Aes; use crate::lnurl::pay::validate_lnurl_pay; use crate::lnurl::withdraw::validate_lnurl_withdraw; use crate::lsp::LspInformation; use crate::models::{ - ChannelState, ClosedChannelPaymentDetails, Config, EnvironmentType, FiatAPI, - GreenlightCredentials, LnUrlCallbackStatus, LspAPI, Network, NodeAPI, NodeState, parse_short_channel_id, + parse_short_channel_id, ChannelState, ClosedChannelPaymentDetails, Config, EnvironmentType, + FiatAPI, GreenlightCredentials, LnUrlCallbackStatus, LspAPI, Network, NodeAPI, NodeState, Payment, PaymentDetails, PaymentType, PaymentTypeFilter, ReverseSwapPairInfo, ReverseSwapperAPI, SwapInfo, SwapperAPI, }; @@ -49,6 +46,9 @@ use crate::moonpay::MoonPayApi; use crate::persist::db::SqliteStorage; use crate::reverseswap::BTCSendSwap; use crate::swap::BTCReceiveSwap; +use crate::BuyBitcoinProvider::Moonpay; +use crate::*; +use crate::{BuyBitcoinProvider, LnUrlAuthRequestData, LnUrlWithdrawRequestData, PaymentResponse}; /// Trait that can be used to react to various [BreezEvent]s emitted by the SDK. pub trait EventListener: Send + Sync { @@ -207,7 +207,7 @@ impl BreezServices { Some(parsed_invoice), payment_res, ) - .await + .await } /// Pay directly to a node id using keysend @@ -620,7 +620,7 @@ impl BreezServices { invoice, }, }) - .await?; + .await?; return Err(payment_res.err().unwrap()); } let payment = payment_res.unwrap(); @@ -1067,7 +1067,7 @@ impl BreezServicesBuilder { self.seed.clone().unwrap(), self.creds.clone().unwrap(), ) - .await?; + .await?; let gl_arc = Arc::new(greenlight); node_api = Some(gl_arc.clone()); if backup_transport.is_none() { @@ -1440,15 +1440,15 @@ pub(crate) mod tests { use regex::Regex; use reqwest::Url; - use crate::{ - BuyBitcoinProvider, input_parser, InputType, parse_short_channel_id, test_utils::*, - }; - use crate::{NodeAPI, PaymentType}; use crate::breez_services::{BreezServices, BreezServicesBuilder}; use crate::fiat::Rate; use crate::lnurl::pay::model::MessageSuccessActionData; use crate::lnurl::pay::model::SuccessActionProcessed; use crate::models::{LnPaymentDetails, NodeState, Payment, PaymentDetails, PaymentTypeFilter}; + use crate::{ + input_parser, parse_short_channel_id, test_utils::*, BuyBitcoinProvider, InputType, + }; + use crate::{NodeAPI, PaymentType}; use super::{PaymentReceiver, Receiver}; diff --git a/libs/sdk-core/src/greenlight/node_api.rs b/libs/sdk-core/src/greenlight/node_api.rs index 2278317bb..8042a60ca 100644 --- a/libs/sdk-core/src/greenlight/node_api.rs +++ b/libs/sdk-core/src/greenlight/node_api.rs @@ -13,7 +13,8 @@ use gl_client::pb::cln::{ ListpeerchannelsRequest, }; use gl_client::pb::{ - Amount, Invoice, InvoiceRequest, InvoiceStatus, OffChainPayment, PayStatus, WithdrawResponse, + Amount, Invoice, InvoiceRequest, InvoiceStatus, ListPeersResponse, OffChainPayment, PayStatus, + WithdrawResponse, }; use gl_client::pb::{ListFundsResponse, Peer}; use gl_client::scheduler::Scheduler; @@ -168,12 +169,6 @@ impl NodeAPI for Greenlight { let mut client = self.get_client().await?; let mut node_client = self.get_node_client().await?; - // list all peers - let peers = client - .list_peers(pb::ListPeersRequest::default()) - .await? - .into_inner(); - // get node info let node_info = client .get_info(pb::GetInfoRequest::default()) @@ -182,30 +177,19 @@ impl NodeAPI for Greenlight { // list both off chain funds and on chain fudns let funds = self.list_funds().await?; - let offchain_funds = funds.channels; let onchain_funds = funds.outputs; + // list all peers + let peers = self.peers().await?; + // filter only connected peers - let connected_peers: Vec = peers - .peers - .clone() - .iter() - .filter(|p| p.connected) - .map(|p| hex::encode(p.id.clone())) - .collect(); + let connected_peers = self.connected_peers(Some(&peers)).await?; // make a vector of all channels by searching in peers - let all_channels: &mut Vec = &mut Vec::new(); - peers.peers.clone().iter().for_each(|p| { - let peer_channels = &mut p.channels.clone(); - all_channels.append(peer_channels); - }); + let all_channels = self.all_channels(Some(&peers)).await?; // filter only opened channels - let opened_channels: &mut Vec<&pb::Channel> = &mut all_channels - .iter() - .filter(|c| c.state == *"CHANNELD_NORMAL") - .collect(); + let opened_channels = self.opened_channels(Some(&all_channels)).await?; // Fetch closed channels from greenlight let closed_channels = match node_client @@ -235,16 +219,12 @@ impl NodeAPI for Greenlight { all_channel_models.extend(forgotten_closed_channels?); // calculate channels balance only from opened channels - let channels_balance = offchain_funds.iter().fold(0, |a, b| { - let hex_txid = hex::encode(b.funding_txid.clone()); - if opened_channels.iter().any(|c| c.funding_txid == hex_txid) { - return a + b.our_amount_msat; - } - a - }); + let channels_balance = self + .off_chain_balance(Some(&funds), Some(&opened_channels)) + .await?; // calculate onchain balance - let onchain_balance = self.on_chain_balance().await?; + let onchain_balance = self.on_chain_balance(Some(&funds)).await?; // Collect utxos from onchain funds let utxos = onchain_funds @@ -396,7 +376,7 @@ impl NodeAPI for Greenlight { outputs: vec![cln::OutputDesc { address: to_address, amount: Some(cln::Amount { - msat: self.on_chain_balance().await?, + msat: self.off_chain_balance(None, None).await?, }), }], feerate: Some(cln::Feerate { @@ -616,8 +596,11 @@ impl NodeAPI for Greenlight { return Ok(funds); } - async fn on_chain_balance(&self) -> Result { - let funds = self.list_funds().await?; + async fn on_chain_balance(&self, funds: Option<&ListFundsResponse>) -> Result { + let funds = match funds { + Some(f) => f, + None => &self.list_funds().await?, + }; let on_chain_balance = funds.outputs.iter().fold(0, |a, b| { if b.reserved { return a; @@ -626,6 +609,81 @@ impl NodeAPI for Greenlight { }); return Ok(on_chain_balance); } + + async fn off_chain_balance( + &self, + funds: Option<&ListFundsResponse>, + opened_channels: Option<&Vec<&pb::Channel>>, + ) -> Result { + let funds = match funds { + Some(f) => f, + None => &self.list_funds().await?, + }; + let opened_channels = match opened_channels { + Some(c) => c, + None => &self.opened_channels(None).await?, + }; + let off_chain_balance = funds.channels.iter().fold(0, |a, b| { + let hex_txid = hex::encode(b.funding_txid.clone()); + if opened_channels.iter().any(|c| c.funding_txid == hex_txid) { + return a + b.our_amount_msat; + } + a + }); + return Ok(off_chain_balance); + } + + async fn peers(&self) -> Result { + let mut client = self.get_client().await?; + let peers = client + .list_peers(pb::ListPeersRequest::default()) + .await? + .into_inner(); + return Ok(peers); + } + + async fn connected_peers(&self, peers: Option<&ListPeersResponse>) -> Result> { + let peers = match peers { + Some(p) => p, + None => &self.peers().await?, + }; + let connected_peers = peers + .peers + .clone() + .iter() + .filter(|p| p.connected) + .map(|p| hex::encode(p.id.clone())) + .collect(); + return Ok(connected_peers); + } + + async fn all_channels(&self, peers: Option<&ListPeersResponse>) -> Result> { + let peers = match peers { + Some(p) => p, + None => &self.peers().await?, + }; + let all_channels: &mut Vec = &mut Vec::new(); + peers.peers.clone().iter().for_each(|p| { + let peer_channels = &mut p.channels.clone(); + all_channels.append(peer_channels); + }); + return Ok(all_channels.clone()); + } + + async fn opened_channels( + &self, + all_channels: Option<&Vec>, + ) -> Result> { + let all_channels = match all_channels { + Some(c) => c, + None => &self.all_channels(None).await?, + }; + let opened_channels: &mut Vec<&pb::Channel> = &mut all_channels + .iter() + .filter(|c| c.state == *"CHANNELD_NORMAL") + .collect(); + return Ok(opened_channels.clone()); + } } #[derive(Clone, PartialEq, Eq, Debug, EnumString, Display, Deserialize, Serialize)] diff --git a/libs/sdk-core/src/models.rs b/libs/sdk-core/src/models.rs index 2e5136502..60b6cdb03 100644 --- a/libs/sdk-core/src/models.rs +++ b/libs/sdk-core/src/models.rs @@ -10,7 +10,7 @@ use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; use bitcoin::{Address, Script}; use gl_client::pb::Peer; use gl_client::pb::WithdrawResponse; -use gl_client::pb::{Invoice, ListFundsResponse}; +use gl_client::pb::{Invoice, ListFundsResponse, ListPeersResponse}; use lightning_invoice::RawInvoice; use ripemd::Digest; use ripemd::Ripemd160; @@ -79,7 +79,22 @@ pub trait NodeAPI: Send + Sync { /// Gets the private key at the path specified fn derive_bip32_key(&self, path: Vec) -> Result; async fn list_funds(&self) -> Result; - async fn on_chain_balance(&self) -> Result; + async fn on_chain_balance(&self, funds: Option<&ListFundsResponse>) -> Result; + async fn off_chain_balance( + &self, + funds: Option<&ListFundsResponse>, + opened_channels: Option<&Vec<&gl_client::pb::Channel>>, + ) -> Result; + async fn peers(&self) -> Result; + async fn connected_peers(&self, peers: Option<&ListPeersResponse>) -> Result>; + async fn all_channels( + &self, + peers: Option<&ListPeersResponse>, + ) -> Result>; + async fn opened_channels( + &self, + all_channels: Option<&Vec>, + ) -> Result>; } /// Trait covering LSP-related functionality diff --git a/libs/sdk-core/src/test_utils.rs b/libs/sdk-core/src/test_utils.rs index 4ee4949b1..1c568c701 100644 --- a/libs/sdk-core/src/test_utils.rs +++ b/libs/sdk-core/src/test_utils.rs @@ -11,7 +11,9 @@ use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; use bitcoin::util::bip32::{ChildNumber, ExtendedPrivKey}; use bitcoin::Network; use gl_client::pb::amount::Unit; -use gl_client::pb::{Amount, Invoice, ListFundsResponse, Peer, WithdrawResponse}; +use gl_client::pb::{ + Amount, Channel, Invoice, ListFundsResponse, ListPeersResponse, Peer, WithdrawResponse, +}; use lightning::ln::PaymentSecret; use lightning_invoice::{Currency, InvoiceBuilder, RawInvoice}; use rand::distributions::{Alphanumeric, DistString, Standard}; @@ -361,7 +363,31 @@ impl NodeAPI for MockNodeAPI { Err(anyhow!("Not implemented")) } - async fn on_chain_balance(&self) -> Result { + async fn on_chain_balance(&self, funds: Option<&ListFundsResponse>) -> Result { + Err(anyhow!("Not implemented")) + } + + async fn off_chain_balance( + &self, + funds: Option<&ListFundsResponse>, + opened_channels: Option<&Vec<&Channel>>, + ) -> Result { + Err(anyhow!("Not implemented")) + } + + async fn peers(&self) -> Result { + Err(anyhow!("Not implemented")) + } + + async fn connected_peers(&self, peers: Option<&ListPeersResponse>) -> Result> { + Err(anyhow!("Not implemented")) + } + + async fn all_channels(&self, peers: Option<&ListPeersResponse>) -> Result> { + Err(anyhow!("Not implemented")) + } + + async fn opened_channels(&self, all_channels: Option<&Vec>) -> Result> { Err(anyhow!("Not implemented")) } }