diff --git a/Cargo.lock b/Cargo.lock index e0123eb..ad25173 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,6 +241,8 @@ dependencies = [ "bp-dbc", "bp-seals", "commit_verify", + "serde 1.0.139", + "serde_with", "single_use_seals", "strict_encoding", ] @@ -256,6 +258,8 @@ dependencies = [ "bitcoin_scripts", "commit_verify", "secp256k1", + "serde 1.0.139", + "serde_with", "strict_encoding", ] @@ -271,6 +275,8 @@ dependencies = [ "bp-dbc", "commit_verify", "lnpbp_bech32", + "serde 1.0.139", + "serde_with", "single_use_seals", "strict_encoding", ] @@ -374,6 +380,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits 0.2.15", + "serde 1.0.139", "time", "wasm-bindgen", "winapi", @@ -476,6 +483,8 @@ dependencies = [ "amplify", "bitcoin_hashes", "rand 0.8.5", + "serde 1.0.139", + "serde_with", "strict_encoding", ] @@ -916,6 +925,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "generic-array" version = "0.14.5" @@ -1024,6 +1042,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "1.9.1" @@ -1193,8 +1221,7 @@ dependencies = [ [[package]] name = "lightning_encoding" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56f420c81ea9f113a2ccefffc55124394feddf19f5b10ebbad81edee4763d28" +source = "git+https://github.com/crisdut/lightning_encoding?branch=fix/script-decode#e5635a52b71beda2a3e5ccf584161a992dee3ea8" dependencies = [ "amplify", "bitcoin", @@ -1208,8 +1235,7 @@ dependencies = [ [[package]] name = "lightning_encoding_derive" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d0f5261aacd87fa76a12ff80b5d0e6782ce969c0301ab7cfbabe20f4492881d" +source = "git+https://github.com/crisdut/lightning_encoding?branch=fix/script-decode#e5635a52b71beda2a3e5ccf584161a992dee3ea8" dependencies = [ "amplify_syn", "encoding_derive_helpers", @@ -1250,6 +1276,7 @@ dependencies = [ "amplify", "clap", "clap_complete", + "colored", "configure_me_codegen", "internet2", "lightning-invoice", @@ -1263,8 +1290,7 @@ dependencies = [ [[package]] name = "lnp-core" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238ce14de0c3ceccef8a31317d06964949881c6d8e4898df3076e86bae58ad67" +source = "git+https://github.com/crisdut/lnp-core?branch=exp/ln-interop#5c7b275764f97da052e7e362b103957e7e2db71a" dependencies = [ "amplify", "bitcoin", @@ -1283,8 +1309,7 @@ dependencies = [ [[package]] name = "lnp2p" version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b27ed14a42f7d90e3a16ff602b877e73a9af9a255416e92841ba839c7d138390" +source = "git+https://github.com/crisdut/lnp-core?branch=exp/ln-interop#5c7b275764f97da052e7e362b103957e7e2db71a" dependencies = [ "amplify", "bitcoin", @@ -1327,6 +1352,7 @@ dependencies = [ "lnp-core", "lnp_rpc", "lnpbp", + "lnpbp-invoice", "log", "microservices", "miniscript", @@ -1353,6 +1379,7 @@ dependencies = [ "lightning-invoice", "lnp-core", "lnpbp", + "lnpbp-invoice", "log", "microservices", "serde 1.0.139", @@ -1376,6 +1403,30 @@ dependencies = [ "strict_encoding", ] +[[package]] +name = "lnpbp-invoice" +version = "0.9.0" +source = "git+https://github.com/crisdut/invoices?branch=feat/bolt11#c3fc5d6e153729b584fd09cd8db6be64bb43165a" +dependencies = [ + "amplify", + "bitcoin", + "bitcoin_scripts", + "bp-core", + "chrono", + "commit_verify", + "descriptor-wallet", + "internet2", + "lightning", + "lightning-invoice", + "lnp-core", + "lnpbp", + "miniscript", + "serde 1.0.139", + "serde_with", + "strict_encoding", + "url", +] + [[package]] name = "lnpbp_bech32" version = "0.9.0" @@ -1471,6 +1522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "123a10aae81d0712ecc09b780f6f0ae0b0f506a5c4c912974725760d59ba073e" dependencies = [ "bitcoin", + "serde 1.0.139", ] [[package]] @@ -1564,6 +1616,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -2262,6 +2320,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.19.2" @@ -2308,12 +2381,27 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + [[package]] name = "unicode-ident" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.9.0" @@ -2354,6 +2442,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde 1.0.139", +] + [[package]] name = "version-compare" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 2a183eb..d04bdcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,12 +44,13 @@ required-features = ["server"] [dependencies] # LNP/BP crates -amplify = "3.14.1" +amplify = "3.13.0" strict_encoding = { version = "0.9.0-rc.2", features = ["miniscript"] } bitcoin_scripts = "0.9.0" bitcoin_blockchain = "0.9.0" descriptor-wallet = { version = "0.9.0", features = ["keygen", "miniscript", "electrum", "sign", "construct"] } lnpbp = "0.9.0" +lnpbp-invoice = { version = "0.9.0", features = ["serde", "bolt11"] } lnp-core = "0.9.1" lnp_rpc = { version = "0.9.1", path = "./rpc" } internet2 = { version = "0.9.0", features = ["keygen"] } @@ -119,4 +120,16 @@ embedded = ["microservices/embedded"] tor = ["microservices/tor", "internet2/tor"] [package.metadata.configure_me] -spec = "config_spec.toml" \ No newline at end of file +spec = "config_spec.toml" + +[patch.crates-io] +# TODO: Remove after merge and release: +# - https://github.com/LNP-WG/lnp-core/pull/29 +# - https://github.com/LNP-WG/lnp-core/pull/27 +lnp-core = { git = "https://github.com/crisdut/lnp-core", branch="exp/ln-interop" } +# TODO: Remove after merge and release: +# - https://github.com/LNP-WG/lightning_encoding/pull/4 +lightning_encoding = { git = "https://github.com/crisdut/lightning_encoding", branch="fix/script-decode" } +# TODO: Remove after merge and release: +# - https://github.com/LNP-BP/invoices/pull/9 +lnpbp-invoice = { git = "https://github.com/crisdut/invoices", branch="feat/bolt11" } \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 930f5d7..db2bc13 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -25,6 +25,7 @@ microservices = { version = "0.9.0", default-features = false, features = ["cli" shellexpand = "2.1" clap = { version = "~3.2.23", features = ["derive", "env"] } log = "0.4.14" +colored = "2.0.0" [build-dependencies] amplify = "3.13.0" diff --git a/cli/src/command.rs b/cli/src/command.rs index c052c2e..d7d0a0c 100644 --- a/cli/src/command.rs +++ b/cli/src/command.rs @@ -13,9 +13,12 @@ use std::str::FromStr; +use colored::Colorize; use internet2::addr::NodeId; use lnp::p2p::bolt::{ChannelId, LNP2P_BOLT_PORT}; -use lnp_rpc::{self, Client, CreateChannel, Error, ListenAddr, PayInvoice, RpcMsg, ServiceId}; +use lnp_rpc::{ + self, Client, CreateChannel, CreateInvoice, Error, ListenAddr, PayInvoice, RpcMsg, ServiceId, +}; use microservices::shell::Exec; use crate::{Command, Opts}; @@ -152,7 +155,29 @@ impl Exec for Opts { )?; runtime.report_progress()?; } - Command::Invoice { .. } => todo!("Implement invoice generation"), + Command::Invoice { amount, description, bolt, .. } => { + if bolt { + runtime.request( + ServiceId::LnpBroker, + RpcMsg::CreateInvoice(CreateInvoice { + amount_msat: (amount * 1000), + description, + }), + )?; + runtime.report_response()?; + eprintln!( + "{} {}", + "Warning:".bright_yellow(), + "This is a experimental feature!".yellow() + ); + } else { + eprintln!( + "{} {}", + "Error:".bright_red(), + "Bifrost invoice does not implemented yet!".red() + ); + } + } Command::Pay { invoice, channel: channel_id, amount_msat } => { runtime.request( diff --git a/cli/src/opts.rs b/cli/src/opts.rs index e1af2ac..547d727 100644 --- a/cli/src/opts.rs +++ b/cli/src/opts.rs @@ -214,6 +214,9 @@ pub enum Command { /// Create an invoice Invoice { + /// Invoice Description and/or Propouse + description: String, + /// Asset amount to invoice, in atomic unit (satoshis or smallest asset /// unit type) amount: u64, @@ -221,6 +224,14 @@ pub enum Command { /// Asset ticker in which the invoice should be issued #[clap(default_value = "btc")] asset: String, + + /// Use BOLT lightning network protocol. + #[clap(long, conflicts_with = "bifrost")] + bolt: bool, + + /// Use Bifrost lightning network protocol. + #[clap(long, required_unless_present = "bolt")] + bifrost: bool, }, /// Pay the invoice diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 161a8ac..930fc92 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -19,6 +19,7 @@ strict_encoding = "0.9.0" bitcoin_scripts = "0.9.0" lnp-core = { version = "0.9.0", default-features = false } lnpbp = "0.9.0" +lnpbp-invoice = { version = "0.9.0", features = ["serde", "bolt11"] } bitcoin = { version = "0.29.2", features = ["rand"] } lightning-invoice = { version = "0.21.0", optional = true } internet2 = "0.9.0" diff --git a/rpc/src/messages.rs b/rpc/src/messages.rs index 0e44cd9..c63893d 100644 --- a/rpc/src/messages.rs +++ b/rpc/src/messages.rs @@ -21,7 +21,7 @@ use std::time::Duration; use amplify::{Slice32, ToYamlString, Wrapper}; use bitcoin_scripts::address::AddressCompat; use internet2::addr::{InetSocketAddr, NodeAddr, NodeId}; -use lightning_invoice::Invoice; +use lightning_invoice::Invoice as BoltInvoice; use lnp::addr::LnpAddr; use lnp::channel::bolt::{AssetsBalance, ChannelState, CommonParams, PeerParams}; use lnp::p2p::bolt::{ChannelId, ChannelType}; @@ -86,6 +86,10 @@ pub enum RpcMsg { #[display("create_channel({0})")] CreateChannel(CreateChannel), + /// Requests creation of a new invoice from a `cli` to `lnpd` + #[display("create_invoice({0})")] + CreateInvoice(CreateInvoice), + // Can be issued from a `cli` to `routed` #[display("send({0})")] Send(Send), @@ -130,6 +134,9 @@ pub enum RpcMsg { #[display("funds_info({0})", alt = "{0:#}")] #[from] FundsInfo(FundsInfo), + + #[display("invoice_created({0})", alt = "{0:#}")] + InvoiceCreated(InvoiceRes), } impl RpcMsg { @@ -221,11 +228,30 @@ impl CreateChannel { } } +#[derive(Clone, PartialEq, Eq, Debug, Display, NetworkEncode, NetworkDecode)] +#[display("{description}, {amount_msat}")] +pub struct CreateInvoice { + pub description: String, + pub amount_msat: u64, +} + +// #[derive(Clone, PartialEq, Eq, Debug, Display)] +#[cfg_attr(feature = "serde", serde_as)] +#[derive(Clone, PartialEq, Eq, Debug, Display, NetworkEncode, NetworkDecode)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] +#[display(InvoiceRes::to_yaml_string)] +pub struct InvoiceRes { + pub description: String, + pub amount_msat: u64, + pub payment_secret: String, + pub bolt11: String, +} + #[derive(Clone, PartialEq, Eq, Debug, Display)] #[display("{invoice}, {channel_id}")] pub struct PayInvoice { pub channel_id: ChannelId, - pub invoice: Invoice, + pub invoice: BoltInvoice, pub amount_msat: Option, } @@ -239,7 +265,7 @@ impl StrictDecode for PayInvoice { fn strict_decode(mut d: D) -> Result { Ok(PayInvoice { channel_id: ChannelId::strict_decode(&mut d)?, - invoice: Invoice::from_str(&String::strict_decode(&mut d)?).map_err(|err| { + invoice: BoltInvoice::from_str(&String::strict_decode(&mut d)?).map_err(|err| { strict_encoding::Error::DataIntegrityError(format!( "invalid bech32 lightning invoice: {}", err @@ -338,6 +364,8 @@ impl ToYamlString for ChannelInfo {} impl ToYamlString for FundsInfo {} #[cfg(feature = "serde")] impl ToYamlString for ListPeerInfo {} +#[cfg(feature = "serde")] +impl ToYamlString for InvoiceRes {} #[derive(Wrapper, Clone, PartialEq, Eq, Debug, From, NetworkEncode, NetworkDecode)] #[wrapper(IndexRange)] diff --git a/shell/_lnp-cli b/shell/_lnp-cli index be044d4..96d7087 100644 --- a/shell/_lnp-cli +++ b/shell/_lnp-cli @@ -141,10 +141,13 @@ _arguments "${_arguments_options[@]}" \ _arguments "${_arguments_options[@]}" \ '-R+[ZMQ socket for connecting daemon RPC interface]:CONNECT: ' \ '--rpc=[ZMQ socket for connecting daemon RPC interface]:CONNECT: ' \ +'(--bifrost)--bolt[Use BOLT lightning network protocol]' \ +'--bifrost[Use Bifrost lightning network protocol]' \ '-h[Print help information]' \ '--help[Print help information]' \ '*-v[Set verbosity level]' \ '*--verbose[Set verbosity level]' \ +':description -- Invoice Description and/or Propouse:' \ ':amount -- Asset amount to invoice, in atomic unit (satoshis or smallest asset unit type):' \ '::asset -- Asset ticker in which the invoice should be issued:' \ && ret=0 diff --git a/shell/_lnp-cli.ps1 b/shell/_lnp-cli.ps1 index 4c0892d..2641fae 100644 --- a/shell/_lnp-cli.ps1 +++ b/shell/_lnp-cli.ps1 @@ -137,6 +137,8 @@ Register-ArgumentCompleter -Native -CommandName 'lnp-cli' -ScriptBlock { 'lnp-cli;invoice' { [CompletionResult]::new('-R', 'R', [CompletionResultType]::ParameterName, 'ZMQ socket for connecting daemon RPC interface') [CompletionResult]::new('--rpc', 'rpc', [CompletionResultType]::ParameterName, 'ZMQ socket for connecting daemon RPC interface') + [CompletionResult]::new('--bolt', 'bolt', [CompletionResultType]::ParameterName, 'Use BOLT lightning network protocol') + [CompletionResult]::new('--bifrost', 'bifrost', [CompletionResultType]::ParameterName, 'Use Bifrost lightning network protocol') [CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help information') [CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Set verbosity level') diff --git a/shell/lnp-cli.bash b/shell/lnp-cli.bash index 6352836..08c599e 100644 --- a/shell/lnp-cli.bash +++ b/shell/lnp-cli.bash @@ -184,7 +184,7 @@ _lnp-cli() { return 0 ;; lnp__cli__invoice) - opts="-h -R -v --help --rpc --verbose " + opts="-h -R -v --bolt --bifrost --help --rpc --verbose " if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 diff --git a/src/bus/ctl.rs b/src/bus/ctl.rs index 066e3cc..9135a78 100644 --- a/src/bus/ctl.rs +++ b/src/bus/ctl.rs @@ -12,7 +12,8 @@ // If not, see . use amplify::Slice32; -use bitcoin::Txid; +use bitcoin::secp256k1::ecdsa::Signature; +use bitcoin::{OutPoint, Txid}; use bitcoin_scripts::hlc::HashLock; use bitcoin_scripts::PubkeyScript; use internet2::addr::{NodeAddr, NodeId}; @@ -66,6 +67,16 @@ pub enum CtlMsg { #[display("funding_constructed(...)")] FundingConstructed(Psbt), + /// Constructs first commitment transaction PSBT (aka. refund transaction) for penalty + /// transactions to remotely-created new channel. Sent from peerd to lnpd. + #[display("construct_refund({0})")] + ConstructRefund(RefundParams), + + /// Provides channeld with the information about funding transaction output used to penalty + /// transactions. Sent from lnpd to channeld. + #[display("construct_refund({0}, {1})")] + RefundConstructed(Psbt, OutPoint), + /// Signs previously prepared funding transaction and publishes it to bitcoin network. Sent /// from channeld to lnpd upon receival of `funding_signed` message from a remote peer. #[display("publish_funding({0})")] @@ -86,7 +97,7 @@ pub enum CtlMsg { /// Reports changes in the mining status for previously requested transaction tracked by an /// on-chain service #[display("tx_found({0})")] - TxFound(TxStatus), + TxFound(TxConfirmation), // Routing & payments /// Request to channel daemon to perform payment using provided route @@ -231,6 +242,27 @@ pub struct FundChannel { pub feerate_per_kw: Option, } +/// Request information about constructing funding transaction +#[derive(Clone, PartialEq, Eq, Debug, Display, NetworkEncode, NetworkDecode)] +#[display("refund_params({funding_txid}:{funding_output_index}, ...signature)")] +pub struct RefundParams { + /// The funding transaction ID + pub funding_txid: Txid, + + /// The specific output index funding this channel + pub funding_output_index: u16, + + /// The funding amount + pub funding_amount: u64, + + /// The funding script pubkey + pub funding_script_pubkey: PubkeyScript, + + /// The signature of the channel initiator (funder) on the funding + /// transaction + pub signature: Signature, +} + /// TODO: Move to descriptor wallet /// Information about block position and transaction position in a block #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] @@ -260,6 +292,19 @@ pub struct TxStatus { pub block_pos: Option, } +/// TODO: Move to descriptor wallet +/// Update on a transaction mining status +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[derive(NetworkEncode, NetworkDecode)] +#[display("{txid}, ...")] +pub struct TxConfirmation { + /// Id of a transaction previously requested to be tracked + pub txid: Txid, + + /// block confirmations + pub confirmations: u32, +} + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display, NetworkEncode, NetworkDecode)] #[display("{client}, {status}")] pub struct Report { diff --git a/src/channeld/automata/accept.rs b/src/channeld/automata/accept.rs index 0ff70f6..b7db746 100644 --- a/src/channeld/automata/accept.rs +++ b/src/channeld/automata/accept.rs @@ -10,7 +10,6 @@ // // You should have received a copy of the MIT License along with this software. // If not, see . - use lnp::channel::bolt::Lifecycle; use lnp::p2p::bolt::{ActiveChannelId, ChannelId, FundingSigned, Messages as LnMsg}; use lnp::Extension; @@ -20,7 +19,7 @@ use microservices::esb::Handler; use super::Error; use crate::automata::{Event, StateMachine}; -use crate::bus::{AcceptChannelFrom, BusMsg, CtlMsg}; +use crate::bus::{AcceptChannelFrom, BusMsg, CtlMsg, RefundParams}; use crate::channeld::runtime::Runtime; use crate::{Endpoints, Responder}; @@ -92,6 +91,7 @@ impl ChannelAccept { request: AcceptChannelFrom, ) -> Result { let open_channel = LnMsg::OpenChannel(request.channel_req.clone()); + runtime.state.channel.update_from_peer(&open_channel)?; let _ = runtime.send_ctl( @@ -129,10 +129,10 @@ fn finish_accepted(event: Event, runtime: &mut Runtime) -> Result { @@ -163,9 +163,43 @@ fn finish_signed(event: Event, runtime: &mut Runtime) -> Result { + let mut refund_psbt = runtime.state.channel.refund_tx(refund_psbt, true)?; + let prev_output = outpoint; + + refund_psbt.inputs[0].previous_outpoint = prev_output; + runtime.send_ctl(event.endpoints, ServiceId::Signer, CtlMsg::Sign(refund_psbt))?; + Ok(ChannelAccept::Signed) + } + BusMsg::Ctl(CtlMsg::Signed(refund_psbt)) => { + let channel_id = runtime.state.channel.channel_id().unwrap(); + let funding_pubkey = runtime.state.channel.funding_pubkey(); + let funding_input = + refund_psbt.inputs.get(0).expect("BOLT commitment always has a single input"); + + let signature = funding_input + .partial_sigs + .get(&bitcoin::PublicKey::new(funding_pubkey)) + .ok_or(Error::FundingPsbtUnsigned(funding_pubkey))?; + runtime.send_p2p( event.endpoints, - LnMsg::FundingSigned(FundingSigned { channel_id, signature: funding.signature }), + LnMsg::FundingSigned(FundingSigned { channel_id, signature: signature.sig }), )?; Ok(ChannelAccept::Funded) } diff --git a/src/channeld/automata/mod.rs b/src/channeld/automata/mod.rs index cb57ba1..f0bf1a5 100644 --- a/src/channeld/automata/mod.rs +++ b/src/channeld/automata/mod.rs @@ -107,6 +107,9 @@ pub enum ChannelStateMachine { #[from] Accept(ChannelAccept), + // /// receving commitment sign by a remote peer + // #[display("COMMITMENT")] + // Commitment(ReceivedHtlc), /// active channel operations #[display("ACTIVE")] Active, @@ -192,8 +195,8 @@ impl Runtime { self.report_failure(endpoints, Failure { code, info }); } - let event = Event::with(endpoints, self.identity(), source, request); let channel_id = self.state.channel.active_channel_id(); + let event = Event::with(endpoints, self.identity(), source, request); let updated_state = match self.process_event(event) { Ok(_) => { // Ignoring possible reporting errors here and after: do not want to @@ -247,6 +250,11 @@ impl Runtime { return Ok(()); } + // if let BusMsg::Bolt(LnMsg::UpdateAddHtlc(_)) = event.message { + // self.state.state_machine = self.complete_commitment(event)?; + // return Ok(()); + // } + self.state.state_machine = match self.state.state_machine { ChannelStateMachine::Launch => self.complete_launch(event), ChannelStateMachine::Propose(channel_propose) => { diff --git a/src/channeld/automata/propose.rs b/src/channeld/automata/propose.rs index b8b5d7e..e4f5aac 100644 --- a/src/channeld/automata/propose.rs +++ b/src/channeld/automata/propose.rs @@ -211,7 +211,7 @@ fn complete_accepted( debug!("Funding transaction id is {}", funding_psbt.to_txid()); let channel = &mut runtime.state.channel; - let refund_psbt = channel.refund_tx(funding_psbt, false)?; + let refund_psbt = channel.refund_tx(funding_psbt, true)?; trace!("Refund transaction: {:#?}", refund_psbt); trace!("Local keyset: {:#}", channel.constructor().local_keys()); @@ -261,7 +261,6 @@ fn complete_signing( .expect("unable to change ZMQ channel identity"); // needed to update ESB routing map runtime.send_ctl(event.endpoints, ServiceId::LnpBroker, CtlMsg::Hello)?; - runtime.send_p2p(event.endpoints, LnMsg::FundingCreated(funding_created))?; Ok(ChannelPropose::Funding) } @@ -284,7 +283,11 @@ fn complete_funding( let txid = runtime.state.channel.funding().txid(); debug!("Waiting for funding transaction {} to be mined", txid); - runtime.send_ctl(event.endpoints, ServiceId::Watch, CtlMsg::Track { txid, depth: 0 })?; + let core = runtime.state.channel.constructor(); + runtime.send_ctl(event.endpoints, ServiceId::Watch, CtlMsg::Track { + txid, + depth: core.common_params().minimum_depth, + })?; Ok(ChannelPropose::Published) } diff --git a/src/channeld/runtime.rs b/src/channeld/runtime.rs index e85a5ea..0b9587b 100644 --- a/src/channeld/runtime.rs +++ b/src/channeld/runtime.rs @@ -200,11 +200,12 @@ impl Runtime { ); } - LnMsg::ChannelReestablish(_) - | LnMsg::AcceptChannel(_) + LnMsg::AcceptChannel(_) | LnMsg::FundingCreated(_) | LnMsg::FundingSigned(_) - | LnMsg::FundingLocked(_) => { + | LnMsg::FundingLocked(_) + | LnMsg::ChannelReestablish(_) + | LnMsg::UpdateAddHtlc(_) => { self.process(endpoints, ServiceId::PeerBolt(remote_id), BusMsg::Bolt(message))?; } _ => { @@ -243,6 +244,7 @@ impl Runtime { } CtlMsg::FundingConstructed(_) + | CtlMsg::RefundConstructed(..) | CtlMsg::TxFound(_) | CtlMsg::Signed(_) | CtlMsg::Keyset(..) diff --git a/src/lnpd/funding.rs b/src/lnpd/funding.rs index 91df9a7..8fca51f 100644 --- a/src/lnpd/funding.rs +++ b/src/lnpd/funding.rs @@ -15,13 +15,14 @@ use std::collections::BTreeMap; use std::convert::TryInto; use std::io::Seek; use std::path::Path; +use std::str::FromStr; use std::{fs, io}; use amplify::{IoError, Slice32, Wrapper}; use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::secp256k1::{self, Secp256k1}; use bitcoin::util::bip32::ChildNumber; -use bitcoin::{EcdsaSighashType, Network, OutPoint, Txid}; +use bitcoin::{EcdsaSighashType, Network, OutPoint, Script, Transaction, TxOut, Txid}; use bitcoin_blockchain::locks::SeqNo; use bitcoin_scripts::address::AddressCompat; use bitcoin_scripts::PubkeyScript; @@ -38,7 +39,9 @@ use wallet::hd::{ DerivationAccount, DerivationSubpath, DeriveError, SegmentIndexes, UnhardenedIndex, }; use wallet::onchain::ResolveDescriptor; -use wallet::psbt::Psbt; +use wallet::psbt::{Psbt, PsbtVersion}; + +use crate::bus::RefundParams; // The default fee rate is 2 sats per kilo-vbyte const DEFAULT_FEERATE_PER_KW: u32 = 2u32 * 1000 * 4; @@ -419,6 +422,33 @@ impl FundingWallet { Ok(psbt) } + pub fn construct_refund_psbt( + &mut self, + params: &RefundParams, + ) -> Result<(Psbt, OutPoint), Error> { + let mut psbt = Psbt::with( + Transaction { + version: 2, + lock_time: bitcoin::PackedLockTime(0), + input: vec![], + output: vec![TxOut { + value: params.funding_amount, + script_pubkey: Script::from(params.funding_script_pubkey.clone()), + }], + }, + PsbtVersion::V0, + ) + .expect("dumb manual PSBT creation"); + let _ = psbt.set_channel_funding_output(0); + + let prev_output = OutPoint::from_str( + format!("{}:{}", params.funding_txid, params.funding_output_index).as_str(), + ) + .expect("invalid outpoint parameters"); + + Ok((psbt, prev_output)) + } + #[inline] pub fn get_funding_psbt(&self, txid: Txid) -> Option<&Psbt> { self.wallet_data.pending_fundings.get(&txid).map(|funding| &funding.psbt) diff --git a/src/lnpd/runtime.rs b/src/lnpd/runtime.rs index 361335e..76a7808 100644 --- a/src/lnpd/runtime.rs +++ b/src/lnpd/runtime.rs @@ -15,18 +15,23 @@ use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::PathBuf; use std::time::{Duration, SystemTime}; -use amplify::{DumbDefault, Wrapper}; +use amplify::hex::ToHex; +use amplify::{DumbDefault, Slice32, Wrapper}; +use bitcoin::secp256k1::SECP256K1; use bitcoin::Txid; use bitcoin_scripts::address::AddressCompat; +use bitcoin_scripts::hlc::{HashLock, HashPreimage}; use internet2::addr::{NodeAddr, NodeId}; +use invoice::{Invoice, LnAddress}; +use lightning_invoice::{Invoice as BoltInvoice, RawInvoice}; use lnp::addr::LnpAddr; use lnp::channel::bolt::{CommonParams, LocalKeyset, PeerParams, Policy}; use lnp::p2p; use lnp::p2p::bolt::{ - ActiveChannelId, ChannelId, ChannelReestablish, Messages as LnMsg, TempChannelId, + ActiveChannelId, ChannelId, ChannelReestablish, InitFeatures, Messages as LnMsg, TempChannelId, }; use lnp::p2p::Protocol; -use lnp_rpc::{FailureCode, ListenAddr}; +use lnp_rpc::{CreateInvoice, FailureCode, InvoiceRes, ListenAddr}; use microservices::cli::LogStyle; use microservices::esb::{self, ClientId, Handler}; use microservices::peer::PeerSocket; @@ -61,14 +66,15 @@ pub fn run<'a>( handles: vec![], funding_wallet: config.funding_wallet()?, channel_params: config.channel_params()?, + features: config.initial_features()?, bolt_connections: none!(), bifrost_connections: none!(), channels: none!(), spawning_peers: none!(), creating_channels: none!(), funding_channels: none!(), - accepting_channels: none!(), reestablishing_channels: none!(), + accepting_channels: none!(), }; Service::run(config, runtime, true) @@ -88,6 +94,11 @@ impl Config { // TODO: Read params from config Ok((Policy::default(), CommonParams::default(), PeerParams::default())) } + + fn initial_features(&self) -> Result<(InitFeatures, InitFeatures), Error> { + // TODO: Read params from config + Ok((InitFeatures::new(), InitFeatures::new())) + } } pub struct Runtime { @@ -97,6 +108,7 @@ pub struct Runtime { listens: HashSet, started: SystemTime, handles: Vec>, + pub(super) features: (InitFeatures, InitFeatures), pub(super) funding_wallet: FundingWallet, pub(super) channel_params: (Policy, CommonParams, PeerParams), bolt_connections: HashSet, @@ -369,6 +381,49 @@ impl Runtime { self.creating_channels.insert(channeld_id, launcher); } + RpcMsg::CreateInvoice(CreateInvoice { amount_msat, description }) => { + info!("Creating invoice with {amount_msat} msat and description '{description}'"); + + let key_file = self.node_key_path.clone(); + let node_key = read_node_key_file(key_file.as_path()); + + let preimage = HashPreimage::from_inner(Slice32::random()); + let invoice = Invoice::new( + invoice::Beneficiary::Bolt(LnAddress { + node_id: self.node_id, + features: self.features.1.clone(), + lock: HashLock::from(preimage), + network: self.funding_wallet.network().into(), + secret: None, + min_final_cltv_expiry: Some(114), + path_hints: vec![], + }), + Some(amount_msat), + None, + ); + + let bolt11 = RawInvoice::try_from(invoice).unwrap(); + let signed_invoice = bolt11 + .sign::<_, ()>(|hash| { + let privkey = node_key.private_key(); + let secp_ctx = &SECP256K1; + Ok(secp_ctx.sign_ecdsa_recoverable(hash, &privkey)) + }) + .unwrap(); + + let invoice = BoltInvoice::from_signed(signed_invoice).unwrap(); + let secret = invoice.payment_secret(); + + let resp = InvoiceRes { + description, + amount_msat, + payment_secret: secret.0.to_hex(), + bolt11: invoice.to_string(), + }; + + self.send_rpc(endpoints, client_id, RpcMsg::InvoiceCreated(resp))?; + } + wrong_msg => { error!("Request is not supported by the RPC interface"); return Err(Error::wrong_esb_msg(ServiceBus::Rpc, &wrong_msg)); @@ -411,6 +466,16 @@ impl Runtime { .insert(ChannelId::from_inner(launcher.channel_id()).into(), launcher); } + CtlMsg::ConstructRefund(refund_params) => { + let (psbt, outpoint) = self.funding_wallet.construct_refund_psbt(refund_params)?; + endpoints.send_to( + ServiceBus::Ctl, + self.identity(), + source, + BusMsg::Ctl(CtlMsg::RefundConstructed(psbt, outpoint)), + )?; + } + CtlMsg::PublishFunding => { let launcher = self .creating_channels diff --git a/src/peerd/runtime.rs b/src/peerd/runtime.rs index 86496ed..7e0effa 100644 --- a/src/peerd/runtime.rs +++ b/src/peerd/runtime.rs @@ -22,6 +22,7 @@ use bitcoin::secp256k1::rand::{self, Rng, RngCore}; use internet2::addr::{InetSocketAddr, NodeId}; use internet2::zeromq::ZmqSocketType; use internet2::{presentation, transport, zeromq, CreateUnmarshaller, TypedEnum}; +use lnp::p2p::bolt::{InitFeatures, Messages}; use lnp::p2p::{self, bifrost, bolt}; use lnp_rpc::RpcMsg; use microservices::cli::LogStyle; @@ -187,12 +188,11 @@ impl esb::Handler for Runtime { fn on_ready(&mut self, _: &mut Endpoints) -> Result<(), Error> { if self.connect { info!("{} with the remote peer", "Initializing connection".announce()); - match self.config.protocol { p2p::Protocol::Bolt => { self.sender.send_message(bolt::Messages::Init(bolt::Init { - global_features: none!(), - local_features: none!(), + global_features: InitFeatures::new(), + local_features: InitFeatures::new(), assets: none!(), unknown_tlvs: none!(), }))?; @@ -207,6 +207,7 @@ impl esb::Handler for Runtime { } self.connect = false; + self.messages_sent += 1; } Ok(()) } @@ -349,6 +350,13 @@ impl Runtime { self.messages_received += 1; match &msg { + bolt::Messages::Init(_) => { + if self.messages_sent <= 0 { + self.on_init(msg)?; + } else { + self.ping()?; + } + } bolt::Messages::Ping(bolt::Ping { pong_size, .. }) => { self.pong(*pong_size)?; } @@ -394,6 +402,7 @@ impl Runtime { bolt::Messages::FundingSigned(bolt::FundingSigned { channel_id, .. }) | bolt::Messages::FundingLocked(bolt::FundingLocked { channel_id, .. }) + | bolt::Messages::CommitmentSigned(bolt::CommitmentSigned { channel_id, .. }) | bolt::Messages::UpdateAddHtlc(bolt::UpdateAddHtlc { channel_id, .. }) | bolt::Messages::UpdateFulfillHtlc(bolt::UpdateFulfillHtlc { channel_id, .. }) | bolt::Messages::UpdateFailHtlc(bolt::UpdateFailHtlc { channel_id, .. }) @@ -500,6 +509,19 @@ impl Runtime { Ok(()) } + fn on_init(&mut self, msg: Messages) -> Result<(), Error> { + trace!("Sending our features to the remote peer"); + match self.config.protocol { + p2p::Protocol::Bolt => { + self.sender + .send_message(msg) + .expect("cannot be send LN message to the remote peer"); + } + p2p::Protocol::Bifrost => {} + } + Ok(()) + } + fn ping(&mut self) -> Result<(), Error> { trace!("Sending ping to the remote peer"); if self.awaited_pong.is_some() { diff --git a/src/watchd/runtime.rs b/src/watchd/runtime.rs index c0798d3..fb3a0cc 100644 --- a/src/watchd/runtime.rs +++ b/src/watchd/runtime.rs @@ -88,16 +88,14 @@ impl WatcherRuntime { // TODO: Forward all electrum notifications over the bridge // self.send_over_bridge(msg.into()).expect("watcher bridge is halted"); match msg { - ElectrumUpdate::TxBatch(transactions, _) => { + ElectrumUpdate::TxConfirmations(transactions, _) => { for transaction in transactions { - self.send_over_bridge(BusMsg::Ctl(CtlMsg::TxFound(crate::bus::TxStatus { - txid: transaction.txid(), - block_pos: None, - }))) - .expect("unable forward electrum notifications over the bridge"); + self.send_over_bridge(BusMsg::Ctl(CtlMsg::TxFound(transaction))) + .expect("unable forward electrum notifications over the bridge"); } } - ElectrumUpdate::Connecting + ElectrumUpdate::TxBatch(..) + | ElectrumUpdate::Connecting | ElectrumUpdate::Connected | ElectrumUpdate::Complete | ElectrumUpdate::FeeEstimate(..) @@ -170,7 +168,7 @@ impl Runtime { match request { CtlMsg::TxFound(tx_status) => { if let Some((required_height, service_id)) = self.track_list.get(&tx_status.txid) { - if *required_height >= tx_status.block_pos.map(|b| b.pos).unwrap_or_default() { + if *required_height <= tx_status.confirmations { let service_id = service_id.clone(); self.untrack(tx_status.txid); match self.electrum_worker.untrack_transaction(tx_status.txid) { diff --git a/src/watchd/worker.rs b/src/watchd/worker.rs index 2fea972..2956d72 100644 --- a/src/watchd/worker.rs +++ b/src/watchd/worker.rs @@ -13,13 +13,16 @@ // TODO: Consider making it part of descriptor wallet onchain library +use std::collections::BTreeMap; use std::sync::mpsc; use std::thread::{self, JoinHandle}; use std::time::Duration; -use bitcoin::{Transaction, Txid}; +use bitcoin::{Script, Transaction, Txid}; use electrum_client::{Client as ElectrumClient, ElectrumApi, HeaderNotification}; +use crate::bus::TxConfirmation; + #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error, From)] #[display("failed electrum watcher channel")] #[from(mpsc::SendError)] @@ -48,6 +51,9 @@ pub enum ElectrumUpdate { #[display("tx_batch(...)")] TxBatch(Vec, f32), + #[display("tx_confirmations(...)")] + TxConfirmations(Vec, u32), + #[display("channel_disconnected")] ChannelDisconnected, @@ -193,7 +199,42 @@ impl ElectrumProcessor { if self.tracks.is_empty() { return Ok(None); } - self.client.batch_transaction_get(txids).map(|res| Some(ElectrumUpdate::TxBatch(res, 0.0))) + let transactions = self.client.batch_transaction_get(txids)?; + let scripts: Vec